/* shm.cc: Single unix specification IPC interface for Cygwin Copyright 2001, 2002 Red Hat, Inc. Originally written by Robert Collins 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 #include #include "cygerrno.h" #include #include "security.h" #include "fhandler.h" #include "path.h" #include "dtable.h" #include "cygheap.h" #include #include "thread.h" #include "cygwin_shm.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; } static void delete_inprocess_shmds (shmnode **nodeptr) { shmnode *node = *nodeptr; // remove from the list if (node == shm_head) shm_head = shm_head->next; else { shmnode *tempnode = shm_head; while (tempnode && tempnode->next != node) tempnode = tempnode->next; if (tempnode) tempnode->next = node->next; // else log the unexpected ! } // release the shared data view UnmapViewOfFile (node->shmds); CloseHandle (node->filemap); CloseHandle (node->attachmap); // free the memory delete node; nodeptr = NULL; } 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 */ shmnode *tempnode = shm_head; _shmattach *attachnode; while (tempnode) { // FIXME: Race potential attachnode = tempnode->attachhead; while (attachnode && attachnode->data != shmaddr) attachnode = attachnode->next; if (attachnode) break; tempnode = tempnode->next; } if (!tempnode) { // dt cannot be called by an app that hasn't alreadu at'd set_errno (EINVAL); return -1; } UnmapViewOfFile (attachnode->data); /* tell the daemon we have attached */ client_request_shm *req = new client_request_shm (SHM_DETACH, tempnode->shm_id); int rc; if ((rc = cygserver_request (req))) { debug_printf ("failed to tell deaemon that we have detached\n"); } delete req; return 0; } //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. */ //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 */ // FIXME: create a destructor delete_inprocess_shmds (&tempnode); } break; case IPC_SET: default: set_errno (EINVAL); return -1; } return 0; } /* FIXME: evaluate getuid32() and getgid32() 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 */ InitializeSecurityDescriptor (psd, SECURITY_DESCRIPTOR_REVISION); psd = alloc_sd (getuid32 (), getgid32 (), 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 = getuid32 (); shmtemp->shm_perm.uid = shmtemp->shm_perm.cuid; shmtemp->shm_perm.cgid = getgid32 (); 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 }