/*
* linux/ipc/sem.c
* Copyright (C) 1992 Krishna Balasubramanian
*/
#include <linux/errno.h>
#include <asm/segment.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/sem.h>
#include <linux/ipc.h>
#include <linux/stat.h>
#include <linux/malloc.h>
extern int ipcperms (struct ipc_perm *ipcp, short semflg);
static int newary (key_t, int, int);
static int findkey (key_t key);
static void freeary (int id);
static struct semid_ds *semary[SEMMNI];
static int used_sems = 0, used_semids = 0;
static struct wait_queue *sem_lock = NULL;
static int max_semid = 0;
static unsigned short sem_seq = 0;
void sem_init (void)
{
int i=0;
sem_lock = NULL;
used_sems = used_semids = max_semid = sem_seq = 0;
for (i=0; i < SEMMNI; i++)
semary[i] = (struct semid_ds *) IPC_UNUSED;
return;
}
static int findkey (key_t key)
{
int id;
struct semid_ds *sma;
for (id=0; id <= max_semid; id++) {
while ((sma = semary[id]) == IPC_NOID)
interruptible_sleep_on (&sem_lock);
if (sma == IPC_UNUSED)
continue;
if (key == sma->sem_perm.key)
return id;
}
return -1;
}
static int newary (key_t key, int nsems, int semflg)
{
int id;
struct semid_ds *sma;
struct ipc_perm *ipcp;
int size;
if (!nsems)
return -EINVAL;
if (used_sems + nsems > SEMMNS)
return -ENOSPC;
for (id=0; id < SEMMNI; id++)
if (semary[id] == IPC_UNUSED) {
semary[id] = (struct semid_ds *) IPC_NOID;
goto found;
}
return -ENOSPC;
found:
size = sizeof (*sma) + nsems * sizeof (struct sem);
used_sems += nsems;
sma = (struct semid_ds *) kmalloc (size, GFP_KERNEL);
if (!sma) {
semary[id] = (struct semid_ds *) IPC_UNUSED;
used_sems -= nsems;
if (sem_lock)
wake_up (&sem_lock);
return -ENOMEM;
}
memset (sma, 0, size);
sma->sem_base = (struct sem *) &sma[1];
ipcp = &sma->sem_perm;
ipcp->mode = (semflg & S_IRWXUGO);
ipcp->key = key;
ipcp->cuid = ipcp->uid = current->euid;
ipcp->gid = ipcp->cgid = current->egid;
ipcp->seq = sem_seq;
sma->eventn = sma->eventz = NULL;
sma->sem_nsems = nsems;
sma->sem_ctime = CURRENT_TIME;
if (id > max_semid)
max_semid = id;
used_semids++;
semary[id] = sma;
if (sem_lock)
wake_up (&sem_lock);
return (int) sem_seq * SEMMNI + id;
}
int sys_semget (key_t key, int nsems, int semflg)
{
int id;
struct semid_ds *sma;
if (nsems < 0 || nsems > SEMMSL)
return -EINVAL;
if (key == IPC_PRIVATE)
return newary(key, nsems, semflg);
if ((id = findkey (key)) == -1) { /* key not used */
if (!(semflg & IPC_CREAT))
return -ENOENT;
return newary(key, nsems, semflg);
}
if (semflg & IPC_CREAT && semflg & IPC_EXCL)
return -EEXIST;
sma = semary[id];
if (nsems > sma->sem_nsems)
return -EINVAL;
if (ipcperms(&sma->sem_perm, semflg))
return -EACCES;
return sma->sem_perm.seq*SEMMNI + id;
}
static void freeary (int id)
{
struct semid_ds *sma = semary[id];
struct sem_undo *un;
sma->sem_perm.seq++;
sem_seq++;
used_sems -= sma->sem_nsems;
if (id == max_semid)
while (max_semid && (semary[--max_semid] == IPC_UNUSED));
semary[id] = (struct semid_ds *) IPC_UNUSED;
used_semids--;
for (un=sma->undo; un; un=un->id_next)
un->semadj = 0;
while (sma->eventz || sma->eventn) {
if (sma->eventz)
wake_up (&sma->eventz);
if (sma->eventn)
wake_up (&sma->eventn);
schedule();
}
kfree(sma);
return;
}
int sys_semctl (int semid, int semnum, int cmd, void *arg)
{
int i, id, val = 0;
struct semid_ds *sma, *buf = NULL, tbuf;
struct ipc_perm *ipcp;
struct sem *curr;
struct sem_undo *un;
ushort nsems, *array = NULL;
ushort sem_io[SEMMSL];
if (semid < 0 || semnum < 0 || cmd < 0)
return -EINVAL;
switch (cmd) {
case IPC_INFO:
case SEM_INFO:
{
struct seminfo seminfo, *tmp;
if (!arg || ! (tmp = (struct seminfo *) get_fs_long((int *)arg)))
return -EFAULT;
seminfo.semmni = SEMMNI;
seminfo.semmns = SEMMNS;
seminfo.semmsl = SEMMSL;
seminfo.semopm = SEMOPM;
seminfo.semvmx = SEMVMX;
seminfo.semmnu = SEMMNU;
seminfo.semmap = SEMMAP;
seminfo.semume = SEMUME;
seminfo.semusz = SEMUSZ;
seminfo.semaem = SEMAEM;
if (cmd == SEM_INFO) {
seminfo.semusz = used_semids;
seminfo.semaem = used_sems;
}
i= verify_area(VERIFY_WRITE, tmp, sizeof(struct seminfo));
if (i)
return i;
memcpy_tofs (tmp, &seminfo, sizeof(struct seminfo));
return max_semid;
}
case SEM_STAT:
if (!arg || ! (buf = (struct semid_ds *) get_fs_long((int *) arg)))
return -EFAULT;
i = verify_area (VERIFY_WRITE, buf, sizeof (*sma));
if (i)
return i;
if (semid > max_semid)
return -EINVAL;
sma = semary[semid];
if (sma == IPC_UNUSED || sma == IPC_NOID)
return -EINVAL;
if (ipcperms (&sma->sem_perm, S_IRUGO))
return -EACCES;
id = semid + sma->sem_perm.seq * SEMMNI;
memcpy_tofs (buf, sma, sizeof(*sma));
return id;
}
id = semid % SEMMNI;
sma = semary [id];
if (sma == IPC_UNUSED || sma == IPC_NOID)
return -EINVAL;
ipcp = &sma->sem_perm;
nsems = sma->sem_nsems;
if (ipcp->seq != semid / SEMMNI)
return -EIDRM;
if (semnum >= nsems)
return -EINVAL;
curr = &sma->sem_base[semnum];
switch (cmd) {
case GETVAL:
case GETPID:
case GETNCNT:
case GETZCNT:
case GETALL:
if (ipcperms (ipcp, S_IRUGO))
return -EACCES;
switch (cmd) {
case GETVAL : return curr->semval;
case GETPID : return curr->sempid;
case GETNCNT: return curr->semncnt;
case GETZCNT: return curr->semzcnt;
case GETALL:
if (!arg || ! (array = (ushort *) get_fs_long((int *) arg)))
return -EFAULT;
i = verify_area (VERIFY_WRITE, array, nsems*sizeof(short));
if (i)
return i;
}
break;
case SETVAL:
if (!arg)
return -EFAULT;
if ((val = (int) get_fs_long ((int *) arg)) > SEMVMX || val < 0)
return -ERANGE;
break;
case IPC_RMID:
if (suser() || current->euid == ipcp->cuid ||
current->euid == ipcp->uid) {
freeary (id);
return 0;
}
return -EPERM;
case SETALL: /* arg is a pointer to an array of ushort */
if (!arg || ! (array = (ushort *) get_fs_long ((int *) arg)) )
return -EFAULT;
if ((i = verify_area (VERIFY_READ, array, sizeof tbuf)))
return i;
memcpy_fromfs (sem_io, array, nsems*sizeof(ushort));
for (i=0; i< nsems; i++)
if (sem_io[i] > SEMVMX)
return -ERANGE;
break;
case IPC_STAT:
if (!arg || !(buf = (struct semid_ds *) get_fs_long((int *) arg)))
return -EFAULT;
if ((i = verify_area (VERIFY_WRITE, buf, sizeof(*sma))))
return i;
break;
case IPC_SET:
if (!arg || !(buf = (struct semid_ds *) get_fs_long((int *) arg)))
return -EFAULT;
if ((i = verify_area (VERIFY_READ, buf, sizeof tbuf)))
return i;
memcpy_fromfs (&tbuf, buf, sizeof tbuf);
break;
}
if (semary[id] == IPC_UNUSED || semary[id] == IPC_NOID)
return -EIDRM;
if (ipcp->seq != semid / SEMMNI)
return -EIDRM;
switch (cmd) {
case GETALL:
if (ipcperms (ipcp, S_IRUGO))
return -EACCES;
for (i=0; i< sma->sem_nsems; i++)
sem_io[i] = sma->sem_base[i].semval;
memcpy_tofs (array, sem_io, nsems*sizeof(ushort));
break;
case SETVAL:
if (ipcperms (ipcp, S_IWUGO))
return -EACCES;
for (un = sma->undo; un; un = un->id_next)
if (semnum == un->sem_num)
un->semadj = 0;
sma->sem_ctime = CURRENT_TIME;
curr->semval = val;
if (sma->eventn)
wake_up (&sma->eventn);
if (sma->eventz)
wake_up (&sma->eventz);
break;
case IPC_SET:
if (suser() || current->euid == ipcp->cuid ||
current->euid == ipcp->uid) {
ipcp->uid = tbuf.sem_perm.uid;
ipcp->gid = tbuf.sem_perm.gid;
ipcp->mode = (ipcp->mode & ~S_IRWXUGO)
| (tbuf.sem_perm.mode & S_IRWXUGO);
sma->sem_ctime = CURRENT_TIME;
return 0;
}
return -EPERM;
case IPC_STAT:
if (ipcperms (ipcp, S_IRUGO))
return -EACCES;
memcpy_tofs (buf, sma, sizeof (*sma));
break;
case SETALL:
if (ipcperms (ipcp, S_IWUGO))
return -EACCES;
for (i=0; i<nsems; i++)
sma->sem_base[i].semval = sem_io[i];
for (un = sma->undo; un; un = un->id_next)
un->semadj = 0;
if (sma->eventn)
wake_up (&sma->eventn);
if (sma->eventz)
wake_up (&sma->eventz);
sma->sem_ctime = CURRENT_TIME;
break;
default:
return -EINVAL;
}
return 0;
}
int sys_semop (int semid, struct sembuf *tsops, unsigned nsops)
{
int i, id;
struct semid_ds *sma;
struct sem *curr = NULL;
struct sembuf sops[SEMOPM], *sop;
struct sem_undo *un;
int undos = 0, alter = 0, semncnt = 0, semzcnt = 0;
if (nsops < 1 || semid < 0)
return -EINVAL;
if (nsops > SEMOPM)
return -E2BIG;
if (!tsops)
return -EFAULT;
memcpy_fromfs (sops, tsops, nsops * sizeof(*tsops));
id = semid % SEMMNI;
if ((sma = semary[id]) == IPC_UNUSED || sma == IPC_NOID)
return -EINVAL;
for (i=0; i<nsops; i++) {
sop = &sops[i];
if (sop->sem_num > sma->sem_nsems)
return -EFBIG;
if (sop->sem_flg & SEM_UNDO)
undos++;
if (sop->sem_op) {
alter++;
if (sop->sem_op > 0)
semncnt ++;
}
}
if (ipcperms(&sma->sem_perm, alter ? S_IWUGO : S_IRUGO))
return -EACCES;
/*
* ensure every sop with undo gets an undo structure
*/
if (undos) {
for (i=0; i<nsops; i++) {
if (!(sops[i].sem_flg & SEM_UNDO))
continue;
for (un = current->semun; un; un = un->proc_next)
if ((un->semid == semid) &&
(un->sem_num == sops[i].sem_num))
break;
if (un)
continue;
un = (struct sem_undo *)
kmalloc (sizeof(*un), GFP_ATOMIC);
if (!un)
return -ENOMEM; /* freed on exit */
un->semid = semid;
un->semadj = 0;
un->sem_num = sops[i].sem_num;
un->proc_next = current->semun;
current->semun = un;
un->id_next = sma->undo;
sma->undo = un;
}
}
slept:
if (sma->sem_perm.seq != semid / SEMMNI)
return -EIDRM;
for (i=0; i<nsops; i++) {
sop = &sops[i];
curr = &sma->sem_base[sop->sem_num];
if (sop->sem_op + curr->semval > SEMVMX)
return -ERANGE;
if (!sop->sem_op && curr->semval) {
if (sop->sem_flg & IPC_NOWAIT)
return -EAGAIN;
if (current->signal & ~current->blocked)
return -EINTR;
curr->semzcnt++;
interruptible_sleep_on (&sma->eventz);
curr->semzcnt--;
goto slept;
}
if ((sop->sem_op + curr->semval < 0) ) {
if (sop->sem_flg & IPC_NOWAIT)
return -EAGAIN;
if (current->signal & ~current->blocked)
return -EINTR;
curr->semncnt++;
interruptible_sleep_on (&sma->eventn);
curr->semncnt--;
goto slept;
}
}
for (i=0; i<nsops; i++) {
sop = &sops[i];
curr = &sma->sem_base[sop->sem_num];
curr->sempid = current->pid;
if (!(curr->semval += sop->sem_op))
semzcnt++;
if (!(sop->sem_flg & SEM_UNDO))
continue;
for (un = current->semun; un; un = un->proc_next)
if ((un->semid == semid) &&
(un->sem_num == sop->sem_num))
break;
if (!un) {
printk ("semop : no undo for op %d\n", i);
continue;
}
un->semadj -= sop->sem_op;
}
sma->sem_otime = CURRENT_TIME;
if (semncnt && sma->eventn)
wake_up(&sma->eventn);
if (semzcnt && sma->eventz)
wake_up(&sma->eventz);
return curr->semval;
}
/*
* add semadj values to semaphores, free undo structures.
* undo structures are not freed when semaphore arrays are destroyed
* so some of them may be out of date.
*/
void sem_exit (void)
{
struct sem_undo *u, *un = NULL, **up, **unp;
struct semid_ds *sma;
struct sem *sem = NULL;
for (up = ¤t->semun; (u = *up); *up = u->proc_next, kfree(u)) {
sma = semary[u->semid % SEMMNI];
if (sma == IPC_UNUSED || sma == IPC_NOID)
continue;
if (sma->sem_perm.seq != u->semid / SEMMNI)
continue;
for (unp = &sma->undo; (un = *unp); unp = &un->id_next) {
if (u == un)
goto found;
}
printk ("sem_exit undo list error id=%d\n", u->semid);
break;
found:
*unp = un->id_next;
if (!un->semadj)
continue;
while (1) {
if (sma->sem_perm.seq != un->semid / SEMMNI)
break;
sem = &sma->sem_base[un->sem_num];
if (sem->semval + un->semadj >= 0) {
sem->semval += un->semadj;
sem->sempid = current->pid;
sma->sem_otime = CURRENT_TIME;
if (un->semadj > 0 && sma->eventn)
wake_up (&sma->eventn);
if (!sem->semval && sma->eventz)
wake_up (&sma->eventz);
break;
}
if (current->signal & ~current->blocked)
break;
sem->semncnt++;
interruptible_sleep_on (&sma->eventn);
sem->semncnt--;
}
}
current->semun = NULL;
return;
}