diff options
Diffstat (limited to 'winsup/cygwin/shm.cc')
-rw-r--r-- | winsup/cygwin/shm.cc | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/winsup/cygwin/shm.cc b/winsup/cygwin/shm.cc new file mode 100644 index 0000000..dbe9bb0 --- /dev/null +++ b/winsup/cygwin/shm.cc @@ -0,0 +1,499 @@ +/* shm.cc: Single unix specification IPC interface for Cygwin + + Copyright 2001 Red Hat, Inc. + + Originally written by Robert Collins <robert.collins@hotmail.com> + + This file is part of Cygwin. + + This software is a copyrighted work licensed under the terms of the + Cygwin license. Please consult the file "CYGWIN_LICENSE" for + details. */ + +#include "winsup.h" +#include <sys/stat.h> +#include <errno.h> +#include "cygerrno.h" +#include <unistd.h> +#include "security.h" +#include "fhandler.h" +#include "path.h" +#include "dtable.h" +#include "cygheap.h" +#include <stdio.h> +#include "thread.h" +#include <sys/shm.h> +#include "perprocess.h" +#include "cygserver_shm.h" + +// FIXME IS THIS CORRECT +/* Implementation notes: We use two shared memory regions per key: + * One for the control structure, and one for the shared memory. + * While this has a higher overhead tham a single shared area, + * It allows more flexability. As the entire code is transparent to the user + * We can merge these in the future should it be needed. + */ +extern "C" size_t +getsystemallocgranularity () +{ + SYSTEM_INFO sysinfo; + static size_t buffer_offset = 0; + if (buffer_offset) + return buffer_offset; + GetSystemInfo (&sysinfo); + buffer_offset = sysinfo.dwAllocationGranularity; + return buffer_offset; +} + +client_request_shm::client_request_shm (int ntype, int nshm_id): +client_request (CYGSERVER_REQUEST_SHM_GET, sizeof (parameters)) +{ + buffer = (char *) ¶meters; + parameters.in.shm_id = nshm_id; + parameters.in.type = SHM_REATTACH; + parameters.in.pid = GetCurrentProcessId (); +} + +client_request_shm::client_request_shm (int ntype, int nshm_id, pid_t npid): +client_request (CYGSERVER_REQUEST_SHM_GET, sizeof (parameters)) +{ + buffer = (char *) ¶meters; + parameters.in.shm_id = nshm_id; + parameters.in.type = ntype; + parameters.in.pid = npid; +} + +client_request_shm::client_request_shm (key_t nkey, size_t nsize, + int nshmflg, + char psdbuf[4096], + pid_t npid): +client_request (CYGSERVER_REQUEST_SHM_GET, sizeof (parameters)) +{ + buffer = (char *) ¶meters; + parameters.in.key = nkey; + parameters.in.size = nsize; + parameters.in.shmflg = nshmflg; + parameters.in.type = SHM_CREATE; + parameters.in.pid = npid; + memcpy (parameters.in.sd_buf, psdbuf, 4096); +} + +static shmnode *shm_head = NULL; + +static shmnode * +build_inprocess_shmds (HANDLE hfilemap, HANDLE hattachmap, key_t key, + int shm_id) +{ + HANDLE filemap = hfilemap; + void *mapptr = MapViewOfFile (filemap, FILE_MAP_WRITE, 0, 0, 0); + + if (!mapptr) + { + CloseHandle (hfilemap); + CloseHandle (hattachmap); + //FIXME: close filemap and free the mutex + /* we couldn't access the mapped area with the requested permissions */ + set_errno (EACCES); + return NULL; + } + + /* Now get the user data */ + HANDLE attachmap = hattachmap; + shmid_ds *shmtemp = new shmid_ds; + if (!shmtemp) + { + system_printf ("failed to malloc shm node\n"); + set_errno (ENOMEM); + UnmapViewOfFile (mapptr); + CloseHandle (filemap); + CloseHandle (attachmap); + /* exit mutex */ + return NULL; + } + + /* get the system node data */ + *shmtemp = *(shmid_ds *) mapptr; + + /* process local data */ + shmnode *tempnode = new shmnode; + + tempnode->filemap = filemap; + tempnode->attachmap = attachmap; + shmtemp->mapptr = mapptr; + + /* no need for InterlockedExchange here, we're serialised by the global mutex */ + tempnode->shmds = shmtemp; + tempnode->shm_id = shm_id; + tempnode->key = key; + tempnode->next = shm_head; + tempnode->attachhead = NULL; + shm_head = tempnode; + + /* FIXME: leave the system wide shm mutex */ + + return tempnode; +} + +int __stdcall +fixup_shms_after_fork () +{ + shmnode *tempnode = shm_head; + while (tempnode) + { + void *newshmds = + MapViewOfFile (tempnode->filemap, FILE_MAP_WRITE, 0, 0, 0); + if (!newshmds) + { + /* don't worry about handle cleanup, we're dying! */ + system_printf ("failed to reattach to shm control file view %x\n", + tempnode); + return 1; + } + tempnode->shmds = (class shmid_ds *) newshmds; + tempnode->shmds->mapptr = newshmds; + _shmattach *attachnode = tempnode->attachhead; + while (attachnode) + { + void *newdata = MapViewOfFileEx (tempnode->attachmap, + (attachnode->shmflg & SHM_RDONLY) ? + FILE_MAP_READ : FILE_MAP_WRITE, 0, + 0, 0, attachnode->data); + if (newdata != attachnode->data) + { + /* don't worry about handle cleanup, we're dying! */ + system_printf ("failed to reattach to mapped file view %x\n", + attachnode->data); + return 1; + } + attachnode = attachnode->next; + } + tempnode = tempnode->next; + } + return 0; +} + +/* this is ugly. Yes, I know that. + * FIXME: abstract the lookup functionality, + * So that it can be an array, list, whatever without us being worried + */ + +/* FIXME: after fork, every memory area needs to have the attach count + * incremented. This should be done in the server? + */ + +/* FIXME: tell the daemon when we attach, so at process close it can clean up + * the attach count + */ +extern "C" void * +shmat (int shmid, const void *shmaddr, int shmflg) +{ + shmnode *tempnode = shm_head; + while (tempnode && tempnode->shm_id != shmid) + tempnode = tempnode->next; + + if (!tempnode) + { + /* couldn't find a currently open shm control area for the key - probably because + * shmget hasn't been called. + * Allocate a new control block - this has to be handled by the daemon */ + client_request_shm *req = + new client_request_shm (SHM_REATTACH, shmid, GetCurrentProcessId ()); + + int rc; + if ((rc = cygserver_request (req))) + { + delete req; + set_errno (ENOSYS); /* daemon communication failed */ + return (void *) -1; + } + + if (req->header.error_code) /* shm_get failed in the daemon */ + { + set_errno (req->header.error_code); + delete req; + return (void *) -1; + } + + /* we've got the id, now we open the memory area ourselves. + * This tests security automagically + * FIXME: make this a method of shmnode ? + */ + tempnode = + build_inprocess_shmds (req->parameters.out.filemap, + req->parameters.out.attachmap, + req->parameters.out.key, + req->parameters.out.shm_id); + delete req; + if (!tempnode) + return (void *) -1; + + } + + class shmid_ds *shm = tempnode->shmds; + + if (shmaddr) + { + //FIXME: requested base address ?! (Don't forget to fix the fixup_after_fork too) + set_errno (EINVAL); + return (void *) -1; + } + + void *rv = MapViewOfFile (tempnode->attachmap, + (shmflg & SHM_RDONLY) ? FILE_MAP_READ : + FILE_MAP_WRITE, 0, 0, 0); + + if (!rv) + { + //FIXME: translate GetLastError() + set_errno (EACCES); + return (void *) -1; + } + /* tell the daemon we have attached */ + client_request_shm *req = + new client_request_shm (SHM_ATTACH, shmid); + int rc; + if ((rc = cygserver_request (req))) + { + debug_printf ("failed to tell deaemon that we have attached\n"); + } + delete req; + + _shmattach *attachnode = new _shmattach; + attachnode->data = rv; + attachnode->shmflg = shmflg; + attachnode->next = + (_shmattach *) InterlockedExchangePointer (&tempnode->attachhead, + attachnode); + + + return rv; +} + +/* FIXME: tell the daemon when we detach so it doesn't cleanup incorrectly. + */ +extern "C" int +shmdt (const void *shmaddr) +{ + /* this should be "rare" so a hefty search is ok. If this is common, then we + * should alter the data structs to allow more optimisation + */ + +} + +//FIXME: who is allowed to perform STAT? +extern "C" int +shmctl (int shmid, int cmd, struct shmid_ds *buf) +{ + shmnode *tempnode = shm_head; + while (tempnode && tempnode->shm_id != shmid) + tempnode = tempnode->next; + if (!tempnode) + { + /* couldn't find a currently open shm control area for the key - probably because + * shmget hasn't been called. + * Allocate a new control block - this has to be handled by the daemon */ + client_request_shm *req = + new client_request_shm (SHM_REATTACH, shmid, GetCurrentProcessId ()); + + int rc; + if ((rc = cygserver_request (req))) + { + delete req; + set_errno (ENOSYS); /* daemon communication failed */ + return -1; + } + + if (req->header.error_code) /* shm_get failed in the daemon */ + { + set_errno (req->header.error_code); + delete req; + return -1; + } + + /* we've got the id, now we open the memory area ourselves. + * This tests security automagically + * FIXME: make this a method of shmnode ? + */ + tempnode = + build_inprocess_shmds (req->parameters.out.filemap, + req->parameters.out.attachmap, + req->parameters.out.key, + req->parameters.out.shm_id); + delete req; + if (!tempnode) + return -1; + } + + switch (cmd) + { + case IPC_STAT: + buf->shm_perm = tempnode->shmds->shm_perm; + buf->shm_segsz = tempnode->shmds->shm_segsz; + buf->shm_lpid = tempnode->shmds->shm_lpid; + buf->shm_cpid = tempnode->shmds->shm_cpid; + buf->shm_nattch = tempnode->shmds->shm_nattch; + buf->shm_atime = tempnode->shmds->shm_atime; + buf->shm_dtime = tempnode->shmds->shm_dtime; + buf->shm_ctime = tempnode->shmds->shm_ctime; + break; + case IPC_RMID: + { + /* TODO: check permissions. Or possibly, the daemon gets to be the only + * one with write access to the memory area? + */ + if (tempnode->shmds->shm_nattch) + system_printf + ("call to shmctl with cmd= IPC_RMID when memory area still has" + " attachees\n"); + /* how does this work? + * we mark the ds area as "deleted", and the at and get calls all fail from now on + * on, when nattch becomes 0, the mapped data area is destroyed. + * and each process, as they touch this area detaches. eventually only the + * daemon has an attach. The daemon gets asked to detach immediately. + */ +#if 0 +//waiting for the daemon to handle terminating process's + client_request_shm *req = + new client_request_shm (SHM_DEL, shmid, GetCurrentProcessId ()); + int rc; + if ((rc = cygserver_request (req))) + { + delete req; + set_errno (ENOSYS); /* daemon communication failed */ + return -1; + } + + if (req->header.error_code) /* shm_del failed in the daemon */ + { + set_errno (req->header.error_code); + delete req; + return -1; + } + + /* the daemon has deleted it's references */ + /* now for us */ + +#endif + + } + break; + case IPC_SET: + default: + set_errno (EINVAL); + return -1; + } + return 0; +} + +/* FIXME: evaluate getuid() and getgid() against the requested mode. Then + * choose PAGE_READWRITE | PAGE_READONLY and FILE_MAP_WRITE | FILE_MAP_READ + * appropriately + */ + +/* FIXME: shmid should be a verifyable object + */ + +/* FIXME: on NT we should check everything against the SD. On 95 we just emulate. + */ +extern "C" int +shmget (key_t key, size_t size, int shmflg) +{ + DWORD sd_size = 4096; + char sd_buf[4096]; + PSECURITY_DESCRIPTOR psd = (PSECURITY_DESCRIPTOR) sd_buf; + /* create a sd for our open requests based on shmflag & 0x01ff */ + psd = alloc_sd (getuid (), getgid (), cygheap->user.logsrv (), + shmflg & 0x01ff, psd, &sd_size); + + if (key == (key_t) - 1) + { + set_errno (ENOENT); + return -1; + } + + /* FIXME: enter the checking for existing keys mutex. This mutex _must_ be system wide + * to prevent races on shmget. + */ + + /* walk the list of currently open keys and return the id if found + */ + shmnode *tempnode = shm_head; + while (tempnode) + { + if (tempnode->key == key && key != IPC_PRIVATE) + { + // FIXME: free the mutex + if (size && tempnode->shmds->shm_segsz < size) + { + set_errno (EINVAL); + return -1; + } + if ((shmflg & IPC_CREAT) && (shmflg & IPC_EXCL)) + { + set_errno (EEXIST); + // FIXME: free the mutex + return -1; + } + // FIXME: do we need to other tests of the requested mode with the + // tempnode->shmid mode ? testcase on unix needed. + // FIXME do we need a security test? We are only examining the keys we already have open. + // FIXME: what are the sec implications for fork () if we don't check here? + return tempnode->shm_id; + } + tempnode = tempnode->next; + } + /* couldn't find a currently open shm control area for the key. + * Allocate a new control block - this has to be handled by the daemon */ + client_request_shm *req = + new client_request_shm (key, size, shmflg, sd_buf, + GetCurrentProcessId ()); + + int rc; + if ((rc = cygserver_request (req))) + { + delete req; + set_errno (ENOSYS); /* daemon communication failed */ + return -1; + } + + if (req->header.error_code) /* shm_get failed in the daemon */ + { + set_errno (req->header.error_code); + delete req; + return -1; + } + + /* we've got the id, now we open the memory area ourselves. + * This tests security automagically + * FIXME: make this a method of shmnode ? + */ + shmnode *shmtemp = build_inprocess_shmds (req->parameters.out.filemap, + req->parameters.out.attachmap, + key, + req->parameters.out.shm_id); + delete req; + if (shmtemp) + return shmtemp->shm_id; + return -1; + + +#if 0 + /* fill out the node data */ + shmtemp->shm_perm.cuid = getuid (); + shmtemp->shm_perm.uid = shmtemp->shm_perm.cuid; + shmtemp->shm_perm.cgid = getgid (); + shmtemp->shm_perm.gid = shmtemp->shm_perm.cgid; + shmtemp->shm_perm.mode = shmflg & 0x01ff; + shmtemp->shm_lpid = 0; + shmtemp->shm_nattch = 0; + shmtemp->shm_atime = 0; + shmtemp->shm_dtime = 0; + shmtemp->shm_ctime = time (NULL); + shmtemp->shm_segsz = size; + *(shmid_ds *) mapptr = *shmtemp; + shmtemp->filemap = filemap; + shmtemp->attachmap = attachmap; + shmtemp->mapptr = mapptr; + +#endif +} |