From 6bc1e9cd840f8e007a79b9ddf7cde686a050a8cf Mon Sep 17 00:00:00 2001 From: John Baldwin Date: Fri, 27 Jun 2008 05:39:04 +0000 Subject: [PATCH] Rework the lifetime management of the kernel implementation of POSIX semaphores. Specifically, semaphores are now represented as new file descriptor type that is set to close on exec. This removes the need for all of the manual process reference counting (and fork, exec, and exit event handlers) as the normal file descriptor operations handle all of that for us nicely. It is also suggested as one possible implementation in the spec and at least one other OS (OS X) uses this approach. Some bugs that were fixed as a result include: - References to a named semaphore whose name is removed still work after the sem_unlink() operation. Prior to this patch, if a semaphore's name was removed, valid handles from sem_open() would get EINVAL errors from sem_getvalue(), sem_post(), etc. This fixes that. - Unnamed semaphores created with sem_init() were not cleaned up when a process exited or exec'd. They were only cleaned up if the process did an explicit sem_destroy(). This could result in a leak of semaphore objects that could never be cleaned up. - On the other hand, if another process guessed the id (kernel pointer to 'struct ksem' of an unnamed semaphore (created via sem_init)) and had write access to the semaphore based on UID/GID checks, then that other process could manipulate the semaphore via sem_destroy(), sem_post(), sem_wait(), etc. - As part of the permission check (UID/GID), the umask of the proces creating the semaphore was not honored. Thus if your umask denied group read/write access but the explicit mode in the sem_init() call allowed it, the semaphore would be readable/writable by other users in the same group, for example. This includes access via the previous bug. - If the module refused to unload because there were active semaphores, then it might have deregistered one or more of the semaphore system calls before it noticed that there was a problem. I'm not sure if this actually happened as the order that modules are discovered by the kernel linker depends on how the actual .ko file is linked. One can make the order deterministic by using a single module with a mod_event handler that explicitly registers syscalls (and deregisters during unload after any checks). This also fixes a race where even if the sem_module unloaded first it would have destroyed locks that the syscalls might be trying to access if they are still executing when they are unloaded. XXX: By the way, deregistering system calls doesn't do any blocking to drain any threads from the calls. - Some minor fixes to errno values on error. For example, sem_init() isn't documented to return ENFILE or EMFILE if we run out of semaphores the way that sem_open() can. Instead, it should return ENOSPC in that case. Other changes: - Kernel semaphores now use a hash table to manage the namespace of named semaphores nearly in a similar fashion to the POSIX shared memory object file descriptors. Kernel semaphores can now also have names longer than 14 chars (up to MAXPATHLEN) and can include subdirectories in their pathname. - The UID/GID permission checks for access to a named semaphore are now done via vaccess() rather than a home-rolled set of checks. - Now that kernel semaphores have an associated file object, the various MAC checks for POSIX semaphores accept both a file credential and an active credential. There is also a new posixsem_check_stat() since it is possible to fstat() a semaphore file descriptor. - A small set of regression tests (using the ksem API directly) is present in src/tools/regression/posixsem. Reported by: kris (1) Tested by: kris Reviewed by: rwatson (lightly) MFC after: 1 month --- sys/kern/kern_descrip.c | 6 + sys/kern/uipc_sem.c | 1292 +++++++++++------------ sys/kern/uipc_shm.c | 4 + sys/modules/sem/Makefile | 2 +- sys/security/mac/mac_framework.h | 11 +- sys/security/mac/mac_policy.h | 19 +- sys/security/mac/mac_posix_sem.c | 30 +- sys/security/mac_biba/mac_biba.c | 31 +- sys/security/mac_mls/mac_mls.c | 31 +- sys/security/mac_stub/mac_stub.c | 21 +- sys/security/mac_test/mac_test.c | 35 +- sys/sys/file.h | 1 + sys/sys/ksem.h | 42 +- sys/sys/user.h | 1 + tools/regression/posixsem/Makefile | 11 + tools/regression/posixsem/posixsem.c | 1437 ++++++++++++++++++++++++++ tools/regression/posixsem/posixsem.t | 5 + tools/regression/posixsem/test.c | 128 +++ tools/regression/posixsem/test.h | 59 ++ usr.bin/procstat/procstat_files.c | 4 + 20 files changed, 2432 insertions(+), 738 deletions(-) create mode 100644 tools/regression/posixsem/Makefile create mode 100644 tools/regression/posixsem/posixsem.c create mode 100644 tools/regression/posixsem/posixsem.t create mode 100644 tools/regression/posixsem/test.c create mode 100644 tools/regression/posixsem/test.h diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c index 0bd3ef6379dc..f4e5f568bcf2 100644 --- a/sys/kern/kern_descrip.c +++ b/sys/kern/kern_descrip.c @@ -2633,6 +2633,10 @@ sysctl_kern_proc_filedesc(SYSCTL_HANDLER_ARGS) kif->kf_type = KF_TYPE_SHM; break; + case DTYPE_SEM: + kif->kf_type = KF_TYPE_SEM; + break; + default: kif->kf_type = KF_TYPE_UNKNOWN; break; @@ -2767,6 +2771,8 @@ file_type_to_name(short type) return ("mque"); case DTYPE_SHM: return ("shm"); + case DTYPE_SEM: + return ("ksem"); default: return ("unkn"); } diff --git a/sys/kern/uipc_sem.c b/sys/kern/uipc_sem.c index 9603b1d31fe9..47b711aef824 100644 --- a/sys/kern/uipc_sem.c +++ b/sys/kern/uipc_sem.c @@ -38,585 +38,595 @@ __FBSDID("$FreeBSD$"); #include "opt_posix.h" #include -#include -#include -#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -static int sem_count_proc(struct proc *p); -static struct ksem *sem_lookup_byname(const char *name); -static int sem_create(struct thread *td, const char *name, - struct ksem **ksret, mode_t mode, unsigned int value); -static void sem_free(struct ksem *ksnew); -static int sem_perm(struct thread *td, struct ksem *ks); -static void sem_enter(struct proc *p, struct ksem *ks); -static int sem_leave(struct proc *p, struct ksem *ks); -static void sem_exechook(void *arg, struct proc *p, - struct image_params *imgp); -static void sem_exithook(void *arg, struct proc *p); -static void sem_forkhook(void *arg, struct proc *p1, struct proc *p2, - int flags); -static int sem_hasopen(struct thread *td, struct ksem *ks); - -static int kern_sem_close(struct thread *td, semid_t id); -static int kern_sem_post(struct thread *td, semid_t id); -static int kern_sem_wait(struct thread *td, semid_t id, int tryflag, - struct timespec *abstime); -static int kern_sem_init(struct thread *td, int dir, unsigned int value, - semid_t *idp); -static int kern_sem_open(struct thread *td, int dir, const char *name, - int oflag, mode_t mode, unsigned int value, semid_t *idp); -static int kern_sem_unlink(struct thread *td, const char *name); +/* + * TODO + * + * - Resource limits? + * - Update fstat(1) + * - Replace global sem_lock with mtx_pool locks? + * - Add a MAC check_create() hook for creating new named semaphores. + */ #ifndef SEM_MAX #define SEM_MAX 30 #endif -#define SEM_MAX_NAMELEN 14 - -#define SEM_TO_ID(x) ((intptr_t)(x)) -#define ID_TO_SEM(x) id_to_sem(x) - -/* - * Available semaphores go here, this includes sem_init and any semaphores - * created via sem_open that have not yet been unlinked. - */ -LIST_HEAD(, ksem) ksem_head = LIST_HEAD_INITIALIZER(&ksem_head); - -/* - * Semaphores still in use but have been sem_unlink()'d go here. - */ -LIST_HEAD(, ksem) ksem_deadhead = LIST_HEAD_INITIALIZER(&ksem_deadhead); - -static struct mtx sem_lock; -static MALLOC_DEFINE(M_SEM, "sems", "semaphore data"); - -static int nsems = 0; -SYSCTL_DECL(_p1003_1b); -SYSCTL_INT(_p1003_1b, OID_AUTO, nsems, CTLFLAG_RD, &nsems, 0, ""); - -static eventhandler_tag sem_exit_tag, sem_exec_tag, sem_fork_tag; - #ifdef SEM_DEBUG #define DP(x) printf x #else #define DP(x) #endif -static __inline void -sem_ref(struct ksem *ks) +struct ksem_mapping { + char *km_path; + Fnv32_t km_fnv; + struct ksem *km_ksem; + LIST_ENTRY(ksem_mapping) km_link; +}; + +static MALLOC_DEFINE(M_KSEM, "ksem", "semaphore file descriptor"); +static LIST_HEAD(, ksem_mapping) *ksem_dictionary; +static struct sx ksem_dict_lock; +static struct mtx ksem_count_lock; +static struct mtx sem_lock; +static u_long ksem_hash; +static int ksem_dead; + +#define KSEM_HASH(fnv) (&ksem_dictionary[(fnv) & ksem_hash]) + +static int nsems = 0; +SYSCTL_DECL(_p1003_1b); +SYSCTL_INT(_p1003_1b, OID_AUTO, nsems, CTLFLAG_RD, &nsems, 0, + "Number of active kernel POSIX semaphores"); + +static int kern_sem_wait(struct thread *td, semid_t id, int tryflag, + struct timespec *abstime); +static int ksem_access(struct ksem *ks, struct ucred *ucred); +static struct ksem *ksem_alloc(struct ucred *ucred, mode_t mode, + unsigned int value); +static int ksem_create(struct thread *td, const char *path, + semid_t *semidp, mode_t mode, unsigned int value, + int flags); +static void ksem_drop(struct ksem *ks); +static int ksem_get(struct thread *td, semid_t id, struct file **fpp); +static struct ksem *ksem_hold(struct ksem *ks); +static void ksem_insert(char *path, Fnv32_t fnv, struct ksem *ks); +static struct ksem *ksem_lookup(char *path, Fnv32_t fnv); +static void ksem_module_destroy(void); +static int ksem_module_init(void); +static int ksem_remove(char *path, Fnv32_t fnv, struct ucred *ucred); +static int sem_modload(struct module *module, int cmd, void *arg); + +static fo_rdwr_t ksem_read; +static fo_rdwr_t ksem_write; +static fo_truncate_t ksem_truncate; +static fo_ioctl_t ksem_ioctl; +static fo_poll_t ksem_poll; +static fo_kqfilter_t ksem_kqfilter; +static fo_stat_t ksem_stat; +static fo_close_t ksem_closef; + +/* File descriptor operations. */ +static struct fileops ksem_ops = { + .fo_read = ksem_read, + .fo_write = ksem_write, + .fo_truncate = ksem_truncate, + .fo_ioctl = ksem_ioctl, + .fo_poll = ksem_poll, + .fo_kqfilter = ksem_kqfilter, + .fo_stat = ksem_stat, + .fo_close = ksem_closef, + .fo_flags = DFLAG_PASSABLE +}; + +FEATURE(posix_sem, "POSIX semaphores"); + +static int +ksem_read(struct file *fp, struct uio *uio, struct ucred *active_cred, + int flags, struct thread *td) { - mtx_assert(&sem_lock, MA_OWNED); - ks->ks_ref++; - DP(("sem_ref: ks = %p, ref = %d\n", ks, ks->ks_ref)); -} - -static __inline void -sem_rel(struct ksem *ks) -{ - - mtx_assert(&sem_lock, MA_OWNED); - DP(("sem_rel: ks = %p, ref = %d\n", ks, ks->ks_ref - 1)); - if (--ks->ks_ref == 0) - sem_free(ks); -} - -static __inline -struct ksem * -id_to_sem(semid_t id) -{ - struct ksem *ks; - - mtx_assert(&sem_lock, MA_OWNED); - DP(("id_to_sem: id = %0x,%p\n", id, (struct ksem *)id)); - LIST_FOREACH(ks, &ksem_head, ks_entry) { - DP(("id_to_sem: ks = %p\n", ks)); - if (ks == (struct ksem *)id) - return (ks); - } - return (NULL); -} - -static struct ksem * -sem_lookup_byname(const char *name) -{ - struct ksem *ks; - - mtx_assert(&sem_lock, MA_OWNED); - LIST_FOREACH(ks, &ksem_head, ks_entry) - if (ks->ks_name != NULL && strcmp(ks->ks_name, name) == 0) - return (ks); - return (NULL); + return (EOPNOTSUPP); } static int -sem_create(struct thread *td, const char *name, struct ksem **ksret, - mode_t mode, unsigned int value) +ksem_write(struct file *fp, struct uio *uio, struct ucred *active_cred, + int flags, struct thread *td) +{ + + return (EOPNOTSUPP); +} + +static int +ksem_truncate(struct file *fp, off_t length, struct ucred *active_cred, + struct thread *td) +{ + + return (EINVAL); +} + +static int +ksem_ioctl(struct file *fp, u_long com, void *data, + struct ucred *active_cred, struct thread *td) +{ + + return (EOPNOTSUPP); +} + +static int +ksem_poll(struct file *fp, int events, struct ucred *active_cred, + struct thread *td) +{ + + return (EOPNOTSUPP); +} + +static int +ksem_kqfilter(struct file *fp, struct knote *kn) +{ + + return (EOPNOTSUPP); +} + +static int +ksem_stat(struct file *fp, struct stat *sb, struct ucred *active_cred, + struct thread *td) +{ + struct ksem *ks; +#ifdef MAC + int error; +#endif + + ks = fp->f_data; + +#ifdef MAC + error = mac_posixsem_check_stat(active_cred, fp->f_cred, ks); + if (error) + return (error); +#endif + + /* + * Attempt to return sanish values for fstat() on a semaphore + * file descriptor. + */ + bzero(sb, sizeof(*sb)); + sb->st_mode = S_IFREG | ks->ks_mode; /* XXX */ + + sb->st_atimespec = ks->ks_atime; + sb->st_ctimespec = ks->ks_ctime; + sb->st_mtimespec = ks->ks_mtime; + sb->st_birthtimespec = ks->ks_birthtime; + sb->st_uid = ks->ks_uid; + sb->st_gid = ks->ks_gid; + + return (0); +} + +static int +ksem_closef(struct file *fp, struct thread *td) +{ + struct ksem *ks; + + ks = fp->f_data; + fp->f_data = NULL; + ksem_drop(ks); + + return (0); +} + +/* + * ksem object management including creation and reference counting + * routines. + */ +static struct ksem * +ksem_alloc(struct ucred *ucred, mode_t mode, unsigned int value) +{ + struct ksem *ks; + + mtx_lock(&ksem_count_lock); + if (nsems == p31b_getcfg(CTL_P1003_1B_SEM_NSEMS_MAX) || ksem_dead) { + mtx_unlock(&ksem_count_lock); + return (NULL); + } + nsems++; + mtx_unlock(&ksem_count_lock); + ks = malloc(sizeof(*ks), M_KSEM, M_WAITOK | M_ZERO); + ks->ks_uid = ucred->cr_uid; + ks->ks_gid = ucred->cr_gid; + ks->ks_mode = mode; + ks->ks_value = value; + cv_init(&ks->ks_cv, "ksem"); + vfs_timestamp(&ks->ks_birthtime); + ks->ks_atime = ks->ks_mtime = ks->ks_ctime = ks->ks_birthtime; + refcount_init(&ks->ks_ref, 1); +#ifdef MAC + mac_posixsem_init(ks); + mac_posixsem_create(ucred, ks); +#endif + + return (ks); +} + +static struct ksem * +ksem_hold(struct ksem *ks) +{ + + refcount_acquire(&ks->ks_ref); + return (ks); +} + +static void +ksem_drop(struct ksem *ks) +{ + + if (refcount_release(&ks->ks_ref)) { +#ifdef MAC + mac_posixsem_destroy(ks); +#endif + cv_destroy(&ks->ks_cv); + free(ks, M_KSEM); + mtx_lock(&ksem_count_lock); + nsems--; + mtx_unlock(&ksem_count_lock); + } +} + +/* + * Determine if the credentials have sufficient permissions for read + * and write access. + */ +static int +ksem_access(struct ksem *ks, struct ucred *ucred) { - struct ksem *ret; - struct proc *p; - struct ucred *uc; - size_t len; int error; - DP(("sem_create\n")); - p = td->td_proc; - uc = td->td_ucred; - if (value > SEM_VALUE_MAX) - return (EINVAL); - ret = malloc(sizeof(*ret), M_SEM, M_WAITOK | M_ZERO); - if (name != NULL) { - len = strlen(name); - if (len > SEM_MAX_NAMELEN) { - free(ret, M_SEM); - return (ENAMETOOLONG); - } - - /* Name must start with a '/' but not contain one. */ - if (*name != '/' || len < 2 || index(name + 1, '/') != NULL) { - free(ret, M_SEM); - return (EINVAL); - } - ret->ks_name = malloc(len + 1, M_SEM, M_WAITOK); - strcpy(ret->ks_name, name); - } else { - ret->ks_name = NULL; - } - ret->ks_mode = mode; - ret->ks_value = value; - ret->ks_ref = 1; - ret->ks_waiters = 0; - ret->ks_uid = uc->cr_uid; - ret->ks_gid = uc->cr_gid; - ret->ks_onlist = 0; - cv_init(&ret->ks_cv, "sem"); - LIST_INIT(&ret->ks_users); -#ifdef MAC - mac_posixsem_init(ret); - mac_posixsem_create(uc, ret); -#endif - if (name != NULL) - sem_enter(td->td_proc, ret); - *ksret = ret; - mtx_lock(&sem_lock); - nsems++; - if (nsems > p31b_getcfg(CTL_P1003_1B_SEM_NSEMS_MAX)) { - sem_leave(td->td_proc, ret); - sem_free(ret); - error = ENFILE; - } else - error = 0; - mtx_unlock(&sem_lock); + error = vaccess(VREG, ks->ks_mode, ks->ks_uid, ks->ks_gid, + VREAD | VWRITE, ucred, NULL); + if (error) + error = priv_check_cred(ucred, PRIV_SEM_WRITE, 0); return (error); } +/* + * Dictionary management. We maintain an in-kernel dictionary to map + * paths to semaphore objects. We use the FNV hash on the path to + * store the mappings in a hash table. + */ +static struct ksem * +ksem_lookup(char *path, Fnv32_t fnv) +{ + struct ksem_mapping *map; + + LIST_FOREACH(map, KSEM_HASH(fnv), km_link) { + if (map->km_fnv != fnv) + continue; + if (strcmp(map->km_path, path) == 0) + return (map->km_ksem); + } + + return (NULL); +} + +static void +ksem_insert(char *path, Fnv32_t fnv, struct ksem *ks) +{ + struct ksem_mapping *map; + + map = malloc(sizeof(struct ksem_mapping), M_KSEM, M_WAITOK); + map->km_path = path; + map->km_fnv = fnv; + map->km_ksem = ksem_hold(ks); + LIST_INSERT_HEAD(KSEM_HASH(fnv), map, km_link); +} + +static int +ksem_remove(char *path, Fnv32_t fnv, struct ucred *ucred) +{ + struct ksem_mapping *map; + int error; + + LIST_FOREACH(map, KSEM_HASH(fnv), km_link) { + if (map->km_fnv != fnv) + continue; + if (strcmp(map->km_path, path) == 0) { +#ifdef MAC + error = mac_posixsem_check_unlink(ucred, map->km_ksem); + if (error) + return (error); +#endif + error = ksem_access(map->km_ksem, ucred); + if (error) + return (error); + LIST_REMOVE(map, km_link); + ksem_drop(map->km_ksem); + free(map->km_path, M_KSEM); + free(map, M_KSEM); + return (0); + } + } + + return (ENOENT); +} + +/* Other helper routines. */ +static int +ksem_create(struct thread *td, const char *name, semid_t *semidp, mode_t mode, + unsigned int value, int flags) +{ + struct filedesc *fdp; + struct ksem *ks; + struct file *fp; + char *path; + semid_t semid; + Fnv32_t fnv; + int error, fd; + + if (value > SEM_VALUE_MAX) + return (EINVAL); + + fdp = td->td_proc->p_fd; + mode = (mode & ~fdp->fd_cmask) & ACCESSPERMS; + error = falloc(td, &fp, &fd); + if (error) { + if (name == NULL) + error = ENOSPC; + return (error); + } + + /* + * Go ahead and copyout the file descriptor now. This is a bit + * premature, but it is a lot easier to handle errors as opposed + * to later when we've possibly created a new semaphore, etc. + */ + semid = fd; + error = copyout(&semid, semidp, sizeof(semid)); + if (error) { + fdclose(fdp, fp, fd, td); + fdrop(fp, td); + return (error); + } + + if (name == NULL) { + /* Create an anonymous semaphore. */ + ks = ksem_alloc(td->td_ucred, mode, value); + if (ks == NULL) + error = ENOSPC; + else + ks->ks_flags |= KS_ANONYMOUS; + } else { + path = malloc(MAXPATHLEN, M_KSEM, M_WAITOK); + error = copyinstr(name, path, MAXPATHLEN, NULL); + + /* Require paths to start with a '/' character. */ + if (error == 0 && path[0] != '/') + error = EINVAL; + if (error) { + fdclose(fdp, fp, fd, td); + fdrop(fp, td); + free(path, M_KSEM); + return (error); + } + + fnv = fnv_32_str(path, FNV1_32_INIT); + sx_xlock(&ksem_dict_lock); + ks = ksem_lookup(path, fnv); + if (ks == NULL) { + /* Object does not exist, create it if requested. */ + if (flags & O_CREAT) { + ks = ksem_alloc(td->td_ucred, mode, value); + if (ks == NULL) + error = ENFILE; + else { + ksem_insert(path, fnv, ks); + path = NULL; + } + } else + error = ENOENT; + } else { + /* + * Object already exists, obtain a new + * reference if requested and permitted. + */ + if ((flags & (O_CREAT | O_EXCL)) == + (O_CREAT | O_EXCL)) + error = EEXIST; + else { +#ifdef MAC + error = mac_posixsem_check_open(td->td_ucred, + ks); + if (error == 0) +#endif + error = ksem_access(ks, td->td_ucred); + } + if (error == 0) + ksem_hold(ks); +#ifdef INVARIANTS + else + ks = NULL; +#endif + } + sx_xunlock(&ksem_dict_lock); + if (path) + free(path, M_KSEM); + } + + if (error) { + KASSERT(ks == NULL, ("ksem_create error with a ksem")); + fdclose(fdp, fp, fd, td); + fdrop(fp, td); + return (error); + } + KASSERT(ks != NULL, ("ksem_create w/o a ksem")); + + finit(fp, FREAD | FWRITE, DTYPE_SEM, ks, &ksem_ops); + + FILEDESC_XLOCK(fdp); + if (fdp->fd_ofiles[fd] == fp) + fdp->fd_ofileflags[fd] |= UF_EXCLOSE; + FILEDESC_XUNLOCK(fdp); + fdrop(fp, td); + + return (0); +} + +static int +ksem_get(struct thread *td, semid_t id, struct file **fpp) +{ + struct ksem *ks; + struct file *fp; + int error; + + error = fget(td, id, &fp); + if (error) + return (EINVAL); + if (fp->f_type != DTYPE_SEM) { + fdrop(fp, td); + return (EINVAL); + } + ks = fp->f_data; + if (ks->ks_flags & KS_DEAD) { + fdrop(fp, td); + return (EINVAL); + } + *fpp = fp; + return (0); +} + +/* System calls. */ #ifndef _SYS_SYSPROTO_H_ struct ksem_init_args { - unsigned int value; - semid_t *idp; + unsigned int value; + semid_t *idp; }; -int ksem_init(struct thread *td, struct ksem_init_args *uap); #endif int ksem_init(struct thread *td, struct ksem_init_args *uap) { - return (kern_sem_init(td, UIO_USERSPACE, uap->value, uap->idp)); -} - -static int -kern_sem_init(struct thread *td, int dir, unsigned int value, semid_t *idp) -{ - struct ksem *ks; - semid_t id; - int error; - - error = sem_create(td, NULL, &ks, S_IRWXU | S_IRWXG, value); - if (error) - return (error); - id = SEM_TO_ID(ks); - if (dir == UIO_USERSPACE) { - error = copyout(&id, idp, sizeof(id)); - if (error) { - mtx_lock(&sem_lock); - sem_rel(ks); - mtx_unlock(&sem_lock); - return (error); - } - } else { - *idp = id; - } - mtx_lock(&sem_lock); - LIST_INSERT_HEAD(&ksem_head, ks, ks_entry); - ks->ks_onlist = 1; - mtx_unlock(&sem_lock); - return (error); + return (ksem_create(td, NULL, uap->idp, S_IRWXU | S_IRWXG, uap->value, + 0)); } #ifndef _SYS_SYSPROTO_H_ struct ksem_open_args { - char *name; - int oflag; - mode_t mode; - unsigned int value; - semid_t *idp; + char *name; + int oflag; + mode_t mode; + unsigned int value; + semid_t *idp; }; -int ksem_open(struct thread *td, struct ksem_open_args *uap); #endif int ksem_open(struct thread *td, struct ksem_open_args *uap) { - char name[SEM_MAX_NAMELEN + 1]; - size_t done; - int error; - error = copyinstr(uap->name, name, SEM_MAX_NAMELEN + 1, &done); - if (error) - return (error); - DP((">>> sem_open start\n")); - error = kern_sem_open(td, UIO_USERSPACE, - name, uap->oflag, uap->mode, uap->value, uap->idp); - DP(("<<< sem_open end\n")); - return (error); -} - -static int -kern_sem_open(struct thread *td, int dir, const char *name, int oflag, - mode_t mode, unsigned int value, semid_t *idp) -{ - struct ksem *ksnew, *ks; - int error; - semid_t id; - - ksnew = NULL; - mtx_lock(&sem_lock); - ks = sem_lookup_byname(name); - - /* - * If we found it but O_EXCL is set, error. - */ - if (ks != NULL && (oflag & O_EXCL) != 0) { - mtx_unlock(&sem_lock); - return (EEXIST); - } - - /* - * If we didn't find it... - */ - if (ks == NULL) { - /* - * didn't ask for creation? error. - */ - if ((oflag & O_CREAT) == 0) { - mtx_unlock(&sem_lock); - return (ENOENT); - } - - /* - * We may block during creation, so drop the lock. - */ - mtx_unlock(&sem_lock); - error = sem_create(td, name, &ksnew, mode, value); - if (error != 0) - return (error); - id = SEM_TO_ID(ksnew); - if (dir == UIO_USERSPACE) { - DP(("about to copyout! %d to %p\n", id, idp)); - error = copyout(&id, idp, sizeof(id)); - if (error) { - mtx_lock(&sem_lock); - sem_leave(td->td_proc, ksnew); - sem_rel(ksnew); - mtx_unlock(&sem_lock); - return (error); - } - } else { - DP(("about to set! %d to %p\n", id, idp)); - *idp = id; - } - - /* - * We need to make sure we haven't lost a race while - * allocating during creation. - */ - mtx_lock(&sem_lock); - ks = sem_lookup_byname(name); - if (ks != NULL) { - /* we lost... */ - sem_leave(td->td_proc, ksnew); - sem_rel(ksnew); - /* we lost and we can't loose... */ - if ((oflag & O_EXCL) != 0) { - mtx_unlock(&sem_lock); - return (EEXIST); - } - } else { - DP(("sem_create: about to add to list...\n")); - LIST_INSERT_HEAD(&ksem_head, ksnew, ks_entry); - DP(("sem_create: setting list bit...\n")); - ksnew->ks_onlist = 1; - DP(("sem_create: done, about to unlock...\n")); - } - } else { -#ifdef MAC - error = mac_posixsem_check_open(td->td_ucred, ks); - if (error) - goto err_open; -#endif - /* - * if we aren't the creator, then enforce permissions. - */ - error = sem_perm(td, ks); - if (error) - goto err_open; - sem_ref(ks); - mtx_unlock(&sem_lock); - id = SEM_TO_ID(ks); - if (dir == UIO_USERSPACE) { - error = copyout(&id, idp, sizeof(id)); - if (error) { - mtx_lock(&sem_lock); - sem_rel(ks); - mtx_unlock(&sem_lock); - return (error); - } - } else { - *idp = id; - } - sem_enter(td->td_proc, ks); - mtx_lock(&sem_lock); - sem_rel(ks); - } -err_open: - mtx_unlock(&sem_lock); - return (error); -} - -static int -sem_perm(struct thread *td, struct ksem *ks) -{ - struct ucred *uc; - - /* - * XXXRW: This permission routine appears to be incorrect. If the - * user matches, we shouldn't go on to the group if the user - * permissions don't allow the action? Not changed for now. To fix, - * change from a series of if (); if (); to if () else if () else... - */ - uc = td->td_ucred; - DP(("sem_perm: uc(%d,%d) ks(%d,%d,%o)\n", - uc->cr_uid, uc->cr_gid, - ks->ks_uid, ks->ks_gid, ks->ks_mode)); - if ((uc->cr_uid == ks->ks_uid) && (ks->ks_mode & S_IWUSR) != 0) - return (0); - if ((uc->cr_gid == ks->ks_gid) && (ks->ks_mode & S_IWGRP) != 0) - return (0); - if ((ks->ks_mode & S_IWOTH) != 0) - return (0); - return (priv_check(td, PRIV_SEM_WRITE)); -} - -static void -sem_free(struct ksem *ks) -{ - -#ifdef MAC - mac_posixsem_destroy(ks); -#endif - nsems--; - if (ks->ks_onlist) - LIST_REMOVE(ks, ks_entry); - if (ks->ks_name != NULL) - free(ks->ks_name, M_SEM); - cv_destroy(&ks->ks_cv); - free(ks, M_SEM); -} - -static __inline struct kuser * -sem_getuser(struct proc *p, struct ksem *ks) -{ - struct kuser *k; - - LIST_FOREACH(k, &ks->ks_users, ku_next) - if (k->ku_pid == p->p_pid) - return (k); - return (NULL); -} - -static int -sem_hasopen(struct thread *td, struct ksem *ks) -{ - - return ((ks->ks_name == NULL && sem_perm(td, ks) == 0) - || sem_getuser(td->td_proc, ks) != NULL); -} - -static int -sem_leave(struct proc *p, struct ksem *ks) -{ - struct kuser *k; - - DP(("sem_leave: ks = %p\n", ks)); - k = sem_getuser(p, ks); - DP(("sem_leave: ks = %p, k = %p\n", ks, k)); - if (k != NULL) { - LIST_REMOVE(k, ku_next); - sem_rel(ks); - DP(("sem_leave: about to free k\n")); - free(k, M_SEM); - DP(("sem_leave: returning\n")); - return (0); - } - return (EINVAL); -} - -static void -sem_enter(struct proc *p, struct ksem *ks) -{ - struct kuser *ku, *k; - - ku = malloc(sizeof(*ku), M_SEM, M_WAITOK); - ku->ku_pid = p->p_pid; - mtx_lock(&sem_lock); - k = sem_getuser(p, ks); - if (k != NULL) { - mtx_unlock(&sem_lock); - free(ku, M_TEMP); - return; - } - LIST_INSERT_HEAD(&ks->ks_users, ku, ku_next); - sem_ref(ks); - mtx_unlock(&sem_lock); + if ((uap->oflag & ~(O_CREAT | O_EXCL)) != 0) + return (EINVAL); + return (ksem_create(td, uap->name, uap->idp, uap->mode, uap->value, + uap->oflag)); } #ifndef _SYS_SYSPROTO_H_ struct ksem_unlink_args { - char *name; + char *name; }; -int ksem_unlink(struct thread *td, struct ksem_unlink_args *uap); #endif int ksem_unlink(struct thread *td, struct ksem_unlink_args *uap) { - char name[SEM_MAX_NAMELEN + 1]; - size_t done; + char *path; + Fnv32_t fnv; int error; - error = copyinstr(uap->name, name, SEM_MAX_NAMELEN + 1, &done); - return (error ? error : - kern_sem_unlink(td, name)); -} - -static int -kern_sem_unlink(struct thread *td, const char *name) -{ - struct ksem *ks; - int error; - - mtx_lock(&sem_lock); - ks = sem_lookup_byname(name); - if (ks != NULL) { -#ifdef MAC - error = mac_posixsem_check_unlink(td->td_ucred, ks); - if (error) { - mtx_unlock(&sem_lock); - return (error); - } -#endif - error = sem_perm(td, ks); - } else - error = ENOENT; - DP(("sem_unlink: '%s' ks = %p, error = %d\n", name, ks, error)); - if (error == 0) { - LIST_REMOVE(ks, ks_entry); - LIST_INSERT_HEAD(&ksem_deadhead, ks, ks_entry); - sem_rel(ks); + path = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); + error = copyinstr(uap->name, path, MAXPATHLEN, NULL); + if (error) { + free(path, M_TEMP); + return (error); } - mtx_unlock(&sem_lock); + + fnv = fnv_32_str(path, FNV1_32_INIT); + sx_xlock(&ksem_dict_lock); + error = ksem_remove(path, fnv, td->td_ucred); + sx_xunlock(&ksem_dict_lock); + free(path, M_TEMP); + return (error); } #ifndef _SYS_SYSPROTO_H_ struct ksem_close_args { - semid_t id; + semid_t id; }; -int ksem_close(struct thread *td, struct ksem_close_args *uap); #endif int ksem_close(struct thread *td, struct ksem_close_args *uap) -{ - - return (kern_sem_close(td, uap->id)); -} - -static int -kern_sem_close(struct thread *td, semid_t id) { struct ksem *ks; + struct file *fp; int error; - error = EINVAL; - mtx_lock(&sem_lock); - ks = ID_TO_SEM(id); - - /* - * This is not a valid operation for unnamed sems. - */ - if (ks != NULL && ks->ks_name != NULL) - error = sem_leave(td->td_proc, ks); - mtx_unlock(&sem_lock); + error = ksem_get(td, uap->id, &fp); + if (error) + return (error); + ks = fp->f_data; + if (ks->ks_flags & KS_ANONYMOUS) { + fdrop(fp, td); + return (EINVAL); + } + error = kern_close(td, uap->id); + fdrop(fp, td); return (error); } #ifndef _SYS_SYSPROTO_H_ struct ksem_post_args { - semid_t id; + semid_t id; }; -int ksem_post(struct thread *td, struct ksem_post_args *uap); #endif int ksem_post(struct thread *td, struct ksem_post_args *uap) { - - return (kern_sem_post(td, uap->id)); -} - -static int -kern_sem_post(struct thread *td, semid_t id) -{ + struct file *fp; struct ksem *ks; int error; + error = ksem_get(td, uap->id, &fp); + if (error) + return (error); + ks = fp->f_data; + mtx_lock(&sem_lock); - ks = ID_TO_SEM(id); - if (ks == NULL || !sem_hasopen(td, ks)) { - error = EINVAL; - goto err; - } #ifdef MAC - error = mac_posixsem_check_post(td->td_ucred, ks); + error = mac_posixsem_check_post(td->td_ucred, fp->f_cred, ks); if (error) goto err; #endif @@ -628,16 +638,17 @@ kern_sem_post(struct thread *td, semid_t id) if (ks->ks_waiters > 0) cv_signal(&ks->ks_cv); error = 0; + vfs_timestamp(&ks->ks_ctime); err: mtx_unlock(&sem_lock); + fdrop(fp, td); return (error); } #ifndef _SYS_SYSPROTO_H_ struct ksem_wait_args { - semid_t id; + semid_t id; }; -int ksem_wait(struct thread *td, struct ksem_wait_args *uap); #endif int ksem_wait(struct thread *td, struct ksem_wait_args *uap) @@ -648,10 +659,9 @@ ksem_wait(struct thread *td, struct ksem_wait_args *uap) #ifndef _SYS_SYSPROTO_H_ struct ksem_timedwait_args { - semid_t id; + semid_t id; const struct timespec *abstime; }; -int ksem_timedwait(struct thread *td, struct ksem_timedwait_args *uap); #endif int ksem_timedwait(struct thread *td, struct ksem_timedwait_args *uap) @@ -678,9 +688,8 @@ ksem_timedwait(struct thread *td, struct ksem_timedwait_args *uap) #ifndef _SYS_SYSPROTO_H_ struct ksem_trywait_args { - semid_t id; + semid_t id; }; -int ksem_trywait(struct thread *td, struct ksem_trywait_args *uap); #endif int ksem_trywait(struct thread *td, struct ksem_trywait_args *uap) @@ -695,31 +704,25 @@ kern_sem_wait(struct thread *td, semid_t id, int tryflag, { struct timespec ts1, ts2; struct timeval tv; + struct file *fp; struct ksem *ks; int error; DP((">>> kern_sem_wait entered!\n")); + error = ksem_get(td, id, &fp); + if (error) + return (error); + ks = fp->f_data; mtx_lock(&sem_lock); - ks = ID_TO_SEM(id); - if (ks == NULL) { - DP(("kern_sem_wait ks == NULL\n")); - error = EINVAL; - goto err; - } - sem_ref(ks); - if (!sem_hasopen(td, ks)) { - DP(("kern_sem_wait hasopen failed\n")); - error = EINVAL; - goto err; - } #ifdef MAC - error = mac_posixsem_check_wait(td->td_ucred, ks); + error = mac_posixsem_check_wait(td->td_ucred, fp->f_cred, ks); if (error) { DP(("kern_sem_wait mac failed\n")); goto err; } #endif DP(("kern_sem_wait value = %d, tryflag %d\n", ks->ks_value, tryflag)); + vfs_timestamp(&ks->ks_atime); if (ks->ks_value == 0) { ks->ks_waiters++; if (tryflag != 0) @@ -749,208 +752,159 @@ kern_sem_wait(struct thread *td, semid_t id, int tryflag, ks->ks_value--; error = 0; err: - if (ks != NULL) - sem_rel(ks); mtx_unlock(&sem_lock); + fdrop(fp, td); DP(("<<< kern_sem_wait leaving, error = %d\n", error)); return (error); } #ifndef _SYS_SYSPROTO_H_ struct ksem_getvalue_args { - semid_t id; - int *val; + semid_t id; + int *val; }; -int ksem_getvalue(struct thread *td, struct ksem_getvalue_args *uap); #endif int ksem_getvalue(struct thread *td, struct ksem_getvalue_args *uap) { + struct file *fp; struct ksem *ks; int error, val; + error = ksem_get(td, uap->id, &fp); + if (error) + return (error); + ks = fp->f_data; + mtx_lock(&sem_lock); - ks = ID_TO_SEM(uap->id); - if (ks == NULL || !sem_hasopen(td, ks)) { - mtx_unlock(&sem_lock); - return (EINVAL); - } #ifdef MAC - error = mac_posixsem_check_getvalue(td->td_ucred, ks); + error = mac_posixsem_check_getvalue(td->td_ucred, fp->f_cred, ks); if (error) { mtx_unlock(&sem_lock); + fdrop(fp, td); return (error); } #endif val = ks->ks_value; + vfs_timestamp(&ks->ks_atime); mtx_unlock(&sem_lock); + fdrop(fp, td); error = copyout(&val, uap->val, sizeof(val)); return (error); } #ifndef _SYS_SYSPROTO_H_ struct ksem_destroy_args { - semid_t id; + semid_t id; }; -int ksem_destroy(struct thread *td, struct ksem_destroy_args *uap); #endif int ksem_destroy(struct thread *td, struct ksem_destroy_args *uap) { + struct file *fp; struct ksem *ks; int error; - mtx_lock(&sem_lock); - ks = ID_TO_SEM(uap->id); - if (ks == NULL || !sem_hasopen(td, ks) || - ks->ks_name != NULL) { - error = EINVAL; - goto err; + error = ksem_get(td, uap->id, &fp); + if (error) + return (error); + ks = fp->f_data; + if (!(ks->ks_flags & KS_ANONYMOUS)) { + fdrop(fp, td); + return (EINVAL); } + mtx_lock(&sem_lock); if (ks->ks_waiters != 0) { + mtx_unlock(&sem_lock); error = EBUSY; goto err; } - sem_rel(ks); - error = 0; -err: + ks->ks_flags |= KS_DEAD; mtx_unlock(&sem_lock); + + error = kern_close(td, uap->id); +err: + fdrop(fp, td); return (error); } -/* - * Count the number of kusers associated with a proc, so as to guess at how - * many to allocate when forking. - */ +#define SYSCALL_DATA(syscallname) \ +static int syscallname##_syscall = SYS_##syscallname; \ +static int syscallname##_registered; \ +static struct sysent syscallname##_old_sysent; \ +MAKE_SYSENT(syscallname); + +#define SYSCALL_REGISTER(syscallname) do { \ + error = syscall_register(& syscallname##_syscall, \ + & syscallname##_sysent, & syscallname##_old_sysent); \ + if (error) \ + return (error); \ + syscallname##_registered = 1; \ +} while(0) + +#define SYSCALL_DEREGISTER(syscallname) do { \ + if (syscallname##_registered) { \ + syscallname##_registered = 0; \ + syscall_deregister(& syscallname##_syscall, \ + & syscallname##_old_sysent); \ + } \ +} while(0) + +SYSCALL_DATA(ksem_init); +SYSCALL_DATA(ksem_open); +SYSCALL_DATA(ksem_unlink); +SYSCALL_DATA(ksem_close); +SYSCALL_DATA(ksem_post); +SYSCALL_DATA(ksem_wait); +SYSCALL_DATA(ksem_timedwait); +SYSCALL_DATA(ksem_trywait); +SYSCALL_DATA(ksem_getvalue); +SYSCALL_DATA(ksem_destroy); + static int -sem_count_proc(struct proc *p) +ksem_module_init(void) { - struct ksem *ks; - struct kuser *ku; - int count; + int error; - mtx_assert(&sem_lock, MA_OWNED); + mtx_init(&sem_lock, "sem", NULL, MTX_DEF); + mtx_init(&ksem_count_lock, "ksem count", NULL, MTX_DEF); + sx_init(&ksem_dict_lock, "ksem dictionary"); + ksem_dictionary = hashinit(1024, M_KSEM, &ksem_hash); + p31b_setcfg(CTL_P1003_1B_SEM_NSEMS_MAX, SEM_MAX); + p31b_setcfg(CTL_P1003_1B_SEM_VALUE_MAX, SEM_VALUE_MAX); - count = 0; - LIST_FOREACH(ks, &ksem_head, ks_entry) { - LIST_FOREACH(ku, &ks->ks_users, ku_next) { - if (ku->ku_pid == p->p_pid) - count++; - } - } - LIST_FOREACH(ks, &ksem_deadhead, ks_entry) { - LIST_FOREACH(ku, &ks->ks_users, ku_next) { - if (ku->ku_pid == p->p_pid) - count++; - } - } - return (count); -} - -/* - * When a process forks, the child process must gain a reference to each open - * semaphore in the parent process, whether it is unlinked or not. This - * requires allocating a kuser structure for each semaphore reference in the - * new process. Because the set of semaphores in the parent can change while - * the fork is in progress, we have to handle races -- first we attempt to - * allocate enough storage to acquire references to each of the semaphores, - * then we enter the semaphores and release the temporary references. - */ -static void -sem_forkhook(void *arg, struct proc *p1, struct proc *p2, int flags) -{ - struct ksem *ks, **sem_array; - int count, i, new_count; - struct kuser *ku; - - mtx_lock(&sem_lock); - count = sem_count_proc(p1); - if (count == 0) { - mtx_unlock(&sem_lock); - return; - } -race_lost: - mtx_assert(&sem_lock, MA_OWNED); - mtx_unlock(&sem_lock); - sem_array = malloc(sizeof(struct ksem *) * count, M_TEMP, M_WAITOK); - mtx_lock(&sem_lock); - new_count = sem_count_proc(p1); - if (count < new_count) { - /* Lost race, repeat and allocate more storage. */ - free(sem_array, M_TEMP); - count = new_count; - goto race_lost; - } - - /* - * Given an array capable of storing an adequate number of semaphore - * references, now walk the list of semaphores and acquire a new - * reference for any semaphore opened by p1. - */ - count = new_count; - i = 0; - LIST_FOREACH(ks, &ksem_head, ks_entry) { - LIST_FOREACH(ku, &ks->ks_users, ku_next) { - if (ku->ku_pid == p1->p_pid) { - sem_ref(ks); - sem_array[i] = ks; - i++; - break; - } - } - } - LIST_FOREACH(ks, &ksem_deadhead, ks_entry) { - LIST_FOREACH(ku, &ks->ks_users, ku_next) { - if (ku->ku_pid == p1->p_pid) { - sem_ref(ks); - sem_array[i] = ks; - i++; - break; - } - } - } - mtx_unlock(&sem_lock); - KASSERT(i == count, ("sem_forkhook: i != count (%d, %d)", i, count)); - - /* - * Now cause p2 to enter each of the referenced semaphores, then - * release our temporary reference. This is pretty inefficient. - * Finally, free our temporary array. - */ - for (i = 0; i < count; i++) { - sem_enter(p2, sem_array[i]); - mtx_lock(&sem_lock); - sem_rel(sem_array[i]); - mtx_unlock(&sem_lock); - } - free(sem_array, M_TEMP); + SYSCALL_REGISTER(ksem_init); + SYSCALL_REGISTER(ksem_open); + SYSCALL_REGISTER(ksem_unlink); + SYSCALL_REGISTER(ksem_close); + SYSCALL_REGISTER(ksem_post); + SYSCALL_REGISTER(ksem_wait); + SYSCALL_REGISTER(ksem_timedwait); + SYSCALL_REGISTER(ksem_trywait); + SYSCALL_REGISTER(ksem_getvalue); + SYSCALL_REGISTER(ksem_destroy); + return (0); } static void -sem_exechook(void *arg, struct proc *p, struct image_params *imgp __unused) +ksem_module_destroy(void) { - sem_exithook(arg, p); -} -static void -sem_exithook(void *arg, struct proc *p) -{ - struct ksem *ks, *ksnext; + SYSCALL_DEREGISTER(ksem_init); + SYSCALL_DEREGISTER(ksem_open); + SYSCALL_DEREGISTER(ksem_unlink); + SYSCALL_DEREGISTER(ksem_close); + SYSCALL_DEREGISTER(ksem_post); + SYSCALL_DEREGISTER(ksem_wait); + SYSCALL_DEREGISTER(ksem_timedwait); + SYSCALL_DEREGISTER(ksem_trywait); + SYSCALL_DEREGISTER(ksem_getvalue); + SYSCALL_DEREGISTER(ksem_destroy); - mtx_lock(&sem_lock); - ks = LIST_FIRST(&ksem_head); - while (ks != NULL) { - ksnext = LIST_NEXT(ks, ks_entry); - sem_leave(p, ks); - ks = ksnext; - } - ks = LIST_FIRST(&ksem_deadhead); - while (ks != NULL) { - ksnext = LIST_NEXT(ks, ks_entry); - sem_leave(p, ks); - ks = ksnext; - } - mtx_unlock(&sem_lock); + hashdestroy(ksem_dictionary, M_KSEM, ksem_hash); + sx_destroy(&ksem_dict_lock); + mtx_destroy(&ksem_count_lock); + mtx_destroy(&sem_lock); } static int @@ -960,26 +914,21 @@ sem_modload(struct module *module, int cmd, void *arg) switch (cmd) { case MOD_LOAD: - mtx_init(&sem_lock, "sem", "semaphore", MTX_DEF); - p31b_setcfg(CTL_P1003_1B_SEM_NSEMS_MAX, SEM_MAX); - p31b_setcfg(CTL_P1003_1B_SEM_VALUE_MAX, SEM_VALUE_MAX); - sem_exit_tag = EVENTHANDLER_REGISTER(process_exit, - sem_exithook, NULL, EVENTHANDLER_PRI_ANY); - sem_exec_tag = EVENTHANDLER_REGISTER(process_exec, - sem_exechook, NULL, EVENTHANDLER_PRI_ANY); - sem_fork_tag = EVENTHANDLER_REGISTER(process_fork, - sem_forkhook, NULL, EVENTHANDLER_PRI_ANY); + error = ksem_module_init(); + if (error) + ksem_module_destroy(); break; case MOD_UNLOAD: + mtx_lock(&ksem_count_lock); if (nsems != 0) { error = EOPNOTSUPP; + mtx_unlock(&ksem_count_lock); break; } - EVENTHANDLER_DEREGISTER(process_exit, sem_exit_tag); - EVENTHANDLER_DEREGISTER(process_exec, sem_exec_tag); - EVENTHANDLER_DEREGISTER(process_fork, sem_fork_tag); - mtx_destroy(&sem_lock); + ksem_dead = 1; + mtx_unlock(&ksem_count_lock); + ksem_module_destroy(); break; case MOD_SHUTDOWN: @@ -997,16 +946,5 @@ static moduledata_t sem_mod = { NULL }; -SYSCALL_MODULE_HELPER(ksem_init); -SYSCALL_MODULE_HELPER(ksem_open); -SYSCALL_MODULE_HELPER(ksem_unlink); -SYSCALL_MODULE_HELPER(ksem_close); -SYSCALL_MODULE_HELPER(ksem_post); -SYSCALL_MODULE_HELPER(ksem_wait); -SYSCALL_MODULE_HELPER(ksem_timedwait); -SYSCALL_MODULE_HELPER(ksem_trywait); -SYSCALL_MODULE_HELPER(ksem_getvalue); -SYSCALL_MODULE_HELPER(ksem_destroy); - DECLARE_MODULE(sem, sem_mod, SI_SUB_SYSV_SEM, SI_ORDER_FIRST); MODULE_VERSION(sem, 1); diff --git a/sys/kern/uipc_shm.c b/sys/kern/uipc_shm.c index 899896b27e3f..577a8fec8afd 100644 --- a/sys/kern/uipc_shm.c +++ b/sys/kern/uipc_shm.c @@ -46,6 +46,10 @@ * worry about the bits on disk if the page is swapped out or will the * swapper zero the parts of a page that are invalid if the page is * swapped back in for us? + * + * (6) Add MAC support in mac_biba(4) and mac_mls(4). + * + * (7) Add a MAC check_create() hook for creating new named objects. */ #include diff --git a/sys/modules/sem/Makefile b/sys/modules/sem/Makefile index 8b88444a995d..26178b081af0 100644 --- a/sys/modules/sem/Makefile +++ b/sys/modules/sem/Makefile @@ -3,6 +3,6 @@ .PATH: ${.CURDIR}/../../kern KMOD= sem -SRCS= uipc_sem.c opt_mac.h opt_posix.h +SRCS= uipc_sem.c opt_mac.h opt_posix.h vnode_if.h .include diff --git a/sys/security/mac/mac_framework.h b/sys/security/mac/mac_framework.h index c68d2d1ff8e0..2a8b00ca2f51 100644 --- a/sys/security/mac/mac_framework.h +++ b/sys/security/mac/mac_framework.h @@ -189,11 +189,16 @@ void mac_pipe_init(struct pipepair *); int mac_pipe_label_set(struct ucred *cred, struct pipepair *pp, struct label *label); -int mac_posixsem_check_getvalue(struct ucred *cred,struct ksem *ks); +int mac_posixsem_check_getvalue(struct ucred *active_cred, + struct ucred *file_cred, struct ksem *ks); int mac_posixsem_check_open(struct ucred *cred, struct ksem *ks); -int mac_posixsem_check_post(struct ucred *cred, struct ksem *ks); +int mac_posixsem_check_post(struct ucred *active_cred, + struct ucred *file_cred, struct ksem *ks); +int mac_posixsem_check_stat(struct ucred *active_cred, + struct ucred *file_cred, struct ksem *ks); int mac_posixsem_check_unlink(struct ucred *cred, struct ksem *ks); -int mac_posixsem_check_wait(struct ucred *cred, struct ksem *ks); +int mac_posixsem_check_wait(struct ucred *active_cred, + struct ucred *file_cred, struct ksem *ks); void mac_posixsem_create(struct ucred *cred, struct ksem *ks); void mac_posixsem_destroy(struct ksem *); void mac_posixsem_init(struct ksem *); diff --git a/sys/security/mac/mac_policy.h b/sys/security/mac/mac_policy.h index f0fa7558674b..532ca240d4dd 100644 --- a/sys/security/mac/mac_policy.h +++ b/sys/security/mac/mac_policy.h @@ -288,16 +288,22 @@ typedef int (*mpo_pipe_internalize_label_t)(struct label *label, typedef void (*mpo_pipe_relabel_t)(struct ucred *cred, struct pipepair *pp, struct label *oldlabel, struct label *newlabel); -typedef int (*mpo_posixsem_check_getvalue_t)(struct ucred *cred, - struct ksem *ks, struct label *kslabel); +typedef int (*mpo_posixsem_check_getvalue_t)(struct ucred *active_cred, + struct ucred *file_cred, struct ksem *ks, + struct label *kslabel); typedef int (*mpo_posixsem_check_open_t)(struct ucred *cred, struct ksem *ks, struct label *kslabel); -typedef int (*mpo_posixsem_check_post_t)(struct ucred *cred, - struct ksem *ks, struct label *kslabel); +typedef int (*mpo_posixsem_check_post_t)(struct ucred *active_cred, + struct ucred *file_cred, struct ksem *ks, + struct label *kslabel); +typedef int (*mpo_posixsem_check_stat_t)(struct ucred *active_cred, + struct ucred *file_cred, struct ksem *ks, + struct label *kslabel); typedef int (*mpo_posixsem_check_unlink_t)(struct ucred *cred, struct ksem *ks, struct label *kslabel); -typedef int (*mpo_posixsem_check_wait_t)(struct ucred *cred, - struct ksem *ks, struct label *kslabel); +typedef int (*mpo_posixsem_check_wait_t)(struct ucred *active_cred, + struct ucred *file_cred, struct ksem *ks, + struct label *kslabel); typedef void (*mpo_posixsem_create_t)(struct ucred *cred, struct ksem *ks, struct label *kslabel); typedef void (*mpo_posixsem_destroy_label_t)(struct label *label); @@ -742,6 +748,7 @@ struct mac_policy_ops { mpo_posixsem_check_getvalue_t mpo_posixsem_check_getvalue; mpo_posixsem_check_open_t mpo_posixsem_check_open; mpo_posixsem_check_post_t mpo_posixsem_check_post; + mpo_posixsem_check_stat_t mpo_posixsem_check_stat; mpo_posixsem_check_unlink_t mpo_posixsem_check_unlink; mpo_posixsem_check_wait_t mpo_posixsem_check_wait; mpo_posixsem_create_t mpo_posixsem_create; diff --git a/sys/security/mac/mac_posix_sem.c b/sys/security/mac/mac_posix_sem.c index 68fb56c2d1cf..2296afeeb7ac 100644 --- a/sys/security/mac/mac_posix_sem.c +++ b/sys/security/mac/mac_posix_sem.c @@ -101,21 +101,37 @@ mac_posixsem_check_open(struct ucred *cred, struct ksem *ks) } int -mac_posixsem_check_getvalue(struct ucred *cred, struct ksem *ks) +mac_posixsem_check_getvalue(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks) { int error; - MAC_CHECK(posixsem_check_getvalue, cred, ks, ks->ks_label); + MAC_CHECK(posixsem_check_getvalue, active_cred, file_cred, ks, + ks->ks_label); return (error); } int -mac_posixsem_check_post(struct ucred *cred, struct ksem *ks) +mac_posixsem_check_post(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks) { int error; - MAC_CHECK(posixsem_check_post, cred, ks, ks->ks_label); + MAC_CHECK(posixsem_check_post, active_cred, file_cred, ks, + ks->ks_label); + + return (error); +} + +int +mac_posixsem_check_stat(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks) +{ + int error; + + MAC_CHECK(posixsem_check_stat, active_cred, file_cred, ks, + ks->ks_label); return (error); } @@ -131,11 +147,13 @@ mac_posixsem_check_unlink(struct ucred *cred, struct ksem *ks) } int -mac_posixsem_check_wait(struct ucred *cred, struct ksem *ks) +mac_posixsem_check_wait(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks) { int error; - MAC_CHECK(posixsem_check_wait, cred, ks, ks->ks_label); + MAC_CHECK(posixsem_check_wait, active_cred, file_cred, ks, + ks->ks_label); return (error); } diff --git a/sys/security/mac_biba/mac_biba.c b/sys/security/mac_biba/mac_biba.c index 4c0c85b21f54..960591c28be7 100644 --- a/sys/security/mac_biba/mac_biba.c +++ b/sys/security/mac_biba/mac_biba.c @@ -1504,7 +1504,7 @@ biba_pipe_relabel(struct ucred *cred, struct pipepair *pp, } static int -biba_posixsem_check_write(struct ucred *cred, struct ksem *ks, +biba_posixsem_check_openunlink(struct ucred *cred, struct ksem *ks, struct label *kslabel) { struct mac_biba *subj, *obj; @@ -1522,15 +1522,33 @@ biba_posixsem_check_write(struct ucred *cred, struct ksem *ks, } static int -biba_posixsem_check_rdonly(struct ucred *cred, struct ksem *ks, - struct label *kslabel) +biba_posixsem_check_write(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks, struct label *kslabel) { struct mac_biba *subj, *obj; if (!biba_enabled) return (0); - subj = SLOT(cred->cr_label); + subj = SLOT(active_cred->cr_label); + obj = SLOT(kslabel); + + if (!biba_dominate_effective(subj, obj)) + return (EACCES); + + return (0); +} + +static int +biba_posixsem_check_rdonly(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks, struct label *kslabel) +{ + struct mac_biba *subj, *obj; + + if (!biba_enabled) + return (0); + + subj = SLOT(active_cred->cr_label); obj = SLOT(kslabel); if (!biba_dominate_effective(obj, subj)) @@ -3335,9 +3353,10 @@ static struct mac_policy_ops mac_biba_ops = .mpo_pipe_relabel = biba_pipe_relabel, .mpo_posixsem_check_getvalue = biba_posixsem_check_rdonly, - .mpo_posixsem_check_open = biba_posixsem_check_write, + .mpo_posixsem_check_open = biba_posixsem_check_openunlink, .mpo_posixsem_check_post = biba_posixsem_check_write, - .mpo_posixsem_check_unlink = biba_posixsem_check_write, + .mpo_posixsem_check_stat = biba_posixsem_check_rdonly, + .mpo_posixsem_check_unlink = biba_posixsem_check_openunlink, .mpo_posixsem_check_wait = biba_posixsem_check_write, .mpo_posixsem_create = biba_posixsem_create, .mpo_posixsem_destroy_label = biba_destroy_label, diff --git a/sys/security/mac_mls/mac_mls.c b/sys/security/mac_mls/mac_mls.c index cff9d887181e..34c618ce981d 100644 --- a/sys/security/mac_mls/mac_mls.c +++ b/sys/security/mac_mls/mac_mls.c @@ -1400,7 +1400,7 @@ mls_pipe_relabel(struct ucred *cred, struct pipepair *pp, } static int -mls_posixsem_check_rdonly(struct ucred *cred, struct ksem *ks, +mls_posixsem_check_openunlink(struct ucred *cred, struct ksem *ks, struct label *kslabel) { struct mac_mls *subj, *obj; @@ -1411,6 +1411,24 @@ mls_posixsem_check_rdonly(struct ucred *cred, struct ksem *ks, subj = SLOT(cred->cr_label); obj = SLOT(kslabel); + if (!mls_dominate_effective(obj, subj)) + return (EACCES); + + return (0); +} + +static int +mls_posixsem_check_rdonly(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks, struct label *kslabel) +{ + struct mac_mls *subj, *obj; + + if (!mls_enabled) + return (0); + + subj = SLOT(active_cred->cr_label); + obj = SLOT(kslabel); + if (!mls_dominate_effective(subj, obj)) return (EACCES); @@ -1418,15 +1436,15 @@ mls_posixsem_check_rdonly(struct ucred *cred, struct ksem *ks, } static int -mls_posixsem_check_write(struct ucred *cred, struct ksem *ks, - struct label *kslabel) +mls_posixsem_check_write(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks, struct label *kslabel) { struct mac_mls *subj, *obj; if (!mls_enabled) return (0); - subj = SLOT(cred->cr_label); + subj = SLOT(active_cred->cr_label); obj = SLOT(kslabel); if (!mls_dominate_effective(obj, subj)) @@ -2958,9 +2976,10 @@ static struct mac_policy_ops mls_ops = .mpo_pipe_relabel = mls_pipe_relabel, .mpo_posixsem_check_getvalue = mls_posixsem_check_rdonly, - .mpo_posixsem_check_open = mls_posixsem_check_write, + .mpo_posixsem_check_open = mls_posixsem_check_openunlink, .mpo_posixsem_check_post = mls_posixsem_check_write, - .mpo_posixsem_check_unlink = mls_posixsem_check_write, + .mpo_posixsem_check_stat = mls_posixsem_check_rdonly, + .mpo_posixsem_check_unlink = mls_posixsem_check_openunlink, .mpo_posixsem_check_wait = mls_posixsem_check_write, .mpo_posixsem_create = mls_posixsem_create, .mpo_posixsem_destroy_label = mls_destroy_label, diff --git a/sys/security/mac_stub/mac_stub.c b/sys/security/mac_stub/mac_stub.c index 34f5cadeab2a..36339b551235 100644 --- a/sys/security/mac_stub/mac_stub.c +++ b/sys/security/mac_stub/mac_stub.c @@ -523,8 +523,8 @@ stub_pipe_relabel(struct ucred *cred, struct pipepair *pp, } static int -stub_posixsem_check_getvalue(struct ucred *cred, struct ksem *ks, - struct label *kslabel) +stub_posixsem_check_getvalue(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks, struct label *kslabel) { return (0); @@ -539,8 +539,16 @@ stub_posixsem_check_open(struct ucred *cred, struct ksem *ks, } static int -stub_posixsem_check_post(struct ucred *cred, struct ksem *ks, - struct label *kslabel) +stub_posixsem_check_post(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks, struct label *kslabel) +{ + + return (0); +} + +static int +stub_posixsem_check_stat(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks, struct label *kslabel) { return (0); @@ -555,8 +563,8 @@ stub_posixsem_check_unlink(struct ucred *cred, struct ksem *ks, } static int -stub_posixsem_check_wait(struct ucred *cred, struct ksem *ks, - struct label *kslabel) +stub_posixsem_check_wait(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks, struct label *kslabel) { return (0); @@ -1582,6 +1590,7 @@ static struct mac_policy_ops stub_ops = .mpo_posixsem_check_getvalue = stub_posixsem_check_getvalue, .mpo_posixsem_check_open = stub_posixsem_check_open, .mpo_posixsem_check_post = stub_posixsem_check_post, + .mpo_posixsem_check_stat = stub_posixsem_check_stat, .mpo_posixsem_check_unlink = stub_posixsem_check_unlink, .mpo_posixsem_check_wait = stub_posixsem_check_wait, .mpo_posixsem_create = stub_posixsem_create, diff --git a/sys/security/mac_test/mac_test.c b/sys/security/mac_test/mac_test.c index c25e93784198..5e788b91bcb2 100644 --- a/sys/security/mac_test/mac_test.c +++ b/sys/security/mac_test/mac_test.c @@ -1012,11 +1012,12 @@ test_pipe_relabel(struct ucred *cred, struct pipepair *pp, COUNTER_DECL(posixsem_check_getvalue); static int -test_posixsem_check_getvalue(struct ucred *cred, struct ksem *ks, - struct label *kslabel) +test_posixsem_check_getvalue(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks, struct label *kslabel) { - LABEL_CHECK(cred->cr_label, MAGIC_CRED); + LABEL_CHECK(active_cred->cr_label, MAGIC_CRED); + LABEL_CHECK(file_cred->cr_label, MAGIC_CRED); LABEL_CHECK(kslabel, MAGIC_POSIX_SEM); COUNTER_INC(posixsem_check_getvalue); @@ -1038,17 +1039,31 @@ test_posixsem_check_open(struct ucred *cred, struct ksem *ks, COUNTER_DECL(posixsem_check_post); static int -test_posixsem_check_post(struct ucred *cred, struct ksem *ks, - struct label *kslabel) +test_posixsem_check_post(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks, struct label *kslabel) { - LABEL_CHECK(cred->cr_label, MAGIC_CRED); + LABEL_CHECK(active_cred->cr_label, MAGIC_CRED); + LABEL_CHECK(file_cred->cr_label, MAGIC_CRED); LABEL_CHECK(kslabel, MAGIC_POSIX_SEM); COUNTER_INC(posixsem_check_post); return (0); } +COUNTER_DECL(posixsem_check_stat); +static int +test_posixsem_check_stat(struct ucred *active_cred, + struct ucred *file_cred, struct ksem *ks, struct label *kslabel) +{ + + LABEL_CHECK(active_cred->cr_label, MAGIC_CRED); + LABEL_CHECK(file_cred->cr_label, MAGIC_CRED); + LABEL_CHECK(kslabel, MAGIC_POSIX_SEM); + COUNTER_INC(posixsem_check_stat); + return (0); +} + COUNTER_DECL(posixsem_check_unlink); static int test_posixsem_check_unlink(struct ucred *cred, struct ksem *ks, @@ -1064,11 +1079,12 @@ test_posixsem_check_unlink(struct ucred *cred, struct ksem *ks, COUNTER_DECL(posixsem_check_wait); static int -test_posixsem_check_wait(struct ucred *cred, struct ksem *ks, - struct label *kslabel) +test_posixsem_check_wait(struct ucred *active_cred, struct ucred *file_cred, + struct ksem *ks, struct label *kslabel) { - LABEL_CHECK(cred->cr_label, MAGIC_CRED); + LABEL_CHECK(active_cred->cr_label, MAGIC_CRED); + LABEL_CHECK(file_cred->cr_label, MAGIC_CRED); LABEL_CHECK(kslabel, MAGIC_POSIX_SEM); COUNTER_INC(posixsem_check_wait); @@ -2881,6 +2897,7 @@ static struct mac_policy_ops test_ops = .mpo_posixsem_check_getvalue = test_posixsem_check_getvalue, .mpo_posixsem_check_open = test_posixsem_check_open, .mpo_posixsem_check_post = test_posixsem_check_post, + .mpo_posixsem_check_stat = test_posixsem_check_stat, .mpo_posixsem_check_unlink = test_posixsem_check_unlink, .mpo_posixsem_check_wait = test_posixsem_check_wait, .mpo_posixsem_create = test_posixsem_create, diff --git a/sys/sys/file.h b/sys/sys/file.h index b2a6820c333e..6f7dc0021a1b 100644 --- a/sys/sys/file.h +++ b/sys/sys/file.h @@ -61,6 +61,7 @@ struct socket; #define DTYPE_CRYPTO 6 /* crypto */ #define DTYPE_MQUEUE 7 /* posix message queue */ #define DTYPE_SHM 8 /* swap-backed shared memory */ +#define DTYPE_SEM 9 /* posix semaphore */ #ifdef _KERNEL diff --git a/sys/sys/ksem.h b/sys/sys/ksem.h index 71979da328bb..da868fad1016 100644 --- a/sys/sys/ksem.h +++ b/sys/sys/ksem.h @@ -34,26 +34,32 @@ #endif #include -#include - -struct kuser { - pid_t ku_pid; - LIST_ENTRY(kuser) ku_next; -}; struct ksem { - LIST_ENTRY(ksem) ks_entry; /* global list entry */ - int ks_onlist; /* boolean if on a list (ks_entry) */ - char *ks_name; /* if named, this is the name */ - int ks_ref; /* number of references */ - mode_t ks_mode; /* protection bits */ - uid_t ks_uid; /* creator uid */ - gid_t ks_gid; /* creator gid */ - unsigned int ks_value; /* current value */ - struct cv ks_cv; /* waiters sleep here */ - int ks_waiters; /* number of waiters */ - LIST_HEAD(, kuser) ks_users; /* pids using this sem */ - struct label *ks_label; /* MAC label */ + int ks_ref; /* number of references */ + mode_t ks_mode; /* protection bits */ + uid_t ks_uid; /* creator uid */ + gid_t ks_gid; /* creator gid */ + unsigned int ks_value; /* current value */ + struct cv ks_cv; /* waiters sleep here */ + int ks_waiters; /* number of waiters */ + int ks_flags; + + /* + * Values maintained solely to make this a better-behaved file + * descriptor for fstat() to run on. + * + * XXX: dubious + */ + struct timespec ks_atime; + struct timespec ks_mtime; + struct timespec ks_ctime; + struct timespec ks_birthtime; + + struct label *ks_label; /* MAC label */ }; +#define KS_ANONYMOUS 0x0001 /* Anonymous (unnamed) semaphore. */ +#define KS_DEAD 0x0002 /* No new waiters allowed. */ + #endif /* !_POSIX4_KSEM_H_ */ diff --git a/sys/sys/user.h b/sys/sys/user.h index a6237f4e0c98..e8b6a1f2f4e3 100644 --- a/sys/sys/user.h +++ b/sys/sys/user.h @@ -249,6 +249,7 @@ struct user { #define KF_TYPE_CRYPTO 6 #define KF_TYPE_MQUEUE 7 #define KF_TYPE_SHM 8 +#define KF_TYPE_SEM 9 #define KF_TYPE_UNKNOWN 255 #define KF_VTYPE_VNON 0 diff --git a/tools/regression/posixsem/Makefile b/tools/regression/posixsem/Makefile new file mode 100644 index 000000000000..f7568f40781b --- /dev/null +++ b/tools/regression/posixsem/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +PROG= posixsem +SRCS= posixsem.c test.c +DPADD= ${LIBKVM} +LDADD= -lkvm +NO_MAN= + +WARNS?= 3 + +.include diff --git a/tools/regression/posixsem/posixsem.c b/tools/regression/posixsem/posixsem.c new file mode 100644 index 000000000000..465d6e7f26df --- /dev/null +++ b/tools/regression/posixsem/posixsem.c @@ -0,0 +1,1437 @@ +/*- + * Copyright (c) 2008 Yahoo!, Inc. + * All rights reserved. + * Written by: John Baldwin + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test.h" + +/* Cut and pasted from kernel header, bah! */ + +/* Operations on timespecs */ +#define timespecclear(tvp) ((tvp)->tv_sec = (tvp)->tv_nsec = 0) +#define timespecisset(tvp) ((tvp)->tv_sec || (tvp)->tv_nsec) +#define timespeccmp(tvp, uvp, cmp) \ + (((tvp)->tv_sec == (uvp)->tv_sec) ? \ + ((tvp)->tv_nsec cmp (uvp)->tv_nsec) : \ + ((tvp)->tv_sec cmp (uvp)->tv_sec)) +#define timespecadd(vvp, uvp) \ + do { \ + (vvp)->tv_sec += (uvp)->tv_sec; \ + (vvp)->tv_nsec += (uvp)->tv_nsec; \ + if ((vvp)->tv_nsec >= 1000000000) { \ + (vvp)->tv_sec++; \ + (vvp)->tv_nsec -= 1000000000; \ + } \ + } while (0) +#define timespecsub(vvp, uvp) \ + do { \ + (vvp)->tv_sec -= (uvp)->tv_sec; \ + (vvp)->tv_nsec -= (uvp)->tv_nsec; \ + if ((vvp)->tv_nsec < 0) { \ + (vvp)->tv_sec--; \ + (vvp)->tv_nsec += 1000000000; \ + } \ + } while (0) + + +#define TEST_PATH "/tmp/posixsem_regression_test" + +#define ELAPSED(elapsed, limit) (abs((elapsed) - (limit)) < 100) + +/* Macros for passing child status to parent over a pipe. */ +#define CSTAT(class, error) ((class) << 16 | (error)) +#define CSTAT_CLASS(stat) ((stat) >> 16) +#define CSTAT_ERROR(stat) ((stat) & 0xffff) + +/* + * Helper routine for tests that use a child process. This routine + * creates a pipe and forks a child process. The child process runs + * the 'func' routine which returns a status integer. The status + * integer gets written over the pipe to the parent and returned in + * '*stat'. If there is an error in pipe(), fork(), or wait() this + * returns -1 and fails the test. + */ +static int +child_worker(int (*func)(void *arg), void *arg, int *stat) +{ + pid_t pid; + int pfd[2], cstat; + + if (pipe(pfd) < 0) { + fail_errno("pipe"); + return (-1); + } + + pid = fork(); + switch (pid) { + case -1: + /* Error. */ + fail_errno("fork"); + close(pfd[0]); + close(pfd[1]); + return (-1); + case 0: + /* Child. */ + cstat = func(arg); + write(pfd[1], &cstat, sizeof(cstat)); + exit(0); + } + + if (read(pfd[0], stat, sizeof(*stat)) < 0) { + fail_errno("read(pipe)"); + close(pfd[0]); + close(pfd[1]); + return (-1); + } + if (waitpid(pid, NULL, 0) < 0) { + fail_errno("wait"); + close(pfd[0]); + close(pfd[1]); + return (-1); + } + close(pfd[0]); + close(pfd[1]); + return (0); +} + +/* + * Attempt a ksem_open() that should fail with an expected error of + * 'error'. + */ +static void +ksem_open_should_fail(const char *path, int flags, mode_t mode, unsigned int + value, int error) +{ + semid_t id; + + if (ksem_open(&id, path, flags, mode, value) >= 0) { + fail_err("ksem_open() didn't fail"); + ksem_close(id); + return; + } + if (errno != error) { + fail_errno("ksem_open"); + return; + } + pass(); +} + +/* + * Attempt a ksem_unlink() that should fail with an expected error of + * 'error'. + */ +static void +ksem_unlink_should_fail(const char *path, int error) +{ + + if (ksem_unlink(path) >= 0) { + fail_err("ksem_unlink() didn't fail"); + return; + } + if (errno != error) { + fail_errno("ksem_unlink"); + return; + } + pass(); +} + +/* + * Attempt a ksem_close() that should fail with an expected error of + * 'error'. + */ +static void +ksem_close_should_fail(semid_t id, int error) +{ + + if (ksem_close(id) >= 0) { + fail_err("ksem_close() didn't fail"); + return; + } + if (errno != error) { + fail_errno("ksem_close"); + return; + } + pass(); +} + +/* + * Attempt a ksem_init() that should fail with an expected error of + * 'error'. + */ +static void +ksem_init_should_fail(unsigned int value, int error) +{ + semid_t id; + + if (ksem_init(&id, value) >= 0) { + fail_err("ksem_init() didn't fail"); + ksem_destroy(id); + return; + } + if (errno != error) { + fail_errno("ksem_init"); + return; + } + pass(); +} + +/* + * Attempt a ksem_destroy() that should fail with an expected error of + * 'error'. + */ +static void +ksem_destroy_should_fail(semid_t id, int error) +{ + + if (ksem_destroy(id) >= 0) { + fail_err("ksem_destroy() didn't fail"); + return; + } + if (errno != error) { + fail_errno("ksem_destroy"); + return; + } + pass(); +} + +/* + * Attempt a ksem_post() that should fail with an expected error of + * 'error'. + */ +static void +ksem_post_should_fail(semid_t id, int error) +{ + + if (ksem_post(id) >= 0) { + fail_err("ksem_post() didn't fail"); + return; + } + if (errno != error) { + fail_errno("ksem_post"); + return; + } + pass(); +} + +static void +open_after_unlink(void) +{ + semid_t id; + + if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) { + fail_errno("ksem_open(1)"); + return; + } + ksem_close(id); + + if (ksem_unlink(TEST_PATH) < 0) { + fail_errno("ksem_unlink"); + return; + } + + ksem_open_should_fail(TEST_PATH, O_RDONLY, 0777, 1, ENOENT); +} +TEST(open_after_unlink, "open after unlink"); + +static void +open_invalid_path(void) +{ + + ksem_open_should_fail("blah", 0, 0777, 1, EINVAL); +} +TEST(open_invalid_path, "open invalid path"); + +static void +open_extra_flags(void) +{ + + ksem_open_should_fail(TEST_PATH, O_RDONLY | O_DIRECT, 0777, 1, EINVAL); +} +TEST(open_extra_flags, "open with extra flags"); + +static void +open_bad_value(void) +{ + + (void)ksem_unlink(TEST_PATH); + + ksem_open_should_fail(TEST_PATH, O_CREAT, 0777, UINT_MAX, EINVAL); +} +TEST(open_bad_value, "open with invalid initial value"); + +static void +open_bad_path_pointer(void) +{ + + ksem_open_should_fail((char *)1024, O_RDONLY, 0777, 1, EFAULT); +} +TEST(open_bad_path_pointer, "open bad path pointer"); + +static void +open_path_too_long(void) +{ + char *page; + + page = malloc(MAXPATHLEN + 1); + memset(page, 'a', MAXPATHLEN); + page[MAXPATHLEN] = '\0'; + ksem_open_should_fail(page, O_RDONLY, 0777, 1, ENAMETOOLONG); + free(page); +} +TEST(open_path_too_long, "open pathname too long"); + +static void +open_nonexisting_semaphore(void) +{ + + ksem_open_should_fail("/notreallythere", 0, 0777, 1, ENOENT); +} +TEST(open_nonexisting_semaphore, "open nonexistent semaphore"); + +static void +exclusive_create_existing_semaphore(void) +{ + semid_t id; + + if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) { + fail_errno("ksem_open(O_CREAT)"); + return; + } + ksem_close(id); + + ksem_open_should_fail(TEST_PATH, O_CREAT | O_EXCL, 0777, 1, EEXIST); + + ksem_unlink(TEST_PATH); +} +TEST(exclusive_create_existing_semaphore, "O_EXCL of existing semaphore"); + +static void +init_bad_value(void) +{ + + ksem_init_should_fail(UINT_MAX, EINVAL); +} +TEST(init_bad_value, "init with invalid initial value"); + +static void +unlink_bad_path_pointer(void) +{ + + ksem_unlink_should_fail((char *)1024, EFAULT); +} +TEST(unlink_bad_path_pointer, "unlink bad path pointer"); + +static void +unlink_path_too_long(void) +{ + char *page; + + page = malloc(MAXPATHLEN + 1); + memset(page, 'a', MAXPATHLEN); + page[MAXPATHLEN] = '\0'; + ksem_unlink_should_fail(page, ENAMETOOLONG); + free(page); +} +TEST(unlink_path_too_long, "unlink pathname too long"); + +static void +destroy_named_semaphore(void) +{ + semid_t id; + + if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) { + fail_errno("ksem_open(O_CREAT)"); + return; + } + + ksem_destroy_should_fail(id, EINVAL); + + ksem_close(id); + ksem_unlink(TEST_PATH); +} +TEST(destroy_named_semaphore, "destroy named semaphore"); + +static void +close_unnamed_semaphore(void) +{ + semid_t id; + + if (ksem_init(&id, 1) < 0) { + fail_errno("ksem_init"); + return; + } + + ksem_close_should_fail(id, EINVAL); + + ksem_destroy(id); +} +TEST(close_unnamed_semaphore, "close unnamed semaphore"); + +static void +destroy_invalid_fd(void) +{ + + ksem_destroy_should_fail(STDERR_FILENO, EINVAL); +} +TEST(destroy_invalid_fd, "destroy non-semaphore file descriptor"); + +static void +close_invalid_fd(void) +{ + + ksem_close_should_fail(STDERR_FILENO, EINVAL); +} +TEST(close_invalid_fd, "close non-semaphore file descriptor"); + +static void +create_unnamed_semaphore(void) +{ + semid_t id; + + if (ksem_init(&id, 1) < 0) { + fail_errno("ksem_init"); + return; + } + + if (ksem_destroy(id) < 0) { + fail_errno("ksem_destroy"); + return; + } + pass(); +} +TEST(create_unnamed_semaphore, "create unnamed semaphore"); + +static void +open_named_semaphore(void) +{ + semid_t id; + + if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 1) < 0) { + fail_errno("ksem_open(O_CREAT)"); + return; + } + + if (ksem_close(id) < 0) { + fail_errno("ksem_close"); + return; + } + + if (ksem_unlink(TEST_PATH) < 0) { + fail_errno("ksem_unlink"); + return; + } + pass(); +} +TEST(open_named_semaphore, "create named semaphore"); + +static void +getvalue_invalid_semaphore(void) +{ + int val; + + if (ksem_getvalue(STDERR_FILENO, &val) >= 0) { + fail_err("ksem_getvalue() didn't fail"); + return; + } + if (errno != EINVAL) { + fail_errno("ksem_getvalue"); + return; + } + pass(); +} +TEST(getvalue_invalid_semaphore, "get value of invalid semaphore"); + +static void +post_invalid_semaphore(void) +{ + + ksem_post_should_fail(STDERR_FILENO, EINVAL); +} +TEST(post_invalid_semaphore, "post of invalid semaphore"); + +static void +wait_invalid_semaphore(void) +{ + + if (ksem_wait(STDERR_FILENO) >= 0) { + fail_err("ksem_wait() didn't fail"); + return; + } + if (errno != EINVAL) { + fail_errno("ksem_wait"); + return; + } + pass(); +} +TEST(wait_invalid_semaphore, "wait for invalid semaphore"); + +static void +trywait_invalid_semaphore(void) +{ + + if (ksem_trywait(STDERR_FILENO) >= 0) { + fail_err("ksem_trywait() didn't fail"); + return; + } + if (errno != EINVAL) { + fail_errno("ksem_trywait"); + return; + } + pass(); +} +TEST(trywait_invalid_semaphore, "try wait for invalid semaphore"); + +static void +timedwait_invalid_semaphore(void) +{ + + if (ksem_timedwait(STDERR_FILENO, NULL) >= 0) { + fail_err("ksem_timedwait() didn't fail"); + return; + } + if (errno != EINVAL) { + fail_errno("ksem_timedwait"); + return; + } + pass(); +} +TEST(timedwait_invalid_semaphore, "timed wait for invalid semaphore"); + +static int +checkvalue(semid_t id, int expected) +{ + int val; + + if (ksem_getvalue(id, &val) < 0) { + fail_errno("ksem_getvalue"); + return (-1); + } + if (val != expected) { + fail_err("sem value should be %d instead of %d", expected, val); + return (-1); + } + return (0); +} + +static void +post_test(void) +{ + semid_t id; + + if (ksem_init(&id, 1) < 0) { + fail_errno("ksem_init"); + return; + } + if (checkvalue(id, 1) < 0) { + ksem_destroy(id); + return; + } + if (ksem_post(id) < 0) { + fail_errno("ksem_post"); + ksem_destroy(id); + return; + } + if (checkvalue(id, 2) < 0) { + ksem_destroy(id); + return; + } + if (ksem_destroy(id) < 0) { + fail_errno("ksem_destroy"); + return; + } + pass(); +} +TEST(post_test, "simple post"); + +static void +use_after_unlink_test(void) +{ + semid_t id; + + /* + * Create named semaphore with value of 1 and then unlink it + * while still retaining the initial reference. + */ + if (ksem_open(&id, TEST_PATH, O_CREAT | O_EXCL, 0777, 1) < 0) { + fail_errno("ksem_open(O_CREAT | O_EXCL)"); + return; + } + if (ksem_unlink(TEST_PATH) < 0) { + fail_errno("ksem_unlink"); + ksem_close(id); + return; + } + if (checkvalue(id, 1) < 0) { + ksem_close(id); + return; + } + + /* Post the semaphore to set its value to 2. */ + if (ksem_post(id) < 0) { + fail_errno("ksem_post"); + ksem_close(id); + return; + } + if (checkvalue(id, 2) < 0) { + ksem_close(id); + return; + } + + /* Wait on the semaphore which should set its value to 1. */ + if (ksem_wait(id) < 0) { + fail_errno("ksem_wait"); + ksem_close(id); + return; + } + if (checkvalue(id, 1) < 0) { + ksem_close(id); + return; + } + + if (ksem_close(id) < 0) { + fail_errno("ksem_close"); + return; + } + pass(); +} +TEST(use_after_unlink_test, "use named semaphore after unlink"); + +static void +unlocked_trywait(void) +{ + semid_t id; + + if (ksem_init(&id, 1) < 0) { + fail_errno("ksem_init"); + return; + } + + /* This should succeed and decrement the value to 0. */ + if (ksem_trywait(id) < 0) { + fail_errno("ksem_trywait()"); + ksem_destroy(id); + return; + } + if (checkvalue(id, 0) < 0) { + ksem_destroy(id); + return; + } + + if (ksem_destroy(id) < 0) { + fail_errno("ksem_destroy"); + return; + } + pass(); +} +TEST(unlocked_trywait, "unlocked trywait"); + +static void +locked_trywait(void) +{ + semid_t id; + + if (ksem_init(&id, 0) < 0) { + fail_errno("ksem_init"); + return; + } + + /* This should fail with EAGAIN and leave the value at 0. */ + if (ksem_trywait(id) >= 0) { + fail_err("ksem_trywait() didn't fail"); + ksem_destroy(id); + return; + } + if (errno != EAGAIN) { + fail_errno("wrong error from ksem_trywait()"); + ksem_destroy(id); + return; + } + if (checkvalue(id, 0) < 0) { + ksem_destroy(id); + return; + } + + if (ksem_destroy(id) < 0) { + fail_errno("ksem_destroy"); + return; + } + pass(); +} +TEST(locked_trywait, "locked trywait"); + +/* + * Use a timer to post a specific semaphore after a timeout. A timer + * is scheduled via schedule_post(). check_alarm() must be called + * afterwards to clean up and check for errors. + */ +static semid_t alarm_id = -1; +static int alarm_errno; +static int alarm_handler_installed; + +static void +alarm_handler(int signo) +{ + + if (ksem_post(alarm_id) < 0) + alarm_errno = errno; +} + +static int +check_alarm(int just_clear) +{ + struct itimerval it; + + bzero(&it, sizeof(it)); + if (just_clear) { + setitimer(ITIMER_REAL, &it, NULL); + alarm_errno = 0; + alarm_id = -1; + return (0); + } + if (setitimer(ITIMER_REAL, &it, NULL) < 0) { + fail_errno("setitimer"); + return (-1); + } + if (alarm_errno != 0 && !just_clear) { + errno = alarm_errno; + fail_errno("ksem_post() (via timeout)"); + alarm_errno = 0; + return (-1); + } + alarm_id = -1; + + return (0); +} + +static int +schedule_post(semid_t id, u_int msec) +{ + struct itimerval it; + + if (!alarm_handler_installed) { + if (signal(SIGALRM, alarm_handler) == SIG_ERR) { + fail_errno("signal(SIGALRM)"); + return (-1); + } + alarm_handler_installed = 1; + } + if (alarm_id != -1) { + fail_err("ksem_post() already scheduled"); + return (-1); + } + alarm_id = id; + bzero(&it, sizeof(it)); + it.it_value.tv_sec = msec / 1000; + it.it_value.tv_usec = (msec % 1000) * 1000; + if (setitimer(ITIMER_REAL, &it, NULL) < 0) { + fail_errno("setitimer"); + return (-1); + } + return (0); +} + +static int +timedwait(semid_t id, u_int msec, u_int *delta, int error) +{ + struct timespec start, end; + + if (clock_gettime(CLOCK_REALTIME, &start) < 0) { + fail_errno("clock_gettime(CLOCK_REALTIME)"); + return (-1); + } + end.tv_sec = msec / 1000; + end.tv_nsec = msec % 1000 * 1000000; + timespecadd(&end, &start); + if (ksem_timedwait(id, &end) < 0) { + if (errno != error) { + fail_errno("ksem_timedwait"); + return (-1); + } + } else if (error != 0) { + fail_err("ksem_timedwait() didn't fail"); + return (-1); + } + if (clock_gettime(CLOCK_REALTIME, &end) < 0) { + fail_errno("clock_gettime(CLOCK_REALTIME)"); + return (-1); + } + timespecsub(&end, &start); + *delta = end.tv_nsec / 1000000; + *delta += end.tv_sec * 1000; + return (0); +} + +static void +unlocked_timedwait(void) +{ + semid_t id; + u_int elapsed; + + if (ksem_init(&id, 1) < 0) { + fail_errno("ksem_init"); + return; + } + + /* This should succeed right away and set the value to 0. */ + if (timedwait(id, 5000, &elapsed, 0) < 0) { + ksem_destroy(id); + return; + } + if (!ELAPSED(elapsed, 0)) { + fail_err("ksem_timedwait() of unlocked sem took %ums", elapsed); + ksem_destroy(id); + return; + } + if (checkvalue(id, 0) < 0) { + ksem_destroy(id); + return; + } + + if (ksem_destroy(id) < 0) { + fail_errno("ksem_destroy"); + return; + } + pass(); +} +TEST(unlocked_timedwait, "unlocked timedwait"); + +static void +expired_timedwait(void) +{ + semid_t id; + u_int elapsed; + + if (ksem_init(&id, 0) < 0) { + fail_errno("ksem_init"); + return; + } + + /* This should fail with a timeout and leave the value at 0. */ + if (timedwait(id, 2500, &elapsed, ETIMEDOUT) < 0) { + ksem_destroy(id); + return; + } + if (!ELAPSED(elapsed, 2500)) { + fail_err( + "ksem_timedwait() of locked sem took %ums instead of 2500ms", + elapsed); + ksem_destroy(id); + return; + } + if (checkvalue(id, 0) < 0) { + ksem_destroy(id); + return; + } + + if (ksem_destroy(id) < 0) { + fail_errno("ksem_destroy"); + return; + } + pass(); +} +TEST(expired_timedwait, "locked timedwait timeout"); + +static void +locked_timedwait(void) +{ + semid_t id; + u_int elapsed; + + if (ksem_init(&id, 0) < 0) { + fail_errno("ksem_init"); + return; + } + + /* + * Schedule a post to trigger after 1000 ms. The subsequent + * timedwait should succeed after 1000 ms as a result w/o + * timing out. + */ + if (schedule_post(id, 1000) < 0) { + ksem_destroy(id); + return; + } + if (timedwait(id, 2000, &elapsed, 0) < 0) { + check_alarm(1); + ksem_destroy(id); + return; + } + if (!ELAPSED(elapsed, 1000)) { + fail_err( + "ksem_timedwait() with delayed post took %ums instead of 1000ms", + elapsed); + check_alarm(1); + ksem_destroy(id); + return; + } + if (check_alarm(0) < 0) { + ksem_destroy(id); + return; + } + + if (ksem_destroy(id) < 0) { + fail_errno("ksem_destroy"); + return; + } + pass(); +} +TEST(locked_timedwait, "locked timedwait"); + +static int +testwait(semid_t id, u_int *delta) +{ + struct timespec start, end; + + if (clock_gettime(CLOCK_REALTIME, &start) < 0) { + fail_errno("clock_gettime(CLOCK_REALTIME)"); + return (-1); + } + if (ksem_wait(id) < 0) { + fail_errno("ksem_wait"); + return (-1); + } + if (clock_gettime(CLOCK_REALTIME, &end) < 0) { + fail_errno("clock_gettime(CLOCK_REALTIME)"); + return (-1); + } + timespecsub(&end, &start); + *delta = end.tv_nsec / 1000000; + *delta += end.tv_sec * 1000; + return (0); +} + +static void +unlocked_wait(void) +{ + semid_t id; + u_int elapsed; + + if (ksem_init(&id, 1) < 0) { + fail_errno("ksem_init"); + return; + } + + /* This should succeed right away and set the value to 0. */ + if (testwait(id, &elapsed) < 0) { + ksem_destroy(id); + return; + } + if (!ELAPSED(elapsed, 0)) { + fail_err("ksem_wait() of unlocked sem took %ums", elapsed); + ksem_destroy(id); + return; + } + if (checkvalue(id, 0) < 0) { + ksem_destroy(id); + return; + } + + if (ksem_destroy(id) < 0) { + fail_errno("ksem_destroy"); + return; + } + pass(); +} +TEST(unlocked_wait, "unlocked wait"); + +static void +locked_wait(void) +{ + semid_t id; + u_int elapsed; + + if (ksem_init(&id, 0) < 0) { + fail_errno("ksem_init"); + return; + } + + /* + * Schedule a post to trigger after 1000 ms. The subsequent + * wait should succeed after 1000 ms as a result. + */ + if (schedule_post(id, 1000) < 0) { + ksem_destroy(id); + return; + } + if (testwait(id, &elapsed) < 0) { + check_alarm(1); + ksem_destroy(id); + return; + } + if (!ELAPSED(elapsed, 1000)) { + fail_err( + "ksem_wait() with delayed post took %ums instead of 1000ms", + elapsed); + check_alarm(1); + ksem_destroy(id); + return; + } + if (check_alarm(0) < 0) { + ksem_destroy(id); + return; + } + + if (ksem_destroy(id) < 0) { + fail_errno("ksem_destroy"); + return; + } + pass(); +} +TEST(locked_wait, "locked wait"); + +/* + * Fork off a child process. The child will open the semaphore via + * the same name. The child will then block on the semaphore waiting + * for the parent to post it. + */ +static int +wait_twoproc_child(void *arg) +{ + semid_t id; + + if (ksem_open(&id, TEST_PATH, 0, 0, 0) < 0) + return (CSTAT(1, errno)); + if (ksem_wait(id) < 0) + return (CSTAT(2, errno)); + if (ksem_close(id) < 0) + return (CSTAT(3, errno)); + return (CSTAT(0, 0)); +} + +static void +wait_twoproc_test(void) +{ + semid_t id; + int stat; + + if (ksem_open(&id, TEST_PATH, O_CREAT, 0777, 0)) { + fail_errno("ksem_open"); + return; + } + + if (schedule_post(id, 500) < 0) { + ksem_close(id); + ksem_unlink(TEST_PATH); + return; + } + + if (child_worker(wait_twoproc_child, NULL, &stat) < 0) { + check_alarm(1); + ksem_close(id); + ksem_unlink(TEST_PATH); + return; + } + + errno = CSTAT_ERROR(stat); + switch (CSTAT_CLASS(stat)) { + case 0: + pass(); + break; + case 1: + fail_errno("child ksem_open()"); + break; + case 2: + fail_errno("child ksem_wait()"); + break; + case 3: + fail_errno("child ksem_close()"); + break; + default: + fail_err("bad child state %#x", stat); + break; + } + + check_alarm(1); + ksem_close(id); + ksem_unlink(TEST_PATH); +} +TEST(wait_twoproc_test, "two proc wait"); + +static void +maxvalue_test(void) +{ + semid_t id; + int val; + + if (ksem_init(&id, SEM_VALUE_MAX) < 0) { + fail_errno("ksem_init"); + return; + } + if (ksem_getvalue(id, &val) < 0) { + fail_errno("ksem_getvalue"); + ksem_destroy(id); + return; + } + if (val != SEM_VALUE_MAX) { + fail_err("value %d != SEM_VALUE_MAX"); + ksem_destroy(id); + return; + } + if (val < 0) { + fail_err("value < 0"); + ksem_destroy(id); + return; + } + if (ksem_destroy(id) < 0) { + fail_errno("ksem_destroy"); + return; + } + pass(); +} +TEST(maxvalue_test, "get value of SEM_VALUE_MAX semaphore"); + +static void +maxvalue_post_test(void) +{ + semid_t id; + + if (ksem_init(&id, SEM_VALUE_MAX) < 0) { + fail_errno("ksem_init"); + return; + } + + ksem_post_should_fail(id, EOVERFLOW); + + ksem_destroy(id); +} +TEST(maxvalue_post_test, "post SEM_VALUE_MAX semaphore"); + +static void +busy_destroy_test(void) +{ + char errbuf[_POSIX2_LINE_MAX]; + struct kinfo_proc *kp; + semid_t id; + pid_t pid; + kvm_t *kd; + int count; + + kd = kvm_openfiles(NULL, "/dev/null", NULL, O_RDONLY, errbuf); + if (kd == NULL) { + fail_err("kvm_openfiles: %s", errbuf); + return; + } + + if (ksem_init(&id, 0) < 0) { + fail_errno("ksem_init"); + kvm_close(kd); + return; + } + + pid = fork(); + switch (pid) { + case -1: + /* Error. */ + fail_errno("fork"); + ksem_destroy(id); + kvm_close(kd); + return; + case 0: + /* Child. */ + ksem_wait(id); + exit(0); + } + + /* + * Wait for the child process to block on the semaphore. This + * is a bit gross. + */ + for (;;) { + kp = kvm_getprocs(kd, KERN_PROC_PID, pid, &count); + if (kp == NULL) { + fail_err("kvm_getprocs: %s", kvm_geterr(kd)); + kvm_close(kd); + ksem_destroy(id); + return; + } + if (kp->ki_stat == SSLEEP && + (strcmp(kp->ki_wmesg, "sem") == 0 || + strcmp(kp->ki_wmesg, "ksem") == 0)) + break; + usleep(1000); + } + kvm_close(kd); + + ksem_destroy_should_fail(id, EBUSY); + + /* Cleanup. */ + ksem_post(id); + waitpid(pid, NULL, 0); + ksem_destroy(id); +} +TEST(busy_destroy_test, "destroy unnamed semaphore with waiter"); + +static int +exhaust_unnamed_child(void *arg) +{ + semid_t id; + int i, max; + + max = (intptr_t)arg; + for (i = 0; i < max + 1; i++) { + if (ksem_init(&id, 1) < 0) { + if (errno == ENOSPC) + return (CSTAT(0, 0)); + return (CSTAT(1, errno)); + } + } + return (CSTAT(2, 0)); +} + +static void +exhaust_unnamed_sems(void) +{ + size_t len; + int nsems_max, stat; + + len = sizeof(nsems_max); + if (sysctlbyname("p1003_1b.sem_nsems_max", &nsems_max, &len, NULL, 0) < + 0) { + fail_errno("sysctl(p1003_1b.sem_nsems_max)"); + return; + } + + if (child_worker(exhaust_unnamed_child, (void *)nsems_max, &stat)) + return; + errno = CSTAT_ERROR(stat); + switch (CSTAT_CLASS(stat)) { + case 0: + pass(); + break; + case 1: + fail_errno("ksem_init"); + break; + case 2: + fail_err("Limit of %d semaphores not enforced", nsems_max); + break; + default: + fail_err("bad child state %#x", stat); + break; + } +} +TEST(exhaust_unnamed_sems, "exhaust unnamed semaphores (1)"); + +static int +exhaust_named_child(void *arg) +{ + char buffer[64]; + semid_t id; + int i, max; + + max = (intptr_t)arg; + for (i = 0; i < max + 1; i++) { + snprintf(buffer, sizeof(buffer), "%s%d", TEST_PATH, i); + if (ksem_open(&id, buffer, O_CREAT, 0777, 1) < 0) { + if (errno == ENOSPC || errno == EMFILE || + errno == ENFILE) + return (CSTAT(0, 0)); + return (CSTAT(1, errno)); + } + } + return (CSTAT(2, errno)); +} + +static void +exhaust_named_sems(void) +{ + char buffer[64]; + size_t len; + int i, nsems_max, stat; + + len = sizeof(nsems_max); + if (sysctlbyname("p1003_1b.sem_nsems_max", &nsems_max, &len, NULL, 0) < + 0) { + fail_errno("sysctl(p1003_1b.sem_nsems_max)"); + return; + } + + if (child_worker(exhaust_named_child, (void *)nsems_max, &stat) < 0) + return; + errno = CSTAT_ERROR(stat); + switch (CSTAT_CLASS(stat)) { + case 0: + pass(); + break; + case 1: + fail_errno("ksem_open"); + break; + case 2: + fail_err("Limit of %d semaphores not enforced", nsems_max); + break; + default: + fail_err("bad child state %#x", stat); + break; + } + + /* Cleanup any semaphores created by the child. */ + for (i = 0; i < nsems_max + 1; i++) { + snprintf(buffer, sizeof(buffer), "%s%d", TEST_PATH, i); + ksem_unlink(buffer); + } +} +TEST(exhaust_named_sems, "exhaust named semaphores (1)"); + +static int +fdlimit_set(void *arg) +{ + struct rlimit rlim; + int max; + + max = (intptr_t)arg; + if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) + return (CSTAT(3, errno)); + rlim.rlim_cur = max; + if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) + return (CSTAT(4, errno)); + return (0); +} + +static int +fdlimit_unnamed_child(void *arg) +{ + int stat; + + stat = fdlimit_set(arg); + if (stat == 0) + stat = exhaust_unnamed_child(arg); + return (stat); +} + +static void +fdlimit_unnamed_sems(void) +{ + int nsems_max, stat; + + nsems_max = 10; + if (child_worker(fdlimit_unnamed_child, (void *)nsems_max, &stat)) + return; + errno = CSTAT_ERROR(stat); + switch (CSTAT_CLASS(stat)) { + case 0: + pass(); + break; + case 1: + fail_errno("ksem_init"); + break; + case 2: + fail_err("Limit of %d semaphores not enforced", nsems_max); + break; + case 3: + fail_errno("getrlimit"); + break; + case 4: + fail_errno("getrlimit"); + break; + default: + fail_err("bad child state %#x", stat); + break; + } +} +TEST(fdlimit_unnamed_sems, "exhaust unnamed semaphores (2)"); + +static int +fdlimit_named_child(void *arg) +{ + int stat; + + stat = fdlimit_set(arg); + if (stat == 0) + stat = exhaust_named_child(arg); + return (stat); +} + +static void +fdlimit_named_sems(void) +{ + char buffer[64]; + int i, nsems_max, stat; + + nsems_max = 10; + if (child_worker(fdlimit_named_child, (void *)nsems_max, &stat) < 0) + return; + errno = CSTAT_ERROR(stat); + switch (CSTAT_CLASS(stat)) { + case 0: + pass(); + break; + case 1: + fail_errno("ksem_open"); + break; + case 2: + fail_err("Limit of %d semaphores not enforced", nsems_max); + break; + case 3: + fail_errno("getrlimit"); + break; + case 4: + fail_errno("getrlimit"); + break; + default: + fail_err("bad child state %#x", stat); + break; + } + + /* Cleanup any semaphores created by the child. */ + for (i = 0; i < nsems_max + 1; i++) { + snprintf(buffer, sizeof(buffer), "%s%d", TEST_PATH, i); + ksem_unlink(buffer); + } +} +TEST(fdlimit_named_sems, "exhaust named semaphores (2)"); + +int +main(int argc, char *argv[]) +{ + + signal(SIGSYS, SIG_IGN); + run_tests(); + return (0); +} diff --git a/tools/regression/posixsem/posixsem.t b/tools/regression/posixsem/posixsem.t new file mode 100644 index 000000000000..198d2be6c89c --- /dev/null +++ b/tools/regression/posixsem/posixsem.t @@ -0,0 +1,5 @@ +#!/bin/sh +# +# $FreeBSD$ + +./posixsem diff --git a/tools/regression/posixsem/test.c b/tools/regression/posixsem/test.c new file mode 100644 index 000000000000..8583a0dbbac6 --- /dev/null +++ b/tools/regression/posixsem/test.c @@ -0,0 +1,128 @@ +/*- + * Copyright (c) 2008 Yahoo!, Inc. + * All rights reserved. + * Written by: John Baldwin + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +#include "test.h" + +static int test_index; +static struct regression_test *test; +static int test_acknowleged; + +SET_DECLARE(regression_tests_set, struct regression_test); + +/* + * Outputs a test summary of the following: + * + * [name] [# [fmt args]] + */ +static void +vprint_status(const char *status, const char *fmt, va_list ap) +{ + + printf("%s %d", status, test_index); + if (test->rt_name) + printf(" - %s", test->rt_name); + if (fmt) { + printf(" # "); + vprintf(fmt, ap); + } + printf("\n"); + test_acknowleged = 1; +} + +static void +print_status(const char *status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprint_status(status, fmt, ap); + va_end(ap); +} + +void +pass(void) +{ + + print_status("ok", NULL); +} + +void +fail(void) +{ + + print_status("not ok", NULL); +} + +void +fail_err(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprint_status("not ok", fmt, ap); + va_end(ap); +} + +void +skip(const char *reason) +{ + + print_status("ok", "skip %s", reason); +} + +void +todo(const char *reason) +{ + + print_status("not ok", "TODO %s", reason); +} + +void +run_tests(void) +{ + struct regression_test **testp; + + printf("1..%td\n", SET_COUNT(regression_tests_set)); + test_index = 1; + SET_FOREACH(testp, regression_tests_set) { + test_acknowleged = 0; + test = *testp; + test->rt_function(); + if (!test_acknowleged) + print_status("not ok", "unknown status"); + test_index++; + } +} diff --git a/tools/regression/posixsem/test.h b/tools/regression/posixsem/test.h new file mode 100644 index 000000000000..505679b1fdb0 --- /dev/null +++ b/tools/regression/posixsem/test.h @@ -0,0 +1,59 @@ +/*- + * Copyright (c) 2008 Yahoo!, Inc. + * All rights reserved. + * Written by: John Baldwin + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef __TEST_H__ +#define __TEST_H__ + +#include + +struct regression_test { + void (*rt_function)(void); + const char *rt_name; +}; + +#define TEST(function, name) \ + static struct regression_test _regtest_##function = { \ + (function), \ + (name) \ + }; \ + DATA_SET(regression_tests_set, _regtest_##function) + +void fail(void); +void fail_err(const char *fmt, ...); +void pass(void); +void run_tests(void); +void skip(const char *reason); +void todo(const char *reason); + +#define fail_errno(tag) fail_err("%s: %s", (tag), strerror(errno)) + +#endif /* !__TEST_H__ */ diff --git a/usr.bin/procstat/procstat_files.c b/usr.bin/procstat/procstat_files.c index 950b70df2761..39ce82b4f44c 100644 --- a/usr.bin/procstat/procstat_files.c +++ b/usr.bin/procstat/procstat_files.c @@ -222,6 +222,10 @@ procstat_files(pid_t pid, struct kinfo_proc *kipp) str = "h"; break; + case KF_TYPE_SEM: + str = "e"; + break; + case KF_TYPE_NONE: case KF_TYPE_UNKNOWN: default: