NetBSD/sys/kern/uipc_sem.c

753 lines
18 KiB
C

/* $NetBSD: uipc_sem.c,v 1.4 2003/02/01 06:23:44 thorpej Exp $ */
/*-
* Copyright (c) 2003 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe of Wasabi Systems, Inc.
*
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
*/
/*
* Copyright (c) 2002 Alfred Perlstein <alfred@FreeBSD.org>
* All rights reserved.
*
* 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.
*
* 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 "opt_posix.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/lock.h>
#include <sys/ksem.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <sys/malloc.h>
#include <sys/fcntl.h>
#include <sys/mount.h>
#include <sys/syscallargs.h>
#ifndef SEM_MAX
#define SEM_MAX 30
#endif
#define SEM_MAX_NAMELEN 14
#define SEM_VALUE_MAX (~0U)
#define SEM_TO_ID(x) ((intptr_t)(x))
MALLOC_DEFINE(M_SEM, "p1003_1b_sem", "p1003_1b semaphores");
/*
* Note: to read the ks_name member, you need either the ks_interlock
* or the ksem_slock. To write the ks_name member, you need both. Make
* sure the order is ksem_slock -> ks_interlock.
*/
struct ksem {
LIST_ENTRY(ksem) ks_entry; /* global list entry */
struct simplelock ks_interlock; /* lock on this ksem */
char *ks_name; /* if named, this is the name */
unsigned 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 */
unsigned int ks_waiters; /* number of waiters */
};
struct ksem_ref {
LIST_ENTRY(ksem_ref) ksr_list;
struct ksem *ksr_ksem;
};
struct ksem_proc {
struct lock kp_lock;
LIST_HEAD(, ksem_ref) kp_ksems;
};
/*
* ksem_slock protects ksem_head and nsems. Only named semaphores go
* onto ksem_head.
*/
static struct simplelock ksem_slock;
static LIST_HEAD(, ksem) ksem_head = LIST_HEAD_INITIALIZER(&ksem_head);
static int nsems = 0;
static void
ksem_free(struct ksem *ks)
{
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
/*
* If the ksem is anonymous (or has been unlinked), then
* this is the end if its life.
*/
if (ks->ks_name == NULL) {
simple_unlock(&ks->ks_interlock);
free(ks, M_SEM);
simple_lock(&ksem_slock);
nsems--;
simple_unlock(&ksem_slock);
return;
}
simple_unlock(&ks->ks_interlock);
}
static __inline void
ksem_addref(struct ksem *ks)
{
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
ks->ks_ref++;
KASSERT(ks->ks_ref != 0); /* XXX KDASSERT */
}
static __inline void
ksem_delref(struct ksem *ks)
{
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
KASSERT(ks->ks_ref != 0); /* XXX KDASSERT */
if (--ks->ks_ref == 0) {
ksem_free(ks);
return;
}
simple_unlock(&ks->ks_interlock);
}
static struct ksem_proc *
ksem_proc_alloc(void)
{
struct ksem_proc *kp;
kp = malloc(sizeof(*kp), M_SEM, M_WAITOK);
lockinit(&kp->kp_lock, PWAIT, "ksproc", 0, 0);
LIST_INIT(&kp->kp_ksems);
return (kp);
}
static void
ksem_add_proc(struct proc *p, struct ksem *ks)
{
struct ksem_proc *kp;
struct ksem_ref *ksr;
if (p->p_ksems == NULL) {
kp = ksem_proc_alloc();
p->p_ksems = kp;
} else
kp = p->p_ksems;
ksr = malloc(sizeof(*ksr), M_SEM, M_WAITOK);
ksr->ksr_ksem = ks;
lockmgr(&kp->kp_lock, LK_EXCLUSIVE, NULL);
LIST_INSERT_HEAD(&kp->kp_ksems, ksr, ksr_list);
lockmgr(&kp->kp_lock, LK_RELEASE, NULL);
}
/* We MUST have a write lock on the ksem_proc list! */
static struct ksem_ref *
ksem_drop_proc(struct ksem_proc *kp, struct ksem *ks)
{
struct ksem_ref *ksr;
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
LIST_FOREACH(ksr, &kp->kp_ksems, ksr_list) {
if (ksr->ksr_ksem == ks) {
ksem_delref(ks);
LIST_REMOVE(ksr, ksr_list);
return (ksr);
}
}
#ifdef DIAGNOSTIC
panic("ksem_drop_proc: ksem_proc %p ksem %p", kp, ks);
#endif
return (NULL);
}
static int
ksem_perm(struct proc *p, struct ksem *ks)
{
struct ucred *uc;
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
uc = p->p_ucred;
if ((uc->cr_uid == ks->ks_uid && (ks->ks_mode & S_IWUSR) != 0) ||
(uc->cr_gid == ks->ks_gid && (ks->ks_mode & S_IWGRP) != 0) ||
(ks->ks_mode & S_IWOTH) != 0 || suser(uc, &p->p_acflag) == 0)
return (0);
return (EPERM);
}
static struct ksem *
ksem_lookup_byname(const char *name)
{
struct ksem *ks;
LOCK_ASSERT(simple_lock_held(&ksem_slock));
LIST_FOREACH(ks, &ksem_head, ks_entry) {
if (strcmp(ks->ks_name, name) == 0) {
simple_lock(&ks->ks_interlock);
return (ks);
}
}
return (NULL);
}
static int
ksem_create(struct proc *p, const char *name, struct ksem **ksret,
mode_t mode, unsigned int value)
{
struct ksem *ret;
struct ucred *uc;
size_t len;
uc = p->p_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 || strchr(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;
simple_lock_init(&ret->ks_interlock);
simple_lock(&ksem_slock);
if (nsems >= SEM_MAX) {
simple_unlock(&ksem_slock);
if (ret->ks_name != NULL)
free(ret->ks_name, M_SEM);
free(ret, M_SEM);
return (ENFILE);
}
nsems++;
simple_unlock(&ksem_slock);
*ksret = ret;
return (0);
}
int
sys__ksem_init(struct lwp *l, void *v, register_t *retval)
{
struct sys__ksem_init_args /* {
unsigned int value;
semid_t *idp;
} */ *uap = v;
struct ksem *ks;
semid_t id;
int error;
/* Note the mode does not matter for anonymous semaphores. */
error = ksem_create(l->l_proc, NULL, &ks, 0, SCARG(uap, value));
if (error)
return (error);
id = SEM_TO_ID(ks);
error = copyout(&id, SCARG(uap, idp), sizeof(id));
if (error) {
simple_lock(&ks->ks_interlock);
ksem_delref(ks);
return (error);
}
ksem_add_proc(l->l_proc, ks);
return (0);
}
int
sys__ksem_open(struct lwp *l, void *v, register_t *retval)
{
struct sys__ksem_open_args /* {
const char *name;
int oflag;
mode_t mode;
unsigned int value;
semid_t *idp;
} */ *uap = v;
char name[SEM_MAX_NAMELEN + 1];
size_t done;
int error;
struct ksem *ksnew, *ks;
semid_t id;
error = copyinstr(SCARG(uap, name), name, sizeof(name), &done);
if (error)
return (error);
ksnew = NULL;
simple_lock(&ksem_slock);
ks = ksem_lookup_byname(name);
/* Found one? */
if (ks != NULL) {
/* Check for exclusive create. */
if (SCARG(uap, oflag) & O_EXCL) {
simple_unlock(&ks->ks_interlock);
simple_unlock(&ksem_slock);
return (EEXIST);
}
found_one:
/*
* Verify permissions. If we can access it, add
* this process's reference.
*/
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
error = ksem_perm(l->l_proc, ks);
if (error == 0)
ksem_addref(ks);
simple_unlock(&ks->ks_interlock);
simple_unlock(&ksem_slock);
if (error)
return (error);
id = SEM_TO_ID(ks);
error = copyout(&id, SCARG(uap, idp), sizeof(id));
if (error) {
simple_lock(&ks->ks_interlock);
ksem_delref(ks);
return (error);
}
ksem_add_proc(l->l_proc, ks);
return (0);
}
/*
* didn't ask for creation? error.
*/
if ((SCARG(uap, oflag) & O_CREAT) == 0) {
simple_unlock(&ksem_slock);
return (ENOENT);
}
/*
* We may block during creation, so drop the lock.
*/
simple_unlock(&ksem_slock);
error = ksem_create(l->l_proc, name, &ksnew, SCARG(uap, mode),
SCARG(uap, value));
if (error != 0)
return (error);
id = SEM_TO_ID(ksnew);
error = copyout(&id, SCARG(uap, idp), sizeof(id));
if (error) {
free(ksnew->ks_name, M_SEM);
ksnew->ks_name = NULL;
simple_lock(&ksnew->ks_interlock);
ksem_delref(ksnew);
return (error);
}
/*
* We need to make sure we haven't lost a race while
* allocating during creation.
*/
simple_lock(&ksem_slock);
if ((ks = ksem_lookup_byname(name)) != NULL) {
if (SCARG(uap, oflag) & O_EXCL) {
simple_unlock(&ks->ks_interlock);
simple_unlock(&ksem_slock);
free(ksnew->ks_name, M_SEM);
ksnew->ks_name = NULL;
simple_lock(&ksnew->ks_interlock);
ksem_delref(ksnew);
return (EEXIST);
}
goto found_one;
} else {
/* ksnew already has its initial reference. */
LIST_INSERT_HEAD(&ksem_head, ksnew, ks_entry);
simple_unlock(&ksem_slock);
ksem_add_proc(l->l_proc, ksnew);
}
return (error);
}
/* We must have a read lock on the ksem_proc list! */
static struct ksem *
ksem_lookup_proc(struct ksem_proc *kp, semid_t id)
{
struct ksem_ref *ksr;
LIST_FOREACH(ksr, &kp->kp_ksems, ksr_list) {
if (id == (semid_t) ksr->ksr_ksem) {
simple_lock(&ksr->ksr_ksem->ks_interlock);
return (ksr->ksr_ksem);
}
}
return (NULL);
}
int
sys__ksem_unlink(struct lwp *l, void *v, register_t *retval)
{
struct sys__ksem_unlink_args /* {
const char *name;
} */ *uap = v;
char name[SEM_MAX_NAMELEN + 1], *cp;
size_t done;
struct ksem *ks;
int error;
error = copyinstr(SCARG(uap, name), name, sizeof(name), &done);
if (error)
return error;
simple_lock(&ksem_slock);
ks = ksem_lookup_byname(name);
if (ks == NULL) {
simple_unlock(&ksem_slock);
return (ENOENT);
}
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
LIST_REMOVE(ks, ks_entry);
cp = ks->ks_name;
ks->ks_name = NULL;
simple_unlock(&ksem_slock);
if (ks->ks_ref == 0)
ksem_free(ks);
else
simple_unlock(&ks->ks_interlock);
free(cp, M_SEM);
return (0);
}
int
sys__ksem_close(struct lwp *l, void *v, register_t *retval)
{
struct sys__ksem_close_args /* {
semid_t id;
} */ *uap = v;
struct ksem_proc *kp;
struct ksem_ref *ksr;
struct ksem *ks;
if ((kp = l->l_proc->p_ksems) == NULL)
return (EINVAL);
lockmgr(&kp->kp_lock, LK_EXCLUSIVE, NULL);
ks = ksem_lookup_proc(kp, SCARG(uap, id));
if (ks == NULL) {
lockmgr(&kp->kp_lock, LK_RELEASE, NULL);
return (EINVAL);
}
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
if (ks->ks_name == NULL) {
simple_unlock(&ks->ks_interlock);
lockmgr(&kp->kp_lock, LK_RELEASE, NULL);
return (EINVAL);
}
ksr = ksem_drop_proc(kp, ks);
lockmgr(&kp->kp_lock, LK_RELEASE, NULL);
free(ksr, M_SEM);
return (0);
}
int
sys__ksem_post(struct lwp *l, void *v, register_t *retval)
{
struct sys__ksem_post_args /* {
semid_t id;
} */ *uap = v;
struct ksem_proc *kp;
struct ksem *ks;
int error;
if ((kp = l->l_proc->p_ksems) == NULL)
return (EINVAL);
lockmgr(&kp->kp_lock, LK_SHARED, NULL);
ks = ksem_lookup_proc(kp, SCARG(uap, id));
lockmgr(&kp->kp_lock, LK_RELEASE, NULL);
if (ks == NULL)
return (EINVAL);
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
if (ks->ks_value == SEM_VALUE_MAX) {
error = EOVERFLOW;
goto out;
}
++ks->ks_value;
if (ks->ks_waiters)
wakeup(ks);
error = 0;
out:
simple_unlock(&ks->ks_interlock);
return (error);
}
static int
ksem_wait(struct lwp *l, semid_t id, int tryflag)
{
struct ksem_proc *kp;
struct ksem *ks;
int error;
if ((kp = l->l_proc->p_ksems) == NULL)
return (EINVAL);
lockmgr(&kp->kp_lock, LK_SHARED, NULL);
ks = ksem_lookup_proc(kp, id);
lockmgr(&kp->kp_lock, LK_RELEASE, NULL);
if (ks == NULL)
return (EINVAL);
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
ksem_addref(ks);
while (ks->ks_value == 0) {
ks->ks_waiters++;
error = tryflag ? EAGAIN : ltsleep(ks, PCATCH, "psem", 0,
&ks->ks_interlock);
ks->ks_waiters--;
if (error)
goto out;
}
ks->ks_value--;
error = 0;
out:
ksem_delref(ks);
return (error);
}
int
sys__ksem_wait(struct lwp *l, void *v, register_t *retval)
{
struct sys__ksem_wait_args /* {
semid_t id;
} */ *uap = v;
return ksem_wait(l, SCARG(uap, id), 0);
}
int
sys__ksem_trywait(struct lwp *l, void *v, register_t *retval)
{
struct sys__ksem_trywait_args /* {
semid_t id;
} */ *uap = v;
return ksem_wait(l, SCARG(uap, id), 1);
}
int
sys__ksem_getvalue(struct lwp *l, void *v, register_t *retval)
{
struct sys__ksem_getvalue_args /* {
semid_t id;
unsigned int *value;
} */ *uap = v;
struct ksem_proc *kp;
struct ksem *ks;
unsigned int val;
if ((kp = l->l_proc->p_ksems) == NULL)
return (EINVAL);
lockmgr(&kp->kp_lock, LK_SHARED, NULL);
ks = ksem_lookup_proc(kp, SCARG(uap, id));
lockmgr(&kp->kp_lock, LK_RELEASE, NULL);
if (ks == NULL)
return (EINVAL);
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
val = ks->ks_value;
simple_unlock(&ks->ks_interlock);
return (copyout(&val, SCARG(uap, value), sizeof(val)));
}
int
sys__ksem_destroy(struct lwp *l, void *v, register_t *retval)
{
struct sys__ksem_destroy_args /*{
semid_t id;
} */ *uap = v;
struct ksem_proc *kp;
struct ksem_ref *ksr;
struct ksem *ks;
if ((kp = l->l_proc->p_ksems) == NULL)
return (EINVAL);
lockmgr(&kp->kp_lock, LK_EXCLUSIVE, NULL);
ks = ksem_lookup_proc(kp, SCARG(uap, id));
if (ks == NULL) {
lockmgr(&kp->kp_lock, LK_RELEASE, NULL);
return (EINVAL);
}
LOCK_ASSERT(simple_lock_held(&ks->ks_interlock));
/*
* XXX This misses named semaphores which have been unlink'd,
* XXX but since behavior of destroying a named semaphore is
* XXX undefined, this is technically allowed.
*/
if (ks->ks_name != NULL) {
simple_unlock(&ks->ks_interlock);
lockmgr(&kp->kp_lock, LK_RELEASE, NULL);
return (EINVAL);
}
if (ks->ks_waiters) {
simple_unlock(&ks->ks_interlock);
lockmgr(&kp->kp_lock, LK_RELEASE, NULL);
return (EBUSY);
}
ksr = ksem_drop_proc(kp, ks);
lockmgr(&kp->kp_lock, LK_RELEASE, NULL);
free(ksr, M_SEM);
return (0);
}
static void
ksem_forkhook(struct proc *p2, struct proc *p1)
{
struct ksem_proc *kp1, *kp2;
struct ksem_ref *ksr, *ksr1;
if ((kp1 = p1->p_ksems) == NULL) {
p2->p_ksems = NULL;
return;
}
p2->p_ksems = kp2 = ksem_proc_alloc();
lockmgr(&kp1->kp_lock, LK_SHARED, NULL);
if (!LIST_EMPTY(&kp1->kp_ksems)) {
LIST_FOREACH(ksr, &kp1->kp_ksems, ksr_list) {
ksr1 = malloc(sizeof(*ksr), M_SEM, M_WAITOK);
ksr1->ksr_ksem = ksr->ksr_ksem;
simple_lock(&ksr->ksr_ksem->ks_interlock);
ksem_addref(ksr->ksr_ksem);
simple_unlock(&ksr->ksr_ksem->ks_interlock);
LIST_INSERT_HEAD(&kp2->kp_ksems, ksr1, ksr_list);
}
}
lockmgr(&kp1->kp_lock, LK_RELEASE, NULL);
}
static void
ksem_exithook(struct proc *p, void *arg)
{
struct ksem_proc *kp;
struct ksem_ref *ksr;
if ((kp = p->p_ksems) == NULL)
return;
/* Don't bother locking; process is dying. */
while ((ksr = LIST_FIRST(&kp->kp_ksems)) != NULL) {
LIST_REMOVE(ksr, ksr_list);
simple_lock(&ksr->ksr_ksem->ks_interlock);
ksem_delref(ksr->ksr_ksem);
free(ksr, M_SEM);
}
}
void
ksem_init(void)
{
simple_lock_init(&ksem_slock);
exithook_establish(ksem_exithook, NULL);
exechook_establish(ksem_exithook, NULL);
forkhook_establish(ksem_forkhook);
}