/* cygserver_shm.cc: Single unix specification IPC interface for Cygwin. Copyright 2002 Red Hat, Inc. Written by Conrad Scott . Based on code 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 "woutsup.h" #include #include #include #include #include #include "cygserver_ipc.h" #include "cygserver_shm.h" #include "security.h" #include "cygwin/cygserver.h" #include "cygwin/cygserver_process.h" #include "cygwin/cygserver_transport.h" /*---------------------------------------------------------------------------* * class server_shmmgr * * A singleton class. *---------------------------------------------------------------------------*/ #define shmmgr (server_shmmgr::instance ()) class server_shmmgr { private: class attach_t { public: class process *const _client; unsigned int _refcnt; attach_t *_next; attach_t (class process *const client) : _client (client), _refcnt (0), _next (NULL) {} }; class segment_t { private: // Bits for the _flg field. enum { IS_DELETED = 0x01 }; public: const int _intid; const int _shmid; struct shmid_ds _ds; segment_t *_next; segment_t (const key_t key, const int intid, const HANDLE hFileMap); ~segment_t (); bool is_deleted () const { return _flg & IS_DELETED; } bool is_pending_delete () const { return !_ds.shm_nattch && is_deleted (); } void mark_deleted () { assert (!is_deleted ()); _flg |= IS_DELETED; } int attach (class process *, HANDLE & hFileMap); int detach (class process *); private: static long _sequence; int _flg; const HANDLE _hFileMap; attach_t *_attach_head; // A list sorted by winpid; attach_t *find (const class process *, attach_t **previous = NULL); }; class cleanup_t : public cleanup_routine { public: cleanup_t (const segment_t *const segptr) : cleanup_routine (reinterpret_cast (segptr->_shmid)) { assert (key ()); } int shmid () const { return reinterpret_cast (key ()); } virtual void cleanup (class process *const client) { const int res = shmmgr.shmdt (shmid (), client); if (res != 0) debug_printf ("process cleanup failed [shmid = %d]: %s", shmid (), strerror (-res)); } }; public: static server_shmmgr & instance (); int shmat (HANDLE & hFileMap, int shmid, int shmflg, class process *); int shmctl (int & out_shmid, struct shmid_ds & out_ds, struct shminfo & out_shminfo, struct shm_info & out_shm_info, const int shmid, int cmd, const struct shmid_ds &, class process *); int shmdt (int shmid, class process *); int shmget (int & out_shmid, key_t, size_t, int shmflg, uid_t, gid_t, class process *); private: static server_shmmgr *_instance; static pthread_once_t _instance_once; static void initialise_instance (); CRITICAL_SECTION _segments_lock; segment_t *_segments_head; // A list sorted by int_id. int _shm_ids; // Number of shm segments (for ipcs(8)). int _shm_tot; // Total bytes of shm segments (for ipcs(8)). int _shm_atts; // Number of attached segments (for ipcs(8)). int _intid_max; // Highest intid yet allocated (for ipcs(8)). server_shmmgr (); ~server_shmmgr (); // Undefined (as this class is a singleton): server_shmmgr (const server_shmmgr &); server_shmmgr & operator= (const server_shmmgr &); segment_t *find_by_key (key_t); segment_t *find (int intid, segment_t **previous = NULL); int new_segment (key_t, size_t, int shmflg, pid_t, uid_t, gid_t); segment_t *new_segment (key_t, size_t, HANDLE); void delete_segment (segment_t *); }; /* static */ long server_shmmgr::segment_t::_sequence = 0; /* static */ server_shmmgr *server_shmmgr::_instance = NULL; /* static */ pthread_once_t server_shmmgr::_instance_once = PTHREAD_ONCE_INIT; /*---------------------------------------------------------------------------* * server_shmmgr::segment_t::segment_t () *---------------------------------------------------------------------------*/ server_shmmgr::segment_t::segment_t (const key_t key, const int intid, const HANDLE hFileMap) : _intid (intid), _shmid (ipc_int2ext (intid, IPC_SHMOP, _sequence)), _next (NULL), _flg (0), _hFileMap (hFileMap), _attach_head (NULL) { assert (0 <= _intid && _intid < SHMMNI); memset (&_ds, '\0', sizeof (_ds)); _ds.shm_perm.key = key; } /*---------------------------------------------------------------------------* * server_shmmgr::segment_t::~segment_t () *---------------------------------------------------------------------------*/ server_shmmgr::segment_t::~segment_t () { assert (!_attach_head); if (!CloseHandle (_hFileMap)) syscall_printf ("failed to close file map [handle = 0x%x]: %E", _hFileMap); } /*---------------------------------------------------------------------------* * server_shmmgr::segment_t::attach () *---------------------------------------------------------------------------*/ int server_shmmgr::segment_t::attach (class process *const client, HANDLE & hFileMap) { assert (client); if (!DuplicateHandle (GetCurrentProcess (), _hFileMap, client->handle (), &hFileMap, 0, FALSE, // bInheritHandle DUPLICATE_SAME_ACCESS)) { syscall_printf (("failed to duplicate handle for client " "[key = 0x%016llx, shmid = %d, handle = 0x%x]: %E"), _ds.shm_perm.key, _shmid, _hFileMap); return -EACCES; // FIXME: Case analysis? } _ds.shm_lpid = client->cygpid (); _ds.shm_nattch += 1; _ds.shm_atime = time (NULL); // FIXME: sub-second times. attach_t *previous = NULL; attach_t *attptr = find (client, &previous); if (!attptr) { attptr = safe_new (attach_t, client); if (previous) { attptr->_next = previous->_next; previous->_next = attptr; } else { attptr->_next = _attach_head; _attach_head = attptr; } } attptr->_refcnt += 1; cleanup_t *const cleanup = safe_new (cleanup_t, this); // FIXME: ::add should only fail if the process object is already // cleaning up; but it can't be doing that since this thread has it // locked. const bool result = client->add (cleanup); assert (result); return 0; } /*---------------------------------------------------------------------------* * server_shmmgr::segment_t::detach () *---------------------------------------------------------------------------*/ int server_shmmgr::segment_t::detach (class process *const client) { attach_t *previous = NULL; attach_t *const attptr = find (client, &previous); if (!attptr) return -EINVAL; if (client->is_active ()) { const cleanup_t key (this); if (!client->remove (&key)) syscall_printf (("failed to remove cleanup routine for %d(%lu) " "[shmid = %d]"), client->cygpid (), client->winpid (), _shmid); } attptr->_refcnt -= 1; if (!attptr->_refcnt) { assert (previous ? previous->_next == attptr : _attach_head == attptr); if (previous) previous->_next = attptr->_next; else _attach_head = attptr->_next; safe_delete (attptr); } assert (_ds.shm_nattch > 0); _ds.shm_lpid = client->cygpid (); _ds.shm_nattch -= 1; _ds.shm_dtime = time (NULL); // FIXME: sub-second times. return 0; } /*---------------------------------------------------------------------------* * server_shmmgr::segment_t::find () *---------------------------------------------------------------------------*/ server_shmmgr::attach_t * server_shmmgr::segment_t::find (const class process *const client, attach_t **previous) { if (previous) *previous = NULL; // Nb. The _attach_head list is sorted by winpid. for (attach_t *attptr = _attach_head; attptr; attptr = attptr->_next) if (attptr->_client == client) return attptr; else if (attptr->_client->winpid () > client->winpid ()) return NULL; else if (previous) *previous = attptr; return NULL; } /*---------------------------------------------------------------------------* * server_shmmgr::instance () *---------------------------------------------------------------------------*/ /* static */ server_shmmgr & server_shmmgr::instance () { pthread_once (&_instance_once, &initialise_instance); assert (_instance); return *_instance; } /*---------------------------------------------------------------------------* * server_shmmgr::shmat () *---------------------------------------------------------------------------*/ int server_shmmgr::shmat (HANDLE & hFileMap, const int shmid, const int shmflg, class process *const client) { syscall_printf ("shmat (shmid = %d, shmflg = 0%o) for %d(%lu)", shmid, shmflg, client->cygpid (), client->winpid ()); int result = 0; EnterCriticalSection (&_segments_lock); segment_t *const segptr = find (ipc_ext2int (shmid, IPC_SHMOP)); if (!segptr) result = -EINVAL; else result = segptr->attach (client, hFileMap); if (!result) _shm_atts += 1; LeaveCriticalSection (&_segments_lock); if (result < 0) syscall_printf (("-1 [%d] = shmat (shmid = %d, shmflg = 0%o) " "for %d(%lu)"), -result, shmid, shmflg, client->cygpid (), client->winpid ()); else syscall_printf (("0x%x = shmat (shmid = %d, shmflg = 0%o) " "for %d(%lu)"), hFileMap, shmid, shmflg, client->cygpid (), client->winpid ()); return result; } /*---------------------------------------------------------------------------* * server_shmmgr::shmctl () *---------------------------------------------------------------------------*/ int server_shmmgr::shmctl (int & out_shmid, struct shmid_ds & out_ds, struct shminfo & out_shminfo, struct shm_info & out_shm_info, const int shmid, const int cmd, const struct shmid_ds & ds, class process *const client) { syscall_printf ("shmctl (shmid = %d, cmd = 0x%x) for %d(%lu)", shmid, cmd, client->cygpid (), client->winpid ()); int result = 0; EnterCriticalSection (&_segments_lock); switch (cmd) { case IPC_STAT: case SHM_STAT: // Uses intids rather than shmids. case IPC_SET: case IPC_RMID: { int intid; if (cmd == SHM_STAT) intid = shmid; else intid = ipc_ext2int (shmid, IPC_SHMOP); segment_t *const segptr = find (intid); if (!segptr) result = -EINVAL; else switch (cmd) { case IPC_STAT: out_ds = segptr->_ds; break; case IPC_SET: segptr->_ds.shm_perm.uid = ds.shm_perm.uid; segptr->_ds.shm_perm.gid = ds.shm_perm.gid; segptr->_ds.shm_perm.mode = ds.shm_perm.mode & 0777; segptr->_ds.shm_lpid = client->cygpid (); segptr->_ds.shm_ctime = time (NULL); // FIXME: sub-second times. break; case IPC_RMID: if (segptr->is_deleted ()) result = -EIDRM; else { segptr->mark_deleted (); if (segptr->is_pending_delete ()) delete_segment (segptr); } break; case SHM_STAT: // ipcs(8) i'face. out_ds = segptr->_ds; out_shmid = segptr->_shmid; break; } } break; case IPC_INFO: out_shminfo.shmmax = SHMMAX; out_shminfo.shmmin = SHMMIN; out_shminfo.shmmni = SHMMNI; out_shminfo.shmseg = SHMSEG; out_shminfo.shmall = SHMALL; break; case SHM_INFO: // ipcs(8) i'face. out_shmid = _intid_max; out_shm_info.shm_ids = _shm_ids; out_shm_info.shm_tot = _shm_tot; out_shm_info.shm_atts = _shm_atts; break; default: result = -EINVAL; break; } LeaveCriticalSection (&_segments_lock); if (result < 0) syscall_printf (("-1 [%d] = " "shmctl (shmid = %d, cmd = 0x%x) for %d(%lu)"), -result, shmid, cmd, client->cygpid (), client->winpid ()); else syscall_printf (("%d = " "shmctl (shmid = %d, cmd = 0x%x) for %d(%lu)"), ((cmd == SHM_STAT || cmd == SHM_INFO) ? out_shmid : result), shmid, cmd, client->cygpid (), client->winpid ()); return result; } /*---------------------------------------------------------------------------* * server_shmmgr::shmdt () *---------------------------------------------------------------------------*/ int server_shmmgr::shmdt (const int shmid, class process *const client) { syscall_printf ("shmdt (shmid = %d) for %d(%lu)", shmid, client->cygpid (), client->winpid ()); int result = 0; EnterCriticalSection (&_segments_lock); segment_t *const segptr = find (ipc_ext2int (shmid, IPC_SHMOP)); if (!segptr) result = -EINVAL; else result = segptr->detach (client); if (!result) _shm_atts -= 1; if (!result && segptr->is_pending_delete ()) delete_segment (segptr); LeaveCriticalSection (&_segments_lock); if (result < 0) syscall_printf ("-1 [%d] = shmdt (shmid = %d) for %d(%lu)", -result, shmid, client->cygpid (), client->winpid ()); else syscall_printf ("%d = shmdt (shmid = %d) for %d(%lu)", result, shmid, client->cygpid (), client->winpid ()); return result; } /*---------------------------------------------------------------------------* * server_shmmgr::shmget () *---------------------------------------------------------------------------*/ int server_shmmgr::shmget (int & out_shmid, const key_t key, const size_t size, const int shmflg, const uid_t uid, const gid_t gid, class process *const client) { syscall_printf (("shmget (key = 0x%016llx, size = %u, shmflg = 0%o) " "for %d(%lu)"), key, size, shmflg, client->cygpid (), client->winpid ()); int result = 0; EnterCriticalSection (&_segments_lock); if (key == IPC_PRIVATE) result = new_segment (key, size, shmflg, client->cygpid (), uid, gid); else { segment_t *const segptr = find_by_key (key); if (!segptr) if (shmflg & IPC_CREAT) result = new_segment (key, size, shmflg, client->cygpid (), uid, gid); else result = -ENOENT; else if (segptr->is_deleted ()) result = -EIDRM; else if ((shmflg & IPC_CREAT) && (shmflg & IPC_EXCL)) result = -EEXIST; else if ((shmflg & ~(segptr->_ds.shm_perm.mode)) & 0777) result = -EACCES; else if (size && segptr->_ds.shm_segsz < size) result = -EINVAL; else result = segptr->_shmid; } LeaveCriticalSection (&_segments_lock); if (result >= 0) { out_shmid = result; result = 0; } if (result < 0) syscall_printf (("-1 [%d] = " "shmget (key = 0x%016llx, size = %u, shmflg = 0%o) " "for %d(%lu)"), -result, key, size, shmflg, client->cygpid (), client->winpid ()); else syscall_printf (("%d = " "shmget (key = 0x%016llx, size = %u, shmflg = 0%o) " "for %d(%lu)"), out_shmid, key, size, shmflg, client->cygpid (), client->winpid ()); return result; } /*---------------------------------------------------------------------------* * server_shmmgr::initialise_instance () *---------------------------------------------------------------------------*/ /* static */ void server_shmmgr::initialise_instance () { assert (!_instance); _instance = safe_new0 (server_shmmgr); assert (_instance); } /*---------------------------------------------------------------------------* * server_shmmgr::server_shmmgr () *---------------------------------------------------------------------------*/ server_shmmgr::server_shmmgr () : _segments_head (NULL), _shm_ids (0), _shm_tot (0), _shm_atts (0), _intid_max (0) { InitializeCriticalSection (&_segments_lock); } /*---------------------------------------------------------------------------* * server_shmmgr::~server_shmmgr () *---------------------------------------------------------------------------*/ server_shmmgr::~server_shmmgr () { DeleteCriticalSection (&_segments_lock); } /*---------------------------------------------------------------------------* * server_shmmgr::find_by_key () *---------------------------------------------------------------------------*/ server_shmmgr::segment_t * server_shmmgr::find_by_key (const key_t key) { for (segment_t *segptr = _segments_head; segptr; segptr = segptr->_next) if (segptr->_ds.shm_perm.key == key) return segptr; return NULL; } /*---------------------------------------------------------------------------* * server_shmmgr::find () *---------------------------------------------------------------------------*/ server_shmmgr::segment_t * server_shmmgr::find (const int intid, segment_t **previous) { if (previous) *previous = NULL; for (segment_t *segptr = _segments_head; segptr; segptr = segptr->_next) if (segptr->_intid == intid) return segptr; else if (segptr->_intid > intid) // The list is sorted by intid. return NULL; else if (previous) *previous = segptr; return NULL; } /*---------------------------------------------------------------------------* * server_shmmgr::new_segment () *---------------------------------------------------------------------------*/ int server_shmmgr::new_segment (const key_t key, const size_t size, const int shmflg, const pid_t cygpid, const uid_t uid, const gid_t gid) { if (size < SHMMIN || size > SHMMAX) return -EINVAL; const HANDLE hFileMap = CreateFileMapping (INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, NULL); if (!hFileMap) { syscall_printf ("failed to create file mapping [size = %lu]: %E", size); return -ENOMEM; // FIXME } segment_t *const segptr = new_segment (key, size, hFileMap); if (!segptr) { (void) CloseHandle (hFileMap); return -ENOSPC; } segptr->_ds.shm_perm.cuid = segptr->_ds.shm_perm.uid = uid; segptr->_ds.shm_perm.cgid = segptr->_ds.shm_perm.gid = gid; segptr->_ds.shm_perm.mode = shmflg & 0777; segptr->_ds.shm_segsz = size; segptr->_ds.shm_cpid = cygpid; segptr->_ds.shm_ctime = time (NULL); // FIXME: sub-second times. return segptr->_shmid; } /*---------------------------------------------------------------------------* * server_shmmgr::new_segment () * * Allocate a new segment for the given key and file map with the * lowest available intid and insert into the segment map. *---------------------------------------------------------------------------*/ server_shmmgr::segment_t * server_shmmgr::new_segment (const key_t key, const size_t size, const HANDLE hFileMap) { // FIXME: Overflow risk. if (_shm_tot + size > SHMALL) return NULL; int intid = 0; // Next expected intid value. segment_t *previous = NULL; // Insert pointer. // Find first unallocated intid. for (segment_t *segptr = _segments_head; segptr && segptr->_intid == intid; segptr = segptr->_next, intid++) { previous = segptr; } /* By the time this condition is reached (given the default value of * SHMMNI), the linear searches should all replaced by something * just a *little* cleverer . . . */ if (intid >= SHMMNI) return NULL; segment_t *const segptr = safe_new (segment_t, key, intid, hFileMap); assert (segptr); if (previous) { segptr->_next = previous->_next; previous->_next = segptr; } else { segptr->_next = _segments_head; _segments_head = segptr; } _shm_ids += 1; _shm_tot += size; if (intid > _intid_max) _intid_max = intid; return segptr; } /*---------------------------------------------------------------------------* * server_shmmgr::delete_segment () *---------------------------------------------------------------------------*/ void server_shmmgr::delete_segment (segment_t *const segptr) { assert (segptr); assert (segptr->is_pending_delete ()); segment_t *previous = NULL; const segment_t *const tmp = find (segptr->_intid, &previous); assert (tmp == segptr); assert (previous ? previous->_next == segptr : _segments_head == segptr); if (previous) previous->_next = segptr->_next; else _segments_head = segptr->_next; assert (_shm_ids > 0); _shm_ids -= 1; _shm_tot -= segptr->_ds.shm_segsz; safe_delete (segptr); } /*---------------------------------------------------------------------------* * client_request_shm::client_request_shm () *---------------------------------------------------------------------------*/ client_request_shm::client_request_shm () : client_request (CYGSERVER_REQUEST_SHM, &_parameters, sizeof (_parameters)) { // verbose: syscall_printf ("created"); } /*---------------------------------------------------------------------------* * client_request_shm::serve () *---------------------------------------------------------------------------*/ void client_request_shm::serve (transport_layer_base *const conn, process_cache *const cache) { assert (conn); assert (!error_code ()); if (msglen () != sizeof (_parameters.in)) { syscall_printf ("bad request body length: expecting %lu bytes, got %lu", sizeof (_parameters), msglen ()); error_code (EINVAL); msglen (0); return; } // FIXME: Get a return code out of this and don't continue on error. conn->impersonate_client (); class process *const client = cache->process (_parameters.in.cygpid, _parameters.in.winpid); if (!client) { error_code (EAGAIN); msglen (0); return; } int result = -EINVAL; switch (_parameters.in.shmop) { case SHMOP_shmget: result = shmmgr.shmget (_parameters.out.shmid, _parameters.in.key, _parameters.in.size, _parameters.in.shmflg, _parameters.in.uid, _parameters.in.gid, client); break; case SHMOP_shmat: result = shmmgr.shmat (_parameters.out.hFileMap, _parameters.in.shmid, _parameters.in.shmflg, client); break; case SHMOP_shmdt: result = shmmgr.shmdt (_parameters.in.shmid, client); break; case SHMOP_shmctl: result = shmmgr.shmctl (_parameters.out.shmid, _parameters.out.ds, _parameters.out.shminfo, _parameters.out.shm_info, _parameters.in.shmid, _parameters.in.cmd, _parameters.in.ds, client); break; } client->release (); conn->revert_to_self (); if (result < 0) { error_code (-result); msglen (0); } else msglen (sizeof (_parameters.out)); }