
- rewrite the semop(2) implementation so that it now conforms to the specification, including atomicity, support for blocking more than once, range checks, but also basic fairness support; - fix permissions checking; - fix missing time adjustments; - fix off-by-one errors and other bugs; - do not allocate dynamic memory for GETALL/SETALL; - add test88, which properly tests the semaphore functionality. Change-Id: I85f0d3408c0d6bba41cfb4c91a34c8b46b2a5959
386 lines
9.0 KiB
C
386 lines
9.0 KiB
C
#include "inc.h"
|
|
|
|
/* Private shm_perm.mode flags, synchronized with NetBSD kernel values */
|
|
#define SHM_ALLOC 0x0800 /* slot is in use (SHMSEG_ALLOCATED) */
|
|
|
|
struct shm_struct {
|
|
struct shmid_ds shmid_ds;
|
|
vir_bytes page;
|
|
phys_bytes vm_id;
|
|
};
|
|
static struct shm_struct shm_list[SHMMNI];
|
|
static unsigned int shm_list_nr = 0; /* highest in-use slot number plus one */
|
|
|
|
static struct shm_struct *
|
|
shm_find_key(key_t key)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (key == IPC_PRIVATE)
|
|
return NULL;
|
|
|
|
for (i = 0; i < shm_list_nr; i++) {
|
|
if (!(shm_list[i].shmid_ds.shm_perm.mode & SHM_ALLOC))
|
|
continue;
|
|
if (shm_list[i].shmid_ds.shm_perm._key == key)
|
|
return &shm_list[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct shm_struct *
|
|
shm_find_id(int id)
|
|
{
|
|
struct shm_struct *shm;
|
|
unsigned int i;
|
|
|
|
i = IPCID_TO_IX(id);
|
|
if (i >= shm_list_nr)
|
|
return NULL;
|
|
|
|
shm = &shm_list[i];
|
|
if (!(shm->shmid_ds.shm_perm.mode & SHM_ALLOC))
|
|
return NULL;
|
|
if (shm->shmid_ds.shm_perm._seq != IPCID_TO_SEQ(id))
|
|
return NULL;
|
|
return shm;
|
|
}
|
|
|
|
int
|
|
do_shmget(message * m)
|
|
{
|
|
struct shm_struct *shm;
|
|
unsigned int i, seq;
|
|
key_t key;
|
|
size_t size, old_size;
|
|
int flag;
|
|
void *page;
|
|
|
|
key = m->m_lc_ipc_shmget.key;
|
|
old_size = size = m->m_lc_ipc_shmget.size;
|
|
flag = m->m_lc_ipc_shmget.flag;
|
|
|
|
if ((shm = shm_find_key(key)) != NULL) {
|
|
if (!check_perm(&shm->shmid_ds.shm_perm, m->m_source, flag))
|
|
return EACCES;
|
|
if ((flag & IPC_CREAT) && (flag & IPC_EXCL))
|
|
return EEXIST;
|
|
if (size && shm->shmid_ds.shm_segsz < size)
|
|
return EINVAL;
|
|
i = shm - shm_list;
|
|
} else { /* no key found */
|
|
if (!(flag & IPC_CREAT))
|
|
return ENOENT;
|
|
if (size <= 0)
|
|
return EINVAL;
|
|
size = roundup(size, PAGE_SIZE);
|
|
if (size <= 0)
|
|
return EINVAL;
|
|
|
|
/* Find a free entry. */
|
|
for (i = 0; i < __arraycount(shm_list); i++)
|
|
if (!(shm_list[i].shmid_ds.shm_perm.mode & SHM_ALLOC))
|
|
break;
|
|
if (i == __arraycount(shm_list))
|
|
return ENOSPC;
|
|
|
|
/*
|
|
* Allocate memory to share. For now, we store the page
|
|
* reference as a numerical value so as to avoid issues with
|
|
* live update. TODO: a proper solution.
|
|
*/
|
|
page = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANON, -1, 0);
|
|
if (page == MAP_FAILED)
|
|
return ENOMEM;
|
|
memset(page, 0, size);
|
|
|
|
/* Initialize the entry. */
|
|
shm = &shm_list[i];
|
|
seq = shm->shmid_ds.shm_perm._seq;
|
|
memset(shm, 0, sizeof(*shm));
|
|
|
|
shm->shmid_ds.shm_perm._key = key;
|
|
shm->shmid_ds.shm_perm.cuid =
|
|
shm->shmid_ds.shm_perm.uid = getnuid(m->m_source);
|
|
shm->shmid_ds.shm_perm.cgid =
|
|
shm->shmid_ds.shm_perm.gid = getngid(m->m_source);
|
|
shm->shmid_ds.shm_perm.mode = SHM_ALLOC | (flag & ACCESSPERMS);
|
|
shm->shmid_ds.shm_perm._seq = (seq + 1) & 0x7fff;
|
|
shm->shmid_ds.shm_segsz = old_size;
|
|
shm->shmid_ds.shm_atime = 0;
|
|
shm->shmid_ds.shm_dtime = 0;
|
|
shm->shmid_ds.shm_ctime = clock_time(NULL);
|
|
shm->shmid_ds.shm_cpid = getnpid(m->m_source);
|
|
shm->shmid_ds.shm_lpid = 0;
|
|
shm->shmid_ds.shm_nattch = 0;
|
|
shm->page = (vir_bytes)page;
|
|
shm->vm_id = vm_getphys(sef_self(), page);
|
|
|
|
assert(i <= shm_list_nr);
|
|
if (i == shm_list_nr)
|
|
shm_list_nr++;
|
|
}
|
|
|
|
m->m_lc_ipc_shmget.retid = IXSEQ_TO_IPCID(i, shm->shmid_ds.shm_perm);
|
|
return OK;
|
|
}
|
|
|
|
int
|
|
do_shmat(message * m)
|
|
{
|
|
int id, flag, mask;
|
|
vir_bytes addr;
|
|
void *ret;
|
|
struct shm_struct *shm;
|
|
|
|
id = m->m_lc_ipc_shmat.id;
|
|
addr = (vir_bytes)m->m_lc_ipc_shmat.addr;
|
|
flag = m->m_lc_ipc_shmat.flag;
|
|
|
|
if (addr % PAGE_SIZE) {
|
|
if (flag & SHM_RND)
|
|
addr -= addr % PAGE_SIZE;
|
|
else
|
|
return EINVAL;
|
|
}
|
|
|
|
if ((shm = shm_find_id(id)) == NULL)
|
|
return EINVAL;
|
|
|
|
mask = 0;
|
|
if (flag & SHM_RDONLY)
|
|
mask = IPC_R;
|
|
else
|
|
mask = IPC_R | IPC_W;
|
|
if (!check_perm(&shm->shmid_ds.shm_perm, m->m_source, mask))
|
|
return EACCES;
|
|
|
|
ret = vm_remap(m->m_source, sef_self(), (void *)addr,
|
|
(void *)shm->page, shm->shmid_ds.shm_segsz);
|
|
if (ret == MAP_FAILED)
|
|
return ENOMEM;
|
|
|
|
shm->shmid_ds.shm_atime = clock_time(NULL);
|
|
shm->shmid_ds.shm_lpid = getnpid(m->m_source);
|
|
/* nattch is updated lazily */
|
|
|
|
m->m_lc_ipc_shmat.retaddr = ret;
|
|
return OK;
|
|
}
|
|
|
|
void
|
|
update_refcount_and_destroy(void)
|
|
{
|
|
u8_t rc;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < shm_list_nr; i++) {
|
|
if (!(shm_list[i].shmid_ds.shm_perm.mode & SHM_ALLOC))
|
|
continue;
|
|
|
|
rc = vm_getrefcount(sef_self(), (void *)shm_list[i].page);
|
|
if (rc == (u8_t)-1) {
|
|
printf("IPC: can't find physical region.\n");
|
|
continue;
|
|
}
|
|
shm_list[i].shmid_ds.shm_nattch = rc - 1;
|
|
|
|
if (shm_list[i].shmid_ds.shm_nattch == 0 &&
|
|
(shm_list[i].shmid_ds.shm_perm.mode & SHM_DEST)) {
|
|
munmap((void *)shm_list[i].page,
|
|
roundup(shm_list[i].shmid_ds.shm_segsz,
|
|
PAGE_SIZE));
|
|
/* Mark the entry as free. */
|
|
shm_list[i].shmid_ds.shm_perm.mode &= ~SHM_ALLOC;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now that we may have removed an arbitrary set of slots, ensure that
|
|
* shm_list_nr again equals the highest in-use slot number plus one.
|
|
*/
|
|
while (shm_list_nr > 0 &&
|
|
!(shm_list[shm_list_nr - 1].shmid_ds.shm_perm.mode & SHM_ALLOC))
|
|
shm_list_nr--;
|
|
}
|
|
|
|
int
|
|
do_shmdt(message * m)
|
|
{
|
|
struct shm_struct *shm;
|
|
vir_bytes addr;
|
|
phys_bytes vm_id;
|
|
unsigned int i;
|
|
|
|
addr = (vir_bytes)m->m_lc_ipc_shmdt.addr;
|
|
|
|
if ((vm_id = vm_getphys(m->m_source, (void *)addr)) == 0)
|
|
return EINVAL;
|
|
|
|
for (i = 0; i < shm_list_nr; i++) {
|
|
shm = &shm_list[i];
|
|
|
|
if (!(shm->shmid_ds.shm_perm.mode & SHM_ALLOC))
|
|
continue;
|
|
|
|
if (shm->vm_id == vm_id) {
|
|
shm->shmid_ds.shm_atime = clock_time(NULL);
|
|
shm->shmid_ds.shm_lpid = getnpid(m->m_source);
|
|
/* nattch is updated lazily */
|
|
|
|
vm_unmap(m->m_source, (void *)addr);
|
|
break;
|
|
}
|
|
}
|
|
if (i == shm_list_nr)
|
|
printf("IPC: do_shmdt: ID %lu not found\n", vm_id);
|
|
|
|
update_refcount_and_destroy();
|
|
|
|
return OK;
|
|
}
|
|
|
|
int
|
|
do_shmctl(message * m)
|
|
{
|
|
struct shmid_ds tmp_ds;
|
|
struct shm_struct *shm;
|
|
struct shminfo sinfo;
|
|
struct shm_info s_info;
|
|
vir_bytes buf;
|
|
unsigned int i;
|
|
uid_t uid;
|
|
int r, id, cmd;
|
|
|
|
id = m->m_lc_ipc_shmctl.id;
|
|
cmd = m->m_lc_ipc_shmctl.cmd;
|
|
buf = (vir_bytes)m->m_lc_ipc_shmctl.buf;
|
|
|
|
/*
|
|
* For stat calls, sure that all information is up-to-date. Since this
|
|
* may free the slot, do this before mapping from ID to slot below.
|
|
*/
|
|
if (cmd == IPC_STAT || cmd == SHM_STAT)
|
|
update_refcount_and_destroy();
|
|
|
|
switch (cmd) {
|
|
case IPC_INFO:
|
|
case SHM_INFO:
|
|
shm = NULL;
|
|
break;
|
|
case SHM_STAT:
|
|
if (id < 0 || (unsigned int)id >= shm_list_nr)
|
|
return EINVAL;
|
|
shm = &shm_list[id];
|
|
if (!(shm->shmid_ds.shm_perm.mode & SHM_ALLOC))
|
|
return EINVAL;
|
|
break;
|
|
default:
|
|
if ((shm = shm_find_id(id)) == NULL)
|
|
return EINVAL;
|
|
break;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case IPC_STAT:
|
|
case SHM_STAT:
|
|
/* Check whether the caller has read permission. */
|
|
if (!check_perm(&shm->shmid_ds.shm_perm, m->m_source, IPC_R))
|
|
return EACCES;
|
|
if ((r = sys_datacopy(SELF, (vir_bytes)&shm->shmid_ds,
|
|
m->m_source, buf, sizeof(shm->shmid_ds))) != OK)
|
|
return r;
|
|
if (cmd == SHM_STAT)
|
|
m->m_lc_ipc_shmctl.ret =
|
|
IXSEQ_TO_IPCID(id, shm->shmid_ds.shm_perm);
|
|
break;
|
|
case IPC_SET:
|
|
uid = getnuid(m->m_source);
|
|
if (uid != shm->shmid_ds.shm_perm.cuid &&
|
|
uid != shm->shmid_ds.shm_perm.uid && uid != 0)
|
|
return EPERM;
|
|
if ((r = sys_datacopy(m->m_source, buf, SELF,
|
|
(vir_bytes)&tmp_ds, sizeof(tmp_ds))) != OK)
|
|
return r;
|
|
shm->shmid_ds.shm_perm.uid = tmp_ds.shm_perm.uid;
|
|
shm->shmid_ds.shm_perm.gid = tmp_ds.shm_perm.gid;
|
|
shm->shmid_ds.shm_perm.mode &= ~ACCESSPERMS;
|
|
shm->shmid_ds.shm_perm.mode |=
|
|
tmp_ds.shm_perm.mode & ACCESSPERMS;
|
|
shm->shmid_ds.shm_ctime = clock_time(NULL);
|
|
break;
|
|
case IPC_RMID:
|
|
uid = getnuid(m->m_source);
|
|
if (uid != shm->shmid_ds.shm_perm.cuid &&
|
|
uid != shm->shmid_ds.shm_perm.uid && uid != 0)
|
|
return EPERM;
|
|
shm->shmid_ds.shm_perm.mode |= SHM_DEST;
|
|
/* Destroy if possible. */
|
|
update_refcount_and_destroy();
|
|
break;
|
|
case IPC_INFO:
|
|
memset(&sinfo, 0, sizeof(sinfo));
|
|
sinfo.shmmax = (unsigned long) -1;
|
|
sinfo.shmmin = 1;
|
|
sinfo.shmmni = __arraycount(shm_list);
|
|
sinfo.shmseg = (unsigned long) -1;
|
|
sinfo.shmall = (unsigned long) -1;
|
|
if ((r = sys_datacopy(SELF, (vir_bytes)&sinfo, m->m_source,
|
|
buf, sizeof(sinfo))) != OK)
|
|
return r;
|
|
if (shm_list_nr > 0)
|
|
m->m_lc_ipc_shmctl.ret = shm_list_nr - 1;
|
|
else
|
|
m->m_lc_ipc_shmctl.ret = 0;
|
|
break;
|
|
case SHM_INFO:
|
|
memset(&s_info, 0, sizeof(s_info));
|
|
s_info.used_ids = shm_list_nr;
|
|
s_info.shm_tot = 0;
|
|
for (i = 0; i < shm_list_nr; i++)
|
|
s_info.shm_tot +=
|
|
shm_list[i].shmid_ds.shm_segsz / PAGE_SIZE;
|
|
s_info.shm_rss = s_info.shm_tot;
|
|
s_info.shm_swp = 0;
|
|
s_info.swap_attempts = 0;
|
|
s_info.swap_successes = 0;
|
|
if ((r = sys_datacopy(SELF, (vir_bytes)&s_info, m->m_source,
|
|
buf, sizeof(s_info))) != OK)
|
|
return r;
|
|
if (shm_list_nr > 0)
|
|
m->m_lc_ipc_shmctl.ret = shm_list_nr - 1;
|
|
else
|
|
m->m_lc_ipc_shmctl.ret = 0;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
list_shm_ds(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
printf("key\tid\tpage\n");
|
|
for (i = 0; i < shm_list_nr; i++) {
|
|
if (!(shm_list[i].shmid_ds.shm_perm.mode & SHM_ALLOC))
|
|
continue;
|
|
printf("%ld\t%d\t%lx\n",
|
|
shm_list[i].shmid_ds.shm_perm._key,
|
|
IXSEQ_TO_IPCID(i, shm_list[i].shmid_ds.shm_perm),
|
|
shm_list[i].page);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int
|
|
is_shm_nil(void)
|
|
{
|
|
|
|
return (shm_list_nr == 0);
|
|
}
|