#include "inc.h" #define MAX_SHM_NR 1024 struct shm_struct { key_t key; int id; struct shmid_ds shmid_ds; vir_bytes page; int vm_id; }; static struct shm_struct shm_list[MAX_SHM_NR]; static int shm_list_nr = 0; static struct shm_struct *shm_find_key(key_t key) { int i; if (key == IPC_PRIVATE) return NULL; for (i = 0; i < shm_list_nr; i++) if (shm_list[i].key == key) return shm_list+i; return NULL; } static struct shm_struct *shm_find_id(int id) { int i; for (i = 0; i < shm_list_nr; i++) if (shm_list[i].id == id) return shm_list+i; return NULL; } /*===========================================================================* * do_shmget * *===========================================================================*/ int do_shmget(message *m) { struct shm_struct *shm; long key, size, old_size; int flag; int id; 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))) { if (!check_perm(&shm->shmid_ds.shm_perm, who_e, flag)) return EACCES; if ((flag & IPC_CREAT) && (flag & IPC_EXCL)) return EEXIST; if (size && shm->shmid_ds.shm_segsz < size) return EINVAL; id = shm->id; } else { /* no key found */ if (!(flag & IPC_CREAT)) return ENOENT; if (size <= 0) return EINVAL; /* round up to a multiple of PAGE_SIZE */ if (size % PAGE_SIZE) size += PAGE_SIZE - size % PAGE_SIZE; if (size <= 0) return EINVAL; if (shm_list_nr == MAX_SHM_NR) return ENOMEM; /* TODO: shmmni should be changed... */ if (identifier == SHMMNI) return ENOSPC; shm = &shm_list[shm_list_nr]; memset(shm, 0, sizeof(struct shm_struct)); shm->page = (vir_bytes) mmap(0, size, PROT_READ|PROT_WRITE, MAP_ANON, -1, 0); if (shm->page == (vir_bytes) MAP_FAILED) return ENOMEM; shm->vm_id = vm_getphys(sef_self(), (void *) shm->page); memset((void *)shm->page, 0, size); shm->shmid_ds.shm_perm.cuid = shm->shmid_ds.shm_perm.uid = getnuid(who_e); shm->shmid_ds.shm_perm.cgid = shm->shmid_ds.shm_perm.gid = getngid(who_e); shm->shmid_ds.shm_perm.mode = flag & 0777; shm->shmid_ds.shm_segsz = old_size; shm->shmid_ds.shm_atime = 0; shm->shmid_ds.shm_dtime = 0; shm->shmid_ds.shm_ctime = time(NULL); shm->shmid_ds.shm_cpid = getnpid(who_e); shm->shmid_ds.shm_lpid = 0; shm->shmid_ds.shm_nattch = 0; shm->id = id = identifier++; shm->key = key; shm_list_nr++; } m->m_lc_ipc_shmget.retid = id; return OK; } /*===========================================================================* * do_shmat * *===========================================================================*/ int do_shmat(message *m) { int id, flag; 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 && (addr % PAGE_SIZE)) { if (flag & SHM_RND) addr -= (addr % PAGE_SIZE); else return EINVAL; } if (!(shm = shm_find_id(id))) return EINVAL; if (flag & SHM_RDONLY) flag = 0444; else flag = 0666; if (!check_perm(&shm->shmid_ds.shm_perm, who_e, flag)) return EACCES; ret = vm_remap(who_e, sef_self(), (void *)addr, (void *)shm->page, shm->shmid_ds.shm_segsz); if (ret == MAP_FAILED) return ENOMEM; shm->shmid_ds.shm_atime = time(NULL); shm->shmid_ds.shm_lpid = getnpid(who_e); /* nattach is updated lazily */ m->m_lc_ipc_shmat.retaddr = ret; return OK; } /*===========================================================================* * update_refcount_and_destroy * *===========================================================================*/ void update_refcount_and_destroy(void) { int i, j; for (i = 0, j = 0; i < shm_list_nr; i++) { u8_t rc; 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 || !(shm_list[i].shmid_ds.shm_perm.mode & SHM_DEST)) { if (i != j) shm_list[j] = shm_list[i]; j++; } else { int size = shm_list[i].shmid_ds.shm_segsz; if (size % PAGE_SIZE) size += PAGE_SIZE - size % PAGE_SIZE; munmap((void *)shm_list[i].page, size); } } shm_list_nr = j; } /*===========================================================================* * do_shmdt * *===========================================================================*/ int do_shmdt(message *m) { vir_bytes addr; phys_bytes vm_id; int i; addr = (vir_bytes) m->m_lc_ipc_shmdt.addr; if ((vm_id = vm_getphys(who_e, (void *) addr)) == 0) return EINVAL; for (i = 0; i < shm_list_nr; i++) { if (shm_list[i].vm_id == vm_id) { struct shm_struct *shm = &shm_list[i]; shm->shmid_ds.shm_atime = time(NULL); shm->shmid_ds.shm_lpid = getnpid(who_e); /* nattch is updated lazily */ vm_unmap(who_e, (void *) addr); break; } } if (i == shm_list_nr) printf("IPC: do_shmdt impossible error! could not find id %lu to unmap\n", vm_id); update_refcount_and_destroy(); return OK; } /*===========================================================================* * do_shmctl * *===========================================================================*/ int do_shmctl(message *m) { int id = m->m_lc_ipc_shmctl.id; int cmd = m->m_lc_ipc_shmctl.cmd; struct shmid_ds *ds = (struct shmid_ds *)m->m_lc_ipc_shmctl.buf; struct shmid_ds tmp_ds; struct shm_struct *shm = NULL; struct shminfo sinfo; struct shm_info s_info; uid_t uid; int r, i; if (cmd == IPC_STAT) update_refcount_and_destroy(); if ((cmd == IPC_STAT || cmd == IPC_SET || cmd == IPC_RMID) && !(shm = shm_find_id(id))) return EINVAL; switch (cmd) { case IPC_STAT: if (!ds) return EFAULT; /* check whether it has read permission */ if (!check_perm(&shm->shmid_ds.shm_perm, who_e, 0444)) return EACCES; r = sys_datacopy(SELF, (vir_bytes)&shm->shmid_ds, who_e, (vir_bytes)ds, sizeof(struct shmid_ds)); if (r != OK) return EFAULT; break; case IPC_SET: uid = getnuid(who_e); if (uid != shm->shmid_ds.shm_perm.cuid && uid != shm->shmid_ds.shm_perm.uid && uid != 0) return EPERM; r = sys_datacopy(who_e, (vir_bytes)ds, SELF, (vir_bytes)&tmp_ds, sizeof(struct shmid_ds)); if (r != OK) return EFAULT; 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 &= ~0777; shm->shmid_ds.shm_perm.mode |= tmp_ds.shm_perm.mode & 0666; shm->shmid_ds.shm_ctime = time(NULL); break; case IPC_RMID: uid = getnuid(who_e); 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: if (!ds) return EFAULT; sinfo.shmmax = (unsigned long) -1; sinfo.shmmin = 1; sinfo.shmmni = MAX_SHM_NR; sinfo.shmseg = (unsigned long) -1; sinfo.shmall = (unsigned long) -1; r = sys_datacopy(SELF, (vir_bytes)&sinfo, who_e, (vir_bytes)ds, sizeof(struct shminfo)); if (r != OK) return EFAULT; m->m_lc_ipc_shmctl.ret = (shm_list_nr - 1); if (m->m_lc_ipc_shmctl.ret < 0) m->m_lc_ipc_shmctl.ret = 0; break; case SHM_INFO: if (!ds) return EFAULT; 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; r = sys_datacopy(SELF, (vir_bytes)&s_info, who_e, (vir_bytes)ds, sizeof(struct shm_info)); if (r != OK) return EFAULT; m->m_lc_ipc_shmctl.ret = shm_list_nr - 1; if (m->m_lc_ipc_shmctl.ret < 0) m->m_lc_ipc_shmctl.ret = 0; break; case SHM_STAT: if (id < 0 || id >= shm_list_nr) return EINVAL; shm = &shm_list[id]; r = sys_datacopy(SELF, (vir_bytes)&shm->shmid_ds, who_e, (vir_bytes)ds, sizeof(struct shmid_ds)); if (r != OK) return EFAULT; m->m_lc_ipc_shmctl.ret = shm->id; break; default: return EINVAL; } return OK; } #if 0 static void list_shm_ds(void) { int i; printf("key\tid\tpage\n"); for (i = 0; i < shm_list_nr; i++) printf("%ld\t%d\t%lx\n", shm_list[i].key, shm_list[i].id, shm_list[i].page); } #endif /*===========================================================================* * is_shm_nil * *===========================================================================*/ int is_shm_nil(void) { return (shm_list_nr == 0); }