diff --git a/winsup/cygserver/client.cc b/winsup/cygserver/client.cc index 1df23ad9f..138c9ddc0 100644 --- a/winsup/cygserver/client.cc +++ b/winsup/cygserver/client.cc @@ -4,204 +4,526 @@ Written by Egor Duda - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ +/* to allow this to link into cygwin and the .dll, a little magic is needed. */ #ifdef __OUTSIDE_CYGWIN__ -#undef __INSIDE_CYGWIN__ +#include "woutsup.h" #else #include "winsup.h" #endif -#ifndef __INSIDE_CYGWIN__ -#define debug_printf printf -#define api_fatal printf -#include -#include -#endif -#include +#include #include +#include #include -//#include "security.h" -#include "cygwin/cygserver_transport.h" -#include "cygwin/cygserver_transport_pipes.h" -#include "cygwin/cygserver_transport_sockets.h" + +#include "cygerrno.h" +#include "cygserver_shm.h" +#include "safe_memory.h" + #include "cygwin/cygserver.h" +#include "cygwin/cygserver_transport.h" -/* 0 = untested, 1 = running, 2 = dead */ -int cygserver_running=CYGSERVER_UNKNOWN; -/* on by default during development. For release, we probably want off by default */ -int allow_daemon = TRUE; +int cygserver_running = CYGSERVER_UNKNOWN; // Nb: inherited by children. -client_request_get_version::client_request_get_version () : client_request (CYGSERVER_REQUEST_GET_VERSION, sizeof (version)) +/* On by default during development. For release, we probably want off + * by default. + */ +bool allow_daemon = true; // Nb: inherited by children. + +client_request_get_version::client_request_get_version () + : client_request (CYGSERVER_REQUEST_GET_VERSION, &version, sizeof (version)) { - buffer = (char *)&version; + msglen (0); // No parameters for request. + + // verbose: syscall_printf ("created"); } -client_request_attach_tty::client_request_attach_tty () : client_request (CYGSERVER_REQUEST_ATTACH_TTY, sizeof (req)) +/* + * client_request_get_version::check_version () + * + * The major version and API version numbers must match exactly. An + * older than expected minor version number is accepted (as long as + * the first numbers match, that is). + */ + +bool +client_request_get_version::check_version () const { - buffer = (char *)&req; - req.pid = 0; - req.master_pid = 0; - req.from_master = NULL; - req.to_master = NULL; + const bool ok = (version.major == CYGWIN_SERVER_VERSION_MAJOR + && version.api == CYGWIN_SERVER_VERSION_API + && version.minor <= CYGWIN_SERVER_VERSION_MINOR); + + if (!ok) + syscall_printf (("incompatible version of cygwin server: " + "client version %d.%d.%d.%d, " + "server version %ld.%ld.%ld.%ld"), + CYGWIN_SERVER_VERSION_MAJOR, + CYGWIN_SERVER_VERSION_API, + CYGWIN_SERVER_VERSION_MINOR, + CYGWIN_SERVER_VERSION_PATCH, + version.major, + version.api, + version.minor, + version.patch); + + return ok; } -client_request_attach_tty::client_request_attach_tty (DWORD npid, DWORD nmaster_pid, HANDLE nfrom_master, HANDLE nto_master) : client_request (CYGSERVER_REQUEST_ATTACH_TTY, sizeof (req)) +#ifdef __INSIDE_CYGWIN__ + +client_request_attach_tty::client_request_attach_tty (DWORD nmaster_pid, + HANDLE nfrom_master, + HANDLE nto_master) + : client_request (CYGSERVER_REQUEST_ATTACH_TTY, &req, sizeof (req)) { - buffer = (char *)&req; - req.pid = npid; + req.pid = GetCurrentProcessId (); req.master_pid = nmaster_pid; req.from_master = nfrom_master; req.to_master = nto_master; + + syscall_printf (("created: pid = %lu, master_pid = %lu, " + "from_master = %lu, to_master = %lu"), + req.pid, req.master_pid, req.from_master, req.to_master); } -client_request_shutdown::client_request_shutdown () : client_request (CYGSERVER_REQUEST_SHUTDOWN, 0) +#else /* !__INSIDE_CYGWIN__ */ + +client_request_attach_tty::client_request_attach_tty () + : client_request (CYGSERVER_REQUEST_ATTACH_TTY, &req, sizeof (req)) { - buffer = NULL; + // verbose: syscall_printf ("created"); } -client_request::client_request (cygserver_request_code id, ssize_t buffer_size) : header (id, buffer_size) +#endif /* __INSIDE_CYGWIN__ */ + +/* + * client_request_attach_tty::send () + * + * Wraps the base method to provide error handling support. If the + * reply contains a body but is flagged as an error, close any handles + * that have been returned by cygserver and then discard the message + * body, i.e. the client either sees a successful result with handles + * or an unsuccessful result with no handles. + */ + +void +client_request_attach_tty::send (transport_layer_base * const conn) { + client_request::send (conn); + + if (msglen () && error_code ()) + { + if (from_master ()) + CloseHandle (from_master ()); + if (to_master ()) + CloseHandle (to_master ()); + msglen (0); + } +} + +client_request::header_t::header_t (const request_code_t request_code, + const size_t msglen) + : msglen (msglen), + request_code (request_code) +{ + assert (request_code >= 0 && request_code < CYGSERVER_REQUEST_LAST); +} + +// FIXME: also check write and read result for -1. + +void +client_request::send (transport_layer_base * const conn) +{ + assert (conn); + assert (!(msglen () && !_buf)); // i.e., msglen () implies _buf + assert (msglen () <= _buflen); + + { + const ssize_t count = conn->write (&_header, sizeof (_header)); + + if (count != sizeof (_header)) + { + assert (errno); + error_code (errno); + syscall_printf (("request header write failure: " + "only %ld bytes sent of %ld, " + "error = %d(%lu)"), + count, sizeof (_header), + errno, GetLastError ()); + return; + } + } + + if (msglen ()) + { + const ssize_t count = conn->write (_buf, msglen ()); + + if (count == -1 || (size_t) count != msglen ()) + { + assert (errno); + error_code (errno); + syscall_printf (("request body write failure: " + "only %ld bytes sent of %ld, " + "error = %d(%lu)"), + count, msglen (), + errno, GetLastError ()); + return; + } + } + + // verbose: syscall_printf ("request sent (%ld + %ld bytes)", + // sizeof (_header), msglen ()); + + { + const ssize_t count = conn->read (&_header, sizeof (_header)); + + if (count != sizeof (_header)) + { + assert (errno); + error_code (errno); + syscall_printf (("reply header read failure: " + "only %ld bytes received of %ld, " + "error = %d(%lu)"), + count, sizeof (_header), + errno, GetLastError ()); + return; + } + } + + if (msglen () && !_buf) + { + system_printf ("no client buffer for reply body: %ld bytes needed", + msglen ()); + error_code (EINVAL); + return; + } + + if (msglen () > _buflen) + { + system_printf (("client buffer too small for reply body: " + "have %ld bytes and need %ld"), + _buflen, msglen ()); + error_code (EINVAL); + return; + } + + if (msglen ()) + { + const ssize_t count = conn->read (_buf, msglen ()); + + if (count == -1 || (size_t) count != msglen ()) + { + assert (errno); + error_code (errno); + syscall_printf (("reply body read failure: " + "only %ld bytes received of %ld, " + "error = %d(%lu)"), + count, msglen (), + errno, GetLastError ()); + return; + } + } + + // verbose: syscall_printf ("reply received (%ld + %ld bytes)", + // sizeof (_header), msglen ()); +} + +#ifndef __INSIDE_CYGWIN__ + +/* + * client_request::handle_request () + * + * A server-side method. + * + * This is a factory method for the client_request subclasses. It + * reads the incoming request header and, based on its request code, + * creates an instance of the appropriate class. + * + * FIXME: If the incoming packet is malformed, the server drops it on + * the floor. Should it try and generate some sort of reply for the + * client? As it is, the client will simply get a broken connection. + * + * FIXME: also check write and read result for -1. + */ + +/* static */ void +client_request::handle_request (transport_layer_base *const conn, + process_cache *const cache) +{ + // verbose: debug_printf ("about to read"); + + header_t header; + + { + const ssize_t count = conn->read (&header, sizeof (header)); + + if (count != sizeof (header)) + { + syscall_printf (("request header read failure: " + "only %ld bytes received of %ld, " + "error = %d(%lu)"), + count, sizeof (header), + errno, GetLastError ()); + return; + } + + // verbose: debug_printf ("got header (%ld)", count); + } + + client_request *req = NULL; + + switch (header.request_code) + { + case CYGSERVER_REQUEST_GET_VERSION: + req = safe_new0 (client_request_get_version); + break; + case CYGSERVER_REQUEST_SHUTDOWN: + req = safe_new0 (client_request_shutdown); + break; + case CYGSERVER_REQUEST_ATTACH_TTY: + req = safe_new0 (client_request_attach_tty); + break; + case CYGSERVER_REQUEST_SHM: + req = safe_new0 (client_request_shm); + break; + default: + syscall_printf ("unknown request code %d received: request ignored", + header.request_code); + return; + } + + assert (req); + + req->msglen (header.msglen); + req->handle (conn, cache); + + safe_delete (req); + +#ifndef DEBUGGING + printf ("."); // A little noise when we're being quiet. +#endif +} + +#endif /* !__INSIDE_CYGWIN__ */ + +client_request::client_request (request_code_t const id, + void * const buf, + size_t const buflen) + : _header (id, buflen), + _buf (buf), + _buflen (buflen) +{ + assert ((!_buf && !_buflen) || (_buf && _buflen)); } client_request::~client_request () -{ -} - -client_request::operator class request_header () -{ - return header; -} - -void -client_request::send (transport_layer_base *conn) -{ - if (!conn) - return; - debug_printf("this=%p, conn=%p\n",this, conn); - ssize_t bytes_written, bytes_read; - debug_printf("header.cb = %ld\n",header.cb); - if ((bytes_written = conn->write ((char *)&header, sizeof (header))) - != sizeof(header) || (header.cb && - (bytes_written = conn->write (buffer, header.cb)) != header.cb)) - { - header.error_code = -1; - debug_printf ("bytes written != request size\n"); - return; - } - - debug_printf("Sent request, size (%ld)\n",bytes_written); - - if ((bytes_read = conn->read ((char *)&header, sizeof (header))) - != sizeof (header) || (header.cb && - (bytes_read = conn->read (buffer, header.cb)) != header.cb)) - { - header.error_code = -1; - debug_printf("failed reading response \n"); - return; - } - debug_printf ("completed ok\n"); -} - -/* Oh, BTW: Fix the procedural basis and make this more intuitive. */ +{} int -cygserver_request (client_request * req) +client_request::make_request () { - class transport_layer_base *transport; + assert (cygserver_running == CYGSERVER_UNKNOWN \ + || cygserver_running == CYGSERVER_OK \ + || cygserver_running == CYGSERVER_UNAVAIL); - if (!req || allow_daemon != TRUE) - return -1; + if (cygserver_running == CYGSERVER_UNKNOWN) + cygserver_init (); - /* dont' retry every request if the server's not there */ - if (cygserver_running==CYGSERVER_DEAD && req->header.req_id != CYGSERVER_REQUEST_GET_VERSION) - return -1; + assert (cygserver_running == CYGSERVER_OK \ + || cygserver_running == CYGSERVER_UNAVAIL); - transport = create_server_transport (); - - /* FIXME: have at most one connection per thread. use TLS to store the details */ - /* logic is: - * if not tlskey->conn, new conn, - * then; transport=conn; - */ - if (!transport->connect ()) + /* Don't retry every request if the server's not there */ + if (cygserver_running == CYGSERVER_UNAVAIL) { - delete transport; + syscall_printf ("cygserver un-available"); + error_code (ENOSYS); return -1; } - debug_printf ("connected to server %p\n", transport); + transport_layer_base *const transport = create_server_transport (); - req->send(transport); + assert (transport); - transport->close (); + if (transport->connect () == -1) + { + if (errno) + error_code (errno); + else + error_code (ENOSYS); + safe_delete (transport); + return -1; + } - delete transport; + // verbose: debug_printf ("connected to server %p", transport); + + send (transport); + + safe_delete (transport); return 0; } -#if 0 -BOOL +#ifndef __INSIDE_CYGWIN__ + +/* + * client_request::handle () + * + * A server-side method. + * + * At this point, the header of an incoming request has been read and + * an appropriate client_request object constructed. This method has + * to read the request body into its buffer, if there is such a body, + * then perform the request and send back the results to the client. + * + * FIXME: If the incoming packet is malformed, the server drops it on + * the floor. Should it try and generate some sort of reply for the + * client? As it is, the client will simply get a broken connection. + * + * FIXME: also check write and read result for -1. + */ + +void +client_request::handle (transport_layer_base *const conn, + process_cache *const cache) +{ + if (msglen () && !_buf) + { + system_printf ("no buffer for request body: %ld bytes needed", + msglen ()); + error_code (EINVAL); + return; + } + + if (msglen () > _buflen) + { + system_printf (("buffer too small for request body: " + "have %ld bytes and need %ld"), + _buflen, msglen ()); + error_code (EINVAL); + return; + } + + if (msglen ()) + { + const ssize_t count = conn->read (_buf, msglen ()); + + if (count == -1 || (size_t) count != msglen ()) + { + assert (errno); + error_code (errno); + syscall_printf (("request body read failure: " + "only %ld bytes received of %ld, " + "error = %d(%lu)"), + count, msglen (), + errno, GetLastError ()); + return; + } + } + + // verbose: syscall_printf ("request received (%ld + %ld bytes)", + // sizeof (_header), msglen ()); + + error_code (0); // Overwrites the _header.request_code field. + + /* + * This is not allowed to fail. We must return ENOSYS at a minimum + * to the client. + */ + serve (conn, cache); + + { + const ssize_t count = conn->write (&_header, sizeof (_header)); + + if (count != sizeof (_header)) + { + assert (errno); + error_code (errno); + syscall_printf (("reply header write failure: " + "only %ld bytes sent of %ld, " + "error = %d(%lu)"), + count, sizeof (_header), + errno, GetLastError ()); + return; + } + } + + if (msglen ()) + { + const ssize_t count = conn->write (_buf, msglen ()); + + if (count == -1 || (size_t) count != msglen ()) + { + assert (errno); + error_code (errno); + syscall_printf (("reply body write failure: " + "only %ld bytes sent of %ld, " + "error = %d(%lu)"), + count, msglen (), + errno, GetLastError ()); + return; + } + } + + // verbose: syscall_printf ("reply sent (%ld + %ld bytes)", + // sizeof (_header), msglen ()); +} + +#endif /* !__INSIDE_CYGWIN__ */ + +bool check_cygserver_available () { - BOOL ret_val = FALSE; - HANDLE pipe = CreateFile (pipe_name, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - &sec_all_nih, - OPEN_EXISTING, - 0, - NULL); - if (pipe != INVALID_HANDLE_VALUE || GetLastError () != ERROR_PIPE_BUSY) - ret_val = TRUE; + assert (cygserver_running == CYGSERVER_UNKNOWN \ + || cygserver_running == CYGSERVER_UNAVAIL); - if (pipe && pipe != INVALID_HANDLE_VALUE) - CloseHandle (pipe); + cygserver_running = CYGSERVER_OK; // For make_request (). - return (ret_val); + client_request_get_version req; + + /* This indicates that we failed to connect to cygserver at all but + * that's fine as cygwin doesn't need it to be running. + */ + if (req.make_request () == -1) + return false; + + /* We connected to the server but something went wrong after that + * (in sending the message, in cygserver itself, or in receiving the + * reply). + */ + if (req.error_code ()) + { + syscall_printf ("failure in cygserver version request: %d", + req.error_code ()); + syscall_printf ("process will continue without cygserver support"); + return false; + } + + return req.check_version (); } -#endif void cygserver_init () { - int rc; - if (allow_daemon != TRUE) + if (!allow_daemon) { - cygserver_running = CYGSERVER_DEAD; + syscall_printf ("cygserver use disabled in client"); + cygserver_running = CYGSERVER_UNAVAIL; return; } - if (cygserver_running==CYGSERVER_OK) + assert (cygserver_running == CYGSERVER_UNKNOWN \ + || cygserver_running == CYGSERVER_OK \ + || cygserver_running == CYGSERVER_UNAVAIL); + + if (cygserver_running == CYGSERVER_OK) return; - client_request_get_version *req = - new client_request_get_version (); - - rc = cygserver_request (req); - delete req; - if (rc < 0) - cygserver_running = CYGSERVER_DEAD; - else if (rc > 0) - api_fatal ("error connecting to cygwin server. error: %d", rc); - else if (req->version.major != CYGWIN_SERVER_VERSION_MAJOR || - req->version.api != CYGWIN_SERVER_VERSION_API || - req->version.minor > CYGWIN_SERVER_VERSION_MINOR) - api_fatal ("incompatible version of cygwin server.\n\ - client version %d.%d.%d.%d, server version%ld.%ld.%ld.%ld", - CYGWIN_SERVER_VERSION_MAJOR, - CYGWIN_SERVER_VERSION_API, - CYGWIN_SERVER_VERSION_MINOR, - CYGWIN_SERVER_VERSION_PATCH, - req->version.major, - req->version.api, - req->version.minor, - req->version.patch); - else - cygserver_running = CYGSERVER_OK; + if (!check_cygserver_available ()) + cygserver_running = CYGSERVER_UNAVAIL; } diff --git a/winsup/cygserver/cygserver.cc b/winsup/cygserver/cygserver.cc index af9cee93e..0c0740379 100644 --- a/winsup/cygserver/cygserver.cc +++ b/winsup/cygserver/cygserver.cc @@ -4,61 +4,141 @@ Written by Egor Duda - This file is part of Cygwin. +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. */ +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 -#include + +#include +#include +#include +#include #include +#include #include -#include "wincap.h" +#include +#include + +#include "cygerrno.h" #include "cygwin_version.h" -#include "getopt.h" - -#include "cygwin/cygserver_transport.h" -#include "cygwin/cygserver_transport_pipes.h" -#include "cygwin/cygserver_transport_sockets.h" -#include "threaded_queue.h" -#include "cygwin/cygserver_process.h" #include "cygwin/cygserver.h" -#include "cygserver_shm.h" +#include "cygwin/cygserver_process.h" +#include "cygwin/cygserver_transport.h" -/* for quieter operation, set to 0 */ -#define DEBUG 0 -#define debug_printf if (DEBUG) printf +// Version string. +static const char version[] = "$Revision$"; + +/* + * Support function for the XXX_printf () macros in "woutsup.h". + * Copied verbatim from "strace.cc". + */ +static int +getfunc (char *in_dst, const char *func) +{ + const char *p; + const char *pe; + char *dst = in_dst; + for (p = func; (pe = strchr (p, '(')); p = pe + 1) + if (isalnum ((int)pe[-1]) || pe[-1] == '_') + break; + else if (isspace ((int)pe[-1])) + { + pe--; + break; + } + if (!pe) + pe = strchr (func, '\0'); + for (p = pe; p > func; p--) + if (p != pe && *p == ' ') + { + p++; + break; + } + if (*p == '*') + p++; + while (p < pe) + *dst++ = *p++; + + *dst++ = ':'; + *dst++ = ' '; + *dst = '\0'; + + return dst - in_dst; +} + +/* + * Support function for the XXX_printf () macros in "woutsup.h". + */ +extern "C" void +__cygserver__printf (const char *const function, const char *const fmt, ...) +{ + const DWORD lasterror = GetLastError (); + const int lasterrno = errno; + + va_list ap; + + char *const buf = (char *) alloca (BUFSIZ); + + assert (buf); + + int len = 0; + + if (function) + len += getfunc (buf, function); + + va_start (ap, fmt); + len += vsnprintf (buf + len, BUFSIZ - len, fmt, ap); + va_end (ap); + + len += snprintf (buf + len, BUFSIZ - len, "\n"); + + const int actual = (len > BUFSIZ ? BUFSIZ : len); + + write (2, buf, actual); + + errno = lasterrno; + SetLastError (lasterror); + + return; +} + +#ifdef DEBUGGING + +int __stdcall +__set_errno (const char *func, int ln, int val) +{ + debug_printf ("%s:%d val %d", func, ln, val); + return _impure_ptr->_errno = val; +} + +#endif /* DEBUGGING */ GENERIC_MAPPING access_mapping; -static class transport_layer_base *transport; -DWORD request_count = 0; - -BOOL +static BOOL setup_privileges () { BOOL rc, ret_val; HANDLE hToken = NULL; TOKEN_PRIVILEGES sPrivileges; - rc = OpenProcessToken (GetCurrentProcess() , TOKEN_ALL_ACCESS , &hToken) ; + rc = OpenProcessToken (GetCurrentProcess () , TOKEN_ALL_ACCESS , &hToken) ; if (!rc) { - printf ("error opening process token (%lu)\n", GetLastError ()); + system_printf ("error opening process token (%lu)", GetLastError ()); ret_val = FALSE; goto out; } rc = LookupPrivilegeValue (NULL, SE_DEBUG_NAME, &sPrivileges.Privileges[0].Luid); if (!rc) { - printf ("error getting prigilege luid (%lu)\n", GetLastError ()); + system_printf ("error getting privilege luid (%lu)", GetLastError ()); ret_val = FALSE; goto out; } @@ -67,7 +147,8 @@ setup_privileges () rc = AdjustTokenPrivileges (hToken, FALSE, &sPrivileges, 0, NULL, NULL) ; if (!rc) { - printf ("error adjusting prigilege level. (%lu)\n", GetLastError ()); + system_printf ("error adjusting privilege level. (%lu)", + GetLastError ()); ret_val = FALSE; goto out; } @@ -89,179 +170,223 @@ check_and_dup_handle (HANDLE from_process, HANDLE to_process, HANDLE from_process_token, DWORD access, HANDLE from_handle, - HANDLE* to_handle_ptr, BOOL bInheritHandle = FALSE) + HANDLE *to_handle_ptr, BOOL bInheritHandle = FALSE) { HANDLE local_handle = NULL; int ret_val = EACCES; - char sd_buf [1024]; - PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR) &sd_buf; - DWORD bytes_needed; - PRIVILEGE_SET ps; - DWORD ps_len = sizeof (ps); - BOOL status; if (from_process != GetCurrentProcess ()) { + if (!DuplicateHandle (from_process, from_handle, + GetCurrentProcess (), &local_handle, + 0, bInheritHandle, + DUPLICATE_SAME_ACCESS)) + { + system_printf ("error getting handle(%u) to server (%lu)", + (unsigned int)from_handle, GetLastError ()); + goto out; + } + } else + local_handle = from_handle; - if (!DuplicateHandle (from_process, from_handle, - GetCurrentProcess (), &local_handle, - 0, bInheritHandle, - DUPLICATE_SAME_ACCESS)) + if (!wincap.has_security ()) + assert (!from_process_token); + else { - printf ("error getting handle(%u) to server (%lu)\n", (unsigned int)from_handle, GetLastError ()); - goto out; - } -} else - local_handle = from_handle; + char sd_buf [1024]; + PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR) &sd_buf; + DWORD bytes_needed; + PRIVILEGE_SET ps; + DWORD ps_len = sizeof (ps); + BOOL status; - if (!GetKernelObjectSecurity (local_handle, - OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, - sd, sizeof (sd_buf), &bytes_needed)) - { - printf ("error getting handle SD (%lu)\n", GetLastError ()); - goto out; - } + if (!GetKernelObjectSecurity (local_handle, + (OWNER_SECURITY_INFORMATION + | GROUP_SECURITY_INFORMATION + | DACL_SECURITY_INFORMATION), + sd, sizeof (sd_buf), &bytes_needed)) + { + system_printf ("error getting handle SD (%lu)", GetLastError ()); + goto out; + } - MapGenericMask (&access, &access_mapping); + MapGenericMask (&access, &access_mapping); - if (!AccessCheck (sd, from_process_token, access, &access_mapping, - &ps, &ps_len, &access, &status)) - { - printf ("error checking access rights (%lu)\n", GetLastError ()); - goto out; - } + if (!AccessCheck (sd, from_process_token, access, &access_mapping, + &ps, &ps_len, &access, &status)) + { + system_printf ("error checking access rights (%lu)", + GetLastError ()); + goto out; + } - if (!status) - { - printf ("access to object denied\n"); - goto out; + if (!status) + { + system_printf ("access to object denied"); + goto out; + } } if (!DuplicateHandle (from_process, from_handle, to_process, to_handle_ptr, access, bInheritHandle, 0)) { - printf ("error getting handle to client (%lu)\n", GetLastError ()); + system_printf ("error getting handle to client (%lu)", GetLastError ()); goto out; } - debug_printf ("Duplicated %p to %p\n", from_handle, *to_handle_ptr); + + // verbose: debug_printf ("Duplicated %p to %p", from_handle, *to_handle_ptr); ret_val = 0; -out: + out: if (local_handle && from_process != GetCurrentProcess ()) CloseHandle (local_handle); return (ret_val); } -void -client_request::serve (transport_layer_base *conn, class process_cache *cache) -{ - printf ("*****************************************\n" - "A call to the base client_request class has occured\n" - "This indicates a mismatch in a virtual function definition somewhere\n"); - exit (1); -} +/* + * client_request_attach_tty::serve () + */ void -client_request_attach_tty::serve(transport_layer_base *conn, class process_cache *cache) +client_request_attach_tty::serve (transport_layer_base *const conn, + process_cache *) { - HANDLE from_process_handle = NULL; - HANDLE to_process_handle = NULL; - HANDLE token_handle = NULL; - DWORD rc; + assert (conn); - if (header.cb != sizeof (req)) + assert (!error_code ()); + + if (!wincap.has_security ()) { - header.error_code = EINVAL; + syscall_printf ("operation only supported on systems with security"); + error_code (EINVAL); + msglen (0); return; } - debug_printf ("pid %ld:(%p,%p) -> pid %ld\n", req.master_pid, - req.from_master, req.to_master, - req.pid); - - debug_printf ("opening process %ld\n", req.master_pid); - from_process_handle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.master_pid); - debug_printf ("opening process %ld\n", req.pid); - to_process_handle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.pid); - if (!from_process_handle || !to_process_handle) + if (msglen () != sizeof (req)) { - printf ("error opening process (%lu)\n", GetLastError ()); - header.error_code = EACCES; - goto out; + syscall_printf ("bad request body length: expecting %lu bytes, got %lu", + sizeof (req), msglen ()); + error_code (EINVAL); + msglen (0); + return; } - debug_printf ("Impersonating client\n"); + msglen (0); // Until we fill in some fields. + + // verbose: debug_printf ("pid %ld:(%p,%p) -> pid %ld", + // req.master_pid, req.from_master, req.to_master, + // req.pid); + + // verbose: debug_printf ("opening process %ld", req.master_pid); + + const HANDLE from_process_handle = + OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.master_pid); + + if (!from_process_handle) + { + system_printf ("error opening `from' process, error = %lu", + GetLastError ()); + error_code (EACCES); + return; + } + + // verbose: debug_printf ("opening process %ld", req.pid); + + const HANDLE to_process_handle = + OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.pid); + + if (!to_process_handle) + { + system_printf ("error opening `to' process, error = %lu", + GetLastError ()); + CloseHandle (from_process_handle); + error_code (EACCES); + return; + } + + // verbose: debug_printf ("Impersonating client"); conn->impersonate_client (); - debug_printf ("about to open thread token\n"); - rc = OpenThreadToken (GetCurrentThread (), - TOKEN_QUERY, - TRUE, - &token_handle); + HANDLE token_handle = NULL; - debug_printf ("opened thread token, rc=%lu\n", rc); + // verbose: debug_printf ("about to open thread token"); + const DWORD rc = OpenThreadToken (GetCurrentThread (), + TOKEN_QUERY, + TRUE, + &token_handle); + + // verbose: debug_printf ("opened thread token, rc=%lu", rc); conn->revert_to_self (); if (!rc) { - printf ("error opening thread token (%lu)\n", GetLastError ()); - header.error_code = EACCES; - goto out; + system_printf ("error opening thread token, error = %lu", + GetLastError ()); + CloseHandle (from_process_handle); + CloseHandle (to_process_handle); + error_code (EACCES); + return; } - if (check_and_dup_handle (from_process_handle, to_process_handle, - token_handle, - GENERIC_READ, - req.from_master, - &req.from_master, TRUE) != 0) - { - printf ("error duplicating from_master handle (%lu)\n", GetLastError ()); - header.error_code = EACCES; - goto out; - } + // From this point on, a reply body is returned to the client. - if (req.to_master) - { - if (check_and_dup_handle (from_process_handle, to_process_handle, - token_handle, - GENERIC_WRITE, - req.to_master, - &req.to_master, TRUE) != 0) - { - printf ("error duplicating to_master handle (%lu)\n", GetLastError ()); - header.error_code = EACCES; - goto out; - } - } + const HANDLE from_master = req.from_master; + const HANDLE to_master = req.to_master; -#if DEBUG - printf ("%ld -> %ld(%p,%p)\n", req.master_pid, req.pid, - req.from_master, req.to_master); -#endif + req.from_master = NULL; + req.to_master = NULL; - header.error_code = 0; + msglen (sizeof (req)); -out: - if (from_process_handle) - CloseHandle (from_process_handle); - if (to_process_handle) - CloseHandle (to_process_handle); - if (token_handle) - CloseHandle (token_handle); + if (from_master) + if (check_and_dup_handle (from_process_handle, to_process_handle, + token_handle, + GENERIC_READ, + from_master, + &req.from_master, TRUE) != 0) + { + system_printf ("error duplicating from_master handle, error = %lu", + GetLastError ()); + error_code (EACCES); + } + + if (to_master) + if (check_and_dup_handle (from_process_handle, to_process_handle, + token_handle, + GENERIC_WRITE, + to_master, + &req.to_master, TRUE) != 0) + { + system_printf ("error duplicating to_master handle, error = %lu", + GetLastError ()); + error_code (EACCES); + } + + CloseHandle (from_process_handle); + CloseHandle (to_process_handle); + CloseHandle (token_handle); + + debug_printf ("%lu(%lu, %lu) -> %lu(%lu,%lu)", + req.master_pid, from_master, to_master, + req.pid, req.from_master, req.to_master); + + return; } void -client_request_get_version::serve(transport_layer_base *conn, class process_cache *cache) +client_request_get_version::serve (transport_layer_base *, process_cache *) { - if (header.cb != sizeof (version)) - { - header.error_code = EINVAL; - return; - } - header.error_code = 0; + assert (!error_code ()); + + if (msglen ()) + syscall_printf ("unexpected request body ignored: %lu bytes", msglen ()); + + msglen (sizeof (version)); + version.major = CYGWIN_SERVER_VERSION_MAJOR; version.api = CYGWIN_SERVER_VERSION_API; version.minor = CYGWIN_SERVER_VERSION_MINOR; @@ -270,280 +395,380 @@ client_request_get_version::serve(transport_layer_base *conn, class process_cach class server_request : public queue_request { - public: - server_request (transport_layer_base *newconn, class process_cache *newcache); - virtual void process (); - private: - char request_buffer [MAX_REQUEST_SIZE]; - transport_layer_base *conn; - class process_cache *cache; -}; +public: + server_request (transport_layer_base *const conn, process_cache *const cache) + : _conn (conn), _cache (cache) + {} -class server_process_param : public queue_process_param -{ - public: - transport_layer_base *transport; - server_process_param () : queue_process_param (false) {}; -}; - -class server_request_queue : public threaded_queue -{ - public: - class process_cache *cache; - void process_requests (transport_layer_base *transport); - virtual void add (transport_layer_base *conn); -}; -class server_request_queue request_queue; - -static DWORD WINAPI -request_loop (LPVOID LpParam) -{ - class server_process_param *params = (server_process_param *) LpParam; - class server_request_queue *queue = (server_request_queue *) params->queue; - class transport_layer_base * transport = params->transport; - while (queue->active) + virtual ~server_request () { - transport_layer_base * new_conn = transport->accept (); - /* FIXME: this is a little ugly. What we really want is to wait on two objects: - * one for the pipe/socket, and one for being told to shutdown. Otherwise - * this will stay a problem (we won't actually shutdown until the request - * _AFTER_ the shutdown request. And sending ourselves a request is ugly - */ - if (new_conn && queue->active) - queue->add (new_conn); + safe_delete (_conn); } - return 0; + + virtual void process () + { + client_request::handle_request (_conn, _cache); + } + +private: + transport_layer_base *const _conn; + process_cache *const _cache; +}; + +class server_submission_loop : public queue_submission_loop +{ +public: + server_submission_loop (threaded_queue *const queue, + transport_layer_base *const transport, + process_cache *const cache) + : queue_submission_loop (queue, false), + _transport (transport), + _cache (cache) + { + assert (_transport); + assert (_cache); + } + +private: + transport_layer_base *const _transport; + process_cache *const _cache; + + virtual void request_loop (); +}; + +/* FIXME: this is a little ugly. What we really want is to wait on + * two objects: one for the pipe/socket, and one for being told to + * shutdown. Otherwise this will stay a problem (we won't actually + * shutdown until the request _AFTER_ the shutdown request. And + * sending ourselves a request is ugly + */ +void +server_submission_loop::request_loop () +{ + /* I'd like the accepting thread's priority to be above any "normal" + * thread in the system to avoid overflowing the listen queue (for + * sockets; similar issues exist for named pipes); but, for example, + * a normal priority thread in a foregrounded process is boosted to + * THREAD_PRIORITY_HIGHEST (AFAICT). Thus try to set the current + * thread's priority to a level one above that. This fails on + * win9x/ME so assume any failure in that call is due to that and + * simply call again at one priority level lower. + */ + if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST + 1)) + if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST)) + debug_printf ("failed to raise accept thread priority, error = %lu", + GetLastError ()); + + while (_running) + { + bool recoverable = false; + transport_layer_base *const conn = _transport->accept (&recoverable); + if (!conn && !recoverable) + { + system_printf ("fatal error on IPC transport: closing down"); + return; + } + // EINTR probably implies a shutdown request; so back off for a + // moment to let the main thread take control, otherwise the + // server spins here receiving EINTR repeatedly since the signal + // handler in the main thread doesn't get a chance to be called. + if (!conn && errno == EINTR) + { + if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_NORMAL)) + debug_printf ("failed to reset thread priority, error = %lu", + GetLastError ()); + + Sleep (0); + if (!SetThreadPriority (GetCurrentThread (), + THREAD_PRIORITY_HIGHEST + 1)) + if (!SetThreadPriority (GetCurrentThread (), + THREAD_PRIORITY_HIGHEST)) + debug_printf ("failed to raise thread priority, error = %lu", + GetLastError ()); + } + if (conn) + _queue->add (safe_new (server_request, conn, _cache)); + } } -/* TODO: check we are not being asked to service a already serviced transport */ -void -server_request_queue::process_requests (transport_layer_base *transport) +client_request_shutdown::client_request_shutdown () + : client_request (CYGSERVER_REQUEST_SHUTDOWN) { - class server_process_param *params = new server_process_param; - params->transport = transport; - threaded_queue::process_requests (params, request_loop); + // verbose: syscall_printf ("created"); } void -client_request_shutdown::serve (transport_layer_base *conn, class process_cache *cache) +client_request_shutdown::serve (transport_layer_base *, process_cache *) { + assert (!error_code ()); + + if (msglen ()) + syscall_printf ("unexpected request body ignored: %lu bytes", msglen ()); + /* FIXME: link upwards, and then this becomes a trivial method call to * only shutdown _this queue_ */ - /* tell the main thread to shutdown */ - request_queue.active=false; + + kill (getpid (), SIGINT); + + msglen (0); } -server_request::server_request (transport_layer_base *newconn, class process_cache *newcache) -{ - conn = newconn; - cache = newcache; -} +static sig_atomic_t shutdown_server = false; -void -server_request::process () -{ - ssize_t bytes_read, bytes_written; - struct request_header* req_ptr = (struct request_header*) &request_buffer; - client_request *req = NULL; - debug_printf ("about to read\n"); - - bytes_read = conn->read (request_buffer, sizeof (struct request_header)); - if (bytes_read != sizeof (struct request_header)) - { - printf ("error reading from connection (%lu)\n", GetLastError ()); - goto out; - } - debug_printf ("got header (%ld)\n", bytes_read); - - switch (req_ptr->req_id) - { - case CYGSERVER_REQUEST_GET_VERSION: - req = new client_request_get_version (); break; - case CYGSERVER_REQUEST_ATTACH_TTY: - req = new client_request_attach_tty (); break; - case CYGSERVER_REQUEST_SHUTDOWN: - req = new client_request_shutdown (); break; - case CYGSERVER_REQUEST_SHM_GET: - req = new client_request_shm (); break; - default: - req = new client_request (CYGSERVER_REQUEST_INVALID, 0); - req->header.error_code = ENOSYS; - debug_printf ("Bad client request - returning ENOSYS\n"); - } - - if (req->header.cb != req_ptr->cb) - { - debug_printf ("Mismatch in request buffer sizes\n"); - goto out; - } - - if (req->header.cb) - { - - bytes_read = conn->read (req->buffer, req->header.cb); - if (bytes_read != req->header.cb) - { - debug_printf ("error reading from connection (%lu)\n", GetLastError ()); - goto out; - } - debug_printf ("got body (%ld)\n",bytes_read); - } - - /* this is not allowed to fail. We must return ENOSYS at a minimum to the client */ - req->serve (conn, cache); - - if ((bytes_written = conn->write ((char *)&req->header, sizeof (req->header))) - != sizeof(req->header) || (req->header.cb && - (bytes_written = conn->write (req->buffer, req->header.cb)) != req->header.cb)) - { - req->header.error_code = -1; - printf ("error writing to connection (%lu)\n", GetLastError ()); - goto out; - } - - debug_printf("Sent reply, size (%ld)\n",bytes_written); - printf ("."); - -out: - conn->close (); - delete conn; - if (req) - delete (req); -} - -void -server_request_queue::add (transport_layer_base *conn) -{ - /* safe to not "Try" because workers don't hog this, they wait on the event - */ - /* every derived ::add must enter the section! */ - EnterCriticalSection (&queuelock); - if (!running) - { - conn->close (); - delete conn; - LeaveCriticalSection (&queuelock); - return; - } - queue_request * listrequest = new server_request (conn, cache); - threaded_queue::add (listrequest); - LeaveCriticalSection (&queuelock); -} - -void -handle_signal (int signal) +static void +handle_signal (const int signum) { /* any signal makes us die :} */ - /* FIXME: link upwards, and then this becomes a trivial method call to - * only shutdown _this queue_ - */ - /* tell the main thread to shutdown */ - request_queue.active=false; + + shutdown_server = true; } -struct option longopts[] = { - {"shutdown", no_argument, NULL, 's'}, - {0, no_argument, NULL, 0} -}; +/* + * print_usage () + */ -char opts[] = "s"; +static void +print_usage (const char *const pgm) +{ + printf ("Usage: %s [OPTIONS]\n", pgm); + printf (" -c, --cleanup-threads number of cleanup threads to use\n"); + printf (" -h, --help output usage information and exit\n"); + printf (" -r, --request-threads number of request threads to use\n"); + printf (" -s, --shutdown shutdown the daemon\n"); + printf (" -v, --version output version information and exit\n"); +} + +/* + * print_version () + */ + +static void +print_version (const char *const pgm) +{ + char *vn = NULL; + + const char *const colon = strchr (version, ':'); + + if (!colon) + { + vn = strdup ("?"); + } + else + { + vn = strdup (colon + 2); // Skip ": " + + char *const spc = strchr (vn, ' '); + + if (spc) + *spc = '\0'; + } + + char buf[200]; + snprintf (buf, sizeof (buf), "%d.%d.%d(%d.%d/%d/%d)-(%d.%d.%d.%d) %s", + cygwin_version.dll_major / 1000, + cygwin_version.dll_major % 1000, + cygwin_version.dll_minor, + cygwin_version.api_major, + cygwin_version.api_minor, + cygwin_version.shared_data, + CYGWIN_SERVER_VERSION_MAJOR, + CYGWIN_SERVER_VERSION_API, + CYGWIN_SERVER_VERSION_MINOR, + CYGWIN_SERVER_VERSION_PATCH, + cygwin_version.mount_registry, + cygwin_version.dll_build_date); + + printf ("%s (cygwin) %s\n", pgm, vn); + printf ("API version %s\n", buf); + printf ("Copyright 2001, 2002 Red Hat, Inc.\n"); + printf ("Compiled on %s\n", __DATE__); + + free (vn); +} + +/* + * main () + */ int -main (int argc, char **argv) +main (const int argc, char *argv[]) { - int shutdown=0; - char i; + const struct option longopts[] = { + {"cleanup-threads", required_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {"request-threads", required_argument, NULL, 'r'}, + {"shutdown", no_argument, NULL, 's'}, + {"version", no_argument, NULL, 'v'}, + {0, no_argument, NULL, 0} + }; - while ((i = getopt_long (argc, argv, opts, longopts, NULL)) != EOF) - switch (i) - { - case 's': - shutdown = 1; - break; - default: - break; - /*NOTREACHED*/ - } + const char opts[] = "c:hr:sv"; - wincap.init(); + int cleanup_threads = 2; + int request_threads = 10; + bool shutdown = false; + + const char *pgm = NULL; + + if (!(pgm = strrchr (*argv, '\\')) && !(pgm = strrchr (*argv, '/'))) + pgm = *argv; + else + pgm++; + + wincap.init (); if (wincap.has_security ()) setup_privileges (); - transport = create_server_transport (); + + int opt; + + while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != EOF) + switch (opt) + { + case 'c': + cleanup_threads = atoi (optarg); + if (cleanup_threads <= 0) + { + fprintf (stderr, + "%s: number of cleanup threads must be positive\n", + pgm); + exit (1); + } + break; + + case 'h': + print_usage (pgm); + return 0; + + case 'r': + request_threads = atoi (optarg); + if (request_threads <= 0) + { + fprintf (stderr, + "%s: number of request threads must be positive\n", + pgm); + exit (1); + } + break; + + case 's': + shutdown = true; + break; + + case 'v': + print_version (pgm); + return 0; + + case '?': + fprintf (stderr, "Try `%s --help' for more information.\n", pgm); + exit (1); + } + + if (optind != argc) + { + fprintf (stderr, "%s: too many arguments\n", pgm); + exit (1); + } if (shutdown) { - if (!transport->connect()) + /* Setting `cygserver_running' stops the request code making a + * version request, which is not much to the point. + */ + cygserver_running = CYGSERVER_OK; + + client_request_shutdown req; + + if (req.make_request () == -1 || req.error_code ()) { - printf ("couldn't establish connection with server\n"); + fprintf (stderr, "%s: shutdown request failed: %s\n", + pgm, strerror (req.error_code ())); exit (1); } - client_request_shutdown *request = - new client_request_shutdown (); - request->send (transport); - transport->close(); - delete transport; - delete request; - exit(0); + + // FIXME: It would be nice to wait here for the daemon to exit. + + return 0; } - char version[200]; - /* Cygwin dll release */ - snprintf (version, 200, "%d.%d.%d(%d.%d/%d/%d)-(%d.%d.%d.%d) %s", - cygwin_version.dll_major / 1000, - cygwin_version.dll_major % 1000, - cygwin_version.dll_minor, - cygwin_version.api_major, - cygwin_version.api_minor, - cygwin_version.shared_data, - CYGWIN_SERVER_VERSION_MAJOR, - CYGWIN_SERVER_VERSION_API, - CYGWIN_SERVER_VERSION_MINOR, - CYGWIN_SERVER_VERSION_PATCH, - cygwin_version.mount_registry, - cygwin_version.dll_build_date); +#define SIGHANDLE(SIG) \ + do \ + { \ + struct sigaction act; \ + \ + act.sa_handler = &handle_signal; \ + act.sa_mask = 0; \ + act.sa_flags = 0; \ + \ + if (sigaction (SIG, &act, NULL) == -1) \ + { \ + system_printf ("failed to install handler for " #SIG ": %s", \ + strerror (errno)); \ + exit (1); \ + } \ + } while (false) + + SIGHANDLE (SIGHUP); + SIGHANDLE (SIGINT); + SIGHANDLE (SIGTERM); + + print_version (pgm); setbuf (stdout, NULL); - printf ("daemon version %s starting up", version); - if (signal (SIGQUIT, handle_signal) == SIG_ERR) + printf ("daemon starting up"); + + threaded_queue request_queue (request_threads); + printf ("."); + + transport_layer_base *const transport = create_server_transport (); + assert (transport); + printf ("."); + + process_cache cache (cleanup_threads); + printf ("."); + + server_submission_loop submission_loop (&request_queue, transport, &cache); + printf ("."); + + request_queue.add_submission_loop (&submission_loop); + printf ("."); + + if (transport->listen () == -1) { - printf ("\ncould not install signal handler (%d)- aborting startup\n", errno); exit (1); } printf ("."); - transport->listen (); + + cache.start (); printf ("."); - class process_cache cache (2); - request_queue.initial_workers = 10; - request_queue.cache = &cache; - request_queue.create_workers (); + + request_queue.start (); printf ("."); - request_queue.process_requests (transport); - printf ("."); - cache.create_workers (); - printf ("."); - cache.process_requests (); - printf (".complete\n"); - /* TODO: wait on multiple objects - the thread handle for each request loop + - * all the process handles. This should be done by querying the request_queue and - * the process cache for all their handles, and then waiting for (say) 30 seconds. - * after that we recreate the list of handles to wait on, and wait again. - * the point of all this abstraction is that we can trivially server both sockets - * and pipes simply by making a new transport, and then calling - * request_queue.process_requests (transport2); + + printf ("complete\n"); + + /* TODO: wait on multiple objects - the thread handle for each + * request loop + all the process handles. This should be done by + * querying the request_queue and the process cache for all their + * handles, and then waiting for (say) 30 seconds. after that we + * recreate the list of handles to wait on, and wait again. the + * point of all this abstraction is that we can trivially server + * both sockets and pipes simply by making a new transport, and then + * calling request_queue.process_requests (transport2); */ /* WaitForMultipleObjects abort && request_queue && process_queue && signal -- if signal event then retrigger it - */ - while (1 && request_queue.active) - { - sleep (1); - } - printf ("\nShutdown request recieved - new requests will be denied\n"); - request_queue.cleanup (); + */ + while (!shutdown_server && request_queue.running () && cache.running ()) + pause (); + + printf ("\nShutdown request received - new requests will be denied\n"); + request_queue.stop (); printf ("All pending requests processed\n"); - transport->close (); + safe_delete (transport); printf ("No longer accepting requests - cygwin will operate in daemonless mode\n"); - cache.cleanup (); + cache.stop (); printf ("All outstanding process-cache activities completed\n"); printf ("daemon shutdown\n"); + + return 0; } diff --git a/winsup/cygserver/ipc.h b/winsup/cygserver/ipc.h new file mode 100644 index 000000000..0d0ebbc76 --- /dev/null +++ b/winsup/cygserver/ipc.h @@ -0,0 +1,84 @@ +/* cygserver_ipc.h + + Copyright 2002 Red Hat, Inc. + + Originally written by Conrad Scott + +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. */ + +#ifndef __CYGSERVER_IPC_H__ +#define __CYGSERVER_IPC_H__ + +#include +#include /* For OPEN_MAX. */ + +/* + * The sysv ipc id's (msgid, semid, shmid) are integers arranged such + * that they no subsystem will generate the same id as some other + * subsystem; nor do these ids overlap file descriptors (the other + * common integer ids). Since Cygwin can allocate more than OPEN_MAX + * file descriptors, it can't be guaranteed not to overlap, but it + * should help catch some errors. + * + * msgid's: OPEN_MAX, OPEN_MAX + 3, OPEN_MAX + 6, . . . + * semid's: OPEN_MAX + 1, OPEN_MAX + 4, OPEN_MAX + 7, . . . + * shmid's: OPEN_MAX + 2, OPEN_MAX + 5, OPEN_MAX + 8, . . . + * + * To further ensure that ids are unique, if ipc objects are created + * and destroyed and then re-created, they are given new ids by + * munging the basic id (as above) with a sequence number. + * + * Internal ipc id's, which are 0, 1, ... within each subsystem (and + * not munged with a sequence number), are used solely by the ipcs(8) + * interface. + */ + +enum ipc_subsys_t + { + IPC_MSGOP = 0, + IPC_SEMOP = 1, + IPC_SHMOP = 2, + IPC_SUBSYS_COUNT + }; + +/* + * IPCMNI - The absolute maximum number of simultaneous ipc ids for + * any one subsystem. + */ + +enum + { + IPCMNI = 0x10000 // Must be a power of two. + }; + +inline int +ipc_int2ext (const int intid, const ipc_subsys_t subsys, long & sequence) +{ + assert (0 <= intid && intid < IPCMNI); + + const long tmp = InterlockedIncrement (&sequence); + + return (((tmp & 0x7fff) << 16) + | (OPEN_MAX + (intid * IPC_SUBSYS_COUNT) + subsys)); +} + +inline int +ipc_ext2int_subsys (const int extid) +{ + return ((extid & (IPCMNI - 1)) - OPEN_MAX) % IPC_SUBSYS_COUNT; +} + +inline int +ipc_ext2int (const int extid, const ipc_subsys_t subsys) +{ + if (ipc_ext2int_subsys (extid) != subsys) + return -1; + else + return ((extid & (IPCMNI - 1)) - OPEN_MAX) / IPC_SUBSYS_COUNT; +} + +#endif /* __CYGSERVER_IPC_H__ */ diff --git a/winsup/cygserver/process.cc b/winsup/cygserver/process.cc index dd13f37fb..7118bbcd9 100644 --- a/winsup/cygserver/process.cc +++ b/winsup/cygserver/process.cc @@ -4,385 +4,429 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +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 -#include -#include -#include "wincap.h" -#include -#include -#include -#define debug_printf if (DEBUG) printf -#define DEBUG 1 +#include +#include +#include -/* the cache structures and classes are designed for one cache per server process. - * To make multiple process caches, a redesign will be needed - */ +#include "cygerrno.h" -/* process cache */ -process_cache::process_cache (unsigned int num_initial_workers): -head (NULL) +#include "cygwin/cygserver_process.h" + +/*****************************************************************************/ + +#define elements(ARRAY) (sizeof (ARRAY) / sizeof (*ARRAY)) + +/*****************************************************************************/ + +process_cleanup::~process_cleanup () { - /* there can only be one */ - InitializeCriticalSection (&cache_write_access); - if ((cache_add_trigger = CreateEvent (NULL, FALSE, FALSE, NULL)) == NULL) - { - printf ("Failed to create cache add trigger (%lu), terminating\n", - GetLastError ()); - exit (1); - } - initial_workers = num_initial_workers; + safe_delete (_process); } -process_cache::~process_cache () +void +process_cleanup::process () +{ + _process->cleanup (); +} + +/*****************************************************************************/ + +/* cleanup_routine */ +cleanup_routine::~cleanup_routine () { } -class process * -process_cache::process (long pid) +/*****************************************************************************/ + +process::process (const pid_t cygpid, const DWORD winpid) + : _cygpid (cygpid), + _winpid (winpid), + _hProcess (NULL), + _cleaning_up (false), + _exit_status (STILL_ACTIVE), + _routines_head (NULL), + _next (NULL) { - class process *entry = head; - /* TODO: make this more granular, so a search doesn't involve the write lock */ - EnterCriticalSection (&cache_write_access); - if (!entry) + _hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, winpid); + if (!_hProcess) { - entry = new class process (pid); - entry->next = - (class process *) InterlockedExchangePointer (&head, entry); - PulseEvent (cache_add_trigger); + system_printf ("unable to obtain handle for new cache process %d(%lu)", + _cygpid, _winpid); + _hProcess = INVALID_HANDLE_VALUE; + _exit_status = 0; } else - { - while (entry->winpid != pid && entry->next) - entry = entry->next; - if (entry->winpid != pid) - { - class process *new_entry = new class process (pid); - new_entry->next = - (class process *) InterlockedExchangePointer (&entry->next, - new_entry); - entry = new_entry; - PulseEvent (cache_add_trigger); - } - } - LeaveCriticalSection (&cache_write_access); - return entry; -} - -static DWORD WINAPI -request_loop (LPVOID LpParam) -{ - class process_process_param *params = (process_process_param *) LpParam; - return params->request_loop (); -} - -void -process_cache::process_requests () -{ - class process_process_param *params = new process_process_param; - threaded_queue::process_requests (params, request_loop); -} - -void -process_cache::add_task (class process * theprocess) -{ - /* safe to not "Try" because workers don't hog this, they wait on the event - */ - /* every derived ::add must enter the section! */ - EnterCriticalSection (&queuelock); - queue_request *listrequest = new process_cleanup (theprocess); - threaded_queue::add (listrequest); - LeaveCriticalSection (&queuelock); -} - -/* NOT fully MT SAFE: must be called by only one thread in a program */ -void -process_cache::remove_process (class process *theprocess) -{ - class process *entry = head; - /* unlink */ - EnterCriticalSection (&cache_write_access); - if (entry == theprocess) - { - entry = (class process *) InterlockedExchangePointer (&head, theprocess->next); - if (entry != theprocess) - { - printf ("Bug encountered, process cache corrupted\n"); - exit (1); - } - } - else - { - while (entry->next && entry->next != theprocess) - entry = entry->next; - class process *temp = (class process *) InterlockedExchangePointer (&entry->next, theprocess->next); - if (temp != theprocess) - { - printf ("Bug encountered, process cache corrupted\n"); - exit (1); - } - } - LeaveCriticalSection (&cache_write_access); - /* Process any cleanup tasks */ - add_task (theprocess); -} - -/* copy <= max_copy HANDLEs to dest[], starting at an offset into _our list_ of - * begin_at. (Ie begin_at = 5, the first copied handle is still written to dest[0] - * NOTE: Thread safe, but not thread guaranteed - a newly added process may be missed. - * Who cares - It'll get caught the next time. - */ -int -process_cache::handle_snapshot (HANDLE * hdest, class process ** edest, - ssize_t max_copy, int begin_at) -{ - /* TODO:? grab a delete-lock, to prevent deletes during this process ? */ - class process *entry = head; - int count = begin_at; - /* skip begin_at entries */ - while (entry && count) - { - if (entry->exit_code () == STILL_ACTIVE) - count--; - entry = entry->next; - } - /* hit the end of the list within begin_at entries */ - if (count) - return 0; - HANDLE *hto = hdest; - class process **eto = edest; - while (entry && count < max_copy) - { - /* hack */ - if (entry->exit_code () == STILL_ACTIVE) - { - *hto = entry->handle (); - *eto = entry; - count++; - hto++; - eto++; - } - entry = entry->next; - } - return count; -} - -/* process's */ -/* global process crit section */ -static CRITICAL_SECTION process_access; -static pthread_once_t process_init; - -void -do_process_init (void) -{ - InitializeCriticalSection (&process_access); - /* we don't have a cache shutdown capability today */ -} - -process::process (long pid): -winpid (pid), next (NULL), cleaning_up (0), head (NULL), _exit_status (STILL_ACTIVE) -{ - pthread_once (&process_init, do_process_init); - EnterCriticalSection (&process_access); - thehandle = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); - if (!thehandle) - { - printf ("unable to obtain handle for new cache process %ld\n", pid); - thehandle = INVALID_HANDLE_VALUE; - } - debug_printf ("Got handle %p for new cache process %ld\n", thehandle, pid); - InitializeCriticalSection (&access); - LeaveCriticalSection (&process_access); + debug_printf ("got handle %p for new cache process %d(%lu)", + _hProcess, _cygpid, _winpid); + InitializeCriticalSection (&_access); } process::~process () { - DeleteCriticalSection (&access); + DeleteCriticalSection (&_access); + (void) CloseHandle (_hProcess); } -HANDLE -process::handle () +/* No need to be thread-safe as this is only ever called by + * process_cache::remove_process (). If it has to be made thread-safe + * later on, it should not use the `access' critical section as that + * is held by the client request handlers for an arbitrary length of + * time, i.e. while they do whatever processing is required for a + * client request. + */ +DWORD +process::check_exit_code () { -// DWORD exitstate = exit_code (); -// if (exitstate == STILL_ACTIVE) - return thehandle; - - /* FIXME: call the cleanup list ? */ - -// CloseHandle (thehandle); -// debug_printf ("Process id %ld has terminated, attempting to open a new handle\n", -// winpid); -// thehandle = OpenProcess (PROCESS_ALL_ACCESS, FALSE, winpid); -// debug_printf ("Got handle %p when refreshing cache process %ld\n", thehandle, winpid); -// /* FIXME: what if OpenProcess fails ? */ -// if (thehandle) -// { -// _exit_status = STILL_ACTIVE; -// exit_code (); -// } -// else -// thehandle = INVALID_HANDLE_VALUE; -// return thehandle; -} - -DWORD process::exit_code () -{ - if (_exit_status != STILL_ACTIVE) - return _exit_status; - bool - err = GetExitCodeProcess (thehandle, &_exit_status); - if (!err) + if (_hProcess && _hProcess != INVALID_HANDLE_VALUE + && _exit_status == STILL_ACTIVE + && !GetExitCodeProcess (_hProcess, &_exit_status)) { - debug_printf ("Failed to retrieve exit code (%ld)\n", GetLastError ()); - thehandle = INVALID_HANDLE_VALUE; - return _exit_status; + system_printf ("failed to retrieve exit code for %d(%lu), error = %lu", + _cygpid, _winpid, GetLastError ()); + _hProcess = INVALID_HANDLE_VALUE; } - else if (_exit_status == STILL_ACTIVE) - return _exit_status; - /* add new cleanup task etc etc ? */ return _exit_status; } -/* this is single threaded. It's called after the process is removed from the cache, - * but inserts may be attemped by worker threads that have a pointer to it */ -void -process::cleanup () +bool +process::add (cleanup_routine *const entry) { - /* Serialize this */ - EnterCriticalSection (&access); - InterlockedIncrement (&(long)cleaning_up); - class cleanup_routine *entry = head; - while (entry) + assert (entry); + + bool res = false; + EnterCriticalSection (&_access); + + if (!_cleaning_up) { - class cleanup_routine *temp; - entry->cleanup (winpid); - temp = entry->next; - delete entry; - entry = temp; + entry->_next = _routines_head; + _routines_head = entry; + res = true; } - LeaveCriticalSection (&access); + + LeaveCriticalSection (&_access); + return res; } bool -process::add_cleanup_routine (class cleanup_routine *new_cleanup) +process::remove (const cleanup_routine *const entry) { - if (cleaning_up) - return false; - EnterCriticalSection (&access); - /* check that we didn't block with ::cleanup () - * This rigmarole is to get around win9x's glaring missing TryEnterCriticalSection call - * which would be a whole lot easier - */ - if (cleaning_up) - { - LeaveCriticalSection (&access); - return false; - } - new_cleanup->next = head; - head = new_cleanup; - LeaveCriticalSection (&access); - return true; -} + assert (entry); -/* process_cleanup */ -void -process_cleanup::process () -{ - theprocess->cleanup (); - delete theprocess; -} + bool res = false; + EnterCriticalSection (&_access); -/* process_process_param */ -DWORD -process_process_param::request_loop () -{ - process_cache *cache = (process_cache *) queue; - /* always malloc one, so there is no special case in the loop */ - ssize_t HandlesSize = 2; - HANDLE *Handles = (HANDLE *) malloc (sizeof (HANDLE) * HandlesSize); - process **Entries = (process **) malloc (sizeof (LPVOID) * HandlesSize); - /* TODO: put [1] at the end as it will also get done if a process dies? */ - Handles[0] = interrupt; - Handles[1] = cache->cache_add_trigger; - while (cache->active && !shutdown) + if (!_cleaning_up) { - int copied; - copied = -1; - int offset; - offset = 1; - int count; - count = 2; - while ((copied == HandlesSize - 2 - offset) || copied < 0) + cleanup_routine *previous = NULL; + + for (cleanup_routine *ptr = _routines_head; + ptr; + previous = ptr, ptr = ptr->_next) { - /* we need more storage to cope with all the HANDLES */ - if (copied == HandlesSize - 2 - offset) + if (*ptr == *entry) { - HANDLE *temp = (HANDLE *) realloc (Handles, - sizeof (HANDLE) * - HandlesSize + 10); - if (!temp) - { - printf - ("cannot allocate more storage for the handle array!\n"); - exit (1); - } - Handles = temp; - process **ptemp = (process **) realloc (Entries, - sizeof (LPVOID) * - HandlesSize + 10); - if (!ptemp) - { - printf - ("cannot allocate more storage for the handle array!\n"); - exit (1); - } - Entries = ptemp; - HandlesSize += 10; + if (previous) + previous->_next = ptr->_next; + else + _routines_head = ptr->_next; + + safe_delete (ptr); + res = true; + break; } - offset += copied; - copied = - cache->handle_snapshot (&Handles[2], &Entries[2], - HandlesSize - 2 - offset, offset); - count += copied; } - debug_printf ("waiting on %u objects\n", count); - DWORD rc = WaitForMultipleObjects (count, Handles, FALSE, INFINITE); - if (rc == WAIT_FAILED) + } + + LeaveCriticalSection (&_access); + return res; +} + +/* This is single threaded. It's called after the process is removed + * from the cache, but inserts may be attemped by worker threads that + * have a pointer to it. + */ +void +process::cleanup () +{ + EnterCriticalSection (&_access); + assert (!is_active ()); + assert (!_cleaning_up); + InterlockedExchange (&_cleaning_up, true); + cleanup_routine *entry = _routines_head; + _routines_head = NULL; + LeaveCriticalSection (&_access); + + while (entry) + { + cleanup_routine *const ptr = entry; + entry = entry->_next; + ptr->cleanup (this); + safe_delete (ptr); + } +} + +/*****************************************************************************/ + +void +process_cache::submission_loop::request_loop () +{ + assert (this); + assert (_cache); + assert (_interrupt_event); + + while (_running) + _cache->wait_for_processes (_interrupt_event); +} + +/*****************************************************************************/ + +process_cache::process_cache (const unsigned int initial_workers) + : _queue (initial_workers), + _submitter (this, &_queue), // true == interruptible + _processes_count (0), + _processes_head (NULL), + _cache_add_trigger (NULL) +{ + /* there can only be one */ + InitializeCriticalSection (&_cache_write_access); + + _cache_add_trigger = CreateEvent (NULL, // SECURITY_ATTRIBUTES + FALSE, // Auto-reset + FALSE, // Initially non-signalled + NULL); // Anonymous + + if (!_cache_add_trigger) + { + system_printf ("failed to create cache add trigger, error = %lu", + GetLastError ()); + abort (); + } + + _queue.add_submission_loop (&_submitter); +} + +process_cache::~process_cache () +{ + (void) CloseHandle (_cache_add_trigger); + DeleteCriticalSection (&_cache_write_access); +} + +/* This returns the process object to the caller already locked, that + * is, with the object's `access' critical region entered. Thus the + * caller must unlock the object when it's finished with it (via + * process::release ()). It must then not try to access the object + * afterwards, except by going through this routine again, as it may + * have been deleted once it has been unlocked. + */ +class process * +process_cache::process (const pid_t cygpid, const DWORD winpid) +{ + /* TODO: make this more granular, so a search doesn't involve the + * write lock. + */ + EnterCriticalSection (&_cache_write_access); + class process *previous = NULL; + class process *entry = find (winpid, &previous); + + if (!entry) + { + if (_processes_count + SPECIALS_COUNT >= MAXIMUM_WAIT_OBJECTS) { - printf ("Could not wait on the process handles (%ld)!\n", - GetLastError ()); - exit (1); + LeaveCriticalSection (&_cache_write_access); + system_printf (("process limit (%d processes) reached; " + "new connection refused for %d(%lu)"), + MAXIMUM_WAIT_OBJECTS - SPECIALS_COUNT, + cygpid, winpid); + set_errno (EAGAIN); + return NULL; } - int objindex = rc - WAIT_OBJECT_0; - if (objindex > 1 && objindex < count) + + entry = safe_new (class process, cygpid, winpid); + if (!entry->is_active ()) { - debug_printf ("Process %ld has left the building\n", - Entries[objindex]->winpid); - /* fire off the termination routines */ - cache->remove_process (Entries[objindex]); + LeaveCriticalSection (&_cache_write_access); + safe_delete (entry); + set_errno (ESRCH); + return NULL; } - else if (objindex >= 0 && objindex < 2) + + if (previous) { - /* 0 is shutdown - do nothing */ - /* 1 is a cache add event - just rebuild the object list */ + entry->_next = previous->_next; + previous->_next = entry; } else { - printf - ("unexpected return code from WaitForMultiple objects in process_process_param::request_loop\n"); + entry->_next = _processes_head; + _processes_head = entry; } + + _processes_count += 1; + SetEvent (_cache_add_trigger); } - running = false; - return 0; + + EnterCriticalSection (&entry->_access); // To be released by the caller. + LeaveCriticalSection (&_cache_write_access); + assert (entry); + assert (entry->_winpid == winpid); + return entry; } + +void +process_cache::wait_for_processes (const HANDLE interrupt_event) +{ + // Update `_wait_array' with handles of all current processes. + const size_t count = sync_wait_array (interrupt_event); + + debug_printf ("waiting on %u objects in total (%u processes)", + count, _processes_count); + + const DWORD rc = WaitForMultipleObjects (count, _wait_array, + FALSE, INFINITE); + + if (rc == WAIT_FAILED) + { + system_printf ("could not wait on the process handles, error = %lu", + GetLastError ()); + abort (); + } + + const size_t start = rc - WAIT_OBJECT_0; + + if (rc < WAIT_OBJECT_0 || start > count) + { + system_printf (("unexpected return code %rc " + "from WaitForMultipleObjects: " + "expected [%u .. %u)"), + rc, WAIT_OBJECT_0, WAIT_OBJECT_0 + count); + abort (); + } + + // Tell all the processes, from the signalled point up, the bad news. + for (size_t index = start; index != count; index++) + if (_process_array[index]) + check_and_remove_process (index); +} + +/* + * process_cache::sync_wait_array () + * + * Fill-in the wait array with the handles that the cache needs to wait on. + * These handles are: + * - the process_process_param's interrupt event + * - the process_cache's cache_add_trigger event + * - the handle for each live process in the cache. + * + * Return value: the number of live handles in the array. + */ + +size_t +process_cache::sync_wait_array (const HANDLE interrupt_event) +{ + assert (this); + assert (_cache_add_trigger && _cache_add_trigger != INVALID_HANDLE_VALUE); + assert (interrupt_event && interrupt_event != INVALID_HANDLE_VALUE); + + EnterCriticalSection (&_cache_write_access); + + assert (_processes_count + SPECIALS_COUNT <= elements (_wait_array)); + + size_t index = 0; + + for (class process *ptr = _processes_head; ptr; ptr = ptr->_next) + { + assert (ptr->_hProcess && ptr->_hProcess != INVALID_HANDLE_VALUE); + assert (ptr->is_active ()); + + _wait_array[index] = ptr->handle (); + _process_array[index++] = ptr; + + assert (index <= elements (_wait_array)); + } + + /* Sorry for shouting, but THESE MUST BE ADDED AT THE END! */ + /* Well, not strictly `must', but it's more efficient if they are :-) */ + + _wait_array[index] = interrupt_event; + _process_array[index++] = NULL; + + _wait_array[index] = _cache_add_trigger; + _process_array[index++] = NULL; + + /* Phew, back to normal volume now. */ + + assert (index <= elements (_wait_array)); + + LeaveCriticalSection (&_cache_write_access); + + return index; +} + +void +process_cache::check_and_remove_process (const size_t index) +{ + assert (this); + assert (index < elements (_wait_array) - SPECIALS_COUNT); + + class process *const process = _process_array[index]; + + assert (process); + assert (process->handle () == _wait_array[index]); + + if (process->check_exit_code () == STILL_ACTIVE) + return; + + debug_printf ("process %d(%lu) has left the building ($? = %lu)", + process->_cygpid, process->_winpid, process->_exit_status); + + /* Unlink the process object from the process list. */ + + EnterCriticalSection (&_cache_write_access); + + class process *previous = NULL; + + const class process *const tmp = find (process->_winpid, &previous); + + assert (tmp == process); + assert (previous ? previous->_next == process : _processes_head == process); + + if (previous) + previous->_next = process->_next; + else + _processes_head = process->_next; + + _processes_count -= 1; + LeaveCriticalSection (&_cache_write_access); + + /* Schedule any cleanup tasks for this process. */ + _queue.add (safe_new (process_cleanup, process)); +} + +class process * +process_cache::find (const DWORD winpid, class process **previous) +{ + if (previous) + *previous = NULL; + + for (class process *ptr = _processes_head; ptr; ptr = ptr->_next) + if (ptr->_winpid == winpid) + return ptr; + else if (ptr->_winpid > winpid) // The list is sorted by winpid. + return NULL; + else if (previous) + *previous = ptr; + + return NULL; +} + +/*****************************************************************************/ diff --git a/winsup/cygserver/shm.cc b/winsup/cygserver/shm.cc index 260a5b1bd..18b1c3d83 100644 --- a/winsup/cygserver/shm.cc +++ b/winsup/cygserver/shm.cc @@ -1,8 +1,9 @@ -/* cygserver_shm.cc: Single unix specification IPC interface for Cygwin +/* cygserver_shm.cc: Single unix specification IPC interface for Cygwin. -Copyright 2001, 2002 Red Hat, Inc. + Copyright 2002 Red Hat, Inc. -Originally written by Robert Collins + Written by Conrad Scott . + Based on code by Robert Collins . This file is part of Cygwin. @@ -10,656 +11,886 @@ This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ -#ifdef __OUTSIDE_CYGWIN__ -#undef __INSIDE_CYGWIN__ -#else -#include "winsup.h" -#endif +#include "woutsup.h" -#ifndef __INSIDE_CYGWIN__ -#define DEBUG 0 -#define system_printf printf -#define debug_printf if (DEBUG) printf -#define api_fatal printf -#include -#include -#endif - -#include #include -#include "cygerrno.h" -#include -#include "security.h" -//#include "fhandler.h" -//#include "dtable.h" -//#include "cygheap.h" +#include #include -//#include "thread.h" -#ifndef __INSIDE_CYGWIN__ -#define __INSIDE_CYGWIN__ -#include -#undef __INSIDE_CYGWIN__ -#else -#include -#endif -//#include "perprocess.h" -#include -#include +#include +#include + +#include "cygserver_ipc.h" #include "cygserver_shm.h" +#include "security.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. - * Also, IPC_PRIVATE keys create unique mappings each time. The shm_ids just - * keep monotonically incrementing - system wide. - */ -size_t -getsystemallocgranularity () +#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 { - SYSTEM_INFO sysinfo; - static size_t buffer_offset = 0; - if (buffer_offset) - return buffer_offset; - GetSystemInfo (&sysinfo); - buffer_offset = sysinfo.dwAllocationGranularity; - return buffer_offset; +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 () + *---------------------------------------------------------------------------*/ -client_request_shm::client_request_shm ():client_request (CYGSERVER_REQUEST_SHM_GET, - sizeof (parameters)) +server_shmmgr::segment_t::~segment_t () { - buffer = (char *) ¶meters; + assert (!_attach_head); + + if (!CloseHandle (_hFileMap)) + syscall_printf ("failed to close file map [handle = 0x%x]: %E", _hFileMap); } -/* FIXME: If building on a 64-bit compiler, the address->int typecast will fail. - * Solution: manually calculate the next id value - */ +/*---------------------------------------------------------------------------* + * server_shmmgr::segment_t::attach () + *---------------------------------------------------------------------------*/ -#if 0 -extern -"C" void * -shmat (int shmid, const void *shmaddr, int parameters.in.shmflg) +int +server_shmmgr::segment_t::attach (class process *const client, + HANDLE & hFileMap) { - class shmid_ds * - shm = (class shmid_ds *) - shmid; //FIXME: verifyable object test + assert (client); - if (shmaddr) + if (!DuplicateHandle (GetCurrentProcess (), + _hFileMap, + client->handle (), + &hFileMap, + 0, + FALSE, // bInheritHandle + DUPLICATE_SAME_ACCESS)) { - //FIXME: requested base address ?! - set_errno (EINVAL); - return (void *) -1; + 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? } - void * - rv = - MapViewOfFile (shm->attachmap, + _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); - (parameters.in.shmflg & SHM_RDONLY) ? - FILE_MAP_READ : FILE_MAP_WRITE, 0, - 0, 0); - - if (!rv) + if (!attptr) { - //FIXME: translate GetLastError() - set_errno (EACCES); - return (void *) -1; - } + attptr = safe_new (attach_t, client); -/* FIXME: this needs to be globally protected to prevent a mismatch betwen - * attach count and attachees list - */ - - InterlockedIncrement (&shm->shm_nattch); - _shmattach * - attachnode = - new - _shmattach; - - attachnode->data = rv; - attachnode->next = - (_shmattach *) InterlockedExchangePointer ((LONG *) & shm->attachhead, - (long int) attachnode); - return rv; -} -#endif - -/* FIXME: evaluate getuid() and getgid() against the requested mode. Then - * choose PAGE_READWRITE | PAGE_READONLY and FILE_MAP_WRITE | FILE_MAP_READ - * appropriately - */ - -/* Test result from openbsd: shm ids are persistent cross process if a handle is left - * open. This could lead to resource starvation: we're not copying that behaviour - * unless we have to. (It will involve acygwin1.dll gloal shared list :[ ). - */ -/* FIXME: shmid should be a verifyable object - */ - -/* FIXME: on NT we should check everything against the SD. On 95 we just emulate. - */ - -extern GENERIC_MAPPING - access_mapping; - -extern int -check_and_dup_handle (HANDLE from_process, HANDLE to_process, - HANDLE from_process_token, - DWORD access, - HANDLE from_handle, - HANDLE * to_handle_ptr, BOOL bInheritHandle); - -//FIXME: where should this live -static shmnode * - shm_head = - NULL; -//FIXME: ditto. -static shmnode * - deleted_head = NULL; -/* must be long for InterlockedIncrement */ -static long - new_id = - 0; -static long - new_private_key = - 0; - -static void -delete_shmnode (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->mapptr); - delete node->shmds; - CloseHandle (node->filemap); - CloseHandle (node->attachmap); - - // free the memory - delete node; - nodeptr = NULL; -} - -void -client_request_shm::serve (transport_layer_base * conn, process_cache * cache) -{ -// DWORD sd_size = 4096; -// char sd_buf[4096]; - PSECURITY_DESCRIPTOR psd = (PSECURITY_DESCRIPTOR) parameters.in.sd_buf; -// /* create a sd for our open requests based on shmflag & 0x01ff */ -// psd = alloc_sd (getuid (), getgid (), cygheap->user.logsrv (), -// parameters.in.shmflg & 0x01ff, psd, &sd_size); - - HANDLE from_process_handle = NULL; - HANDLE token_handle = NULL; - DWORD rc; - - from_process_handle = cache->process (parameters.in.pid)->handle (); - /* possible TODO: reduce the access on the handle before we use it */ - /* Note that unless we do this, we don't need to call CloseHandle - it's kept open - * by the process cache until the process terminates. - * We may need a refcount on the cache however... - */ - if (!from_process_handle) - { - debug_printf ("error opening process (%lu)\n", GetLastError ()); - header.error_code = EACCES; - return; - } - - conn->impersonate_client (); - - rc = OpenThreadToken (GetCurrentThread (), - TOKEN_QUERY, TRUE, &token_handle); - - conn->revert_to_self (); - - if (!rc) - { - debug_printf ("error opening thread token (%lu)\n", GetLastError ()); - header.error_code = EACCES; - CloseHandle (from_process_handle); - return; - } - - - /* we trust the clients request - we will be doing it as them, and - * the worst they can do is open their own permissions - */ - - - SECURITY_ATTRIBUTES sa; - sa.nLength = sizeof (sa); - sa.lpSecurityDescriptor = psd; - sa.bInheritHandle = TRUE; /* the memory structures inherit ok */ - - char *shmname = NULL, *shmaname = NULL; - char stringbuf[29], stringbuf1[29]; - - /* TODO: make this code block a function! */ - if (parameters.in.type == SHM_REATTACH) - { - /* just find and fill out the existing shm_id */ - shmnode *tempnode = shm_head; - while (tempnode) + if (previous) { - if (tempnode->shm_id == parameters.in.shm_id) - { - parameters.out.shm_id = tempnode->shm_id; - parameters.out.key = tempnode->key; - if (check_and_dup_handle - (GetCurrentProcess (), from_process_handle, token_handle, - DUPLICATE_SAME_ACCESS, tempnode->filemap, - ¶meters.out.filemap, TRUE) != 0) - { - debug_printf ("error duplicating filemap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; - } - if (check_and_dup_handle - (GetCurrentProcess (), from_process_handle, token_handle, - DUPLICATE_SAME_ACCESS, tempnode->attachmap, - ¶meters.out.attachmap, TRUE) != 0) - { - debug_printf ("error duplicating attachmap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; - } - CloseHandle (token_handle); - return; - } - tempnode = tempnode->next; - } - header.error_code = EINVAL; - CloseHandle (token_handle); - return; - } - - /* someone attached */ - /* someone can send shm_id's they don't have and currently we will increment those - * attach counts. If someone wants to fix that, please go ahead. - * The problem is that shm_get has nothing to do with the ability to attach. Attach - * requires a permission check, which we get the OS to do in MapViewOfFile. - */ - if (parameters.in.type == SHM_ATTACH) - { - shmnode *tempnode = shm_head; - while (tempnode) - { - if (tempnode->shm_id == parameters.in.shm_id) - { - InterlockedIncrement (&tempnode->shmds->shm_nattch); - header.error_code = 0; - CloseHandle (token_handle); - return; - } - tempnode = tempnode->next; - } - header.error_code = EINVAL; - CloseHandle (token_handle); - return; - } - - /* Someone detached */ - if (parameters.in.type == SHM_DETACH) - { - shmnode *tempnode = shm_head; - while (tempnode) - { - if (tempnode->shm_id == parameters.in.shm_id) - { - InterlockedDecrement (&tempnode->shmds->shm_nattch); - header.error_code = 0; - CloseHandle (token_handle); - return; - } - tempnode = tempnode->next; - } - header.error_code = EINVAL; - CloseHandle (token_handle); - return; - } - - /* Someone wants the ID removed. */ - if (parameters.in.type == SHM_DEL) - { - shmnode **tempnode = &shm_head; - while (*tempnode) - { - if ((*tempnode)->shm_id == parameters.in.shm_id) - { - // unlink from the accessible node list - shmnode *temp2 = *tempnode; - *tempnode = temp2->next; - // link into the deleted list - temp2->next = deleted_head; - deleted_head = temp2; - - // FIXME: when/where do we delete the handles? - if (temp2->shmds->shm_nattch) - { - // FIXME: add to a pending queue? - } - else - { - delete_shmnode (&temp2); - } - - header.error_code = 0; - CloseHandle (token_handle); - return; - } - tempnode = &(*tempnode)->next; - } - header.error_code = EINVAL; - CloseHandle (token_handle); - return; - } - - - if (parameters.in.type == SHM_CREATE) - { - /* FIXME: enter the checking for existing keys mutex. This mutex _must_ be system wide - * to prevent races on shmget. - */ - - if (parameters.in.key == IPC_PRIVATE) - { - /* create the mapping name (CYGWINSHMKPRIVATE_0x01234567 */ - /* The K refers to Key, the actual mapped area has D */ - long private_key = (int) InterlockedIncrement (&new_private_key); - snprintf (stringbuf, 29, "CYGWINSHMKPRIVATE_0x%0x", private_key); - shmname = stringbuf; - snprintf (stringbuf1, 29, "CYGWINSHMDPRIVATE_0x%0x", private_key); - shmaname = stringbuf1; + attptr->_next = previous->_next; + previous->_next = attptr; } else { - /* create the mapping name (CYGWINSHMK0x0123456789abcdef */ - /* The K refers to Key, the actual mapped area has D */ - - snprintf (stringbuf, 29, "CYGWINSHMK0x%0qx", parameters.in.key); - shmname = stringbuf; - snprintf (stringbuf1, 29, "CYGWINSHMD0x%0qx", parameters.in.key); - shmaname = stringbuf1; - debug_printf ("system id strings are \n%s\n%s\n", shmname, - shmaname); - debug_printf ("key input value is 0x%0qx\n", parameters.in.key); + attptr->_next = _attach_head; + _attach_head = attptr; } + } - /* attempt to open the key */ + attptr->_refcnt += 1; - /* get an existing key */ - /* On unix the same shmid identifier is returned on multiple calls to shm_get - * with the same key and size. Different modes is a ?. - */ + 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); - /* walk the list of known keys and return the id if found. remember, we are - * authoritative... - */ + assert (result); - shmnode *tempnode = shm_head; - while (tempnode) - { - if (tempnode->key == parameters.in.key - && parameters.in.key != IPC_PRIVATE) + 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) { - // FIXME: free the mutex - if (parameters.in.size - && tempnode->shmds->shm_segsz < parameters.in.size) - { - header.error_code = EINVAL; - CloseHandle (token_handle); - return; - } - /* FIXME: can the same process call this twice without error ? test - * on unix - */ - if ((parameters.in.shmflg & IPC_CREAT) - && (parameters.in.shmflg & IPC_EXCL)) - { - header.error_code = EEXIST; - debug_printf - ("attempt to exclusively create already created shm_area with key 0x%0qx\n", - parameters.in.key); - // FIXME: free the mutex - CloseHandle (token_handle); - return; - } - // FIXME: do we need to other tests of the requested mode with the - // tempnode->shm_id mode ? testcase on unix needed. - // FIXME how do we do the security test? or - // do we wait for shmat to bother with that? - /* One possibly solution: impersonate the client, and then test we can - * reopen the area. In fact we'll probably have to do that to get - * handles back to them, alternatively just tell them the id, and then - * let them attempt the open. - */ - parameters.out.shm_id = tempnode->shm_id; - if (check_and_dup_handle - (GetCurrentProcess (), from_process_handle, token_handle, - DUPLICATE_SAME_ACCESS, tempnode->filemap, - ¶meters.out.filemap, TRUE) != 0) - { - printf ("error duplicating filemap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; -/*mutex*/ - CloseHandle (token_handle); - return; - } - if (check_and_dup_handle - (GetCurrentProcess (), from_process_handle, token_handle, - DUPLICATE_SAME_ACCESS, tempnode->attachmap, - ¶meters.out.attachmap, TRUE) != 0) - { - printf ("error duplicating attachmap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; -/*mutex*/ - CloseHandle (token_handle); - return; - } + case IPC_STAT: + out_ds = segptr->_ds; + break; - CloseHandle (token_handle); - return; + 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; } - tempnode = tempnode->next; - } - /* couldn't find a currently open shm area. */ + } + break; - /* create one */ - /* do this as the client */ - conn->impersonate_client (); - /* This may need sh_none... it's only a control structure */ - HANDLE filemap = CreateFileMapping (INVALID_HANDLE_VALUE, // system pagefile. - &sa, - PAGE_READWRITE, // protection - 0x00000000, - getsystemallocgranularity (), - shmname // object name - ); - int lasterr = GetLastError (); - conn->revert_to_self (); + case IPC_INFO: + out_shminfo.shmmax = SHMMAX; + out_shminfo.shmmin = SHMMIN; + out_shminfo.shmmni = SHMMNI; + out_shminfo.shmseg = SHMSEG; + out_shminfo.shmall = SHMALL; + break; - if (filemap == NULL) - { - /* We failed to open the filemapping ? */ - system_printf ("failed to open file mapping: %lu\n", - GetLastError ()); - // free the mutex - // we can assume that it exists, and that it was an access problem. - header.error_code = EACCES; - CloseHandle (token_handle); - return; - } + 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; - /* successfully opened the control region mapping */ - /* did we create it ? */ - int oldmapping = lasterr == ERROR_ALREADY_EXISTS; - if (oldmapping) - { - /* should never happen - we are the global daemon! */ -#if 0 - if ((parameters.in.shmflg & IPC_CREAT) - && (parameters.in.shmflg & IPC_EXCL)) -#endif - { - /* FIXME free mutex */ - CloseHandle (filemap); - header.error_code = EEXIST; - CloseHandle (token_handle); - return; - } - } + default: + result = -EINVAL; + break; + } - /* we created a new mapping */ - if (parameters.in.key != IPC_PRIVATE && - (parameters.in.shmflg & IPC_CREAT) == 0) - { - CloseHandle (filemap); - /* FIXME free mutex */ - header.error_code = ENOENT; - CloseHandle (token_handle); - return; - } + LeaveCriticalSection (&_segments_lock); - conn->impersonate_client (); - void *mapptr = MapViewOfFile (filemap, FILE_MAP_WRITE, 0, 0, 0); - conn->revert_to_self (); + 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 ()); - if (!mapptr) - { - CloseHandle (filemap); - //FIXME: close filemap and free the mutex - /* we couldn't access the mapped area with the requested permissions */ - header.error_code = EACCES; - CloseHandle (token_handle); - return; - } + return result; +} - conn->impersonate_client (); - /* Now get the user data */ - HANDLE attachmap = CreateFileMapping (INVALID_HANDLE_VALUE, // system pagefile - &sa, - PAGE_READWRITE, // protection (FIXME) - 0x00000000, - parameters.in.size + - parameters.in.size % - getsystemallocgranularity (), - shmaname // object name - ); - conn->revert_to_self (); +/*---------------------------------------------------------------------------* + * server_shmmgr::shmdt () + *---------------------------------------------------------------------------*/ - if (attachmap == NULL) - { - system_printf ("failed to get shm attachmap\n"); - header.error_code = ENOMEM; - UnmapViewOfFile (mapptr); - CloseHandle (filemap); - /* FIXME exit the mutex */ - CloseHandle (token_handle); - return; - } +int +server_shmmgr::shmdt (const int shmid, class process *const client) +{ + syscall_printf ("shmdt (shmid = %d) for %d(%lu)", + shmid, client->cygpid (), client->winpid ()); - shmid_ds *shmtemp = new shmid_ds; - if (!shmtemp) - { - system_printf ("failed to malloc shm node\n"); - header.error_code = ENOMEM; - UnmapViewOfFile (mapptr); - CloseHandle (filemap); - CloseHandle (attachmap); - /* FIXME exit mutex */ - CloseHandle (token_handle); - return; - } + int result = 0; + EnterCriticalSection (&_segments_lock); - /* 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 = parameters.in.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 = parameters.in.size; - *(shmid_ds *) mapptr = *shmtemp; - shmtemp->mapptr = mapptr; + segment_t *const segptr = find (ipc_ext2int (shmid, IPC_SHMOP)); - /* no need for InterlockedExchange here, we're serialised by the global mutex */ - tempnode = new shmnode; - tempnode->shmds = shmtemp; - tempnode->shm_id = (int) InterlockedIncrement (&new_id); - tempnode->key = parameters.in.key; - tempnode->filemap = filemap; - tempnode->attachmap = attachmap; - tempnode->next = shm_head; - shm_head = tempnode; + if (!segptr) + result = -EINVAL; + else + result = segptr->detach (client); - /* we now have the area in the daemon list, opened. + if (!result) + _shm_atts -= 1; - FIXME: leave the system wide shm mutex */ + if (!result && segptr->is_pending_delete ()) + delete_segment (segptr); - parameters.out.shm_id = tempnode->shm_id; - if (check_and_dup_handle (GetCurrentProcess (), from_process_handle, - token_handle, - DUPLICATE_SAME_ACCESS, - tempnode->filemap, ¶meters.out.filemap, - TRUE) != 0) - { - printf ("error duplicating filemap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; - CloseHandle (token_handle); -/* mutex et al */ - return; - } - if (check_and_dup_handle (GetCurrentProcess (), from_process_handle, - token_handle, - DUPLICATE_SAME_ACCESS, - tempnode->attachmap, - ¶meters.out.attachmap, TRUE) != 0) - { - printf ("error duplicating attachmap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; - CloseHandle (from_process_handle); - CloseHandle (token_handle); -/* more cleanup... yay! */ - return; - } - CloseHandle (token_handle); + 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; } - header.error_code = ENOSYS; - CloseHandle (token_handle); + // 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); - return; + 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)); } diff --git a/winsup/cygserver/shm.h b/winsup/cygserver/shm.h index f1dcaa53f..b1ff353da 100755 --- a/winsup/cygserver/shm.h +++ b/winsup/cygserver/shm.h @@ -1,7 +1,9 @@ -/* cygserver_shm.h +/* cygserver_shm.h: Single unix specification IPC interface for Cygwin. - Copyright 2001, 2002 Red Hat Inc. - Written by Robert Collins + Copyright 2002 Red Hat, Inc. + + Written by Conrad Scott . + Based on code by Robert Collins . This file is part of Cygwin. @@ -9,84 +11,137 @@ This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ +#ifndef __CYGSERVER_SHM_H__ +#define __CYGSERVER_SHM_H__ + #include -#include "cygwin/cygserver_transport.h" +#include + +#include +#include + +#include "cygserver_ipc.h" + #include "cygwin/cygserver.h" -#define SHM_CREATE 0 -#define SHM_REATTACH 1 -#define SHM_ATTACH 2 -#define SHM_DETACH 3 -#define SHM_DEL 4 +/*---------------------------------------------------------------------------* + * Values for the shminfo entries. + * + * Nb. The values are segregated between two enums so that the `small' + * values aren't promoted to `unsigned long' equivalents. + *---------------------------------------------------------------------------*/ +enum + { + SHMMAX = ULONG_MAX, + SHMSEG = ULONG_MAX, + SHMALL = ULONG_MAX + }; + +enum + { + SHMMIN = 1, + SHMMNI = IPCMNI // Must be <= IPCMNI. + }; + +/*---------------------------------------------------------------------------* + * class client_request_shm + *---------------------------------------------------------------------------*/ + +#ifndef __INSIDE_CYGWIN__ +class transport_layer_base; +class process_cache; +#endif class client_request_shm : public client_request { - public: -#ifndef __INSIDE_CYGWIN__ - virtual void serve (transport_layer_base *conn, process_cache *cache); + friend class client_request; + +public: + enum shmop_t + { + SHMOP_shmat, + SHMOP_shmctl, + SHMOP_shmdt, + SHMOP_shmget + }; + +#ifdef __INSIDE_CYGWIN__ + client_request_shm (int shmid, int shmflg); // shmat + client_request_shm (int shmid, int cmd, const struct shmid_ds *); // shmctl + client_request_shm (int shmid); // shmdt + client_request_shm (key_t, size_t, int shmflg); // shmget #endif - client_request_shm (key_t, size_t, int, char psdbuf[4096], pid_t); + + // Accessors for out parameters. + + int shmid () const + { + assert (!error_code ()); + return _parameters.out.shmid; + } + + HANDLE hFileMap () const + { + assert (!error_code ()); + return _parameters.out.hFileMap; + } + + const struct shmid_ds & ds () const + { + assert (!error_code ()); + return _parameters.out.ds; + } + + const struct shminfo & shminfo () const + { + assert (!error_code ()); + return _parameters.out.shminfo; + } + + const struct shm_info & shm_info () const + { + assert (!error_code ()); + return _parameters.out.shm_info; + } + +private: + union + { + struct + { + shmop_t shmop; + key_t key; + size_t size; + int shmflg; + int shmid; + int cmd; + pid_t cygpid; + DWORD winpid; + uid_t uid; + gid_t gid; + struct shmid_ds ds; + } in; + + struct { + int shmid; + union + { + HANDLE hFileMap; + struct shmid_ds ds; + struct shminfo shminfo; + struct shm_info shm_info; + }; + } out; + } _parameters; + +#ifndef __INSIDE_CYGWIN__ client_request_shm (); - client_request_shm (int, int, pid_t); - client_request_shm (int, int); - union { - struct {int type; pid_t pid; int shm_id; key_t key; size_t size; int shmflg; char sd_buf[4096];} in; - struct {int shm_id; HANDLE filemap; HANDLE attachmap; key_t key;} out; - } parameters; -}; +#endif #ifndef __INSIDE_CYGWIN__ -class shm_cleanup : cleanup_routine -{ -public: - virtual void cleanup (long winpid); -}; + virtual void serve (transport_layer_base *, process_cache *); #endif -#if 0 -class _shmattach { -public: - void *data; - class _shmattach *next; }; -class shmid_ds { -public: - struct ipc_perm shm_perm; - size_t shm_segsz; - pid_t shm_lpid; - pid_t shm_cpid; - shmatt_t shm_nattch; - time_t shm_atime; - time_t shm_dtime; - time_t shm_ctime; - HANDLE filemap; - HANDLE attachmap; - void *mapptr; - class _shmattach *attachhead; -}; - -class shmnode { -public: - class shmid_ds * shmid; - class shmnode *next; - key_t key; -}; -//.... -struct shmid_ds { - struct ipc_perm shm_perm; - size_t shm_segsz; - pid_t shm_lpid; - pid_t shm_cpid; - shmatt_t shm_nattch; - time_t shm_atime; - time_t shm_dtime; - time_t shm_ctime; -}; - -void *shmat(int, const void *, int); -int shmctl(int, int, struct shmid_ds *); -int shmdt(const void *); -int shmget(key_t, size_t, int); - -#endif +#endif /* __CYGSERVER_SHM_H__ */ diff --git a/winsup/cygserver/threaded_queue.cc b/winsup/cygserver/threaded_queue.cc index 321fa1612..ba0fe4178 100644 --- a/winsup/cygserver/threaded_queue.cc +++ b/winsup/cygserver/threaded_queue.cc @@ -4,247 +4,405 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +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 #include -#include "wincap.h" #include "threaded_queue.h" -#define DEBUG 1 -#define debug_printf if (DEBUG) printf + +/*****************************************************************************/ + +/* queue_request */ + +queue_request::~queue_request () +{} + +/*****************************************************************************/ /* threaded_queue */ -DWORD WINAPI -worker_function (LPVOID LpParam) +threaded_queue::threaded_queue (const size_t initial_workers) + : _workers_count (0), + _running (false), + _submitters_head (NULL), + _requests_count (0), + _requests_head (NULL), + _requests_sem (NULL) { - class threaded_queue *queue = (class threaded_queue *) LpParam; - class queue_request *request; - /* FIXME use a threadsafe pop instead for speed? */ - while (queue->active) + InitializeCriticalSection (&_queue_lock); + + // This semaphore's count is the number of requests on the queue. + // The maximum count (129792) is calculated as MAXIMUM_WAIT_OBJECTS + // multiplied by max. threads per process (2028?), which is (a few) + // more requests than could ever be pending with the current design. + + _requests_sem = CreateSemaphore (NULL, // SECURITY_ATTRIBUTES + 0, // Initial count + 129792, // Maximum count + NULL); // Anonymous + + if (!_requests_sem) { - EnterCriticalSection (&queue->queuelock); - while (!queue->request && queue->active) - { - LeaveCriticalSection (&queue->queuelock); - DWORD rc = WaitForSingleObject (queue->event, INFINITE); - if (rc == WAIT_FAILED) - { - printf ("Wait for event failed\n"); - queue->running--; - ExitThread (0); - } - EnterCriticalSection (&queue->queuelock); - } - if (!queue->active) - { - queue->running--; - LeaveCriticalSection (&queue->queuelock); - ExitThread (0); - } - /* not needed, but it is efficient */ - request = - (class queue_request *) InterlockedExchangePointer (&queue->request, - queue->request-> - next); - LeaveCriticalSection (&queue->queuelock); - request->process (); - delete request; + system_printf (("failed to create the request queue semaphore, " + "error = %lu"), + GetLastError ()); + abort (); } - queue->running--; - ExitThread (0); + + create_workers (initial_workers); } -void -threaded_queue::create_workers () +threaded_queue::~threaded_queue () { - InitializeCriticalSection (&queuelock); - if ((event = CreateEvent (NULL, FALSE, FALSE, NULL)) == NULL) - { - printf ("Failed to create event queue (%lu), terminating\n", - GetLastError ()); - exit (1); - } - active = true; + if (_running) + stop (); - /* FIXME: Use a stack pair and create threads on the fly whenever - * we have to to service a request. - */ - for (unsigned int i = 0; i < initial_workers; i++) + debug_printf ("deleting all pending queue requests"); + queue_request *reqptr = _requests_head; + while (reqptr) { - HANDLE hThread; - DWORD tid; - hThread = CreateThread (NULL, 0, worker_function, this, 0, &tid); - if (hThread == NULL) - { - printf ("Failed to create thread (%lu), terminating\n", - GetLastError ()); - exit (1); - } - CloseHandle (hThread); - running++; + queue_request *const ptr = reqptr; + reqptr = reqptr->_next; + safe_delete (ptr); } -} -void -threaded_queue::cleanup () -{ - /* harvest the threads */ - active = false; - /* kill the request processing loops */ - queue_process_param *reqloop; - /* make sure we don't race with a incoming request creation */ - EnterCriticalSection (&queuelock); - reqloop = - (queue_process_param *) InterlockedExchangePointer (&process_head, NULL); - while (reqloop) - { - queue_process_param *t = reqloop; - reqloop = reqloop->next; - delete t; - } - LeaveCriticalSection (&queuelock); - if (!running) - return; - printf ("Waiting for current queue threads to terminate\n"); - for (int n = running; n; n--) - PulseEvent (event); - while (running) - sleep (1); - DeleteCriticalSection (&queuelock); - CloseHandle (event); -} - -/* FIXME: return success or failure */ -void -threaded_queue::add (queue_request * therequest) -{ - /* safe to not "Try" because workers don't hog this, they wait on the event - */ - EnterCriticalSection (&queuelock); - if (!running) - { - printf ("No worker threads to handle request!\n"); - } - if (!request) - request = therequest; - else - { - /* add to the queue end. */ - queue_request *listrequest = request; - while (listrequest->next) - listrequest = listrequest->next; - listrequest->next = therequest; - } - PulseEvent (event); - LeaveCriticalSection (&queuelock); + DeleteCriticalSection (&_queue_lock); + if (_requests_sem) + (void) CloseHandle (_requests_sem); } /* FIXME: return success or failure rather than quitting */ void -threaded_queue::process_requests (queue_process_param * params, - threaded_queue_thread_function * - request_loop) +threaded_queue::add_submission_loop (queue_submission_loop *const submitter) { - if (params->start (request_loop, this) == false) - exit (1); - params->next = - (queue_process_param *) InterlockedExchangePointer (&process_head, - params); -} + assert (this); + assert (submitter); + assert (submitter->_queue == this); + assert (!submitter->_next); -/* queue_process_param */ -/* How does a constructor return an error? */ -queue_process_param::queue_process_param (bool ninterruptible):running (false), shutdown (false), -interruptible -(ninterruptible) -{ - if (!interruptible) - return; - debug_printf ("creating an interruptible processing thread\n"); - if ((interrupt = CreateEvent (NULL, FALSE, FALSE, NULL)) == NULL) - { - printf ("Failed to create interrupt event (%lu), terminating\n", - GetLastError ()); - exit (1); - } -} + submitter->_next = + TInterlockedExchangePointer (&_submitters_head, submitter); -queue_process_param::~queue_process_param () -{ - if (running) - stop (); - if (!interruptible) - return; - CloseHandle (interrupt); + if (_running) + submitter->start (); } bool - queue_process_param::start (threaded_queue_thread_function * request_loop, - threaded_queue * thequeue) +threaded_queue::start () { - queue = thequeue; - hThread = CreateThread (NULL, 0, request_loop, this, 0, &tid); - if (hThread) + EnterCriticalSection (&_queue_lock); + const bool was_running = _running; + _running = true; + queue_submission_loop *loopptr = _submitters_head; + LeaveCriticalSection (&_queue_lock); + + if (!was_running) { - running = true; - return true; + debug_printf ("starting all queue submission loops"); + + while (loopptr) + { + queue_submission_loop *const ptr = loopptr; + loopptr = loopptr->_next; + ptr->start (); + } } - printf ("Failed to create thread (%lu), terminating\n", GetLastError ()); - return false; + + return was_running; } -void -queue_process_param::stop () +bool +threaded_queue::stop () { - if (interruptible) + EnterCriticalSection (&_queue_lock); + const bool was_running = _running; + _running = false; + queue_submission_loop *loopptr = _submitters_head; + LeaveCriticalSection (&_queue_lock); + + if (was_running) { - InterlockedExchange (&shutdown, true); - PulseEvent (interrupt); - /* Wait up to 50 ms for the thread to exit. If it doesn't _and_ we get - * scheduled again, we print an error and exit. We _should_ loop or - * try resignalling. We don't want to hand here though... - */ - int n = 5; - while (n-- && WaitForSingleObject (hThread, 1000) == WAIT_TIMEOUT); - if (!n) + debug_printf ("stopping all queue submission loops"); + while (loopptr) { - printf ("Process thread didn't shutdown cleanly after 200ms!\n"); - exit (1); + queue_submission_loop *const ptr = loopptr; + loopptr = loopptr->_next; + ptr->stop (); } - else - running = false; + + ReleaseSemaphore (_requests_sem, _workers_count, NULL); + while (_workers_count) + { + debug_printf (("waiting for worker threads to terminate: " + "%lu still running"), + _workers_count); + Sleep (1000); + } + debug_printf ("all worker threads have terminated"); } + + return was_running; +} + +/* FIXME: return success or failure */ +void +threaded_queue::add (queue_request *const therequest) +{ + assert (this); + assert (therequest); + assert (!therequest->_next); + + if (!_workers_count) + { + system_printf ("warning: no worker threads to handle request!"); + // FIXME: And then what? + } + + EnterCriticalSection (&_queue_lock); + if (!_requests_head) + _requests_head = therequest; else { - printf ("killing request loop thread %ld\n", tid); - int rc; - if (!(rc = TerminateThread (hThread, 0))) - { - printf ("error shutting down request loop worker thread\n"); - } - running = false; + /* Add to the queue end. */ + queue_request *reqptr = _requests_head; + for (; reqptr->_next; reqptr = reqptr->_next) + {} + assert (reqptr); + assert (!reqptr->_next); + reqptr->_next = therequest; } - CloseHandle (hThread); + + _requests_count += 1; + assert (_requests_count > 0); + LeaveCriticalSection (&_queue_lock); + + (void) ReleaseSemaphore (_requests_sem, 1, NULL); } -/* queue_request */ -queue_request::queue_request ():next (NULL) +/*static*/ DWORD WINAPI +threaded_queue::start_routine (const LPVOID lpParam) { + class threaded_queue *const queue = (class threaded_queue *) lpParam; + assert (queue); + + queue->worker_loop (); + + const long count = InterlockedDecrement (&queue->_workers_count); + assert (count >= 0); + + if (queue->_running) + debug_printf ("worker loop has exited; thread about to terminate"); + + return 0; +} + +/* Called from the constructor: so no need to be thread-safe until the + * worker threads start to be created; thus the interlocked increment + * of the `_workers_count' field. + */ + +void +threaded_queue::create_workers (const size_t initial_workers) +{ + assert (initial_workers > 0); + + for (unsigned int i = 0; i != initial_workers; i++) + { + const long count = InterlockedIncrement (&_workers_count); + assert (count > 0); + + DWORD tid; + const HANDLE hThread = + CreateThread (NULL, 0, start_routine, this, 0, &tid); + + if (!hThread) + { + system_printf ("failed to create thread, error = %lu", + GetLastError ()); + abort (); + } + + (void) CloseHandle (hThread); + } } void -queue_request::process (void) +threaded_queue::worker_loop () { - printf ("\n**********************************************\n" - "Oh no! we've hit the base queue_request process() function, and this indicates a coding\n" - "fault !!!\n" "***********************************************\n"); + while (true) + { + const DWORD rc = WaitForSingleObject (_requests_sem, INFINITE); + if (rc == WAIT_FAILED) + { + system_printf ("wait for request semaphore failed, error = %lu", + GetLastError ()); + return; + } + assert (rc == WAIT_OBJECT_0); + + EnterCriticalSection (&_queue_lock); + if (!_running) + { + LeaveCriticalSection (&_queue_lock); + return; + } + + assert (_requests_head); + queue_request *const reqptr = _requests_head; + _requests_head = reqptr->_next; + + _requests_count -= 1; + assert (_requests_count >= 0); + LeaveCriticalSection (&_queue_lock); + + assert (reqptr); + reqptr->process (); + safe_delete (reqptr); + } } + +/*****************************************************************************/ + +/* queue_submission_loop */ + +queue_submission_loop::queue_submission_loop (threaded_queue *const queue, + const bool ninterruptible) + : _running (false), + _interrupt_event (NULL), + _queue (queue), + _interruptible (ninterruptible), + _hThread (NULL), + _tid (0), + _next (NULL) +{ + if (_interruptible) + { + // verbose: debug_printf ("creating an interruptible processing thread"); + + _interrupt_event = CreateEvent (NULL, // SECURITY_ATTRIBUTES + FALSE, // Auto-reset + FALSE, // Initially non-signalled + NULL); // Anonymous + + if (!_interrupt_event) + { + system_printf ("failed to create interrupt event, error = %lu", + GetLastError ()); + abort (); + } + } +} + +queue_submission_loop::~queue_submission_loop () +{ + if (_running) + stop (); + if (_interrupt_event) + (void) CloseHandle (_interrupt_event); + if (_hThread) + (void) CloseHandle (_hThread); +} + +bool +queue_submission_loop::start () +{ + assert (this); + assert (!_hThread); + + const bool was_running = _running; + + if (!was_running) + { + _running = true; + + _hThread = CreateThread (NULL, 0, start_routine, this, 0, &_tid); + if (!_hThread) + { + system_printf ("failed to create thread, error = %lu", + GetLastError ()); + abort (); + } + } + + return was_running; +} + +bool +queue_submission_loop::stop () +{ + assert (this); + assert (_hThread && _hThread != INVALID_HANDLE_VALUE); + + const bool was_running = _running; + + if (_running) + { + _running = false; + + if (_interruptible) + { + assert (_interrupt_event + && _interrupt_event != INVALID_HANDLE_VALUE); + + SetEvent (_interrupt_event); + + if (WaitForSingleObject (_hThread, 1000) == WAIT_TIMEOUT) + { + system_printf (("request loop thread %lu failed to shutdown " + "when asked politely: about to get heavy"), + _tid); + + if (!TerminateThread (_hThread, 0)) + { + system_printf (("failed to kill request loop thread %lu" + ", error = %lu"), + _tid, GetLastError ()); + abort (); + } + } + } + else + { + // FIXME: could wait to see if the request loop notices that + // the submission loop is no longer running and shuts down + // voluntarily. + + debug_printf ("killing request loop thread %lu", _tid); + + if (!TerminateThread (_hThread, 0)) + system_printf (("failed to kill request loop thread %lu" + ", error = %lu"), + _tid, GetLastError ()); + } + } + + return was_running; +} + +/*static*/ DWORD WINAPI +queue_submission_loop::start_routine (const LPVOID lpParam) +{ + class queue_submission_loop *const submission_loop = + (class queue_submission_loop *) lpParam; + assert (submission_loop); + + submission_loop->request_loop (); + + debug_printf ("submission loop has exited; thread about to terminate"); + + submission_loop->stop (); + + return 0; +} + +/*****************************************************************************/ diff --git a/winsup/cygserver/transport.cc b/winsup/cygserver/transport.cc index 01f044406..8684a6148 100644 --- a/winsup/cygserver/transport.cc +++ b/winsup/cygserver/transport.cc @@ -4,89 +4,48 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +/* to allow this to link into cygwin and the .dll, a little magic is needed. */ +#ifdef __OUTSIDE_CYGWIN__ +#include "woutsup.h" +#else +#include "winsup.h" +#endif -#include -#include -#include -#include -#include #include -#include -#include "wincap.h" + +#include "safe_memory.h" + #include "cygwin/cygserver_transport.h" #include "cygwin/cygserver_transport_pipes.h" #include "cygwin/cygserver_transport_sockets.h" -/* to allow this to link into cygwin and the .dll, a little magic is needed. */ -#ifndef __OUTSIDE_CYGWIN__ -#include "winsup.h" -#else -#define debug_printf printf -#endif - /* The factory */ -class transport_layer_base *create_server_transport() +transport_layer_base * +create_server_transport () { - transport_layer_base *temp; - /* currently there is only the base class! */ if (wincap.is_winnt ()) - temp = new transport_layer_pipes (); + return safe_new0 (transport_layer_pipes); else - temp = new transport_layer_sockets (); - return temp; + return safe_new0 (transport_layer_sockets); } - -transport_layer_base::transport_layer_base () -{ - /* should we throw an error of some sort ? */ -} - -void -transport_layer_base::listen () -{ -} - -class transport_layer_base * -transport_layer_base::accept () -{ - return NULL; -} - -void -transport_layer_base::close() -{ -} - -ssize_t -transport_layer_base::read (char *buf, size_t len) -{ - return 0; -} - -ssize_t -transport_layer_base::write (char *buf, size_t len) -{ - return 0; -} - -bool -transport_layer_base::connect () -{ - return false; -} +#ifndef __INSIDE_CYGWIN__ void transport_layer_base::impersonate_client () -{ -} +{} void transport_layer_base::revert_to_self () -{ -} +{} + +#endif /* !__INSIDE_CYGWIN__ */ + +transport_layer_base::~transport_layer_base () +{} diff --git a/winsup/cygserver/transport_pipes.cc b/winsup/cygserver/transport_pipes.cc index f2221700f..f318a7592 100644 --- a/winsup/cygserver/transport_pipes.cc +++ b/winsup/cygserver/transport_pipes.cc @@ -4,207 +4,360 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +/* to allow this to link into cygwin and the .dll, a little magic is needed. */ +#ifdef __OUTSIDE_CYGWIN__ +#include "woutsup.h" +#else +#include "winsup.h" +#endif -#include -#include -#include -#include #include -#include + +#include +#include #include -#include "wincap.h" +#include +#include + +#include "cygerrno.h" #include "cygwin/cygserver_transport.h" #include "cygwin/cygserver_transport_pipes.h" -/* to allow this to link into cygwin and the .dll, a little magic is needed. */ -#ifndef __OUTSIDE_CYGWIN__ -#include "winsup.h" -#else -#define DEBUG 0 -#define debug_printf if (DEBUG) printf +#ifndef __INSIDE_CYGWIN__ +#include "cygwin/cygserver.h" #endif -//SECURITY_DESCRIPTOR transport_layer_pipes::sd; -//SECURITY_ATTRIBUTES transport_layer_pipes::sec_none_nih, transport_layer_pipes::sec_all_nih; -//bool transport_layer_pipes::inited = false; +enum + { + MAX_WAIT_NAMED_PIPE_RETRY = 64, + WAIT_NAMED_PIPE_TIMEOUT = 10 // milliseconds + }; -transport_layer_pipes::transport_layer_pipes (HANDLE new_pipe) +#ifndef __INSIDE_CYGWIN__ + +static pthread_once_t pipe_instance_lock_once = PTHREAD_ONCE_INIT; +static CRITICAL_SECTION pipe_instance_lock; +static long pipe_instance = 0; + +static void +initialise_pipe_instance_lock () { - inited = false; //FIXME: allow inited, sd, all_nih_.. to be static members - pipe = new_pipe; - if (inited != true) - init_security(); -}; + assert (pipe_instance == 0); + InitializeCriticalSection (&pipe_instance_lock); +} + +#endif /* !__INSIDE_CYGWIN__ */ + +#ifndef __INSIDE_CYGWIN__ + +transport_layer_pipes::transport_layer_pipes (const HANDLE hPipe) + : _pipe_name (""), + _hPipe (hPipe), + _is_accepted_endpoint (true), + _is_listening_endpoint (false) +{ + assert (_hPipe); + assert (_hPipe != INVALID_HANDLE_VALUE); + + init_security (); +} + +#endif /* !__INSIDE_CYGWIN__ */ transport_layer_pipes::transport_layer_pipes () + : _pipe_name ("\\\\.\\pipe\\cygwin_lpc"), + _hPipe (NULL), + _is_accepted_endpoint (false), + _is_listening_endpoint (false) { - inited = false; - pipe = NULL; - strcpy(pipe_name, "\\\\.\\pipe\\cygwin_lpc"); - if (inited != true) - init_security(); + init_security (); } void -transport_layer_pipes::init_security() +transport_layer_pipes::init_security () { + assert (wincap.has_security ()); + /* FIXME: pthread_once or equivalent needed */ - InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION); - SetSecurityDescriptorDacl (&sd, TRUE, 0, FALSE); - sec_none_nih.nLength = sec_all_nih.nLength = sizeof (SECURITY_ATTRIBUTES); - sec_none_nih.bInheritHandle = sec_all_nih.bInheritHandle = FALSE; - sec_none_nih.lpSecurityDescriptor = NULL; - sec_all_nih.lpSecurityDescriptor = &sd; - inited = true; + InitializeSecurityDescriptor (&_sd, SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl (&_sd, TRUE, NULL, FALSE); + + _sec_all_nih.nLength = sizeof (SECURITY_ATTRIBUTES); + _sec_all_nih.lpSecurityDescriptor = &_sd; + _sec_all_nih.bInheritHandle = FALSE; } -void +transport_layer_pipes::~transport_layer_pipes () +{ + close (); +} + +#ifndef __INSIDE_CYGWIN__ + +int transport_layer_pipes::listen () { + assert (!_hPipe); + assert (!_is_accepted_endpoint); + assert (!_is_listening_endpoint); + + _is_listening_endpoint = true; + /* no-op */ + return 0; } class transport_layer_pipes * -transport_layer_pipes::accept () +transport_layer_pipes::accept (bool *const recoverable) { - if (pipe) + assert (!_hPipe); + assert (!_is_accepted_endpoint); + assert (_is_listening_endpoint); + + pthread_once (&pipe_instance_lock_once, &initialise_pipe_instance_lock); + + EnterCriticalSection (&pipe_instance_lock); + + // Read: http://www.securityinternals.com/research/papers/namedpipe.php + // See also the Microsoft security bulletins MS00-053 and MS01-031. + + // FIXME: Remove FILE_CREATE_PIPE_INSTANCE. + + const bool first_instance = (pipe_instance == 0); + + const HANDLE accept_pipe = + CreateNamedPipe (_pipe_name, + (PIPE_ACCESS_DUPLEX + | (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0)), + (PIPE_TYPE_BYTE | PIPE_WAIT), + PIPE_UNLIMITED_INSTANCES, + 0, 0, 1000, + &_sec_all_nih); + + const bool duplicate = (accept_pipe == INVALID_HANDLE_VALUE + && pipe_instance == 0 + && GetLastError () == ERROR_ACCESS_DENIED); + + if (accept_pipe != INVALID_HANDLE_VALUE) + InterlockedIncrement (&pipe_instance); + + LeaveCriticalSection (&pipe_instance_lock); + + if (duplicate) { - debug_printf ("Already have a pipe in this %p\n",this); + *recoverable = false; + system_printf ("failed to create named pipe: " + "is the daemon already running?"); return NULL; } - pipe = CreateNamedPipe (pipe_name, - PIPE_ACCESS_DUPLEX, - PIPE_TYPE_BYTE | PIPE_WAIT, - PIPE_UNLIMITED_INSTANCES, - 0, 0, 1000, - &sec_all_nih ); - if (pipe == INVALID_HANDLE_VALUE) + if (accept_pipe == INVALID_HANDLE_VALUE) { - debug_printf ("error creating pipe (%lu)\n.", GetLastError ()); + debug_printf ("error creating pipe (%lu).", GetLastError ()); + *recoverable = true; // FIXME: case analysis? return NULL; } - if ( !ConnectNamedPipe ( pipe, NULL ) && - GetLastError () != ERROR_PIPE_CONNECTED) + assert (accept_pipe); + + if (!ConnectNamedPipe (accept_pipe, NULL) + && GetLastError () != ERROR_PIPE_CONNECTED) { - printf ("error connecting to pipe (%lu)\n.", GetLastError ()); - CloseHandle (pipe); - pipe = NULL; + debug_printf ("error connecting to pipe (%lu)\n.", GetLastError ()); + (void) CloseHandle (accept_pipe); + *recoverable = true; // FIXME: case analysis? return NULL; } - transport_layer_pipes *new_conn = new transport_layer_pipes (pipe); - pipe = NULL; - - return new_conn; + return safe_new (transport_layer_pipes, accept_pipe); } +#endif /* !__INSIDE_CYGWIN__ */ + void -transport_layer_pipes::close() +transport_layer_pipes::close () { - debug_printf ("closing pipe %p\n", pipe); - if (pipe && pipe != INVALID_HANDLE_VALUE) + // verbose: debug_printf ("closing pipe %p", _hPipe); + + if (_hPipe) { - FlushFileBuffers (pipe); - DisconnectNamedPipe (pipe); - CloseHandle (pipe); + assert (_hPipe != INVALID_HANDLE_VALUE); + +#ifndef __INSIDE_CYGWIN__ + + if (_is_accepted_endpoint) + { + (void) FlushFileBuffers (_hPipe); // Blocks until client reads. + (void) DisconnectNamedPipe (_hPipe); + EnterCriticalSection (&pipe_instance_lock); + (void) CloseHandle (_hPipe); + assert (pipe_instance > 0); + InterlockedDecrement (&pipe_instance); + LeaveCriticalSection (&pipe_instance_lock); + } + else + (void) CloseHandle (_hPipe); + +#else /* __INSIDE_CYGWIN__ */ + + assert (!_is_accepted_endpoint); + (void) ForceCloseHandle (_hPipe); + +#endif /* __INSIDE_CYGWIN__ */ + + _hPipe = NULL; } } ssize_t -transport_layer_pipes::read (char *buf, size_t len) +transport_layer_pipes::read (void *const buf, const size_t len) { - debug_printf ("reading from pipe %p\n", pipe); - if (!pipe || pipe == INVALID_HANDLE_VALUE) - return -1; + // verbose: debug_printf ("reading from pipe %p", _hPipe); - DWORD bytes_read; - DWORD rc = ReadFile (pipe, buf, len, &bytes_read, NULL); - if (!rc) + assert (_hPipe); + assert (_hPipe != INVALID_HANDLE_VALUE); + assert (!_is_listening_endpoint); + + DWORD count; + if (!ReadFile (_hPipe, buf, len, &count, NULL)) { - debug_printf ("error reading from pipe (%lu)\n", GetLastError ()); + debug_printf ("error reading from pipe (%lu)", GetLastError ()); + set_errno (EINVAL); // FIXME? return -1; } - return bytes_read; + + return count; } ssize_t -transport_layer_pipes::write (char *buf, size_t len) +transport_layer_pipes::write (void *const buf, const size_t len) { - debug_printf ("writing to pipe %p\n", pipe); - DWORD bytes_written, rc; - if (!pipe || pipe == INVALID_HANDLE_VALUE) - return -1; + // verbose: debug_printf ("writing to pipe %p", _hPipe); - rc = WriteFile (pipe, buf, len, &bytes_written, NULL); - if (!rc) + assert (_hPipe); + assert (_hPipe != INVALID_HANDLE_VALUE); + assert (!_is_listening_endpoint); + + DWORD count; + if (!WriteFile (_hPipe, buf, len, &count, NULL)) { - debug_printf ("error writing to pipe (%lu)\n", GetLastError ()); + debug_printf ("error writing to pipe, error = %lu", GetLastError ()); + set_errno (EINVAL); // FIXME? return -1; } - return bytes_written; + + return count; } -bool +/* + * This routine holds a static variable, assume_cygserver, that is set + * if the transport has good reason to think that cygserver is + * running, i.e. if if successfully connected to it with the previous + * attempt. If this is set, the code tries a lot harder to get a + * connection, making the assumption that any failures are just + * congestion and overloading problems. + */ + +int transport_layer_pipes::connect () { - if (pipe && pipe != INVALID_HANDLE_VALUE) + assert (!_hPipe); + assert (!_is_accepted_endpoint); + assert (!_is_listening_endpoint); + + static bool assume_cygserver = false; + + BOOL rc = TRUE; + int retries = 0; + + while (rc) { - debug_printf ("Already have a pipe in this %p\n",this); - return false; - } + _hPipe = CreateFile (_pipe_name, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &_sec_all_nih, + OPEN_EXISTING, + SECURITY_IMPERSONATION, + NULL); - while (1) - { - pipe = CreateFile (pipe_name, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - &sec_all_nih, - OPEN_EXISTING, - 0, NULL); - - if (pipe != INVALID_HANDLE_VALUE) - /* got the pipe */ - return true; - - if (GetLastError () != ERROR_PIPE_BUSY) + if (_hPipe != INVALID_HANDLE_VALUE) { - debug_printf ("Error opening the pipe (%lu)\n", GetLastError ()); - pipe = NULL; - return false; + assert (_hPipe); +#ifdef __INSIDE_CYGWIN__ + ProtectHandle (_hPipe); +#endif + assume_cygserver = true; + return 0; } - if (!WaitNamedPipe (pipe_name, 20000)) - debug_printf ( "error connecting to server pipe after 20 seconds (%lu)\n", GetLastError () ); - /* We loop here, because the pipe exists but is busy. If it doesn't exist - * the != ERROR_PIPE_BUSY will catch it. + + _hPipe = NULL; + + if (!assume_cygserver && GetLastError () != ERROR_PIPE_BUSY) + { + debug_printf ("Error opening the pipe (%lu)", GetLastError ()); + return -1; + } + + /* Note: `If no instances of the specified named pipe exist, the + * WaitNamedPipe function returns immediately, regardless of the + * time-out value.' Thus the explicit Sleep if the call fails + * with ERROR_FILE_NOT_FOUND. */ + while (retries != MAX_WAIT_NAMED_PIPE_RETRY + && !(rc = WaitNamedPipe (_pipe_name, WAIT_NAMED_PIPE_TIMEOUT))) + { + if (GetLastError () == ERROR_FILE_NOT_FOUND) + Sleep (0); // Give the server a chance. + + retries += 1; + } } + + assert (retries == MAX_WAIT_NAMED_PIPE_RETRY); + + system_printf ("lost connection to cygserver, error = %lu", + GetLastError ()); + + assume_cygserver = false; + + return -1; } +#ifndef __INSIDE_CYGWIN__ + void transport_layer_pipes::impersonate_client () { - debug_printf ("impersonating pipe %p\n", pipe); - if (pipe && pipe != INVALID_HANDLE_VALUE) + assert (_hPipe); + assert (_hPipe != INVALID_HANDLE_VALUE); + assert (_is_accepted_endpoint); + + // verbose: debug_printf ("impersonating pipe %p", _hPipe); + if (_hPipe) { - BOOL rv = ImpersonateNamedPipeClient (pipe); - if (!rv) - debug_printf ("Failed to Impersonate the client, (%lu)\n", GetLastError ()); + assert (_hPipe != INVALID_HANDLE_VALUE); + + if (!ImpersonateNamedPipeClient (_hPipe)) + debug_printf ("Failed to Impersonate the client, (%lu)", + GetLastError ()); } - debug_printf("I am who you are\n"); + // verbose: debug_printf ("I am who you are"); } void transport_layer_pipes::revert_to_self () { + assert (_is_accepted_endpoint); + RevertToSelf (); - debug_printf("I am who I yam\n"); + // verbose: debug_printf ("I am who I yam"); } +#endif /* !__INSIDE_CYGWIN__ */ diff --git a/winsup/cygserver/transport_sockets.cc b/winsup/cygserver/transport_sockets.cc index a3a98b3e5..6ade14bff 100644 --- a/winsup/cygserver/transport_sockets.cc +++ b/winsup/cygserver/transport_sockets.cc @@ -4,128 +4,384 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ +/* to allow this to link into cygwin and the .dll, a little magic is needed. */ +#ifdef __OUTSIDE_CYGWIN__ +#include "woutsup.h" +#else +#include "winsup.h" +#endif + +#include +#include +#include + +#include #include #include #include -#include -#include -#include -#include -#include "wincap.h" + #include "cygwin/cygserver_transport.h" #include "cygwin/cygserver_transport_sockets.h" /* to allow this to link into cygwin and the .dll, a little magic is needed. */ #ifndef __OUTSIDE_CYGWIN__ -#include "winsup.h" -extern "C" int -cygwin_socket (int af, int type, int protocol); -extern "C" int -cygwin_connect (int fd, - const struct sockaddr *name, - int namelen); -extern "C" int -cygwin_accept (int fd, struct sockaddr *peer, int *len); -extern "C" int -cygwin_listen (int fd, int backlog); -extern "C" int -cygwin_bind (int fd, const struct sockaddr *my_addr, int addrlen); -#else -#define cygwin_accept(A,B,C) ::accept(A,B,C) -#define cygwin_socket(A,B,C) ::socket(A,B,C) -#define cygwin_listen(A,B) ::listen(A,B) -#define cygwin_bind(A,B,C) ::bind(A,B,C) -#define cygwin_connect(A,B,C) ::connect(A,B,C) -#define debug_printf printf -#endif +extern "C" int cygwin_accept (int fd, struct sockaddr *, int *len); +extern "C" int cygwin_bind (int fd, const struct sockaddr *, int len); +extern "C" int cygwin_connect (int fd, const struct sockaddr *, int len); +extern "C" int cygwin_listen (int fd, int backlog); +extern "C" int cygwin_shutdown (int fd, int how); +extern "C" int cygwin_socket (int af, int type, int protocol); -transport_layer_sockets::transport_layer_sockets (int newfd): fd(newfd) +#else /* __OUTSIDE_CYGWIN__ */ + +#define cygwin_accept(A,B,C) ::accept (A,B,C) +#define cygwin_bind(A,B,C) ::bind (A,B,C) +#define cygwin_connect(A,B,C) ::connect (A,B,C) +#define cygwin_listen(A,B) ::listen (A,B) +#define cygwin_shutdown(A,B) ::shutdown (A,B) +#define cygwin_socket(A,B,C) ::socket (A,B,C) + +#endif /* __OUTSIDE_CYGWIN__ */ + +enum + { + MAX_CONNECT_RETRY = 64 + }; + +transport_layer_sockets::transport_layer_sockets (const int fd) + : _fd (fd), + _addr_len (0), + _is_accepted_endpoint (true), + _is_listening_endpoint (false) { - /* This may not be needed in this constructor - it's only used - * when creating a connection via bind or connect - */ - sockdetails.sa_family = AF_UNIX; - strcpy (sockdetails.sa_data, "/tmp/cygdaemo"); - sdlen = strlen(sockdetails.sa_data) + sizeof(sockdetails.sa_family); -}; + assert (_fd != -1); -transport_layer_sockets::transport_layer_sockets (): fd (-1) -{ - sockdetails.sa_family = AF_UNIX; - strcpy (sockdetails.sa_data, "/tmp/cygdaemo"); - sdlen = strlen(sockdetails.sa_data) + sizeof(sockdetails.sa_family); + memset (&_addr, '\0', sizeof (_addr)); } -void +transport_layer_sockets::transport_layer_sockets () + : _fd (-1), + _addr_len (0), + _is_accepted_endpoint (false), + _is_listening_endpoint (false) +{ + memset (&_addr, '\0', sizeof (_addr)); + + _addr.sun_family = AF_UNIX; + strcpy (_addr.sun_path, "/tmp/cygdaemo"); // FIXME: $TMP? + _addr_len = SUN_LEN (&_addr); +} + +transport_layer_sockets::~transport_layer_sockets () +{ + close (); +} + +#ifndef __INSIDE_CYGWIN__ + +int transport_layer_sockets::listen () { - /* we want a thread pool based approach. */ - if ((fd = cygwin_socket (AF_UNIX, SOCK_STREAM,0)) < 0) - printf ("Socket not created error %d\n", errno); - if (cygwin_bind(fd, &sockdetails, sdlen)) - printf ("Bind doesn't like you. Tsk Tsk. Bind said %d\n", errno); - if (cygwin_listen(fd, 5) < 0) - printf ("And the OS just isn't listening, all it says is %d\n", errno); + assert (_fd == -1); + assert (!_is_accepted_endpoint); + assert (!_is_listening_endpoint); + + debug_printf ("listen () [this = %p]", this); + + struct stat sbuf; + + if (stat (_addr.sun_path, &sbuf) == -1) + { + if (errno != ENOENT) + { + system_printf ("cannot access socket file `%s': %s", + _addr.sun_path, strerror (errno)); + return -1; + } + } + else if (S_ISSOCK (sbuf.st_mode)) + { + // The socket already exists: is a duplicate cygserver running? + + const int newfd = cygwin_socket (AF_UNIX, SOCK_STREAM, 0); + + if (newfd == -1) + { + system_printf ("failed to create UNIX domain socket: %s", + strerror (errno)); + return -1; + } + + if (cygwin_connect (newfd, (struct sockaddr *) &_addr, _addr_len) == 0) + { + system_printf ("the daemon is already running"); + (void) cygwin_shutdown (newfd, SHUT_WR); + char buf[BUFSIZ]; + while (::read (newfd, buf, sizeof (buf)) > 0) + {} + (void) ::close (newfd); + return -1; + } + + if (unlink (_addr.sun_path) == -1) + { + system_printf ("failed to remove `%s': %s", + _addr.sun_path, strerror (errno)); + (void) ::close (newfd); + return -1; + } + } + else + { + system_printf ("cannot create socket `%s': File already exists", + _addr.sun_path); + return -1; + } + + _fd = cygwin_socket (AF_UNIX, SOCK_STREAM, 0); + + if (_fd == -1) + { + system_printf ("failed to create UNIX domain socket: %s", + strerror (errno)); + return -1; + } + + if (cygwin_bind (_fd, (struct sockaddr *) &_addr, _addr_len) == -1) + { + const int saved_errno = errno; + close (); + errno = saved_errno; + system_printf ("failed to bind UNIX domain socket `%s': %s", + _addr.sun_path, strerror (errno)); + return -1; + } + + _is_listening_endpoint = true; // i.e. this really means "have bound". + + if (cygwin_listen (_fd, SOMAXCONN) == -1) + { + const int saved_errno = errno; + close (); + errno = saved_errno; + system_printf ("failed to listen on UNIX domain socket `%s': %s", + _addr.sun_path, strerror (errno)); + return -1; + } + + debug_printf ("0 = listen () [this = %p, fd = %d]", this, _fd); + + return 0; } class transport_layer_sockets * -transport_layer_sockets::accept () +transport_layer_sockets::accept (bool *const recoverable) { - /* FIXME: check we have listened */ - int new_fd; + assert (_fd != -1); + assert (!_is_accepted_endpoint); + assert (_is_listening_endpoint); - if ((new_fd = cygwin_accept(fd, &sockdetails, &sdlen)) < 0) + debug_printf ("accept () [this = %p, fd = %d]", this, _fd); + + struct sockaddr_un client_addr; + socklen_t client_addr_len = sizeof (client_addr); + + const int accept_fd = + cygwin_accept (_fd, (struct sockaddr *) &client_addr, &client_addr_len); + + if (accept_fd == -1) { - printf ("Nup, could' accept. %d\n",errno); + system_printf ("failed to accept connection: %s", strerror (errno)); + switch (errno) + { + case ECONNABORTED: + case EINTR: + case EMFILE: + case ENFILE: + case ENOBUFS: + case ENOMEM: + *recoverable = true; + break; + + default: + *recoverable = false; + break; + } return NULL; } - transport_layer_sockets *new_conn = new transport_layer_sockets (new_fd); - - return new_conn; + debug_printf ("%d = accept () [this = %p, fd = %d]", accept_fd, this, _fd); + return safe_new (transport_layer_sockets, accept_fd); } +#endif /* !__INSIDE_CYGWIN__ */ + void -transport_layer_sockets::close() +transport_layer_sockets::close () { - /* FIXME - are we open? */ - ::close (fd); + debug_printf ("close () [this = %p, fd = %d]", this, _fd); + + if (_is_listening_endpoint) + (void) unlink (_addr.sun_path); + + if (_fd != -1) + { + (void) cygwin_shutdown (_fd, SHUT_WR); + if (!_is_listening_endpoint) + { + char buf[BUFSIZ]; + while (::read (_fd, buf, sizeof (buf)) > 0) + {} + } + (void) ::close (_fd); + _fd = -1; + } + + _is_listening_endpoint = false; } ssize_t -transport_layer_sockets::read (char *buf, size_t len) +transport_layer_sockets::read (void *const buf, const size_t buf_len) { - /* FIXME: are we open? */ - return ::read (fd, buf, len); + assert (_fd != -1); + assert (!_is_listening_endpoint); + + assert (buf); + assert (buf_len > 0); + + // verbose: debug_printf ("read (buf = %p, len = %u) [this = %p, fd = %d]", + // buf, buf_len, this, _fd); + + char *read_buf = static_cast (buf); + size_t read_buf_len = buf_len; + ssize_t res = 0; + + while (read_buf_len != 0 + && (res = ::read (_fd, read_buf, read_buf_len)) > 0) + { + read_buf += res; + read_buf_len -= res; + + assert (read_buf_len >= 0); + } + + if (res != -1) + { + if (res == 0) + errno = EIO; // FIXME? + + res = buf_len - read_buf_len; + } + + if (res != static_cast (buf_len)) + debug_printf ("%d = read (buf = %p, len = %u) [this = %p, fd = %d]: %s", + res, buf, buf_len, this, _fd, + (res == -1 ? strerror (errno) : "EOF")); + else + { + // verbose: debug_printf ("%d = read (buf = %p, len = %u) [this = %p, fd = %d]", + // res, buf, buf_len, this, _fd); + } + + return res; } ssize_t -transport_layer_sockets::write (char *buf, size_t len) +transport_layer_sockets::write (void *const buf, const size_t buf_len) { - /* FIXME: are we open? */ - return ::write (fd, buf, len); + assert (_fd != -1); + assert (!_is_listening_endpoint); + + assert (buf); + assert (buf_len > 0); + + // verbose: debug_printf ("write (buf = %p, len = %u) [this = %p, fd = %d]", + // buf, buf_len, this, _fd); + + char *write_buf = static_cast (buf); + size_t write_buf_len = buf_len; + ssize_t res = 0; + + while (write_buf_len != 0 + && (res = ::write (_fd, write_buf, write_buf_len)) > 0) + { + write_buf += res; + write_buf_len -= res; + + assert (write_buf_len >= 0); + } + + if (res != -1) + { + if (res == 0) + errno = EIO; // FIXME? + + res = buf_len - write_buf_len; + } + + if (res != static_cast (buf_len)) + debug_printf ("%d = write (buf = %p, len = %u) [this = %p, fd = %d]: %s", + res, buf, buf_len, this, _fd, + (res == -1 ? strerror (errno) : "EOF")); + else + { + // verbose: debug_printf ("%d = write (buf = %p, len = %u) [this = %p, fd = %d]", + // res, buf, buf_len, this, _fd); + } + + return res; } -bool +int transport_layer_sockets::connect () { - /* are we already connected? */ - if (fd != -1) - return false; - fd = cygwin_socket (AF_UNIX, SOCK_STREAM, 0); - if (cygwin_connect (fd, &sockdetails, sdlen) < 0) + assert (_fd == -1); + assert (!_is_accepted_endpoint); + assert (!_is_listening_endpoint); + + static bool assume_cygserver = false; + + debug_printf ("connect () [this = %p]", this); + + for (int retries = 0; retries != MAX_CONNECT_RETRY; retries++) { - debug_printf("client connect failure %d\n", errno); - ::close (fd); - return false; + _fd = cygwin_socket (AF_UNIX, SOCK_STREAM, 0); + + if (_fd == -1) + { + system_printf ("failed to create UNIX domain socket: %s", + strerror (errno)); + return -1; + } + + if (cygwin_connect (_fd, (struct sockaddr *) &_addr, _addr_len) == 0) + { + assume_cygserver = true; + debug_printf ("0 = connect () [this = %p, fd = %d]", this, _fd); + return 0; + } + + if (!assume_cygserver || errno != ECONNREFUSED) + { + debug_printf ("failed to connect to server: %s", strerror (errno)); + (void) ::close (_fd); + _fd = -1; + return -1; + } + + (void) ::close (_fd); + _fd = -1; + Sleep (0); // Give the server a chance. } - return true; + + debug_printf ("failed to connect to server: %s", strerror (errno)); + return -1; } diff --git a/winsup/cygserver/woutsup.h b/winsup/cygserver/woutsup.h new file mode 100644 index 000000000..c048f1c19 --- /dev/null +++ b/winsup/cygserver/woutsup.h @@ -0,0 +1,110 @@ +/* woutsup.h: for Cygwin code compiled outside the DLL (i.e. cygserver). + + Copyright 2002 Red Hat, Inc. + +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. */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef __INSIDE_CYGWIN__ +#error "woutsup.h is not for code being compiled inside the dll" +#endif + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif + +#if _WIN32_WINNT < 0x0500 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif + +#define WIN32_LEAN_AND_MEAN 1 +#define _WINGDI_H +#define _WINUSER_H +#define _WINNLS_H +#define _WINVER_H +#define _WINNETWK_H +#define _WINSVC_H +#include +#include +#include +#undef _WINGDI_H +#undef _WINUSER_H +#undef _WINNLS_H +#undef _WINVER_H +#undef _WINNETWK_H +#undef _WINSVC_H + +#include "wincap.h" + +/* The one function we use from winuser.h most of the time */ +extern "C" DWORD WINAPI GetLastError (void); + +extern int cygserver_running; + +#if !defined(__STDC_VERSION__) || __STDC_VERSION__ >= 199900L +#define NEW_MACRO_VARARGS +#endif + +/* + * A reproduction of the macros. This allows code that + * runs both inside and outside the Cygwin DLL to use the same macros + * for logging messages. + */ + +extern "C" void __cygserver__printf (const char *, const char *, ...); + +#ifdef NEW_MACRO_VARARGS + +#define system_printf(...) \ + do \ + { \ + __cygserver__printf (__PRETTY_FUNCTION__, __VA_ARGS__); \ + } while (false) + +#define __noop_printf(...) do {;} while (false) + +#else /* !NEW_MACRO_VARARGS */ + +#define system_printf(args...) \ + do \ + { \ + __cygserver__printf (__PRETTY_FUNCTION__, ## args); \ + } while (false) + +#define __noop_printf(args...) do {;} while (false) + +#endif /* !NEW_MACRO_VARARGS */ + +#ifdef DEBUGGING +#define debug_printf system_printf +#define paranoid_printf system_printf +#define select_printf system_printf +#define sigproc_printf system_printf +#define syscall_printf system_printf +#define termios_printf system_printf +#define wm_printf system_printf +#define minimal_printf system_printf +#define malloc_printf system_printf +#define thread_printf system_printf +#else +#define debug_printf __noop_printf +#define paranoid_printf __noop_printf +#define select_printf __noop_printf +#define sigproc_printf __noop_printf +#define syscall_printf __noop_printf +#define termios_printf __noop_printf +#define wm_printf __noop_printf +#define minimal_printf __noop_printf +#define malloc_printf __noop_printf +#define thread_printf __noop_printf +#endif + +#include "safe_memory.h" diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index a8512a489..eaf19dff0 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,1110 @@ +2002-09-22 Conrad Scott + + GNUify non-GNU formatted functions calls throughout. + +2002-09-22 Conrad Scott + + * cygserver_shm.cc (with_strerr): Remove macro. + (server_shmmgr::segment_t::~segment_t): Remove calls to with_strerr. + (server_shmmgr::segment_t::attach): Ditto. + (server_shmmgr::new_segment): Ditto. + * shm.cc (with_strerr): Remove macro. + (client_shmmgr::shmdt): Remove calls to with_strerr. + (client_shmmgr::attach): Ditto. + +2002-09-21 Conrad Scott + + * include/sys/ipc.h: Move to "include/cygwin/ipc.h". + * include/sys/msg.h: Move to "include/cygwin/msg.h". + * include/sys/sem.h: Move to "include/cygwin/sem.h". + * include/sys/shm.h: Move to "include/cygwin/shm.h". + * include/cygwin/ipc.h: New file. + * include/cygwin/msg.h: Ditto. + * include/cygwin/sem.h: Ditto. + * include/cygwin/shm.h: Ditto. + * cygserver_shm.h: Update includes. + * msg.cc: Ditto. + * sem.cc: Ditto. + +2002-09-21 Conrad Scott + + * safe_memory.h (safe_delete): Make a templated function. + * cygserver.cc (~server_request): Update use of safe_delete. + (main): Ditto. + * cygserver_client.cc (client_request::handle_request): Ditto. + (client_request::make_request): Ditto. + * cygserver_process.cc (~process_cleanup): Ditto. + (process::remove): Ditto. + (process::cleanup): Ditto. + (process_cache::process): Ditto. + * cygserver_shm.cc (server_shmmgr::segment_t::detach): Ditto. + (server_shmmgr::delete_segment): Ditto. + * shm.cc (client_shmmgr::shmdt): Ditto. + * threaded_queue.cc (~threaded_queue): Ditto. + (threaded_queue::worker_loop): Ditto. + +2002-08-29 Conrad Scott + + * safe_memory.h: Replace #include with an explicit + definition of the placement new operator. + (safe_delete): Remove unnecessary ## operator. + +2002-07-28 Conrad Scott + + * cygserver_shm.cc (class server_shmmgr): Remove `cleanup_t' + friend declaration. + (cleanup_t::cleanup_t): Use the segment's shmid as the key rather + than the segment pointer itself. + (cleanup_t::segptr): Remove method. + (cleanup_t::shmid): New method. + (cleanup_t::cleanup): Update for new key value. + (server_shmmgr::find (segment_t *)): Remove method. + * include/cygwin/cygserver_process.h (cleanup_routine::key): Make + method const. + +2002-07-27 Conrad Scott + + * include/cygwin/cygserver_process.h + (cleanup_routine::_key): New field. + (cleanup_routine::cleanup_routine): Initialise new field with new + argument. + (cleanup_routine::operator==): New method. + (cleanup_routine::key): New method. + (cleanup_routine::cleanup): Make argument non-const. + (process::is_active): New method. + (process::remove): Ditto. + (process::check_exit_code): Rename method. + * cygserver_process.cc (process::add): Reorganize code. + (process::remove): New method. + (process::check_exit_code): Rename method. + (process::cleanup): Use new `process::is_active' method. + (process_cache::process): Ditto. + (process_cache::sync_wait_array): Ditto. + (process_cache::check_and_remove_process): Ditto. + * cygserver_shm.cc (server_shmmgr): Make `cleanup_t' a friend. + (segment_t::detach): Make argument non-const. Remove cleanup + object from client if appropriate. + (cleanup_t::_segptr): Remove field. + (cleanup_t::cleanup_t): Initialise parent explicitly. Remove + field. + (cleanup_t::segptr): New method. + (cleanup_t::cleanup): Add error checking and reporting. + (server_shmmgr::shmdt): Make argument non-const. + (server_shmmgr::find (segment_t *)): New method. + +2002-07-27 Conrad Scott + + * cygserver.cc (client_request_shutdown::client_request_shutdown): + Comment out verbose tracing statement. + * cygserver_client.cc + (client_request_get_version::client_request_get_version): Ditto. + (client_request_attach_tty::client_request_attach_tty): Ditto. + * cygserver_shm.cc (client_request_shm::client_request_shm): + Ditto. + +2002-07-27 Conrad Scott + + * cygserver_transport_pipes.cc (transport_layer_pipes::listen): + Set `_is_listening_endpoint' appropriately. + +2002-07-27 Conrad Scott + + * include/cygwin/cygserver_transport.h + (transport_layer_base::listen): Change return type. + (transport_layer_base::connect): Ditto. + * include/cygwin/cygserver_transport_pipes.h + (transport_layer_pipes::listen): Change return type. + (transport_layer_pipes::connect): Ditto. + (transport_layer_pipes::_sec_none_nih): Remove unused field. + (transport_layer_pipes::_is_listening_endpoint): New field. + * cygserver_transport_pipes.cc: Synchronize with sockets code. + (transport_layer_pipes::transport_layer_pipes): Initialise new + field. Separate out asserts. + (transport_layer_pipes::listen): Change return type. Add asserts. + (transport_layer_pipes::accept): Add asserts. + (transport_layer_pipes::read): Change conditional to an assert. + Add assert. + (transport_layer_pipes::write): Ditto. + (transport_layer_pipes::connect): Change return type. Change + conditional to an assert. Add asserts. Rationalize error code + slightly. + (transport_layer_pipes::impersonate_client): Add asserts. + * include/cygwin/cygserver_transport_sockets.h + (transport_layer_sockets::listen): Change return type. + (transport_layer_sockets::connect): Ditto. + (transport_layer_sockets::_addr): Change type of field. + (transport_layer_sockets::_addr_len): Ditto. + (transport_layer_sockets::_is_accepted_endpoint): New field. + (transport_layer_sockets::_is_listening_endpoint): Ditto. + * cygserver_transport_sockets.cc + (MAX_CONNECT_RETRY): New constant. + (transport_layer_sockets::transport_layer_sockets): Initialise new + fields. Only initialise the socket address where necessary. + (transport_layer_sockets::listen): Change return type. Rewrite. + (transport_layer_sockets::accept): Add asserts. Add tracing + statements. Use a local variable to hold the accepted address. + (transport_layer_sockets::close): Add tracing statements. Unlink + the UNIX domain socket file as appropriate. Close the socket + cleanly. + (transport_layer_sockets::read): Rewrite method. + (transport_layer_sockets::write): Ditto. + (transport_layer_sockets::connect): Change return type. Rewrite. + * cygserver.cc (server_submission_loop::request_loop): Run the + listening thread at high priority with special handling for + shutdown. + (main): Print the request error code rather than errno in shutdown + request code. Install signal handlers with sigaction(2) to avoid + setting SA_RESTART. Check value of the listen method call, now it + has one. + * cygserver_client.cc (client_request::make_request): Check new + return value on connect method call. + +2002-07-27 Conrad Scott + + * include/cygwin/cygserver_transport_pipes.h + (cygserver_transport_pipes::_sd): Rename field. + (cygserver_transport_pipes::_sec_none_nih): Ditto. + (cygserver_transport_pipes::_sec_all_nih): Ditto. + (cygserver_transport_pipes::_pipe_name): Ditto. + (cygserver_transport_pipes::_hPipe): Ditto. + (cygserver_transport_pipes::_is_accepted_endpoint): Ditto. + * cygserver_transport_pipes.cc + (transport_layer_pipes::transport_layer_pipes): Rename fields. + (transport_layer_pipes::init_security): Ditto. + (transport_layer_pipes::listen): Ditto. + (transport_layer_pipes::accept): Ditto. + (transport_layer_pipes::close): Ditto. + (transport_layer_pipes::read): Ditto. + (transport_layer_pipes::write): Ditto. + (transport_layer_pipes::connect): Ditto. + (transport_layer_pipes::impersonate_client): Ditto. + (transport_layer_pipes::revert_to_self): Ditto. + * include/cygwin/cygserver_transport_sockets.h + (cygserver_transport_sockets::_fd): Rename field. + (cygserver_transport_sockets::_addr): Ditto. + (cygserver_transport_sockets::_addr_len): Ditto. + * cygserver_transport_sockets.cc + (transport_layer_sockets::transport_layer_sockets): Rename fields. + (transport_layer_sockets::listen): Ditto. + (transport_layer_sockets::accept): Ditto. + (transport_layer_sockets::close): Ditto. + (transport_layer_sockets::read): Ditto. + (transport_layer_sockets::write): Ditto. + (transport_layer_sockets::connect): Ditto. + +2002-07-27 Conrad Scott + + * cygserver_shm.cc (with_strerr): Fix use of %p format. + * shm.cc (client_shmmgr::shmat): Ditto. + (client_shmmgr::shmctl): Ditto. + (client_shmmgr::shmdt): Ditto. + (client_shmmgr::attach): Ditto. + +2002-07-14 Christopher Faylor + + * woutsup.h (system_printf): Remove extraneous semicolon from macro + definition. + +2002-07-14 Conrad Scott + + * cygserver_transport_pipes.cc + (transport_layer_pipes::connect): Use ProtectHandle in DLL code. + (transport_layer_pipes::close): Use ForceCloseHandle in DLL code. + +2002-07-13 Nicholas Wourms + + * threaded_queue.h (class queue_submission_loop): Correct friend + declaration for GCC 3.1.1. + * include/cygwin/cygserver_process.h (class process): Ditto. + (class process_cache): Ditto. + +2002-07-12 Conrad Scott + + * cygserver_shm.cc (server_shmmgr::shmdt): Only call + delete_segment if the segment exists [sic]. + +2002-07-12 Conrad Scott + + * safe_memory.h: Include rather than for gcc 3. + +2002-07-11 Conrad Scott + + * safe_memory.h: New file extracted from "woutsup.h". + * woutsup.h: Move the "safe" new/delete macros into the new + "safe_memory.h" header file and include that here. + * cygserver_client.cc: Explicitly include "safe_memory.h" for + client-side code. + (client_request::make_request): Use the "safe" new/delete macros + unconditionally, i.e. use them on the client side as well as on + the server side. + * cygserver_transport.cc: Explicitly include "safe_memory.h" for + client-side code. + (create_server_transport): Use the "safe" new/delete macros + unconditionally, i.e. use them on the client side as well as on + the server side. + * shm.cc: Include "safe_memory.h". + (client_shmmgr::instance): Use the "safe" new/delete macros. + (client_shmmgr::shmdt): Ditto. + (client_shmmgr::new_segment): Ditto. + +2002-07-11 Conrad Scott + + * cygserver_process (process::process): Add the client's cygpid + and winpid to all tracing statements as appropriate. + (process::exit_code): Ditto. + (process_cache::check_and_remove_process): Ditto. + * cygserver_shm.cc (server_shmmgr::shmat): Ditto. + (server_shmmgr::shmdt): Ditto. + (server_shmmgr::shmctl): Add a process object argument and remove + the explicit cygpid argument. Add the client's cygpid and winpid + to all tracing statements as appropriate. + (server_shmmgr::shmget): Ditto. + (client_request_shm::serve): Update for the new signature of the + shmctl and shmget methods. + +2002-07-11 Conrad Scott + + * cygserver.cc (client_request_shutdown::serve): Don't set the + shutdown flag directly, but send a SIGINT, as the signal handler + sets the flag and the signal breaks the pause(2) in the main loop. + (print_usage): Add new options. + (main): Add new --cleanup-threads and --request-threads options to + set the number of threads used by the daemon. Use pause(2) rather + the win32 Sleep in the main loop. + * shm.cc (shmat): Add sigframe. + (shmctl): Ditto. + (shmdt): Ditto. + (shmget): Ditto. + +2002-07-11 Conrad Scott + + * cygserver_shm.cc: Automatically detach processes from any + segments they are attached to at exit. + (class server_shmmgr::attach_t): New class. + (server_shmmgr::segment_t::IS_DELETED): Rename and make private. + (server_shmmgr::segment_t::_sequence): Make private. + (server_shmmgr::segment_t::_flg): Ditto. + (server_shmmgr::segment_t::_hFileMap): Ditto. + (server_shmmgr::segment_t::_attach_head): New private field. + (server_shmmgr::segment_t::segment_t): Initialise new fields. + Make non-inline. + (server_shmmgr::segment_t::~segment_t): New method. + (server_shmmgr::segment_t::is_deleted): Ditto. + (server_shmmgr::segment_t::is_pending_delete): Ditto. + (server_shmmgr::segment_t::mark_deleted): Ditto. + (server_shmmgr::segment_t::attach): Ditto. + (server_shmmgr::segment_t::detach): Ditto. + (server_shmmgr::segment_t::find): Ditto. + (class server_shmmgr::cleanup_t): New class. + (server_shmmgr::_shm_atts): New private field. + (server_shmmgr::shmat): Add a process object argument to replace + the removed process_cache, cygpid and winpid arguments. Remove + the process_cache manipulations. Move body of code to the + segment_t::attach method. Increment _shm_atts when required. + Update tracing statements. + (server_shmmgr::shmdt): Add a process object argument to replace + the removed cygpid argument. Move body of code to the + segment_t::detach method. Decrement _shm_atts when required. + Update tracing statements. + (server_shmmgr::shmget): Use the new segment_t::is_deleted method. + (server_shmmgr::server_shmmgr): Initialise the new _shm_atts + field. + (server_shmmgr::delete_segment): Remove the CloseHandle code, as + this is now done in the segment_t destructor. + (client_request_shm::serve): Look up the client's process object + and pass to the server_shmmgr::shmat and server_shmmgr::shmdt + methods rather than passing the cache, winpid and cygpid. + * cygserver_process.h: Add a cygpid to the process object to make + it more useful and then pass process objects rather than winpids + where possible. + (cleanup_routine::cleanup): Change argument to be a pointer to a + process object. + (class process): Re-order fields for no discernible reason. + (process::_cygpid): New field. + (process::process): Add a cygpid argument. + (process::winpid): New method. + (process::cygpid): Ditto. + (process::add): Make public, as it always should have been. + (process_cache::process): Add a cygpid argument. + * cygserver_process.cc (process::process): Add a cygpid argument + and use it to initialise the `_cygpid' field. Re-order + initialisers to match new field order. + (process::cleanup): Pass `this' rather than just the winpid to + cleanup_routine::cleanup. + (process_cache::process): Add a cygpid argument and pass it to the + process object constructor. + * include/sys/shm.h (shmatt_t): Make unsigned as per SUSv3. + (shm_info::shm_atts): New field. + +2002-07-11 Conrad Scott + + * cygserver_shm.cc (class server_shmmgr::segment_t): Add `_' + prefix to the names of all fields. + +2002-07-10 Conrad Scott + + * msg.cc: New file of stub functions, no functionality. + * sem.cc: Ditto. + * shm.cc (client_shmmgr::shmctl): Add support for an out shm_info + buffer for the SHM_INFO command. + (client_shmmgr::shmget): Use %X to print keys. + * include/sys/ipc.h: Comment all fields and values. + (IPC_PRIVATE): Change to be non-negative. + * include/sys/msg.h: New file with SUSv3 and ipcs(8) interfaces. + * include/sys/sem.h: Ditto. + * include/sys/shm.h: Comment all fields and values. + (struct shm_info): New struct. + * cygserver_shm.h (client_request_shm::shminfo): Rename. + (client_request_shm::shm_info): New method. + (client_request_shm::_parameters.out.hFileMap): Move into union. + (client_request_shm::_parameters.out.shminfo): Rename. + (client_request_shm::_parameters.out.shm_info): New field. + * cygserver_shm.cc (server_shmmgr::_shm_ids): Rename. + (server_shmmgr::_shm_tot): New field. + (server_shmmgr::shmctl): Rename `out_shminfo' argument. Add + `out_shm_info' argument. Fill in the `out_shm_info' argument in + the SHM_INFO command. + (server_shmmgr::shmget): Check `shmflg' against the mode of + existing segments as per Stevens 1990, p. 123. + (server_shmmgr::server_shmmgr): Initialise the new `_shm_tot' + field. + (server_shmmgr::new_segment): Set ENOMEM if CreateFileMapping + fails. Pass `size' to new_segment. + (server_shmmgr::new_segment): Add size argument and use it to + check against and update the new `_shm_tot' field. + (server_shmmgr::delete_segment): Update the new `_shm_tot' field. + * Makefile.in (DLL_OFILES): Add new DLL object files. + +2002-07-09 Conrad Scott + + * cygserver_transport_pipes.cc: The main change is to make the + client try harder to connect to the server if it's previously + connected, and so has good grounds for believing that the server + is running. + (MAX_WAIT_NAMED_PIPE_RETRY): Change to be an enumerator. + (WAIT_NAMED_PIPE_TIMEOUT): Ditto. + (transport_layer_pipes::accept): Use interlocked operators on + `pipe_instance'. + (transport_layer_pipes::close): Rearrange so that FlushFileBuffers + and DisconnectNamedPipe are only called for accepted endpoints. + Use interlocked operators on `pipe_instance'. + (transport_layer_pipes::read): Use set_errno where required. + (transport_layer_pipes::write): Ditto. + (transport_layer_pipes::connect): Add local static variable + `assume_cygserver'. Set it if a connection is made to cygserver, + clear it if a connection is not made even after retrying. If set, + ignore all errors from CreateFile and retry the connection. Catch + the situation where WaitNamedPipe fails to wait [sic] and add a + `Sleep (0)' so that the server gets a chance to run. + +2002-07-03 Conrad Scott + + * dcrt0.cc: Only check for cygserver if and when required. + (dll_crt0_1): Remove call to `cygserver_init ()'. + * fhandler_tty.cc (fhandler_tty_slave::open): Change the cygserver + logic to allow for the fact that `cygserver_init ()' may not yet + have been called. + (fhandler_tty_slave::cygserver_attach_tty): Tweak the cygserver + request logic to conform to the practice elsewhere in the code. + * tty.cc (tty::common_init): Add an explicit call to + `cygserver_init ()' if it hasn't already been called. + * include/cygwin/cygserver.h (CYGSERVER_UNAVAIL): Rename from + `CYGSERVER_DEAD'. + (client_request_get_version::check_version): Make available in + cygserver as well the DLL. + (check_cygserver_available): Ditto. Remove `check_version_too' + argument. + (cygserver_init): Ditto. And likewise. + * cygserver_client.cc (client_request_get_version::check_version): + Make available in cygserver as well the DLL. + (client_request::make_request): This may now be called without + `cygserver_init ()' having been called first. Detect this and + call it as required. Add tracing. + (check_cygserver_available): Make available in cygserver as well + the DLL. Remove `check_version_too' argument and always check the + version information. And since this is called from within + `cygserver_init ()', force `cygserver_running' before calling + `client_request::make_request ()'. + (cygserver_init): Make available in cygserver as well the DLL. + Remove `check_version_too' argument. + +2002-07-03 Conrad Scott + + * cygserver_shm.cc: Implement the ipcs(8) interfaces, IPC_INFO, + SHM_STAT and SHM_INFO. + (server_shmmgr::segment_t::sequence): New static field. + (server_shmmgr::segment_t::key): Remove field, use the new + ds.shm_perm.key field instead. + (server_shmmgr::segment_t::shmid): Remove field. + (server_shmmgr::segment_t::intid): New field. + (server_shmmgr::segment_t::segment_t): Use the `key' argument to + initialise `ds.shm_perm.key'. Change from using `shmid' to + `intid'. + (server_shmmgr::_shmseg_cnt): Renamed from `_shmid_cnt'. + (server_shmmgr::_intid_max): Renamed from `_shmid_max. + (server_shmmgr::shmat): Move the out arguments to the start of the + argument list. Rename the `pid' argument as `cygpid'. Add + tracing. Pass an intid to `find ()', not a shmid. + (server_shmmgr::shmctl): Add separate out arguments. Rename the + `pid' argument as `cygpid'. Add support for the ipcs(8) + interfaces. Add tracing. Pass an intid to `find ()', not a + shmid. + (server_shmmgr::shmdt): Rename the `pid' argument as `cygpid'. + Add tracing. Pass an intid to `find ()', not a shmid. + (server_shmmgr::shmget): Add a separate out arguments. Rename the + `pid' argument as `cygpid'. Add tracing. + (server_shmmgr::server_shmmgr): Update for new field names. + (server_shmmgr::find_by_key): Update for the new `ds.shm_perm.key' + field. + (server_shmmgr::find): Update to use the new `segment_t::intid' + field. + (server_shmmgr::new_segment): Rename the `pid' argument as + `cygpid'. Check that the requested size is within bounds. Handle + new error result from `new_segment (key, HANDLE)'. + (server_shmmgr::new_segment): Work with intids, not shmids. Check + that the new intid is within bounds. Update for new field names. + (server_shmmgr::delete_segment): Pass an intid to `find ()', not a + shmid. Update for new field names. + (client_request_shm::serve): Check that the incoming message + length is the size of the `_parameters.in' struct, not of the + whole in/out parameter union. Likewise, set the outgoing message + length to the size of the `_parameters.out' struct. Update for + the new server_shmmgr interfaces. + * include/sys/ipc.h (ipc_perm::key): New field. + * include/sys/shm.h (SHM_INFO): New constant. + * cygserver_ipc.h (IPCMNI): New constant. + (ipc_int2ext): Add `sequence' argument and munge this into the + external ipc id. + (ipc_ext2int_subsys): Unmunge the sequence number from the + external ipc id. + (ipc_ext2int): Ditto. + (ipc_inc_id): Remove. + (ipc_dec_id): Remove. + * cygserver_shm.h (SHMMAX): New constant. + (SHMMIN): Ditto. + (SHMMNI): Ditto. + (SHMSEG): Ditto. + (SHMALL): Ditto. + (client_request_shm::_parameters): Re-arrange as a union of two + separate structs, one for in arguments, the other for out. + (client_request_shm::shmid): Update for the new parameter layout. + (client_request_shm::ds): Ditto. + (client_request_shm::info): New method. + * shm.cc (client_shmmgr::_shmat_cnt): New static field. + (client_shmmgr::shmat): Add locking. Add tracing. + (client_shmmgr::shmctl): Update for ipcs(8) commands. Add + tracing. Add more argument checking. + (client_shmmgr::shmdt): Add locking. Add tracing. Update the new + `_shmat_cnt' field. + (client_shmmgr::shmget): Add tracing. + (client_shmmgr::fixup_shms_after_fork): Add tracing. Add + consistency checking. + (client_shmmgr::attach): Add more tracing. + (client_shmmgr::new_segment): Update the new `_shmat_cnt' field. + (client_request_shm::client_request_shm): Update for the new + parameter layout. Set the outgoing message length to the size of + the `_parameters.in' struct, not of the whole in/out parameter + union. + +2002-07-02 Conrad Scott + + * shm.cc: Remove the use of a static client_shmmgr object. + (client_shmmgr::_instance): New static variable. + (client_shmmgr::instance): Allocate a new shmmgr on the heap, + rather than using a local static object. + +2002-07-01 Conrad Scott + + * cygserver_transport.cc (create_server_transport): Fix + cut-and-paste error. + +2002-06-30 Conrad Scott + + * cygserver_client.cc (client_request::handle_request): Don't + bother with the client request activity marker when compiled with + debugging output. + +2002-06-30 Conrad Scott + + * cygserver_transport_pipes.cc + (MAX_WAIT_NAMED_PIPE_RETRY): New constant. + (WAIT_NAMED_PIPE_TIMEOUT): Ditto. + (transport_layer_pipes::close): The `pipe' field is now either + NULL or a valid handle, and it should never have the value + `INVALID_HANDLE_VALUE'. + (transport_layer_pipes::read): Ditto. + (transport_layer_pipes::write): Ditto. + (transport_layer_pipes::connect): Ditto. + (transport_layer_pipes::impersonate_client): Ditto. + (transport_layer_pipes::connect): Ditto. New, but still bogus, + retry logic. + +2002-06-30 Conrad Scott + + * cygserver_shm.cc (server_shmmgr::server_shmmgr): All fields have + to be initialized now that the singleton is no longer static. + +2002-06-30 Conrad Scott + + * cygserver_shm.cc (server_shmmgr::_instance): New static field. + (server_shmmgr::_instance_once): Ditto. + (server_shmmgr::initialise_instance): New static method. + (server_shmmgr::instance): Use a pthread_once_t rather than + relying on a local static variable. + +2002-06-30 Conrad Scott + + * woutsup.h: Remove all uses of the C++ new and delete operators + throughout cygserver until they are fully thread-safe. + (safe_new0): New macro to replace the C++ new operator. + (safe_new): Ditto. + (safe_delete): New macro to replace the C++ delete operator. + * cygserver_client.cc (client_request::handle_request): Replace + all uses of the C++ new and delete operators with the new macros + from "woutsup.h". + (client_request::make_request): Ditto. + * cygserver_process.cc (~process_cleanup): Ditto. + (process::cleanup): Ditto. + (process_cache::process): Ditto. + (process_cache::check_and_remove_process): Ditto. + * cygserver_shm.cc (server_shmmgr::new_segment): Ditto. + (server_shmmgr::delete_segment): Ditto. + * cygserver_transport.cc (create_server_transport): Ditto. + * cygserver_transport_pipes.cc + (transport_layer_pipes::accept): Ditto. + * cygserver_transport_sockets.cc + (transport_layer_sockets::accept): Ditto. + * threaded_queue.cc (~threaded_queue): Ditto. + (threaded_queue::worker_loop): Ditto. + (threaded_queue::stop): Replace sleep(3) with win32 Sleep. + * cygserver.cc (~server_request): Replace all uses of the C++ new + and delete operators with the new macros from "woutsup.h". + (server_submission_loop::request_loop): Ditto. + (main): Ditto. Replace sleep(3) with win32 Sleep. Replace + iostreams with FILEs. + (print_usage): Replace iostreams with FILEs. + (print_version): Ditto. + +2002-06-30 Conrad Scott + + * cygserver_transport_sockets.cc + (transport_layer_sockets::accept): Rename local variable + `accept_fd' to avoid shadowing the `fd' field. + +2002-06-29 Conrad Scott + + * cygwin_ipc.h: Moved (back) to "include/sys/ipc.h". + * cygwin_shm.h: Moved (back) to "include/sys/shm.h". + * include/sys/ipc.h: New file. + * include/sys/shm.h: New file. + * cygserver_shm.h: Update for new header file locations. + * ipc.cc: Ditto. + +2002-06-28 Conrad Scott + + * cygserver_client.cc (client_request::make_request): Comment out + a verbose tracing statement. + * cygserver_process.cc (process_cache::sync_wait_array): Fix + broken assert. + * include/cygwin/cygserver.h (class client_request): Remove excess + use of `class' qualifier in declarations. + (class client_request_get_version): Ditto. + (class client_request_shutdown): Ditto. + (class client_request_attach_tty): Ditto. + +2002-06-28 Conrad Scott + + * cygserver_ipc.h: New file. + * cygserver_shm.h: Re-written from scratch. + * cygserver_shm.cc: Ditto. + * shm.cc: Ditto. + +2002-06-28 Conrad Scott + + * threaded_queue.h (class queue_request): Re-write. + (threaded_queue_thread_function): Remove. + (class queue_process_param): Remove. + (class threaded_queue): Re-write. + (class queue_submission_loop): New version of the old + `queue_process_param' class. + (TInterlockedExchangePointer): New templated function. + (TInterlockedCompareExchangePointer): Ditto. + * threaded_queue.cc (worker_function): Remove. + (class threaded_queue): Re-write. + (class queue_process_param): Remove. + (class queue_submission_loop): New version of the old + `queue_process_param' class. + * include/cygwin/cygserver_process.h (process_cleanup): Re-write. + (class process_process_param): Remove. + (class cleanup_routine): Re-write. + (class process): Re-write. + (class process_cache): Re-write. + * cygserver_process.cc (process_cleanup): Re-write. + (class process_process_param): Remove. + (class cleanup_routine): Re-write. + (class process): Re-write. + (class process_cache): Re-write. + * cygserver.cc (request_count): Remove unused variable. + (class server_request): Move methods inline. + (class server_process_param): Remove. + (class server_request_queue): Remove. + (request_queue): Move into `main ()' and change type to + `threaded_queue'. + (request_loop): Remove. + (class server_submission_loop): New version of the old + `server_process_param' class. + (shutdown_server): New variable. + (client_request_shutdown::serve): Set `shutdown_server' to trigger + shutdown. + (handle_signal): Ditto. + (main): Install signal handler for SIGINT rather than SIGQUIT. + Use new interfaces for the `request_queue' and the `cache'. + Create a `server_submission_loop' and add to the `request_queue'. + Add check for the `shutdown_server' variable to the main loop. + * cygserver_shm.cc (client_request_shm::serve): Release the + process object after use. + +2002-06-27 Conrad Scott + + * cygserver_client.cc (client_request::handle_request): Correct + tracing statement. + * cygserver_transport_pipes.cc: Remove local definition of + FILE_FLAG_FIRST_PIPE_INSTANCE constant. + * cygwin_ipc.h: Update copyright notice. + * cygwin_shm.h: Ditto. + * woutsup.h: Add definition of _WIN32_WINNT. + +2002-06-24 Conrad Scott + + * cygserver_client (client_request::make_request): Replace my + inappropriate use of set_errno () with error_code () throughout. + +2002-06-24 Conrad Scott + + * include/cygwin/cygserver.h: Add forward declarations of class + transport_layer_base and class process_cache to reduce + dependencies between header files. + * include/cygwin/cygserver_process.h: Add include of + "threaded_queue.h". + * cygserver.cc: Remove unnecessary cygserver header files. + * cygserver_client.cc: Ditto. + * cygserver_process.cc: Ditto. + * cygserver_shm.cc: Ditto. + * cygserver_shm.h: Ditto. + * cygserver_transport_pipes.cc: Ditto. + * dcrt0.cc: Ditto. + * fhandler_tty.cc: Ditto. + * tty.cc: Ditto. + +2002-06-24 Conrad Scott + + * cygserver_shm.h: Replace with "cygwin_shm.h" after + merge from HEAD. + * cygwin_ipc.h: Update with changes to include/sys/ipc.h lost in + merge from HEAD. + * cygwin_shm.h: Ditto. + +2002-06-21 Conrad Scott + + * cygserver.cc: The tests for a duplicate server instance are now + the responsibility of the transport layer. + (request_loop): Use new `recoverable' flag in call to + `cygserver_transport::accept ()' and shutdown on an unrecoverable + error. + (main): Never call `cygserver_init ()'. Fake `cygserver_running' + just for sending a shutdown request. + * cygserver_client.cc (client_request::send): Comment out + message-size tracing statements as verbose. + (client_request::handle): Ditto. + (client_request_get_version::check_version): #ifdef as DLL-only. + (check_cygserver_available): Ditto. + (cygserver_init): Ditto. + * include/cygwin/cygserver.h + (client_request_get_version::check_version): #ifdef as DLL-only. + (check_cygserver_available): Ditto. + (cygserver_init): Ditto. + * include/cygwin/cygserver_transport.h + (transport_layer_base::impersonate_client): #ifdef as + cygserver-only. + (transport_layer_base::revert_to_self): Ditto. + (transport_layer_base::listen): Ditto. + (transport_layer_base::accept): Ditto. Add a `recoverable' out + flag for error handling. + * include/cygwin/cygserver_transport_sockets.h: Ditto. + * include/cygwin/cygserver_transport_pipes.h: Ditto. + (transport_layer_pipes): Change type of the `pipe_name' field. + Remove the `inited' field, as unnecessary. Add new + `is_accepted_endpoint' field. + * include/cygwin/cygserver_transport.cc + (transport_layer_base::impersonate_client): #ifdef as + cygserver-only. + (transport_layer_base::revert_to_self): Ditto. + * include/cygwin/cygserver_transport_sockets.cc + (transport_layer_sockets::listen): #ifdef as cygserver-only. + (transport_layer_sockets::accept): #ifdef as cygserver-only. + Analyse any errno from `accept ()' and set `recoverable' as + appropriate. + * cygserver_transport_pipes.cc: Add local #define of + `FILE_FLAG_FIRST_PIPE_INSTANCE'. + (pipe_instance_lock_once): New variable. + (pipe_instance_lock): Ditto. + (pipe_instance): Ditto. + (initialise_pipe_instance_lock): New function. + (transport_layer_pipes::transport_layer_pipes): Change + initialization of `pipe_name'. Initialize `is_accepted_endpoint' + as appropriate. Remove use of `inited'. + (transport_layer_pipes::impersonate_client): #ifdef as + cygserver-only. + (transport_layer_pipes::revert_to_self): Ditto. + (transport_layer_pipes::listen): Ditto. + (transport_layer_pipes::accept): Ditto. Keep track of how often + many named pipes have been created, in the `pipe_instance' + variable, and pass the `FILE_FLAG_FIRST_PIPE_INSTANCE' flag on the + open of the first instance. Analyse the error code from + `CreateNamedPipe ()' and set the `recoverable' flag as + appropriate. + (transport_layer_pipes::close): Update the `pipe_instance' count. + +2002-06-18 Conrad Scott + + * woutsup.h (cygserver_running): Add declaration. + (api_fatal): Eliminate. + * include/cygwin/cygserver.h + (client_request_get_version::check_version): Change return type to + bool. + (check_cygserver_available): New function. + (cygserver_init): Add check_version_too argument. + * cygserver_client.cc (allow_daemon): Make a bool. + (client_request_get_version::make_request): See errno on error. + Remove special case for CYGSERVER_REQUEST_GET_VERSION; this is now + handled in cygserver_init(). + (client_request_get_version::check_version): Use syscall_printf() + instead of api_fatal(). Return true if cygserver version is + compatible. + (check_cygserver_available): New function; code moved here from + cygserver_init(). + (cygserver_init): Move some code into check_cygserver_available(). + * cygserver.cc (__set_errno): Copy from debug.cc so that + set_errno() can be used when __OUTSIDE_CYGWIN__. + (main): Call cygserver_init() to set up cygserver_running and add + checks against this to (try and) prevent multiple copies of + cygserver running simultaneously. Remember to delete all + transport connections so that (one day) the transport classes can + tidy up on cygserver shutdown. + +2002-06-17 Conrad Scott + + * cygserver.cc (main): Adjust tracing output for a cleaner display + when compiled without --enable-debugging. + * threaded_queue.cc (threaded_queue::cleanup): Ditto. + (queue_process_param::stop): Ditto. + * include/cygwin/cygserver.h + (client_request::make_request): Make non-virtual. + (client_request::send): Make virtual and protected, not private. + (client_request_attach_tty::send): New virtual method. + * cygserver_client.cc: Use the `msglen()' accessor rather than + `_header.msglen' throughout. + (client_request_attach_tty::send): New method. + (client_request::make_request): Remove the explicit close of + `transport' as it is closed on deletion. + +2002-06-17 Conrad Scott + + * include/cygwin/cygserver.h: Change the client_request classes to + give greater encapsulation and to allow variable length requests + and replies. + (enum cygserver_request_code): Now client_request::request_code_t. + (class request_header): Now client_request::header_t. Make a + union of the request_code and the error_code. The `cb' field, + which was the buffer length, is now the `size_t msglen' field. + (struct request_get_version): Now + client_request_get_version::request_get_version. + (struct request_shutdown): Remove unused type. + (struct request_attach_tty): Now + client_request_attach_tty::request_attach_tty. + (client_request::_buf): Make field const. + (client_request::_buflen): New const private field. + (client_request::request_code): New accessor. + (client_request::error_code): Ditto. + (client_request::msglen): Ditto. + (client_request::handle_request): New static method. + (client_request::make_request): New virtual method. + (client_request::handle): New method. + (client_request::send): Make private. + (client_request_get_version::check_version): New method. + (client_request_get_version::serve): Make private. + (client_request_get_version::version): Ditto. + (client_request_shutdown::serve): Ditto. + (client_request_attach_tty::req): Ditto. + (client_request_attach_tty::serve): Ditto. + (client_request_attach_tty::from_master): Make method const. + (client_request_attach_tty::from_master): Ditto. + * cygserver_client.cc + (client_request_get_version::client_request_get_version): Track + changes to the client_request classes. + (client_request_attach_tty::client_request_attach_tty): Ditto. + (client_request_get_version::check_version): New method to + encapsulate code from cygserver_init(). + (client_request_shutdown::client_request_shutdown): Move into + "cygserver.cc". + (client_request::send): Track changes to the client_request + classes. Add more error checking. + (client_request::handle_request): New static method containing the + first half of the old server_request::process() code. + (client_request::make_request): New method to replace the old + cygserver_request() function. + (client_request::handle): New method containing the second half of + the old server_request::process() code. + (cygserver_init): Track changes to the client_request classes. In + particular, some code moved into the + client_request_get_version::check_version() method. + * cygserver.cc (client_request_attach_tty::serve): Track changes + to the client_request classes. In particular, only return a reply + body if some handles are successfully duplicated for the client. + And remove goto's. + (client_request_get_version::serve): Track changes to the + client_request classes. + (client_request_shutdown::serve): Ditto. + (class client_request_invalid): Dead, and so young too. + (server_request::request_buffer): Remove unnecessary field. + (client_request_shutdown::client_request_shutdown): Moved here + from "cygserver_client.cc". + (server_request::process): Implementation moved into the new + client_request::handle_request() and client_request::handle() + methods. + * cygserver_shm.h (class client_request_shm): Put client- and + server-specific interfaces inside #ifdef/#ifndef __INSIDE_CYGWIN__ + guards. + (client_request_shm::serve): Make private. + * cygserver_shm.cc + (client_request_shm::client_request_shm): Track changes to the + client_request classes. + (client_request_shm::serve): Ditto + * shm.cc (client_request_shm::client_request_shm): Ditto. Use + alloc_sd() rather than set_security_attribute() to get access to + the SECURITY_DESCRIPTOR length, so that we can use it to set the + request body length. + (shmat): Track changes to the client_request classes. In + particular, allocate client_request objects on the stack rather + than on the heap, and use the client_request::make_request() + method rather than the old cygserver_request() function. + (shmdt): Ditto. + (shmctl): Ditto. + (shmget): Ditto. + * fhandler_tty.cc (fhandler_tty_slave::cygserver_attach_tty): Ditto. + +2002-06-17 Conrad Scott + + * include/cygwin/cygserver_transport.h + (cygserver_transport::read): Change buffer type to void *. + (cygserver_transport::write): Ditto. + * include/cygwin/cygserver_transport_sockets.h + (cygserver_transport_sockets::read): Ditto. + (cygserver_transport_sockets::write): Ditto. + * include/cygwin/cygserver_transport_pipes.h + (cygserver_transport_pipes::read): Ditto. + (cygserver_transport_pipes::write): Ditto. + * cygserver_transport_sockets.cc + (cygserver_transport_sockets::read): Ditto. + (cygserver_transport_sockets::write): Ditto. + * cygserver_transport_pipes.cc + (cygserver_transport_pipes::read): Ditto. Set errno on error, to + match behaviour of cygserver_transport_sockets class. + (cygserver_transport_pipes::write): Ditto. + +2002-06-16 Conrad Scott + + * cygserver.cc (version): New static variable. + (server_request_queue::add_connection): Remove my gratuitous use + of studly caps. + (setup_privileges): Declare static. + (handle_signal): Ditto. + (longopts): Make a local variable of main(). + (opts): Ditto. + (print_usage): New function. + (print_version): Ditto (tip of the hat to Joshua Daniel Franklin + for inspiration here). + (main): More argument checking. Add --help and --version options. + +2002-06-16 Conrad Scott + + * include/cygwin/cygserver.h (client_request::serve): Make pure + virtual. + * cygserver.cc (client_request::serve): Remove definition of pure + virtual method. + (class client_request_invalid): New class. + (server_request::process): Use new client_request_invalid + class. And remove goto's. + +2002-06-16 Conrad Scott + + * cygserver.cc (class server_request): Add virtual destructor. + (server_request_queue::addConnection): New method to replace bad + virtual add() method. + (request_loop): Replace call to queue->add() with call to + queue->addConnection(). + (server_request::server_request): Use field initialization. + (server_request::~server_request): New virtual destructor. + (server_request::process): Remove close and delete of + transport_layer_base object. It is deleted by the server_request's + own destructor and closed by its own destructor. + * include/cygwin/cygserver.h + (client_request::operator request_header): Remove unused method. + * cygserver_client.cc: Ditto. + * include/cygwin/cygserver_process.h + (class cleanup_routine): Add virtual destructor. + (cleanup_routine::cleanup): Make pure virtual. + (class process_cache): Make destructor non-virtual. + (process_cache::add): Ditto. + * cygserver_process.cc + (cleanup_routine::~cleanup_routine): New virtual destructor. + * include/cygwin/cygserver_transport.h + (class transport_layer_base): Add virtual destructor. + * cygserver_transport.cc + (transport_layer_base::~transport_layer_base): New virtual + destructor. + * include/cygwin/cygserver_transport_pipes.h + (class transport_layer_pipes): Add virtual destructor. + * cygserver_transport_pipes.cc + (transport_layer_pipes::~transport_layer_pipes): New virtual + destructor. + (transport_layer_pipes::close): Null out handle after closing. + * include/cygwin/cygserver_transport_sockets.h + (class transport_layer_sockets): Add virtual destructor. + * cygserver_transport_sockets.cc + (transport_layer_sockets::~transport_layer_sockets): New virtual + destructor. + (transport_layer_sockets::close): Null out fd after closing. + * threaded_queue.h (class queue_request): Add virtual destructor. + (queue_request::process): Make pure virtual. + * threaded_queue.cc (~queue_request): New virtual destructor. + (queue_request::process): Remove definition of pure virtual + method. + +2002-06-16 Conrad Scott + + * include/cygwin/cygserver.h (client_request::send): Make + non-virtual. + (class client_request_attach_tty): Put client- and server-specific + interfaces inside #ifdef/#ifndef __INSIDE_CYGWIN__ guards. + * cygserver_client.cc: Ditto. + (cygserver_init): Fix error handling. + +2002-06-16 Conrad Scott + + * cygserver.cc: Throughout the code, check and correct level of + the XXX_printf() functions used. Comment out several of the + debug_printf() calls with "// verbose:". Reformat and correct + typos of some of the XXX_printf() formats. + * cygserver_process.cc: Ditto. + * cygserver_shm.cc: Ditto. + * cygserver_transport_pipes.cc: Ditto. + * cygserver_transport_sockets.cc: Ditto. + * shm.cc (hi_ulong): New function to allow printing of a 64-bit + key with current small_printf implementation. + (lo_ulong): Ditto. + (client_request_shm::client_request_shm): Use hi_ulong() and + lo_ulong() in call to debug_printf(). + +2002-06-16 Conrad Scott + + * cygserver_shm.cc: Remove #define __INSIDE_CYGWIN__ from around + as it no longer contains any internal code. + +2002-06-16 Conrad Scott + + * include/sys/ipc.h (IPC_PRIVATE): Add cast to key_t. + (IPC_INFO): New flag for ipcs(8). + (IPC_RMID IPC_SET IPC_STAT): Renumber. + * include/sys/shm.h (SHM_RDONLY SHM_RND): Renumber with distinct + values [sic]. + (class _shmattach): Internal type moved to "cygserver_shm.h". + (class shmnode): Ditto. + (class shmid_ds): Ditto. + (struct shmid_ds): Add spare fields. + (struct shminfo): New type for IPC_INFO interface. + * cygserver_shm.h: Remove obsolete #if 0 ... #endif block. + (class shm_cleanup): Remove unused class. + (struct _shmattach): Internal type moved from . + (struct shmnode): Ditto. + (struct int_shmid_ds): Ditto. Renamed to avoid name clash with + public interface struct shmid_ds. Use the shmid_bs structure as a + field. + * cygserver_shm.cc: Remove obsolete #if 0 ... #endif block. + (client_request_shm::serve): Update for redefinition of + int_shmid_ds structure. + * shm.cc (build_inprocess_shmds): Ditto. + (fixup_shms_after_fork): Ditto. + (shmctl): Ditto. + (shmget): Ditto. Remove obsolete #if 0 ... #endif code. + +2002-06-16 Conrad Scott + + * include/cygwin/cygserver_transport.h + (transport_layer_base::transport_layer_base): Remove since it is + now redundant. + (transport_layer_base::listen): Make a pure virtual method. + (transport_layer_base::accept): Ditto. + (transport_layer_base::close): Ditto. + (transport_layer_base::read): Ditto. + (transport_layer_base::write): Ditto. + (transport_layer_base::connect): Ditto. + * cygserver_transport.cc + (transport_layer_base::transport_layer_base): Remove since it is + now redundant. + (transport_layer_base::listen): Remove since it is now a pure + virtual method. + (transport_layer_base::accept): Ditto. + (transport_layer_base::close): Ditto. + (transport_layer_base::read): Ditto. + (transport_layer_base::write): Ditto. + (transport_layer_base::connect): Ditto. + +2002-06-16 Conrad Scott + + * cygserver.cc (check_and_dup_handle): Only use security code if + running on NT, i.e. if wincap.has_security(). + (client_request_attach_tty::serve): Add check for has_security(). + * cygserver_process.cc (process_cache::process): Use DWORD winpid + throughout to avoid win32 vs. cygwin pid confusion. + (process::process): Ditto. + * cygserver_shm.cc (client_request_shm::serve): Only use security + code if running on NT, i.e. if wincap.has_security(). + * cygserver_shm.h (client_request_shm::parameters.in): Replace the + ambiguous pid field with cygpid and winpid fields. + (client_request_shm::client_request_shm): Reduce to only two + client-side constructors: one for SHM_CREATE, another for all the + other requests. + * shm.cc (client_request_shm::client_request_shm): + Ditto. Initialize cygpid and winpid fields here. On NT initialize + sd_buf here using set_security_attribute() to make use of the euid + and egid. + (shmat): Use new client_request_shm constructor. + (shmdt): Ditto. + (shmctl): Ditto. + (shmget): Ditto. Remove security code, now performed in the + relevant client_request_shm constructor. + * include/cygwin/cygserver_process.h: (class cleanup_routine): + Change winpid type to DWORD. + (class process): Ditto. + +2002-06-15 Conrad Scott + + * woutsup.h: New file. + * cygserver.cc: Use "woutsup.h" and new XXX_printf macros. + (getfunc): New function, copied verbatim from "strace.cc". + (__cygserver__printf): New function. + * cygserver_client.cc: Use "woutsup.h" and new XXX_printf macros. + * cygserver_process.cc: Ditto. + * cygserver_shm.cc: Ditto. + * cygserver_transport.cc: Ditto. + * cygserver_transport_pipes.cc: Ditto. + * cygserver_transport_sockets.cc: Ditto. + * threaded_queue.cc: Ditto. + * shm.cc: Remove trailing \n from XXX_printf format strings. + * Makefile.in: Remove special __OUTSIDE_CYGWIN__ case for + cygserver_shm.cc. + 2002-09-21 Christopher Faylor * init.cc (dll_entry): Temporarily disable thread detach code. diff --git a/winsup/cygwin/Makefile.in b/winsup/cygwin/Makefile.in index eaaccd8a1..7bdb6aad7 100644 --- a/winsup/cygwin/Makefile.in +++ b/winsup/cygwin/Makefile.in @@ -118,24 +118,28 @@ MALLOC_OFILES=@MALLOC_OFILES@ DLL_IMPORTS:=$(w32api_lib)/libuuid.a $(w32api_lib)/libshell32.a $(w32api_lib)/libkernel32.a # Please maintain this list in sorted order, with maximum files per 80 col line -DLL_OFILES:=assert.o autoload.o cygheap.o cygserver_client.o \ - cygserver_transport.o cygserver_transport_pipes.o \ - cygserver_transport_sockets.o cygthread.o dcrt0.o debug.o delqueue.o \ - dir.o dlfcn.o dll_init.o dtable.o environ.o errno.o exceptions.o \ - exec.o external.o fcntl.o fhandler.o fhandler_clipboard.o \ - fhandler_console.o fhandler_disk_file.o fhandler_dsp.o \ - fhandler_floppy.o fhandler_mem.o fhandler_proc.o fhandler_process.o \ - fhandler_random.o fhandler_raw.o fhandler_registry.o fhandler_serial.o \ - fhandler_socket.o fhandler_tape.o fhandler_termios.o fhandler_tty.o \ - fhandler_virtual.o fhandler_windows.o fhandler_zero.o fnmatch.o fork.o \ - glob.o grp.o heap.o init.o ioctl.o ipc.o localtime.o malloc.o \ - malloc_wrapper.o miscfuncs.o mmap.o net.o ntea.o passwd.o path.o \ - pinfo.o pipe.o poll.o pthread.o regcomp.o regerror.o regexec.o \ - regfree.o registry.o resource.o scandir.o sched.o sec_acl.o \ - sec_helper.o security.o select.o shared.o shm.o signal.o \ - sigproc.o smallprint.o spawn.o strace.o strsep.o sync.o syscalls.o \ - sysconf.o syslog.o termios.o thread.o times.o tty.o uinfo.o uname.o \ - v8_regexp.o v8_regerror.o v8_regsub.o wait.o wincap.o window.o \ +DLL_OFILES:= \ + assert.o autoload.o cygheap.o cygserver_client.o \ + cygserver_transport.o cygserver_transport_pipes.o \ + cygserver_transport_sockets.o cygthread.o dcrt0.o debug.o \ + delqueue.o dir.o dlfcn.o dll_init.o dtable.o environ.o errno.o \ + exceptions.o exec.o external.o fcntl.o fhandler.o \ + fhandler_clipboard.o fhandler_console.o fhandler_disk_file.o \ + fhandler_dsp.o fhandler_floppy.o fhandler_mem.o \ + fhandler_proc.o fhandler_process.o fhandler_random.o \ + fhandler_raw.o fhandler_registry.o fhandler_serial.o \ + fhandler_socket.o fhandler_tape.o fhandler_termios.o \ + fhandler_tty.o fhandler_virtual.o fhandler_windows.o \ + fhandler_zero.o fnmatch.o fork.o glob.o grp.o heap.o init.o \ + ioctl.o ipc.o localtime.o malloc.o malloc_wrapper.o \ + miscfuncs.o mmap.o msg.o net.o ntea.o passwd.o path.o pinfo.o \ + pipe.o poll.o pthread.o regcomp.o regerror.o regexec.o \ + regfree.o registry.o resource.o scandir.o sched.o sec_acl.o \ + sec_helper.o security.o select.o sem.o shared.o shm.o signal.o \ + sigproc.o smallprint.o spawn.o strace.o strsep.o sync.o \ + syscalls.o sysconf.o syslog.o termios.o thread.o times.o tty.o \ + uinfo.o uname.o v8_regexp.o v8_regerror.o v8_regsub.o wait.o \ + wincap.o window.o \ $(EXTRA_DLL_OFILES) $(EXTRA_OFILES) $(MALLOC_OFILES) $(MT_SAFE_OBJECTS) GMON_OFILES:=gmon.o mcount.o profil.o @@ -334,9 +338,6 @@ cygserver_transport_sockets_outside.o: cygserver_transport_sockets.cc cygserver_client_outside.o: cygserver_client.cc $(COMPILE_CXX) -D__OUTSIDE_CYGWIN__ -o $@ $< -cygserver_shm.o: cygserver_shm.cc - $(COMPILE_CXX) -D__OUTSIDE_CYGWIN__ -o $@ $< - cygserver.exe: cygserver.o cygserver_shm.o cygserver_transport_outside.o cygserver_transport_pipes_outside.o cygserver_transport_sockets_outside.o cygserver_client_outside.o cygserver_process.o threaded_queue.o wincap.o version.o smallprint.o $(CXX) -o $@ $^ -lstdc++ #ifdef VERBOSE diff --git a/winsup/cygwin/cygserver.cc b/winsup/cygwin/cygserver.cc index af9cee93e..0c0740379 100755 --- a/winsup/cygwin/cygserver.cc +++ b/winsup/cygwin/cygserver.cc @@ -4,61 +4,141 @@ Written by Egor Duda - This file is part of Cygwin. +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. */ +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 -#include + +#include +#include +#include +#include #include +#include #include -#include "wincap.h" +#include +#include + +#include "cygerrno.h" #include "cygwin_version.h" -#include "getopt.h" - -#include "cygwin/cygserver_transport.h" -#include "cygwin/cygserver_transport_pipes.h" -#include "cygwin/cygserver_transport_sockets.h" -#include "threaded_queue.h" -#include "cygwin/cygserver_process.h" #include "cygwin/cygserver.h" -#include "cygserver_shm.h" +#include "cygwin/cygserver_process.h" +#include "cygwin/cygserver_transport.h" -/* for quieter operation, set to 0 */ -#define DEBUG 0 -#define debug_printf if (DEBUG) printf +// Version string. +static const char version[] = "$Revision$"; + +/* + * Support function for the XXX_printf () macros in "woutsup.h". + * Copied verbatim from "strace.cc". + */ +static int +getfunc (char *in_dst, const char *func) +{ + const char *p; + const char *pe; + char *dst = in_dst; + for (p = func; (pe = strchr (p, '(')); p = pe + 1) + if (isalnum ((int)pe[-1]) || pe[-1] == '_') + break; + else if (isspace ((int)pe[-1])) + { + pe--; + break; + } + if (!pe) + pe = strchr (func, '\0'); + for (p = pe; p > func; p--) + if (p != pe && *p == ' ') + { + p++; + break; + } + if (*p == '*') + p++; + while (p < pe) + *dst++ = *p++; + + *dst++ = ':'; + *dst++ = ' '; + *dst = '\0'; + + return dst - in_dst; +} + +/* + * Support function for the XXX_printf () macros in "woutsup.h". + */ +extern "C" void +__cygserver__printf (const char *const function, const char *const fmt, ...) +{ + const DWORD lasterror = GetLastError (); + const int lasterrno = errno; + + va_list ap; + + char *const buf = (char *) alloca (BUFSIZ); + + assert (buf); + + int len = 0; + + if (function) + len += getfunc (buf, function); + + va_start (ap, fmt); + len += vsnprintf (buf + len, BUFSIZ - len, fmt, ap); + va_end (ap); + + len += snprintf (buf + len, BUFSIZ - len, "\n"); + + const int actual = (len > BUFSIZ ? BUFSIZ : len); + + write (2, buf, actual); + + errno = lasterrno; + SetLastError (lasterror); + + return; +} + +#ifdef DEBUGGING + +int __stdcall +__set_errno (const char *func, int ln, int val) +{ + debug_printf ("%s:%d val %d", func, ln, val); + return _impure_ptr->_errno = val; +} + +#endif /* DEBUGGING */ GENERIC_MAPPING access_mapping; -static class transport_layer_base *transport; -DWORD request_count = 0; - -BOOL +static BOOL setup_privileges () { BOOL rc, ret_val; HANDLE hToken = NULL; TOKEN_PRIVILEGES sPrivileges; - rc = OpenProcessToken (GetCurrentProcess() , TOKEN_ALL_ACCESS , &hToken) ; + rc = OpenProcessToken (GetCurrentProcess () , TOKEN_ALL_ACCESS , &hToken) ; if (!rc) { - printf ("error opening process token (%lu)\n", GetLastError ()); + system_printf ("error opening process token (%lu)", GetLastError ()); ret_val = FALSE; goto out; } rc = LookupPrivilegeValue (NULL, SE_DEBUG_NAME, &sPrivileges.Privileges[0].Luid); if (!rc) { - printf ("error getting prigilege luid (%lu)\n", GetLastError ()); + system_printf ("error getting privilege luid (%lu)", GetLastError ()); ret_val = FALSE; goto out; } @@ -67,7 +147,8 @@ setup_privileges () rc = AdjustTokenPrivileges (hToken, FALSE, &sPrivileges, 0, NULL, NULL) ; if (!rc) { - printf ("error adjusting prigilege level. (%lu)\n", GetLastError ()); + system_printf ("error adjusting privilege level. (%lu)", + GetLastError ()); ret_val = FALSE; goto out; } @@ -89,179 +170,223 @@ check_and_dup_handle (HANDLE from_process, HANDLE to_process, HANDLE from_process_token, DWORD access, HANDLE from_handle, - HANDLE* to_handle_ptr, BOOL bInheritHandle = FALSE) + HANDLE *to_handle_ptr, BOOL bInheritHandle = FALSE) { HANDLE local_handle = NULL; int ret_val = EACCES; - char sd_buf [1024]; - PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR) &sd_buf; - DWORD bytes_needed; - PRIVILEGE_SET ps; - DWORD ps_len = sizeof (ps); - BOOL status; if (from_process != GetCurrentProcess ()) { + if (!DuplicateHandle (from_process, from_handle, + GetCurrentProcess (), &local_handle, + 0, bInheritHandle, + DUPLICATE_SAME_ACCESS)) + { + system_printf ("error getting handle(%u) to server (%lu)", + (unsigned int)from_handle, GetLastError ()); + goto out; + } + } else + local_handle = from_handle; - if (!DuplicateHandle (from_process, from_handle, - GetCurrentProcess (), &local_handle, - 0, bInheritHandle, - DUPLICATE_SAME_ACCESS)) + if (!wincap.has_security ()) + assert (!from_process_token); + else { - printf ("error getting handle(%u) to server (%lu)\n", (unsigned int)from_handle, GetLastError ()); - goto out; - } -} else - local_handle = from_handle; + char sd_buf [1024]; + PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR) &sd_buf; + DWORD bytes_needed; + PRIVILEGE_SET ps; + DWORD ps_len = sizeof (ps); + BOOL status; - if (!GetKernelObjectSecurity (local_handle, - OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, - sd, sizeof (sd_buf), &bytes_needed)) - { - printf ("error getting handle SD (%lu)\n", GetLastError ()); - goto out; - } + if (!GetKernelObjectSecurity (local_handle, + (OWNER_SECURITY_INFORMATION + | GROUP_SECURITY_INFORMATION + | DACL_SECURITY_INFORMATION), + sd, sizeof (sd_buf), &bytes_needed)) + { + system_printf ("error getting handle SD (%lu)", GetLastError ()); + goto out; + } - MapGenericMask (&access, &access_mapping); + MapGenericMask (&access, &access_mapping); - if (!AccessCheck (sd, from_process_token, access, &access_mapping, - &ps, &ps_len, &access, &status)) - { - printf ("error checking access rights (%lu)\n", GetLastError ()); - goto out; - } + if (!AccessCheck (sd, from_process_token, access, &access_mapping, + &ps, &ps_len, &access, &status)) + { + system_printf ("error checking access rights (%lu)", + GetLastError ()); + goto out; + } - if (!status) - { - printf ("access to object denied\n"); - goto out; + if (!status) + { + system_printf ("access to object denied"); + goto out; + } } if (!DuplicateHandle (from_process, from_handle, to_process, to_handle_ptr, access, bInheritHandle, 0)) { - printf ("error getting handle to client (%lu)\n", GetLastError ()); + system_printf ("error getting handle to client (%lu)", GetLastError ()); goto out; } - debug_printf ("Duplicated %p to %p\n", from_handle, *to_handle_ptr); + + // verbose: debug_printf ("Duplicated %p to %p", from_handle, *to_handle_ptr); ret_val = 0; -out: + out: if (local_handle && from_process != GetCurrentProcess ()) CloseHandle (local_handle); return (ret_val); } -void -client_request::serve (transport_layer_base *conn, class process_cache *cache) -{ - printf ("*****************************************\n" - "A call to the base client_request class has occured\n" - "This indicates a mismatch in a virtual function definition somewhere\n"); - exit (1); -} +/* + * client_request_attach_tty::serve () + */ void -client_request_attach_tty::serve(transport_layer_base *conn, class process_cache *cache) +client_request_attach_tty::serve (transport_layer_base *const conn, + process_cache *) { - HANDLE from_process_handle = NULL; - HANDLE to_process_handle = NULL; - HANDLE token_handle = NULL; - DWORD rc; + assert (conn); - if (header.cb != sizeof (req)) + assert (!error_code ()); + + if (!wincap.has_security ()) { - header.error_code = EINVAL; + syscall_printf ("operation only supported on systems with security"); + error_code (EINVAL); + msglen (0); return; } - debug_printf ("pid %ld:(%p,%p) -> pid %ld\n", req.master_pid, - req.from_master, req.to_master, - req.pid); - - debug_printf ("opening process %ld\n", req.master_pid); - from_process_handle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.master_pid); - debug_printf ("opening process %ld\n", req.pid); - to_process_handle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.pid); - if (!from_process_handle || !to_process_handle) + if (msglen () != sizeof (req)) { - printf ("error opening process (%lu)\n", GetLastError ()); - header.error_code = EACCES; - goto out; + syscall_printf ("bad request body length: expecting %lu bytes, got %lu", + sizeof (req), msglen ()); + error_code (EINVAL); + msglen (0); + return; } - debug_printf ("Impersonating client\n"); + msglen (0); // Until we fill in some fields. + + // verbose: debug_printf ("pid %ld:(%p,%p) -> pid %ld", + // req.master_pid, req.from_master, req.to_master, + // req.pid); + + // verbose: debug_printf ("opening process %ld", req.master_pid); + + const HANDLE from_process_handle = + OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.master_pid); + + if (!from_process_handle) + { + system_printf ("error opening `from' process, error = %lu", + GetLastError ()); + error_code (EACCES); + return; + } + + // verbose: debug_printf ("opening process %ld", req.pid); + + const HANDLE to_process_handle = + OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.pid); + + if (!to_process_handle) + { + system_printf ("error opening `to' process, error = %lu", + GetLastError ()); + CloseHandle (from_process_handle); + error_code (EACCES); + return; + } + + // verbose: debug_printf ("Impersonating client"); conn->impersonate_client (); - debug_printf ("about to open thread token\n"); - rc = OpenThreadToken (GetCurrentThread (), - TOKEN_QUERY, - TRUE, - &token_handle); + HANDLE token_handle = NULL; - debug_printf ("opened thread token, rc=%lu\n", rc); + // verbose: debug_printf ("about to open thread token"); + const DWORD rc = OpenThreadToken (GetCurrentThread (), + TOKEN_QUERY, + TRUE, + &token_handle); + + // verbose: debug_printf ("opened thread token, rc=%lu", rc); conn->revert_to_self (); if (!rc) { - printf ("error opening thread token (%lu)\n", GetLastError ()); - header.error_code = EACCES; - goto out; + system_printf ("error opening thread token, error = %lu", + GetLastError ()); + CloseHandle (from_process_handle); + CloseHandle (to_process_handle); + error_code (EACCES); + return; } - if (check_and_dup_handle (from_process_handle, to_process_handle, - token_handle, - GENERIC_READ, - req.from_master, - &req.from_master, TRUE) != 0) - { - printf ("error duplicating from_master handle (%lu)\n", GetLastError ()); - header.error_code = EACCES; - goto out; - } + // From this point on, a reply body is returned to the client. - if (req.to_master) - { - if (check_and_dup_handle (from_process_handle, to_process_handle, - token_handle, - GENERIC_WRITE, - req.to_master, - &req.to_master, TRUE) != 0) - { - printf ("error duplicating to_master handle (%lu)\n", GetLastError ()); - header.error_code = EACCES; - goto out; - } - } + const HANDLE from_master = req.from_master; + const HANDLE to_master = req.to_master; -#if DEBUG - printf ("%ld -> %ld(%p,%p)\n", req.master_pid, req.pid, - req.from_master, req.to_master); -#endif + req.from_master = NULL; + req.to_master = NULL; - header.error_code = 0; + msglen (sizeof (req)); -out: - if (from_process_handle) - CloseHandle (from_process_handle); - if (to_process_handle) - CloseHandle (to_process_handle); - if (token_handle) - CloseHandle (token_handle); + if (from_master) + if (check_and_dup_handle (from_process_handle, to_process_handle, + token_handle, + GENERIC_READ, + from_master, + &req.from_master, TRUE) != 0) + { + system_printf ("error duplicating from_master handle, error = %lu", + GetLastError ()); + error_code (EACCES); + } + + if (to_master) + if (check_and_dup_handle (from_process_handle, to_process_handle, + token_handle, + GENERIC_WRITE, + to_master, + &req.to_master, TRUE) != 0) + { + system_printf ("error duplicating to_master handle, error = %lu", + GetLastError ()); + error_code (EACCES); + } + + CloseHandle (from_process_handle); + CloseHandle (to_process_handle); + CloseHandle (token_handle); + + debug_printf ("%lu(%lu, %lu) -> %lu(%lu,%lu)", + req.master_pid, from_master, to_master, + req.pid, req.from_master, req.to_master); + + return; } void -client_request_get_version::serve(transport_layer_base *conn, class process_cache *cache) +client_request_get_version::serve (transport_layer_base *, process_cache *) { - if (header.cb != sizeof (version)) - { - header.error_code = EINVAL; - return; - } - header.error_code = 0; + assert (!error_code ()); + + if (msglen ()) + syscall_printf ("unexpected request body ignored: %lu bytes", msglen ()); + + msglen (sizeof (version)); + version.major = CYGWIN_SERVER_VERSION_MAJOR; version.api = CYGWIN_SERVER_VERSION_API; version.minor = CYGWIN_SERVER_VERSION_MINOR; @@ -270,280 +395,380 @@ client_request_get_version::serve(transport_layer_base *conn, class process_cach class server_request : public queue_request { - public: - server_request (transport_layer_base *newconn, class process_cache *newcache); - virtual void process (); - private: - char request_buffer [MAX_REQUEST_SIZE]; - transport_layer_base *conn; - class process_cache *cache; -}; +public: + server_request (transport_layer_base *const conn, process_cache *const cache) + : _conn (conn), _cache (cache) + {} -class server_process_param : public queue_process_param -{ - public: - transport_layer_base *transport; - server_process_param () : queue_process_param (false) {}; -}; - -class server_request_queue : public threaded_queue -{ - public: - class process_cache *cache; - void process_requests (transport_layer_base *transport); - virtual void add (transport_layer_base *conn); -}; -class server_request_queue request_queue; - -static DWORD WINAPI -request_loop (LPVOID LpParam) -{ - class server_process_param *params = (server_process_param *) LpParam; - class server_request_queue *queue = (server_request_queue *) params->queue; - class transport_layer_base * transport = params->transport; - while (queue->active) + virtual ~server_request () { - transport_layer_base * new_conn = transport->accept (); - /* FIXME: this is a little ugly. What we really want is to wait on two objects: - * one for the pipe/socket, and one for being told to shutdown. Otherwise - * this will stay a problem (we won't actually shutdown until the request - * _AFTER_ the shutdown request. And sending ourselves a request is ugly - */ - if (new_conn && queue->active) - queue->add (new_conn); + safe_delete (_conn); } - return 0; + + virtual void process () + { + client_request::handle_request (_conn, _cache); + } + +private: + transport_layer_base *const _conn; + process_cache *const _cache; +}; + +class server_submission_loop : public queue_submission_loop +{ +public: + server_submission_loop (threaded_queue *const queue, + transport_layer_base *const transport, + process_cache *const cache) + : queue_submission_loop (queue, false), + _transport (transport), + _cache (cache) + { + assert (_transport); + assert (_cache); + } + +private: + transport_layer_base *const _transport; + process_cache *const _cache; + + virtual void request_loop (); +}; + +/* FIXME: this is a little ugly. What we really want is to wait on + * two objects: one for the pipe/socket, and one for being told to + * shutdown. Otherwise this will stay a problem (we won't actually + * shutdown until the request _AFTER_ the shutdown request. And + * sending ourselves a request is ugly + */ +void +server_submission_loop::request_loop () +{ + /* I'd like the accepting thread's priority to be above any "normal" + * thread in the system to avoid overflowing the listen queue (for + * sockets; similar issues exist for named pipes); but, for example, + * a normal priority thread in a foregrounded process is boosted to + * THREAD_PRIORITY_HIGHEST (AFAICT). Thus try to set the current + * thread's priority to a level one above that. This fails on + * win9x/ME so assume any failure in that call is due to that and + * simply call again at one priority level lower. + */ + if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST + 1)) + if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST)) + debug_printf ("failed to raise accept thread priority, error = %lu", + GetLastError ()); + + while (_running) + { + bool recoverable = false; + transport_layer_base *const conn = _transport->accept (&recoverable); + if (!conn && !recoverable) + { + system_printf ("fatal error on IPC transport: closing down"); + return; + } + // EINTR probably implies a shutdown request; so back off for a + // moment to let the main thread take control, otherwise the + // server spins here receiving EINTR repeatedly since the signal + // handler in the main thread doesn't get a chance to be called. + if (!conn && errno == EINTR) + { + if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_NORMAL)) + debug_printf ("failed to reset thread priority, error = %lu", + GetLastError ()); + + Sleep (0); + if (!SetThreadPriority (GetCurrentThread (), + THREAD_PRIORITY_HIGHEST + 1)) + if (!SetThreadPriority (GetCurrentThread (), + THREAD_PRIORITY_HIGHEST)) + debug_printf ("failed to raise thread priority, error = %lu", + GetLastError ()); + } + if (conn) + _queue->add (safe_new (server_request, conn, _cache)); + } } -/* TODO: check we are not being asked to service a already serviced transport */ -void -server_request_queue::process_requests (transport_layer_base *transport) +client_request_shutdown::client_request_shutdown () + : client_request (CYGSERVER_REQUEST_SHUTDOWN) { - class server_process_param *params = new server_process_param; - params->transport = transport; - threaded_queue::process_requests (params, request_loop); + // verbose: syscall_printf ("created"); } void -client_request_shutdown::serve (transport_layer_base *conn, class process_cache *cache) +client_request_shutdown::serve (transport_layer_base *, process_cache *) { + assert (!error_code ()); + + if (msglen ()) + syscall_printf ("unexpected request body ignored: %lu bytes", msglen ()); + /* FIXME: link upwards, and then this becomes a trivial method call to * only shutdown _this queue_ */ - /* tell the main thread to shutdown */ - request_queue.active=false; + + kill (getpid (), SIGINT); + + msglen (0); } -server_request::server_request (transport_layer_base *newconn, class process_cache *newcache) -{ - conn = newconn; - cache = newcache; -} +static sig_atomic_t shutdown_server = false; -void -server_request::process () -{ - ssize_t bytes_read, bytes_written; - struct request_header* req_ptr = (struct request_header*) &request_buffer; - client_request *req = NULL; - debug_printf ("about to read\n"); - - bytes_read = conn->read (request_buffer, sizeof (struct request_header)); - if (bytes_read != sizeof (struct request_header)) - { - printf ("error reading from connection (%lu)\n", GetLastError ()); - goto out; - } - debug_printf ("got header (%ld)\n", bytes_read); - - switch (req_ptr->req_id) - { - case CYGSERVER_REQUEST_GET_VERSION: - req = new client_request_get_version (); break; - case CYGSERVER_REQUEST_ATTACH_TTY: - req = new client_request_attach_tty (); break; - case CYGSERVER_REQUEST_SHUTDOWN: - req = new client_request_shutdown (); break; - case CYGSERVER_REQUEST_SHM_GET: - req = new client_request_shm (); break; - default: - req = new client_request (CYGSERVER_REQUEST_INVALID, 0); - req->header.error_code = ENOSYS; - debug_printf ("Bad client request - returning ENOSYS\n"); - } - - if (req->header.cb != req_ptr->cb) - { - debug_printf ("Mismatch in request buffer sizes\n"); - goto out; - } - - if (req->header.cb) - { - - bytes_read = conn->read (req->buffer, req->header.cb); - if (bytes_read != req->header.cb) - { - debug_printf ("error reading from connection (%lu)\n", GetLastError ()); - goto out; - } - debug_printf ("got body (%ld)\n",bytes_read); - } - - /* this is not allowed to fail. We must return ENOSYS at a minimum to the client */ - req->serve (conn, cache); - - if ((bytes_written = conn->write ((char *)&req->header, sizeof (req->header))) - != sizeof(req->header) || (req->header.cb && - (bytes_written = conn->write (req->buffer, req->header.cb)) != req->header.cb)) - { - req->header.error_code = -1; - printf ("error writing to connection (%lu)\n", GetLastError ()); - goto out; - } - - debug_printf("Sent reply, size (%ld)\n",bytes_written); - printf ("."); - -out: - conn->close (); - delete conn; - if (req) - delete (req); -} - -void -server_request_queue::add (transport_layer_base *conn) -{ - /* safe to not "Try" because workers don't hog this, they wait on the event - */ - /* every derived ::add must enter the section! */ - EnterCriticalSection (&queuelock); - if (!running) - { - conn->close (); - delete conn; - LeaveCriticalSection (&queuelock); - return; - } - queue_request * listrequest = new server_request (conn, cache); - threaded_queue::add (listrequest); - LeaveCriticalSection (&queuelock); -} - -void -handle_signal (int signal) +static void +handle_signal (const int signum) { /* any signal makes us die :} */ - /* FIXME: link upwards, and then this becomes a trivial method call to - * only shutdown _this queue_ - */ - /* tell the main thread to shutdown */ - request_queue.active=false; + + shutdown_server = true; } -struct option longopts[] = { - {"shutdown", no_argument, NULL, 's'}, - {0, no_argument, NULL, 0} -}; +/* + * print_usage () + */ -char opts[] = "s"; +static void +print_usage (const char *const pgm) +{ + printf ("Usage: %s [OPTIONS]\n", pgm); + printf (" -c, --cleanup-threads number of cleanup threads to use\n"); + printf (" -h, --help output usage information and exit\n"); + printf (" -r, --request-threads number of request threads to use\n"); + printf (" -s, --shutdown shutdown the daemon\n"); + printf (" -v, --version output version information and exit\n"); +} + +/* + * print_version () + */ + +static void +print_version (const char *const pgm) +{ + char *vn = NULL; + + const char *const colon = strchr (version, ':'); + + if (!colon) + { + vn = strdup ("?"); + } + else + { + vn = strdup (colon + 2); // Skip ": " + + char *const spc = strchr (vn, ' '); + + if (spc) + *spc = '\0'; + } + + char buf[200]; + snprintf (buf, sizeof (buf), "%d.%d.%d(%d.%d/%d/%d)-(%d.%d.%d.%d) %s", + cygwin_version.dll_major / 1000, + cygwin_version.dll_major % 1000, + cygwin_version.dll_minor, + cygwin_version.api_major, + cygwin_version.api_minor, + cygwin_version.shared_data, + CYGWIN_SERVER_VERSION_MAJOR, + CYGWIN_SERVER_VERSION_API, + CYGWIN_SERVER_VERSION_MINOR, + CYGWIN_SERVER_VERSION_PATCH, + cygwin_version.mount_registry, + cygwin_version.dll_build_date); + + printf ("%s (cygwin) %s\n", pgm, vn); + printf ("API version %s\n", buf); + printf ("Copyright 2001, 2002 Red Hat, Inc.\n"); + printf ("Compiled on %s\n", __DATE__); + + free (vn); +} + +/* + * main () + */ int -main (int argc, char **argv) +main (const int argc, char *argv[]) { - int shutdown=0; - char i; + const struct option longopts[] = { + {"cleanup-threads", required_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {"request-threads", required_argument, NULL, 'r'}, + {"shutdown", no_argument, NULL, 's'}, + {"version", no_argument, NULL, 'v'}, + {0, no_argument, NULL, 0} + }; - while ((i = getopt_long (argc, argv, opts, longopts, NULL)) != EOF) - switch (i) - { - case 's': - shutdown = 1; - break; - default: - break; - /*NOTREACHED*/ - } + const char opts[] = "c:hr:sv"; - wincap.init(); + int cleanup_threads = 2; + int request_threads = 10; + bool shutdown = false; + + const char *pgm = NULL; + + if (!(pgm = strrchr (*argv, '\\')) && !(pgm = strrchr (*argv, '/'))) + pgm = *argv; + else + pgm++; + + wincap.init (); if (wincap.has_security ()) setup_privileges (); - transport = create_server_transport (); + + int opt; + + while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != EOF) + switch (opt) + { + case 'c': + cleanup_threads = atoi (optarg); + if (cleanup_threads <= 0) + { + fprintf (stderr, + "%s: number of cleanup threads must be positive\n", + pgm); + exit (1); + } + break; + + case 'h': + print_usage (pgm); + return 0; + + case 'r': + request_threads = atoi (optarg); + if (request_threads <= 0) + { + fprintf (stderr, + "%s: number of request threads must be positive\n", + pgm); + exit (1); + } + break; + + case 's': + shutdown = true; + break; + + case 'v': + print_version (pgm); + return 0; + + case '?': + fprintf (stderr, "Try `%s --help' for more information.\n", pgm); + exit (1); + } + + if (optind != argc) + { + fprintf (stderr, "%s: too many arguments\n", pgm); + exit (1); + } if (shutdown) { - if (!transport->connect()) + /* Setting `cygserver_running' stops the request code making a + * version request, which is not much to the point. + */ + cygserver_running = CYGSERVER_OK; + + client_request_shutdown req; + + if (req.make_request () == -1 || req.error_code ()) { - printf ("couldn't establish connection with server\n"); + fprintf (stderr, "%s: shutdown request failed: %s\n", + pgm, strerror (req.error_code ())); exit (1); } - client_request_shutdown *request = - new client_request_shutdown (); - request->send (transport); - transport->close(); - delete transport; - delete request; - exit(0); + + // FIXME: It would be nice to wait here for the daemon to exit. + + return 0; } - char version[200]; - /* Cygwin dll release */ - snprintf (version, 200, "%d.%d.%d(%d.%d/%d/%d)-(%d.%d.%d.%d) %s", - cygwin_version.dll_major / 1000, - cygwin_version.dll_major % 1000, - cygwin_version.dll_minor, - cygwin_version.api_major, - cygwin_version.api_minor, - cygwin_version.shared_data, - CYGWIN_SERVER_VERSION_MAJOR, - CYGWIN_SERVER_VERSION_API, - CYGWIN_SERVER_VERSION_MINOR, - CYGWIN_SERVER_VERSION_PATCH, - cygwin_version.mount_registry, - cygwin_version.dll_build_date); +#define SIGHANDLE(SIG) \ + do \ + { \ + struct sigaction act; \ + \ + act.sa_handler = &handle_signal; \ + act.sa_mask = 0; \ + act.sa_flags = 0; \ + \ + if (sigaction (SIG, &act, NULL) == -1) \ + { \ + system_printf ("failed to install handler for " #SIG ": %s", \ + strerror (errno)); \ + exit (1); \ + } \ + } while (false) + + SIGHANDLE (SIGHUP); + SIGHANDLE (SIGINT); + SIGHANDLE (SIGTERM); + + print_version (pgm); setbuf (stdout, NULL); - printf ("daemon version %s starting up", version); - if (signal (SIGQUIT, handle_signal) == SIG_ERR) + printf ("daemon starting up"); + + threaded_queue request_queue (request_threads); + printf ("."); + + transport_layer_base *const transport = create_server_transport (); + assert (transport); + printf ("."); + + process_cache cache (cleanup_threads); + printf ("."); + + server_submission_loop submission_loop (&request_queue, transport, &cache); + printf ("."); + + request_queue.add_submission_loop (&submission_loop); + printf ("."); + + if (transport->listen () == -1) { - printf ("\ncould not install signal handler (%d)- aborting startup\n", errno); exit (1); } printf ("."); - transport->listen (); + + cache.start (); printf ("."); - class process_cache cache (2); - request_queue.initial_workers = 10; - request_queue.cache = &cache; - request_queue.create_workers (); + + request_queue.start (); printf ("."); - request_queue.process_requests (transport); - printf ("."); - cache.create_workers (); - printf ("."); - cache.process_requests (); - printf (".complete\n"); - /* TODO: wait on multiple objects - the thread handle for each request loop + - * all the process handles. This should be done by querying the request_queue and - * the process cache for all their handles, and then waiting for (say) 30 seconds. - * after that we recreate the list of handles to wait on, and wait again. - * the point of all this abstraction is that we can trivially server both sockets - * and pipes simply by making a new transport, and then calling - * request_queue.process_requests (transport2); + + printf ("complete\n"); + + /* TODO: wait on multiple objects - the thread handle for each + * request loop + all the process handles. This should be done by + * querying the request_queue and the process cache for all their + * handles, and then waiting for (say) 30 seconds. after that we + * recreate the list of handles to wait on, and wait again. the + * point of all this abstraction is that we can trivially server + * both sockets and pipes simply by making a new transport, and then + * calling request_queue.process_requests (transport2); */ /* WaitForMultipleObjects abort && request_queue && process_queue && signal -- if signal event then retrigger it - */ - while (1 && request_queue.active) - { - sleep (1); - } - printf ("\nShutdown request recieved - new requests will be denied\n"); - request_queue.cleanup (); + */ + while (!shutdown_server && request_queue.running () && cache.running ()) + pause (); + + printf ("\nShutdown request received - new requests will be denied\n"); + request_queue.stop (); printf ("All pending requests processed\n"); - transport->close (); + safe_delete (transport); printf ("No longer accepting requests - cygwin will operate in daemonless mode\n"); - cache.cleanup (); + cache.stop (); printf ("All outstanding process-cache activities completed\n"); printf ("daemon shutdown\n"); + + return 0; } diff --git a/winsup/cygwin/cygserver_client.cc b/winsup/cygwin/cygserver_client.cc index 1df23ad9f..138c9ddc0 100755 --- a/winsup/cygwin/cygserver_client.cc +++ b/winsup/cygwin/cygserver_client.cc @@ -4,204 +4,526 @@ Written by Egor Duda - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ +/* to allow this to link into cygwin and the .dll, a little magic is needed. */ #ifdef __OUTSIDE_CYGWIN__ -#undef __INSIDE_CYGWIN__ +#include "woutsup.h" #else #include "winsup.h" #endif -#ifndef __INSIDE_CYGWIN__ -#define debug_printf printf -#define api_fatal printf -#include -#include -#endif -#include +#include #include +#include #include -//#include "security.h" -#include "cygwin/cygserver_transport.h" -#include "cygwin/cygserver_transport_pipes.h" -#include "cygwin/cygserver_transport_sockets.h" + +#include "cygerrno.h" +#include "cygserver_shm.h" +#include "safe_memory.h" + #include "cygwin/cygserver.h" +#include "cygwin/cygserver_transport.h" -/* 0 = untested, 1 = running, 2 = dead */ -int cygserver_running=CYGSERVER_UNKNOWN; -/* on by default during development. For release, we probably want off by default */ -int allow_daemon = TRUE; +int cygserver_running = CYGSERVER_UNKNOWN; // Nb: inherited by children. -client_request_get_version::client_request_get_version () : client_request (CYGSERVER_REQUEST_GET_VERSION, sizeof (version)) +/* On by default during development. For release, we probably want off + * by default. + */ +bool allow_daemon = true; // Nb: inherited by children. + +client_request_get_version::client_request_get_version () + : client_request (CYGSERVER_REQUEST_GET_VERSION, &version, sizeof (version)) { - buffer = (char *)&version; + msglen (0); // No parameters for request. + + // verbose: syscall_printf ("created"); } -client_request_attach_tty::client_request_attach_tty () : client_request (CYGSERVER_REQUEST_ATTACH_TTY, sizeof (req)) +/* + * client_request_get_version::check_version () + * + * The major version and API version numbers must match exactly. An + * older than expected minor version number is accepted (as long as + * the first numbers match, that is). + */ + +bool +client_request_get_version::check_version () const { - buffer = (char *)&req; - req.pid = 0; - req.master_pid = 0; - req.from_master = NULL; - req.to_master = NULL; + const bool ok = (version.major == CYGWIN_SERVER_VERSION_MAJOR + && version.api == CYGWIN_SERVER_VERSION_API + && version.minor <= CYGWIN_SERVER_VERSION_MINOR); + + if (!ok) + syscall_printf (("incompatible version of cygwin server: " + "client version %d.%d.%d.%d, " + "server version %ld.%ld.%ld.%ld"), + CYGWIN_SERVER_VERSION_MAJOR, + CYGWIN_SERVER_VERSION_API, + CYGWIN_SERVER_VERSION_MINOR, + CYGWIN_SERVER_VERSION_PATCH, + version.major, + version.api, + version.minor, + version.patch); + + return ok; } -client_request_attach_tty::client_request_attach_tty (DWORD npid, DWORD nmaster_pid, HANDLE nfrom_master, HANDLE nto_master) : client_request (CYGSERVER_REQUEST_ATTACH_TTY, sizeof (req)) +#ifdef __INSIDE_CYGWIN__ + +client_request_attach_tty::client_request_attach_tty (DWORD nmaster_pid, + HANDLE nfrom_master, + HANDLE nto_master) + : client_request (CYGSERVER_REQUEST_ATTACH_TTY, &req, sizeof (req)) { - buffer = (char *)&req; - req.pid = npid; + req.pid = GetCurrentProcessId (); req.master_pid = nmaster_pid; req.from_master = nfrom_master; req.to_master = nto_master; + + syscall_printf (("created: pid = %lu, master_pid = %lu, " + "from_master = %lu, to_master = %lu"), + req.pid, req.master_pid, req.from_master, req.to_master); } -client_request_shutdown::client_request_shutdown () : client_request (CYGSERVER_REQUEST_SHUTDOWN, 0) +#else /* !__INSIDE_CYGWIN__ */ + +client_request_attach_tty::client_request_attach_tty () + : client_request (CYGSERVER_REQUEST_ATTACH_TTY, &req, sizeof (req)) { - buffer = NULL; + // verbose: syscall_printf ("created"); } -client_request::client_request (cygserver_request_code id, ssize_t buffer_size) : header (id, buffer_size) +#endif /* __INSIDE_CYGWIN__ */ + +/* + * client_request_attach_tty::send () + * + * Wraps the base method to provide error handling support. If the + * reply contains a body but is flagged as an error, close any handles + * that have been returned by cygserver and then discard the message + * body, i.e. the client either sees a successful result with handles + * or an unsuccessful result with no handles. + */ + +void +client_request_attach_tty::send (transport_layer_base * const conn) { + client_request::send (conn); + + if (msglen () && error_code ()) + { + if (from_master ()) + CloseHandle (from_master ()); + if (to_master ()) + CloseHandle (to_master ()); + msglen (0); + } +} + +client_request::header_t::header_t (const request_code_t request_code, + const size_t msglen) + : msglen (msglen), + request_code (request_code) +{ + assert (request_code >= 0 && request_code < CYGSERVER_REQUEST_LAST); +} + +// FIXME: also check write and read result for -1. + +void +client_request::send (transport_layer_base * const conn) +{ + assert (conn); + assert (!(msglen () && !_buf)); // i.e., msglen () implies _buf + assert (msglen () <= _buflen); + + { + const ssize_t count = conn->write (&_header, sizeof (_header)); + + if (count != sizeof (_header)) + { + assert (errno); + error_code (errno); + syscall_printf (("request header write failure: " + "only %ld bytes sent of %ld, " + "error = %d(%lu)"), + count, sizeof (_header), + errno, GetLastError ()); + return; + } + } + + if (msglen ()) + { + const ssize_t count = conn->write (_buf, msglen ()); + + if (count == -1 || (size_t) count != msglen ()) + { + assert (errno); + error_code (errno); + syscall_printf (("request body write failure: " + "only %ld bytes sent of %ld, " + "error = %d(%lu)"), + count, msglen (), + errno, GetLastError ()); + return; + } + } + + // verbose: syscall_printf ("request sent (%ld + %ld bytes)", + // sizeof (_header), msglen ()); + + { + const ssize_t count = conn->read (&_header, sizeof (_header)); + + if (count != sizeof (_header)) + { + assert (errno); + error_code (errno); + syscall_printf (("reply header read failure: " + "only %ld bytes received of %ld, " + "error = %d(%lu)"), + count, sizeof (_header), + errno, GetLastError ()); + return; + } + } + + if (msglen () && !_buf) + { + system_printf ("no client buffer for reply body: %ld bytes needed", + msglen ()); + error_code (EINVAL); + return; + } + + if (msglen () > _buflen) + { + system_printf (("client buffer too small for reply body: " + "have %ld bytes and need %ld"), + _buflen, msglen ()); + error_code (EINVAL); + return; + } + + if (msglen ()) + { + const ssize_t count = conn->read (_buf, msglen ()); + + if (count == -1 || (size_t) count != msglen ()) + { + assert (errno); + error_code (errno); + syscall_printf (("reply body read failure: " + "only %ld bytes received of %ld, " + "error = %d(%lu)"), + count, msglen (), + errno, GetLastError ()); + return; + } + } + + // verbose: syscall_printf ("reply received (%ld + %ld bytes)", + // sizeof (_header), msglen ()); +} + +#ifndef __INSIDE_CYGWIN__ + +/* + * client_request::handle_request () + * + * A server-side method. + * + * This is a factory method for the client_request subclasses. It + * reads the incoming request header and, based on its request code, + * creates an instance of the appropriate class. + * + * FIXME: If the incoming packet is malformed, the server drops it on + * the floor. Should it try and generate some sort of reply for the + * client? As it is, the client will simply get a broken connection. + * + * FIXME: also check write and read result for -1. + */ + +/* static */ void +client_request::handle_request (transport_layer_base *const conn, + process_cache *const cache) +{ + // verbose: debug_printf ("about to read"); + + header_t header; + + { + const ssize_t count = conn->read (&header, sizeof (header)); + + if (count != sizeof (header)) + { + syscall_printf (("request header read failure: " + "only %ld bytes received of %ld, " + "error = %d(%lu)"), + count, sizeof (header), + errno, GetLastError ()); + return; + } + + // verbose: debug_printf ("got header (%ld)", count); + } + + client_request *req = NULL; + + switch (header.request_code) + { + case CYGSERVER_REQUEST_GET_VERSION: + req = safe_new0 (client_request_get_version); + break; + case CYGSERVER_REQUEST_SHUTDOWN: + req = safe_new0 (client_request_shutdown); + break; + case CYGSERVER_REQUEST_ATTACH_TTY: + req = safe_new0 (client_request_attach_tty); + break; + case CYGSERVER_REQUEST_SHM: + req = safe_new0 (client_request_shm); + break; + default: + syscall_printf ("unknown request code %d received: request ignored", + header.request_code); + return; + } + + assert (req); + + req->msglen (header.msglen); + req->handle (conn, cache); + + safe_delete (req); + +#ifndef DEBUGGING + printf ("."); // A little noise when we're being quiet. +#endif +} + +#endif /* !__INSIDE_CYGWIN__ */ + +client_request::client_request (request_code_t const id, + void * const buf, + size_t const buflen) + : _header (id, buflen), + _buf (buf), + _buflen (buflen) +{ + assert ((!_buf && !_buflen) || (_buf && _buflen)); } client_request::~client_request () -{ -} - -client_request::operator class request_header () -{ - return header; -} - -void -client_request::send (transport_layer_base *conn) -{ - if (!conn) - return; - debug_printf("this=%p, conn=%p\n",this, conn); - ssize_t bytes_written, bytes_read; - debug_printf("header.cb = %ld\n",header.cb); - if ((bytes_written = conn->write ((char *)&header, sizeof (header))) - != sizeof(header) || (header.cb && - (bytes_written = conn->write (buffer, header.cb)) != header.cb)) - { - header.error_code = -1; - debug_printf ("bytes written != request size\n"); - return; - } - - debug_printf("Sent request, size (%ld)\n",bytes_written); - - if ((bytes_read = conn->read ((char *)&header, sizeof (header))) - != sizeof (header) || (header.cb && - (bytes_read = conn->read (buffer, header.cb)) != header.cb)) - { - header.error_code = -1; - debug_printf("failed reading response \n"); - return; - } - debug_printf ("completed ok\n"); -} - -/* Oh, BTW: Fix the procedural basis and make this more intuitive. */ +{} int -cygserver_request (client_request * req) +client_request::make_request () { - class transport_layer_base *transport; + assert (cygserver_running == CYGSERVER_UNKNOWN \ + || cygserver_running == CYGSERVER_OK \ + || cygserver_running == CYGSERVER_UNAVAIL); - if (!req || allow_daemon != TRUE) - return -1; + if (cygserver_running == CYGSERVER_UNKNOWN) + cygserver_init (); - /* dont' retry every request if the server's not there */ - if (cygserver_running==CYGSERVER_DEAD && req->header.req_id != CYGSERVER_REQUEST_GET_VERSION) - return -1; + assert (cygserver_running == CYGSERVER_OK \ + || cygserver_running == CYGSERVER_UNAVAIL); - transport = create_server_transport (); - - /* FIXME: have at most one connection per thread. use TLS to store the details */ - /* logic is: - * if not tlskey->conn, new conn, - * then; transport=conn; - */ - if (!transport->connect ()) + /* Don't retry every request if the server's not there */ + if (cygserver_running == CYGSERVER_UNAVAIL) { - delete transport; + syscall_printf ("cygserver un-available"); + error_code (ENOSYS); return -1; } - debug_printf ("connected to server %p\n", transport); + transport_layer_base *const transport = create_server_transport (); - req->send(transport); + assert (transport); - transport->close (); + if (transport->connect () == -1) + { + if (errno) + error_code (errno); + else + error_code (ENOSYS); + safe_delete (transport); + return -1; + } - delete transport; + // verbose: debug_printf ("connected to server %p", transport); + + send (transport); + + safe_delete (transport); return 0; } -#if 0 -BOOL +#ifndef __INSIDE_CYGWIN__ + +/* + * client_request::handle () + * + * A server-side method. + * + * At this point, the header of an incoming request has been read and + * an appropriate client_request object constructed. This method has + * to read the request body into its buffer, if there is such a body, + * then perform the request and send back the results to the client. + * + * FIXME: If the incoming packet is malformed, the server drops it on + * the floor. Should it try and generate some sort of reply for the + * client? As it is, the client will simply get a broken connection. + * + * FIXME: also check write and read result for -1. + */ + +void +client_request::handle (transport_layer_base *const conn, + process_cache *const cache) +{ + if (msglen () && !_buf) + { + system_printf ("no buffer for request body: %ld bytes needed", + msglen ()); + error_code (EINVAL); + return; + } + + if (msglen () > _buflen) + { + system_printf (("buffer too small for request body: " + "have %ld bytes and need %ld"), + _buflen, msglen ()); + error_code (EINVAL); + return; + } + + if (msglen ()) + { + const ssize_t count = conn->read (_buf, msglen ()); + + if (count == -1 || (size_t) count != msglen ()) + { + assert (errno); + error_code (errno); + syscall_printf (("request body read failure: " + "only %ld bytes received of %ld, " + "error = %d(%lu)"), + count, msglen (), + errno, GetLastError ()); + return; + } + } + + // verbose: syscall_printf ("request received (%ld + %ld bytes)", + // sizeof (_header), msglen ()); + + error_code (0); // Overwrites the _header.request_code field. + + /* + * This is not allowed to fail. We must return ENOSYS at a minimum + * to the client. + */ + serve (conn, cache); + + { + const ssize_t count = conn->write (&_header, sizeof (_header)); + + if (count != sizeof (_header)) + { + assert (errno); + error_code (errno); + syscall_printf (("reply header write failure: " + "only %ld bytes sent of %ld, " + "error = %d(%lu)"), + count, sizeof (_header), + errno, GetLastError ()); + return; + } + } + + if (msglen ()) + { + const ssize_t count = conn->write (_buf, msglen ()); + + if (count == -1 || (size_t) count != msglen ()) + { + assert (errno); + error_code (errno); + syscall_printf (("reply body write failure: " + "only %ld bytes sent of %ld, " + "error = %d(%lu)"), + count, msglen (), + errno, GetLastError ()); + return; + } + } + + // verbose: syscall_printf ("reply sent (%ld + %ld bytes)", + // sizeof (_header), msglen ()); +} + +#endif /* !__INSIDE_CYGWIN__ */ + +bool check_cygserver_available () { - BOOL ret_val = FALSE; - HANDLE pipe = CreateFile (pipe_name, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - &sec_all_nih, - OPEN_EXISTING, - 0, - NULL); - if (pipe != INVALID_HANDLE_VALUE || GetLastError () != ERROR_PIPE_BUSY) - ret_val = TRUE; + assert (cygserver_running == CYGSERVER_UNKNOWN \ + || cygserver_running == CYGSERVER_UNAVAIL); - if (pipe && pipe != INVALID_HANDLE_VALUE) - CloseHandle (pipe); + cygserver_running = CYGSERVER_OK; // For make_request (). - return (ret_val); + client_request_get_version req; + + /* This indicates that we failed to connect to cygserver at all but + * that's fine as cygwin doesn't need it to be running. + */ + if (req.make_request () == -1) + return false; + + /* We connected to the server but something went wrong after that + * (in sending the message, in cygserver itself, or in receiving the + * reply). + */ + if (req.error_code ()) + { + syscall_printf ("failure in cygserver version request: %d", + req.error_code ()); + syscall_printf ("process will continue without cygserver support"); + return false; + } + + return req.check_version (); } -#endif void cygserver_init () { - int rc; - if (allow_daemon != TRUE) + if (!allow_daemon) { - cygserver_running = CYGSERVER_DEAD; + syscall_printf ("cygserver use disabled in client"); + cygserver_running = CYGSERVER_UNAVAIL; return; } - if (cygserver_running==CYGSERVER_OK) + assert (cygserver_running == CYGSERVER_UNKNOWN \ + || cygserver_running == CYGSERVER_OK \ + || cygserver_running == CYGSERVER_UNAVAIL); + + if (cygserver_running == CYGSERVER_OK) return; - client_request_get_version *req = - new client_request_get_version (); - - rc = cygserver_request (req); - delete req; - if (rc < 0) - cygserver_running = CYGSERVER_DEAD; - else if (rc > 0) - api_fatal ("error connecting to cygwin server. error: %d", rc); - else if (req->version.major != CYGWIN_SERVER_VERSION_MAJOR || - req->version.api != CYGWIN_SERVER_VERSION_API || - req->version.minor > CYGWIN_SERVER_VERSION_MINOR) - api_fatal ("incompatible version of cygwin server.\n\ - client version %d.%d.%d.%d, server version%ld.%ld.%ld.%ld", - CYGWIN_SERVER_VERSION_MAJOR, - CYGWIN_SERVER_VERSION_API, - CYGWIN_SERVER_VERSION_MINOR, - CYGWIN_SERVER_VERSION_PATCH, - req->version.major, - req->version.api, - req->version.minor, - req->version.patch); - else - cygserver_running = CYGSERVER_OK; + if (!check_cygserver_available ()) + cygserver_running = CYGSERVER_UNAVAIL; } diff --git a/winsup/cygwin/cygserver_ipc.h b/winsup/cygwin/cygserver_ipc.h new file mode 100644 index 000000000..0d0ebbc76 --- /dev/null +++ b/winsup/cygwin/cygserver_ipc.h @@ -0,0 +1,84 @@ +/* cygserver_ipc.h + + Copyright 2002 Red Hat, Inc. + + Originally written by Conrad Scott + +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. */ + +#ifndef __CYGSERVER_IPC_H__ +#define __CYGSERVER_IPC_H__ + +#include +#include /* For OPEN_MAX. */ + +/* + * The sysv ipc id's (msgid, semid, shmid) are integers arranged such + * that they no subsystem will generate the same id as some other + * subsystem; nor do these ids overlap file descriptors (the other + * common integer ids). Since Cygwin can allocate more than OPEN_MAX + * file descriptors, it can't be guaranteed not to overlap, but it + * should help catch some errors. + * + * msgid's: OPEN_MAX, OPEN_MAX + 3, OPEN_MAX + 6, . . . + * semid's: OPEN_MAX + 1, OPEN_MAX + 4, OPEN_MAX + 7, . . . + * shmid's: OPEN_MAX + 2, OPEN_MAX + 5, OPEN_MAX + 8, . . . + * + * To further ensure that ids are unique, if ipc objects are created + * and destroyed and then re-created, they are given new ids by + * munging the basic id (as above) with a sequence number. + * + * Internal ipc id's, which are 0, 1, ... within each subsystem (and + * not munged with a sequence number), are used solely by the ipcs(8) + * interface. + */ + +enum ipc_subsys_t + { + IPC_MSGOP = 0, + IPC_SEMOP = 1, + IPC_SHMOP = 2, + IPC_SUBSYS_COUNT + }; + +/* + * IPCMNI - The absolute maximum number of simultaneous ipc ids for + * any one subsystem. + */ + +enum + { + IPCMNI = 0x10000 // Must be a power of two. + }; + +inline int +ipc_int2ext (const int intid, const ipc_subsys_t subsys, long & sequence) +{ + assert (0 <= intid && intid < IPCMNI); + + const long tmp = InterlockedIncrement (&sequence); + + return (((tmp & 0x7fff) << 16) + | (OPEN_MAX + (intid * IPC_SUBSYS_COUNT) + subsys)); +} + +inline int +ipc_ext2int_subsys (const int extid) +{ + return ((extid & (IPCMNI - 1)) - OPEN_MAX) % IPC_SUBSYS_COUNT; +} + +inline int +ipc_ext2int (const int extid, const ipc_subsys_t subsys) +{ + if (ipc_ext2int_subsys (extid) != subsys) + return -1; + else + return ((extid & (IPCMNI - 1)) - OPEN_MAX) / IPC_SUBSYS_COUNT; +} + +#endif /* __CYGSERVER_IPC_H__ */ diff --git a/winsup/cygwin/cygserver_process.cc b/winsup/cygwin/cygserver_process.cc index dd13f37fb..7118bbcd9 100755 --- a/winsup/cygwin/cygserver_process.cc +++ b/winsup/cygwin/cygserver_process.cc @@ -4,385 +4,429 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +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 -#include -#include -#include "wincap.h" -#include -#include -#include -#define debug_printf if (DEBUG) printf -#define DEBUG 1 +#include +#include +#include -/* the cache structures and classes are designed for one cache per server process. - * To make multiple process caches, a redesign will be needed - */ +#include "cygerrno.h" -/* process cache */ -process_cache::process_cache (unsigned int num_initial_workers): -head (NULL) +#include "cygwin/cygserver_process.h" + +/*****************************************************************************/ + +#define elements(ARRAY) (sizeof (ARRAY) / sizeof (*ARRAY)) + +/*****************************************************************************/ + +process_cleanup::~process_cleanup () { - /* there can only be one */ - InitializeCriticalSection (&cache_write_access); - if ((cache_add_trigger = CreateEvent (NULL, FALSE, FALSE, NULL)) == NULL) - { - printf ("Failed to create cache add trigger (%lu), terminating\n", - GetLastError ()); - exit (1); - } - initial_workers = num_initial_workers; + safe_delete (_process); } -process_cache::~process_cache () +void +process_cleanup::process () +{ + _process->cleanup (); +} + +/*****************************************************************************/ + +/* cleanup_routine */ +cleanup_routine::~cleanup_routine () { } -class process * -process_cache::process (long pid) +/*****************************************************************************/ + +process::process (const pid_t cygpid, const DWORD winpid) + : _cygpid (cygpid), + _winpid (winpid), + _hProcess (NULL), + _cleaning_up (false), + _exit_status (STILL_ACTIVE), + _routines_head (NULL), + _next (NULL) { - class process *entry = head; - /* TODO: make this more granular, so a search doesn't involve the write lock */ - EnterCriticalSection (&cache_write_access); - if (!entry) + _hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, winpid); + if (!_hProcess) { - entry = new class process (pid); - entry->next = - (class process *) InterlockedExchangePointer (&head, entry); - PulseEvent (cache_add_trigger); + system_printf ("unable to obtain handle for new cache process %d(%lu)", + _cygpid, _winpid); + _hProcess = INVALID_HANDLE_VALUE; + _exit_status = 0; } else - { - while (entry->winpid != pid && entry->next) - entry = entry->next; - if (entry->winpid != pid) - { - class process *new_entry = new class process (pid); - new_entry->next = - (class process *) InterlockedExchangePointer (&entry->next, - new_entry); - entry = new_entry; - PulseEvent (cache_add_trigger); - } - } - LeaveCriticalSection (&cache_write_access); - return entry; -} - -static DWORD WINAPI -request_loop (LPVOID LpParam) -{ - class process_process_param *params = (process_process_param *) LpParam; - return params->request_loop (); -} - -void -process_cache::process_requests () -{ - class process_process_param *params = new process_process_param; - threaded_queue::process_requests (params, request_loop); -} - -void -process_cache::add_task (class process * theprocess) -{ - /* safe to not "Try" because workers don't hog this, they wait on the event - */ - /* every derived ::add must enter the section! */ - EnterCriticalSection (&queuelock); - queue_request *listrequest = new process_cleanup (theprocess); - threaded_queue::add (listrequest); - LeaveCriticalSection (&queuelock); -} - -/* NOT fully MT SAFE: must be called by only one thread in a program */ -void -process_cache::remove_process (class process *theprocess) -{ - class process *entry = head; - /* unlink */ - EnterCriticalSection (&cache_write_access); - if (entry == theprocess) - { - entry = (class process *) InterlockedExchangePointer (&head, theprocess->next); - if (entry != theprocess) - { - printf ("Bug encountered, process cache corrupted\n"); - exit (1); - } - } - else - { - while (entry->next && entry->next != theprocess) - entry = entry->next; - class process *temp = (class process *) InterlockedExchangePointer (&entry->next, theprocess->next); - if (temp != theprocess) - { - printf ("Bug encountered, process cache corrupted\n"); - exit (1); - } - } - LeaveCriticalSection (&cache_write_access); - /* Process any cleanup tasks */ - add_task (theprocess); -} - -/* copy <= max_copy HANDLEs to dest[], starting at an offset into _our list_ of - * begin_at. (Ie begin_at = 5, the first copied handle is still written to dest[0] - * NOTE: Thread safe, but not thread guaranteed - a newly added process may be missed. - * Who cares - It'll get caught the next time. - */ -int -process_cache::handle_snapshot (HANDLE * hdest, class process ** edest, - ssize_t max_copy, int begin_at) -{ - /* TODO:? grab a delete-lock, to prevent deletes during this process ? */ - class process *entry = head; - int count = begin_at; - /* skip begin_at entries */ - while (entry && count) - { - if (entry->exit_code () == STILL_ACTIVE) - count--; - entry = entry->next; - } - /* hit the end of the list within begin_at entries */ - if (count) - return 0; - HANDLE *hto = hdest; - class process **eto = edest; - while (entry && count < max_copy) - { - /* hack */ - if (entry->exit_code () == STILL_ACTIVE) - { - *hto = entry->handle (); - *eto = entry; - count++; - hto++; - eto++; - } - entry = entry->next; - } - return count; -} - -/* process's */ -/* global process crit section */ -static CRITICAL_SECTION process_access; -static pthread_once_t process_init; - -void -do_process_init (void) -{ - InitializeCriticalSection (&process_access); - /* we don't have a cache shutdown capability today */ -} - -process::process (long pid): -winpid (pid), next (NULL), cleaning_up (0), head (NULL), _exit_status (STILL_ACTIVE) -{ - pthread_once (&process_init, do_process_init); - EnterCriticalSection (&process_access); - thehandle = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); - if (!thehandle) - { - printf ("unable to obtain handle for new cache process %ld\n", pid); - thehandle = INVALID_HANDLE_VALUE; - } - debug_printf ("Got handle %p for new cache process %ld\n", thehandle, pid); - InitializeCriticalSection (&access); - LeaveCriticalSection (&process_access); + debug_printf ("got handle %p for new cache process %d(%lu)", + _hProcess, _cygpid, _winpid); + InitializeCriticalSection (&_access); } process::~process () { - DeleteCriticalSection (&access); + DeleteCriticalSection (&_access); + (void) CloseHandle (_hProcess); } -HANDLE -process::handle () +/* No need to be thread-safe as this is only ever called by + * process_cache::remove_process (). If it has to be made thread-safe + * later on, it should not use the `access' critical section as that + * is held by the client request handlers for an arbitrary length of + * time, i.e. while they do whatever processing is required for a + * client request. + */ +DWORD +process::check_exit_code () { -// DWORD exitstate = exit_code (); -// if (exitstate == STILL_ACTIVE) - return thehandle; - - /* FIXME: call the cleanup list ? */ - -// CloseHandle (thehandle); -// debug_printf ("Process id %ld has terminated, attempting to open a new handle\n", -// winpid); -// thehandle = OpenProcess (PROCESS_ALL_ACCESS, FALSE, winpid); -// debug_printf ("Got handle %p when refreshing cache process %ld\n", thehandle, winpid); -// /* FIXME: what if OpenProcess fails ? */ -// if (thehandle) -// { -// _exit_status = STILL_ACTIVE; -// exit_code (); -// } -// else -// thehandle = INVALID_HANDLE_VALUE; -// return thehandle; -} - -DWORD process::exit_code () -{ - if (_exit_status != STILL_ACTIVE) - return _exit_status; - bool - err = GetExitCodeProcess (thehandle, &_exit_status); - if (!err) + if (_hProcess && _hProcess != INVALID_HANDLE_VALUE + && _exit_status == STILL_ACTIVE + && !GetExitCodeProcess (_hProcess, &_exit_status)) { - debug_printf ("Failed to retrieve exit code (%ld)\n", GetLastError ()); - thehandle = INVALID_HANDLE_VALUE; - return _exit_status; + system_printf ("failed to retrieve exit code for %d(%lu), error = %lu", + _cygpid, _winpid, GetLastError ()); + _hProcess = INVALID_HANDLE_VALUE; } - else if (_exit_status == STILL_ACTIVE) - return _exit_status; - /* add new cleanup task etc etc ? */ return _exit_status; } -/* this is single threaded. It's called after the process is removed from the cache, - * but inserts may be attemped by worker threads that have a pointer to it */ -void -process::cleanup () +bool +process::add (cleanup_routine *const entry) { - /* Serialize this */ - EnterCriticalSection (&access); - InterlockedIncrement (&(long)cleaning_up); - class cleanup_routine *entry = head; - while (entry) + assert (entry); + + bool res = false; + EnterCriticalSection (&_access); + + if (!_cleaning_up) { - class cleanup_routine *temp; - entry->cleanup (winpid); - temp = entry->next; - delete entry; - entry = temp; + entry->_next = _routines_head; + _routines_head = entry; + res = true; } - LeaveCriticalSection (&access); + + LeaveCriticalSection (&_access); + return res; } bool -process::add_cleanup_routine (class cleanup_routine *new_cleanup) +process::remove (const cleanup_routine *const entry) { - if (cleaning_up) - return false; - EnterCriticalSection (&access); - /* check that we didn't block with ::cleanup () - * This rigmarole is to get around win9x's glaring missing TryEnterCriticalSection call - * which would be a whole lot easier - */ - if (cleaning_up) - { - LeaveCriticalSection (&access); - return false; - } - new_cleanup->next = head; - head = new_cleanup; - LeaveCriticalSection (&access); - return true; -} + assert (entry); -/* process_cleanup */ -void -process_cleanup::process () -{ - theprocess->cleanup (); - delete theprocess; -} + bool res = false; + EnterCriticalSection (&_access); -/* process_process_param */ -DWORD -process_process_param::request_loop () -{ - process_cache *cache = (process_cache *) queue; - /* always malloc one, so there is no special case in the loop */ - ssize_t HandlesSize = 2; - HANDLE *Handles = (HANDLE *) malloc (sizeof (HANDLE) * HandlesSize); - process **Entries = (process **) malloc (sizeof (LPVOID) * HandlesSize); - /* TODO: put [1] at the end as it will also get done if a process dies? */ - Handles[0] = interrupt; - Handles[1] = cache->cache_add_trigger; - while (cache->active && !shutdown) + if (!_cleaning_up) { - int copied; - copied = -1; - int offset; - offset = 1; - int count; - count = 2; - while ((copied == HandlesSize - 2 - offset) || copied < 0) + cleanup_routine *previous = NULL; + + for (cleanup_routine *ptr = _routines_head; + ptr; + previous = ptr, ptr = ptr->_next) { - /* we need more storage to cope with all the HANDLES */ - if (copied == HandlesSize - 2 - offset) + if (*ptr == *entry) { - HANDLE *temp = (HANDLE *) realloc (Handles, - sizeof (HANDLE) * - HandlesSize + 10); - if (!temp) - { - printf - ("cannot allocate more storage for the handle array!\n"); - exit (1); - } - Handles = temp; - process **ptemp = (process **) realloc (Entries, - sizeof (LPVOID) * - HandlesSize + 10); - if (!ptemp) - { - printf - ("cannot allocate more storage for the handle array!\n"); - exit (1); - } - Entries = ptemp; - HandlesSize += 10; + if (previous) + previous->_next = ptr->_next; + else + _routines_head = ptr->_next; + + safe_delete (ptr); + res = true; + break; } - offset += copied; - copied = - cache->handle_snapshot (&Handles[2], &Entries[2], - HandlesSize - 2 - offset, offset); - count += copied; } - debug_printf ("waiting on %u objects\n", count); - DWORD rc = WaitForMultipleObjects (count, Handles, FALSE, INFINITE); - if (rc == WAIT_FAILED) + } + + LeaveCriticalSection (&_access); + return res; +} + +/* This is single threaded. It's called after the process is removed + * from the cache, but inserts may be attemped by worker threads that + * have a pointer to it. + */ +void +process::cleanup () +{ + EnterCriticalSection (&_access); + assert (!is_active ()); + assert (!_cleaning_up); + InterlockedExchange (&_cleaning_up, true); + cleanup_routine *entry = _routines_head; + _routines_head = NULL; + LeaveCriticalSection (&_access); + + while (entry) + { + cleanup_routine *const ptr = entry; + entry = entry->_next; + ptr->cleanup (this); + safe_delete (ptr); + } +} + +/*****************************************************************************/ + +void +process_cache::submission_loop::request_loop () +{ + assert (this); + assert (_cache); + assert (_interrupt_event); + + while (_running) + _cache->wait_for_processes (_interrupt_event); +} + +/*****************************************************************************/ + +process_cache::process_cache (const unsigned int initial_workers) + : _queue (initial_workers), + _submitter (this, &_queue), // true == interruptible + _processes_count (0), + _processes_head (NULL), + _cache_add_trigger (NULL) +{ + /* there can only be one */ + InitializeCriticalSection (&_cache_write_access); + + _cache_add_trigger = CreateEvent (NULL, // SECURITY_ATTRIBUTES + FALSE, // Auto-reset + FALSE, // Initially non-signalled + NULL); // Anonymous + + if (!_cache_add_trigger) + { + system_printf ("failed to create cache add trigger, error = %lu", + GetLastError ()); + abort (); + } + + _queue.add_submission_loop (&_submitter); +} + +process_cache::~process_cache () +{ + (void) CloseHandle (_cache_add_trigger); + DeleteCriticalSection (&_cache_write_access); +} + +/* This returns the process object to the caller already locked, that + * is, with the object's `access' critical region entered. Thus the + * caller must unlock the object when it's finished with it (via + * process::release ()). It must then not try to access the object + * afterwards, except by going through this routine again, as it may + * have been deleted once it has been unlocked. + */ +class process * +process_cache::process (const pid_t cygpid, const DWORD winpid) +{ + /* TODO: make this more granular, so a search doesn't involve the + * write lock. + */ + EnterCriticalSection (&_cache_write_access); + class process *previous = NULL; + class process *entry = find (winpid, &previous); + + if (!entry) + { + if (_processes_count + SPECIALS_COUNT >= MAXIMUM_WAIT_OBJECTS) { - printf ("Could not wait on the process handles (%ld)!\n", - GetLastError ()); - exit (1); + LeaveCriticalSection (&_cache_write_access); + system_printf (("process limit (%d processes) reached; " + "new connection refused for %d(%lu)"), + MAXIMUM_WAIT_OBJECTS - SPECIALS_COUNT, + cygpid, winpid); + set_errno (EAGAIN); + return NULL; } - int objindex = rc - WAIT_OBJECT_0; - if (objindex > 1 && objindex < count) + + entry = safe_new (class process, cygpid, winpid); + if (!entry->is_active ()) { - debug_printf ("Process %ld has left the building\n", - Entries[objindex]->winpid); - /* fire off the termination routines */ - cache->remove_process (Entries[objindex]); + LeaveCriticalSection (&_cache_write_access); + safe_delete (entry); + set_errno (ESRCH); + return NULL; } - else if (objindex >= 0 && objindex < 2) + + if (previous) { - /* 0 is shutdown - do nothing */ - /* 1 is a cache add event - just rebuild the object list */ + entry->_next = previous->_next; + previous->_next = entry; } else { - printf - ("unexpected return code from WaitForMultiple objects in process_process_param::request_loop\n"); + entry->_next = _processes_head; + _processes_head = entry; } + + _processes_count += 1; + SetEvent (_cache_add_trigger); } - running = false; - return 0; + + EnterCriticalSection (&entry->_access); // To be released by the caller. + LeaveCriticalSection (&_cache_write_access); + assert (entry); + assert (entry->_winpid == winpid); + return entry; } + +void +process_cache::wait_for_processes (const HANDLE interrupt_event) +{ + // Update `_wait_array' with handles of all current processes. + const size_t count = sync_wait_array (interrupt_event); + + debug_printf ("waiting on %u objects in total (%u processes)", + count, _processes_count); + + const DWORD rc = WaitForMultipleObjects (count, _wait_array, + FALSE, INFINITE); + + if (rc == WAIT_FAILED) + { + system_printf ("could not wait on the process handles, error = %lu", + GetLastError ()); + abort (); + } + + const size_t start = rc - WAIT_OBJECT_0; + + if (rc < WAIT_OBJECT_0 || start > count) + { + system_printf (("unexpected return code %rc " + "from WaitForMultipleObjects: " + "expected [%u .. %u)"), + rc, WAIT_OBJECT_0, WAIT_OBJECT_0 + count); + abort (); + } + + // Tell all the processes, from the signalled point up, the bad news. + for (size_t index = start; index != count; index++) + if (_process_array[index]) + check_and_remove_process (index); +} + +/* + * process_cache::sync_wait_array () + * + * Fill-in the wait array with the handles that the cache needs to wait on. + * These handles are: + * - the process_process_param's interrupt event + * - the process_cache's cache_add_trigger event + * - the handle for each live process in the cache. + * + * Return value: the number of live handles in the array. + */ + +size_t +process_cache::sync_wait_array (const HANDLE interrupt_event) +{ + assert (this); + assert (_cache_add_trigger && _cache_add_trigger != INVALID_HANDLE_VALUE); + assert (interrupt_event && interrupt_event != INVALID_HANDLE_VALUE); + + EnterCriticalSection (&_cache_write_access); + + assert (_processes_count + SPECIALS_COUNT <= elements (_wait_array)); + + size_t index = 0; + + for (class process *ptr = _processes_head; ptr; ptr = ptr->_next) + { + assert (ptr->_hProcess && ptr->_hProcess != INVALID_HANDLE_VALUE); + assert (ptr->is_active ()); + + _wait_array[index] = ptr->handle (); + _process_array[index++] = ptr; + + assert (index <= elements (_wait_array)); + } + + /* Sorry for shouting, but THESE MUST BE ADDED AT THE END! */ + /* Well, not strictly `must', but it's more efficient if they are :-) */ + + _wait_array[index] = interrupt_event; + _process_array[index++] = NULL; + + _wait_array[index] = _cache_add_trigger; + _process_array[index++] = NULL; + + /* Phew, back to normal volume now. */ + + assert (index <= elements (_wait_array)); + + LeaveCriticalSection (&_cache_write_access); + + return index; +} + +void +process_cache::check_and_remove_process (const size_t index) +{ + assert (this); + assert (index < elements (_wait_array) - SPECIALS_COUNT); + + class process *const process = _process_array[index]; + + assert (process); + assert (process->handle () == _wait_array[index]); + + if (process->check_exit_code () == STILL_ACTIVE) + return; + + debug_printf ("process %d(%lu) has left the building ($? = %lu)", + process->_cygpid, process->_winpid, process->_exit_status); + + /* Unlink the process object from the process list. */ + + EnterCriticalSection (&_cache_write_access); + + class process *previous = NULL; + + const class process *const tmp = find (process->_winpid, &previous); + + assert (tmp == process); + assert (previous ? previous->_next == process : _processes_head == process); + + if (previous) + previous->_next = process->_next; + else + _processes_head = process->_next; + + _processes_count -= 1; + LeaveCriticalSection (&_cache_write_access); + + /* Schedule any cleanup tasks for this process. */ + _queue.add (safe_new (process_cleanup, process)); +} + +class process * +process_cache::find (const DWORD winpid, class process **previous) +{ + if (previous) + *previous = NULL; + + for (class process *ptr = _processes_head; ptr; ptr = ptr->_next) + if (ptr->_winpid == winpid) + return ptr; + else if (ptr->_winpid > winpid) // The list is sorted by winpid. + return NULL; + else if (previous) + *previous = ptr; + + return NULL; +} + +/*****************************************************************************/ diff --git a/winsup/cygwin/cygserver_shm.cc b/winsup/cygwin/cygserver_shm.cc index 260a5b1bd..18b1c3d83 100755 --- a/winsup/cygwin/cygserver_shm.cc +++ b/winsup/cygwin/cygserver_shm.cc @@ -1,8 +1,9 @@ -/* cygserver_shm.cc: Single unix specification IPC interface for Cygwin +/* cygserver_shm.cc: Single unix specification IPC interface for Cygwin. -Copyright 2001, 2002 Red Hat, Inc. + Copyright 2002 Red Hat, Inc. -Originally written by Robert Collins + Written by Conrad Scott . + Based on code by Robert Collins . This file is part of Cygwin. @@ -10,656 +11,886 @@ This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ -#ifdef __OUTSIDE_CYGWIN__ -#undef __INSIDE_CYGWIN__ -#else -#include "winsup.h" -#endif +#include "woutsup.h" -#ifndef __INSIDE_CYGWIN__ -#define DEBUG 0 -#define system_printf printf -#define debug_printf if (DEBUG) printf -#define api_fatal printf -#include -#include -#endif - -#include #include -#include "cygerrno.h" -#include -#include "security.h" -//#include "fhandler.h" -//#include "dtable.h" -//#include "cygheap.h" +#include #include -//#include "thread.h" -#ifndef __INSIDE_CYGWIN__ -#define __INSIDE_CYGWIN__ -#include -#undef __INSIDE_CYGWIN__ -#else -#include -#endif -//#include "perprocess.h" -#include -#include +#include +#include + +#include "cygserver_ipc.h" #include "cygserver_shm.h" +#include "security.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. - * Also, IPC_PRIVATE keys create unique mappings each time. The shm_ids just - * keep monotonically incrementing - system wide. - */ -size_t -getsystemallocgranularity () +#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 { - SYSTEM_INFO sysinfo; - static size_t buffer_offset = 0; - if (buffer_offset) - return buffer_offset; - GetSystemInfo (&sysinfo); - buffer_offset = sysinfo.dwAllocationGranularity; - return buffer_offset; +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 () + *---------------------------------------------------------------------------*/ -client_request_shm::client_request_shm ():client_request (CYGSERVER_REQUEST_SHM_GET, - sizeof (parameters)) +server_shmmgr::segment_t::~segment_t () { - buffer = (char *) ¶meters; + assert (!_attach_head); + + if (!CloseHandle (_hFileMap)) + syscall_printf ("failed to close file map [handle = 0x%x]: %E", _hFileMap); } -/* FIXME: If building on a 64-bit compiler, the address->int typecast will fail. - * Solution: manually calculate the next id value - */ +/*---------------------------------------------------------------------------* + * server_shmmgr::segment_t::attach () + *---------------------------------------------------------------------------*/ -#if 0 -extern -"C" void * -shmat (int shmid, const void *shmaddr, int parameters.in.shmflg) +int +server_shmmgr::segment_t::attach (class process *const client, + HANDLE & hFileMap) { - class shmid_ds * - shm = (class shmid_ds *) - shmid; //FIXME: verifyable object test + assert (client); - if (shmaddr) + if (!DuplicateHandle (GetCurrentProcess (), + _hFileMap, + client->handle (), + &hFileMap, + 0, + FALSE, // bInheritHandle + DUPLICATE_SAME_ACCESS)) { - //FIXME: requested base address ?! - set_errno (EINVAL); - return (void *) -1; + 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? } - void * - rv = - MapViewOfFile (shm->attachmap, + _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); - (parameters.in.shmflg & SHM_RDONLY) ? - FILE_MAP_READ : FILE_MAP_WRITE, 0, - 0, 0); - - if (!rv) + if (!attptr) { - //FIXME: translate GetLastError() - set_errno (EACCES); - return (void *) -1; - } + attptr = safe_new (attach_t, client); -/* FIXME: this needs to be globally protected to prevent a mismatch betwen - * attach count and attachees list - */ - - InterlockedIncrement (&shm->shm_nattch); - _shmattach * - attachnode = - new - _shmattach; - - attachnode->data = rv; - attachnode->next = - (_shmattach *) InterlockedExchangePointer ((LONG *) & shm->attachhead, - (long int) attachnode); - return rv; -} -#endif - -/* FIXME: evaluate getuid() and getgid() against the requested mode. Then - * choose PAGE_READWRITE | PAGE_READONLY and FILE_MAP_WRITE | FILE_MAP_READ - * appropriately - */ - -/* Test result from openbsd: shm ids are persistent cross process if a handle is left - * open. This could lead to resource starvation: we're not copying that behaviour - * unless we have to. (It will involve acygwin1.dll gloal shared list :[ ). - */ -/* FIXME: shmid should be a verifyable object - */ - -/* FIXME: on NT we should check everything against the SD. On 95 we just emulate. - */ - -extern GENERIC_MAPPING - access_mapping; - -extern int -check_and_dup_handle (HANDLE from_process, HANDLE to_process, - HANDLE from_process_token, - DWORD access, - HANDLE from_handle, - HANDLE * to_handle_ptr, BOOL bInheritHandle); - -//FIXME: where should this live -static shmnode * - shm_head = - NULL; -//FIXME: ditto. -static shmnode * - deleted_head = NULL; -/* must be long for InterlockedIncrement */ -static long - new_id = - 0; -static long - new_private_key = - 0; - -static void -delete_shmnode (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->mapptr); - delete node->shmds; - CloseHandle (node->filemap); - CloseHandle (node->attachmap); - - // free the memory - delete node; - nodeptr = NULL; -} - -void -client_request_shm::serve (transport_layer_base * conn, process_cache * cache) -{ -// DWORD sd_size = 4096; -// char sd_buf[4096]; - PSECURITY_DESCRIPTOR psd = (PSECURITY_DESCRIPTOR) parameters.in.sd_buf; -// /* create a sd for our open requests based on shmflag & 0x01ff */ -// psd = alloc_sd (getuid (), getgid (), cygheap->user.logsrv (), -// parameters.in.shmflg & 0x01ff, psd, &sd_size); - - HANDLE from_process_handle = NULL; - HANDLE token_handle = NULL; - DWORD rc; - - from_process_handle = cache->process (parameters.in.pid)->handle (); - /* possible TODO: reduce the access on the handle before we use it */ - /* Note that unless we do this, we don't need to call CloseHandle - it's kept open - * by the process cache until the process terminates. - * We may need a refcount on the cache however... - */ - if (!from_process_handle) - { - debug_printf ("error opening process (%lu)\n", GetLastError ()); - header.error_code = EACCES; - return; - } - - conn->impersonate_client (); - - rc = OpenThreadToken (GetCurrentThread (), - TOKEN_QUERY, TRUE, &token_handle); - - conn->revert_to_self (); - - if (!rc) - { - debug_printf ("error opening thread token (%lu)\n", GetLastError ()); - header.error_code = EACCES; - CloseHandle (from_process_handle); - return; - } - - - /* we trust the clients request - we will be doing it as them, and - * the worst they can do is open their own permissions - */ - - - SECURITY_ATTRIBUTES sa; - sa.nLength = sizeof (sa); - sa.lpSecurityDescriptor = psd; - sa.bInheritHandle = TRUE; /* the memory structures inherit ok */ - - char *shmname = NULL, *shmaname = NULL; - char stringbuf[29], stringbuf1[29]; - - /* TODO: make this code block a function! */ - if (parameters.in.type == SHM_REATTACH) - { - /* just find and fill out the existing shm_id */ - shmnode *tempnode = shm_head; - while (tempnode) + if (previous) { - if (tempnode->shm_id == parameters.in.shm_id) - { - parameters.out.shm_id = tempnode->shm_id; - parameters.out.key = tempnode->key; - if (check_and_dup_handle - (GetCurrentProcess (), from_process_handle, token_handle, - DUPLICATE_SAME_ACCESS, tempnode->filemap, - ¶meters.out.filemap, TRUE) != 0) - { - debug_printf ("error duplicating filemap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; - } - if (check_and_dup_handle - (GetCurrentProcess (), from_process_handle, token_handle, - DUPLICATE_SAME_ACCESS, tempnode->attachmap, - ¶meters.out.attachmap, TRUE) != 0) - { - debug_printf ("error duplicating attachmap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; - } - CloseHandle (token_handle); - return; - } - tempnode = tempnode->next; - } - header.error_code = EINVAL; - CloseHandle (token_handle); - return; - } - - /* someone attached */ - /* someone can send shm_id's they don't have and currently we will increment those - * attach counts. If someone wants to fix that, please go ahead. - * The problem is that shm_get has nothing to do with the ability to attach. Attach - * requires a permission check, which we get the OS to do in MapViewOfFile. - */ - if (parameters.in.type == SHM_ATTACH) - { - shmnode *tempnode = shm_head; - while (tempnode) - { - if (tempnode->shm_id == parameters.in.shm_id) - { - InterlockedIncrement (&tempnode->shmds->shm_nattch); - header.error_code = 0; - CloseHandle (token_handle); - return; - } - tempnode = tempnode->next; - } - header.error_code = EINVAL; - CloseHandle (token_handle); - return; - } - - /* Someone detached */ - if (parameters.in.type == SHM_DETACH) - { - shmnode *tempnode = shm_head; - while (tempnode) - { - if (tempnode->shm_id == parameters.in.shm_id) - { - InterlockedDecrement (&tempnode->shmds->shm_nattch); - header.error_code = 0; - CloseHandle (token_handle); - return; - } - tempnode = tempnode->next; - } - header.error_code = EINVAL; - CloseHandle (token_handle); - return; - } - - /* Someone wants the ID removed. */ - if (parameters.in.type == SHM_DEL) - { - shmnode **tempnode = &shm_head; - while (*tempnode) - { - if ((*tempnode)->shm_id == parameters.in.shm_id) - { - // unlink from the accessible node list - shmnode *temp2 = *tempnode; - *tempnode = temp2->next; - // link into the deleted list - temp2->next = deleted_head; - deleted_head = temp2; - - // FIXME: when/where do we delete the handles? - if (temp2->shmds->shm_nattch) - { - // FIXME: add to a pending queue? - } - else - { - delete_shmnode (&temp2); - } - - header.error_code = 0; - CloseHandle (token_handle); - return; - } - tempnode = &(*tempnode)->next; - } - header.error_code = EINVAL; - CloseHandle (token_handle); - return; - } - - - if (parameters.in.type == SHM_CREATE) - { - /* FIXME: enter the checking for existing keys mutex. This mutex _must_ be system wide - * to prevent races on shmget. - */ - - if (parameters.in.key == IPC_PRIVATE) - { - /* create the mapping name (CYGWINSHMKPRIVATE_0x01234567 */ - /* The K refers to Key, the actual mapped area has D */ - long private_key = (int) InterlockedIncrement (&new_private_key); - snprintf (stringbuf, 29, "CYGWINSHMKPRIVATE_0x%0x", private_key); - shmname = stringbuf; - snprintf (stringbuf1, 29, "CYGWINSHMDPRIVATE_0x%0x", private_key); - shmaname = stringbuf1; + attptr->_next = previous->_next; + previous->_next = attptr; } else { - /* create the mapping name (CYGWINSHMK0x0123456789abcdef */ - /* The K refers to Key, the actual mapped area has D */ - - snprintf (stringbuf, 29, "CYGWINSHMK0x%0qx", parameters.in.key); - shmname = stringbuf; - snprintf (stringbuf1, 29, "CYGWINSHMD0x%0qx", parameters.in.key); - shmaname = stringbuf1; - debug_printf ("system id strings are \n%s\n%s\n", shmname, - shmaname); - debug_printf ("key input value is 0x%0qx\n", parameters.in.key); + attptr->_next = _attach_head; + _attach_head = attptr; } + } - /* attempt to open the key */ + attptr->_refcnt += 1; - /* get an existing key */ - /* On unix the same shmid identifier is returned on multiple calls to shm_get - * with the same key and size. Different modes is a ?. - */ + 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); - /* walk the list of known keys and return the id if found. remember, we are - * authoritative... - */ + assert (result); - shmnode *tempnode = shm_head; - while (tempnode) - { - if (tempnode->key == parameters.in.key - && parameters.in.key != IPC_PRIVATE) + 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) { - // FIXME: free the mutex - if (parameters.in.size - && tempnode->shmds->shm_segsz < parameters.in.size) - { - header.error_code = EINVAL; - CloseHandle (token_handle); - return; - } - /* FIXME: can the same process call this twice without error ? test - * on unix - */ - if ((parameters.in.shmflg & IPC_CREAT) - && (parameters.in.shmflg & IPC_EXCL)) - { - header.error_code = EEXIST; - debug_printf - ("attempt to exclusively create already created shm_area with key 0x%0qx\n", - parameters.in.key); - // FIXME: free the mutex - CloseHandle (token_handle); - return; - } - // FIXME: do we need to other tests of the requested mode with the - // tempnode->shm_id mode ? testcase on unix needed. - // FIXME how do we do the security test? or - // do we wait for shmat to bother with that? - /* One possibly solution: impersonate the client, and then test we can - * reopen the area. In fact we'll probably have to do that to get - * handles back to them, alternatively just tell them the id, and then - * let them attempt the open. - */ - parameters.out.shm_id = tempnode->shm_id; - if (check_and_dup_handle - (GetCurrentProcess (), from_process_handle, token_handle, - DUPLICATE_SAME_ACCESS, tempnode->filemap, - ¶meters.out.filemap, TRUE) != 0) - { - printf ("error duplicating filemap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; -/*mutex*/ - CloseHandle (token_handle); - return; - } - if (check_and_dup_handle - (GetCurrentProcess (), from_process_handle, token_handle, - DUPLICATE_SAME_ACCESS, tempnode->attachmap, - ¶meters.out.attachmap, TRUE) != 0) - { - printf ("error duplicating attachmap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; -/*mutex*/ - CloseHandle (token_handle); - return; - } + case IPC_STAT: + out_ds = segptr->_ds; + break; - CloseHandle (token_handle); - return; + 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; } - tempnode = tempnode->next; - } - /* couldn't find a currently open shm area. */ + } + break; - /* create one */ - /* do this as the client */ - conn->impersonate_client (); - /* This may need sh_none... it's only a control structure */ - HANDLE filemap = CreateFileMapping (INVALID_HANDLE_VALUE, // system pagefile. - &sa, - PAGE_READWRITE, // protection - 0x00000000, - getsystemallocgranularity (), - shmname // object name - ); - int lasterr = GetLastError (); - conn->revert_to_self (); + case IPC_INFO: + out_shminfo.shmmax = SHMMAX; + out_shminfo.shmmin = SHMMIN; + out_shminfo.shmmni = SHMMNI; + out_shminfo.shmseg = SHMSEG; + out_shminfo.shmall = SHMALL; + break; - if (filemap == NULL) - { - /* We failed to open the filemapping ? */ - system_printf ("failed to open file mapping: %lu\n", - GetLastError ()); - // free the mutex - // we can assume that it exists, and that it was an access problem. - header.error_code = EACCES; - CloseHandle (token_handle); - return; - } + 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; - /* successfully opened the control region mapping */ - /* did we create it ? */ - int oldmapping = lasterr == ERROR_ALREADY_EXISTS; - if (oldmapping) - { - /* should never happen - we are the global daemon! */ -#if 0 - if ((parameters.in.shmflg & IPC_CREAT) - && (parameters.in.shmflg & IPC_EXCL)) -#endif - { - /* FIXME free mutex */ - CloseHandle (filemap); - header.error_code = EEXIST; - CloseHandle (token_handle); - return; - } - } + default: + result = -EINVAL; + break; + } - /* we created a new mapping */ - if (parameters.in.key != IPC_PRIVATE && - (parameters.in.shmflg & IPC_CREAT) == 0) - { - CloseHandle (filemap); - /* FIXME free mutex */ - header.error_code = ENOENT; - CloseHandle (token_handle); - return; - } + LeaveCriticalSection (&_segments_lock); - conn->impersonate_client (); - void *mapptr = MapViewOfFile (filemap, FILE_MAP_WRITE, 0, 0, 0); - conn->revert_to_self (); + 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 ()); - if (!mapptr) - { - CloseHandle (filemap); - //FIXME: close filemap and free the mutex - /* we couldn't access the mapped area with the requested permissions */ - header.error_code = EACCES; - CloseHandle (token_handle); - return; - } + return result; +} - conn->impersonate_client (); - /* Now get the user data */ - HANDLE attachmap = CreateFileMapping (INVALID_HANDLE_VALUE, // system pagefile - &sa, - PAGE_READWRITE, // protection (FIXME) - 0x00000000, - parameters.in.size + - parameters.in.size % - getsystemallocgranularity (), - shmaname // object name - ); - conn->revert_to_self (); +/*---------------------------------------------------------------------------* + * server_shmmgr::shmdt () + *---------------------------------------------------------------------------*/ - if (attachmap == NULL) - { - system_printf ("failed to get shm attachmap\n"); - header.error_code = ENOMEM; - UnmapViewOfFile (mapptr); - CloseHandle (filemap); - /* FIXME exit the mutex */ - CloseHandle (token_handle); - return; - } +int +server_shmmgr::shmdt (const int shmid, class process *const client) +{ + syscall_printf ("shmdt (shmid = %d) for %d(%lu)", + shmid, client->cygpid (), client->winpid ()); - shmid_ds *shmtemp = new shmid_ds; - if (!shmtemp) - { - system_printf ("failed to malloc shm node\n"); - header.error_code = ENOMEM; - UnmapViewOfFile (mapptr); - CloseHandle (filemap); - CloseHandle (attachmap); - /* FIXME exit mutex */ - CloseHandle (token_handle); - return; - } + int result = 0; + EnterCriticalSection (&_segments_lock); - /* 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 = parameters.in.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 = parameters.in.size; - *(shmid_ds *) mapptr = *shmtemp; - shmtemp->mapptr = mapptr; + segment_t *const segptr = find (ipc_ext2int (shmid, IPC_SHMOP)); - /* no need for InterlockedExchange here, we're serialised by the global mutex */ - tempnode = new shmnode; - tempnode->shmds = shmtemp; - tempnode->shm_id = (int) InterlockedIncrement (&new_id); - tempnode->key = parameters.in.key; - tempnode->filemap = filemap; - tempnode->attachmap = attachmap; - tempnode->next = shm_head; - shm_head = tempnode; + if (!segptr) + result = -EINVAL; + else + result = segptr->detach (client); - /* we now have the area in the daemon list, opened. + if (!result) + _shm_atts -= 1; - FIXME: leave the system wide shm mutex */ + if (!result && segptr->is_pending_delete ()) + delete_segment (segptr); - parameters.out.shm_id = tempnode->shm_id; - if (check_and_dup_handle (GetCurrentProcess (), from_process_handle, - token_handle, - DUPLICATE_SAME_ACCESS, - tempnode->filemap, ¶meters.out.filemap, - TRUE) != 0) - { - printf ("error duplicating filemap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; - CloseHandle (token_handle); -/* mutex et al */ - return; - } - if (check_and_dup_handle (GetCurrentProcess (), from_process_handle, - token_handle, - DUPLICATE_SAME_ACCESS, - tempnode->attachmap, - ¶meters.out.attachmap, TRUE) != 0) - { - printf ("error duplicating attachmap handle (%lu)\n", - GetLastError ()); - header.error_code = EACCES; - CloseHandle (from_process_handle); - CloseHandle (token_handle); -/* more cleanup... yay! */ - return; - } - CloseHandle (token_handle); + 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; } - header.error_code = ENOSYS; - CloseHandle (token_handle); + // 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); - return; + 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)); } diff --git a/winsup/cygwin/cygserver_shm.h b/winsup/cygwin/cygserver_shm.h index f1dcaa53f..b1ff353da 100644 --- a/winsup/cygwin/cygserver_shm.h +++ b/winsup/cygwin/cygserver_shm.h @@ -1,7 +1,9 @@ -/* cygserver_shm.h +/* cygserver_shm.h: Single unix specification IPC interface for Cygwin. - Copyright 2001, 2002 Red Hat Inc. - Written by Robert Collins + Copyright 2002 Red Hat, Inc. + + Written by Conrad Scott . + Based on code by Robert Collins . This file is part of Cygwin. @@ -9,84 +11,137 @@ This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ +#ifndef __CYGSERVER_SHM_H__ +#define __CYGSERVER_SHM_H__ + #include -#include "cygwin/cygserver_transport.h" +#include + +#include +#include + +#include "cygserver_ipc.h" + #include "cygwin/cygserver.h" -#define SHM_CREATE 0 -#define SHM_REATTACH 1 -#define SHM_ATTACH 2 -#define SHM_DETACH 3 -#define SHM_DEL 4 +/*---------------------------------------------------------------------------* + * Values for the shminfo entries. + * + * Nb. The values are segregated between two enums so that the `small' + * values aren't promoted to `unsigned long' equivalents. + *---------------------------------------------------------------------------*/ +enum + { + SHMMAX = ULONG_MAX, + SHMSEG = ULONG_MAX, + SHMALL = ULONG_MAX + }; + +enum + { + SHMMIN = 1, + SHMMNI = IPCMNI // Must be <= IPCMNI. + }; + +/*---------------------------------------------------------------------------* + * class client_request_shm + *---------------------------------------------------------------------------*/ + +#ifndef __INSIDE_CYGWIN__ +class transport_layer_base; +class process_cache; +#endif class client_request_shm : public client_request { - public: -#ifndef __INSIDE_CYGWIN__ - virtual void serve (transport_layer_base *conn, process_cache *cache); + friend class client_request; + +public: + enum shmop_t + { + SHMOP_shmat, + SHMOP_shmctl, + SHMOP_shmdt, + SHMOP_shmget + }; + +#ifdef __INSIDE_CYGWIN__ + client_request_shm (int shmid, int shmflg); // shmat + client_request_shm (int shmid, int cmd, const struct shmid_ds *); // shmctl + client_request_shm (int shmid); // shmdt + client_request_shm (key_t, size_t, int shmflg); // shmget #endif - client_request_shm (key_t, size_t, int, char psdbuf[4096], pid_t); + + // Accessors for out parameters. + + int shmid () const + { + assert (!error_code ()); + return _parameters.out.shmid; + } + + HANDLE hFileMap () const + { + assert (!error_code ()); + return _parameters.out.hFileMap; + } + + const struct shmid_ds & ds () const + { + assert (!error_code ()); + return _parameters.out.ds; + } + + const struct shminfo & shminfo () const + { + assert (!error_code ()); + return _parameters.out.shminfo; + } + + const struct shm_info & shm_info () const + { + assert (!error_code ()); + return _parameters.out.shm_info; + } + +private: + union + { + struct + { + shmop_t shmop; + key_t key; + size_t size; + int shmflg; + int shmid; + int cmd; + pid_t cygpid; + DWORD winpid; + uid_t uid; + gid_t gid; + struct shmid_ds ds; + } in; + + struct { + int shmid; + union + { + HANDLE hFileMap; + struct shmid_ds ds; + struct shminfo shminfo; + struct shm_info shm_info; + }; + } out; + } _parameters; + +#ifndef __INSIDE_CYGWIN__ client_request_shm (); - client_request_shm (int, int, pid_t); - client_request_shm (int, int); - union { - struct {int type; pid_t pid; int shm_id; key_t key; size_t size; int shmflg; char sd_buf[4096];} in; - struct {int shm_id; HANDLE filemap; HANDLE attachmap; key_t key;} out; - } parameters; -}; +#endif #ifndef __INSIDE_CYGWIN__ -class shm_cleanup : cleanup_routine -{ -public: - virtual void cleanup (long winpid); -}; + virtual void serve (transport_layer_base *, process_cache *); #endif -#if 0 -class _shmattach { -public: - void *data; - class _shmattach *next; }; -class shmid_ds { -public: - struct ipc_perm shm_perm; - size_t shm_segsz; - pid_t shm_lpid; - pid_t shm_cpid; - shmatt_t shm_nattch; - time_t shm_atime; - time_t shm_dtime; - time_t shm_ctime; - HANDLE filemap; - HANDLE attachmap; - void *mapptr; - class _shmattach *attachhead; -}; - -class shmnode { -public: - class shmid_ds * shmid; - class shmnode *next; - key_t key; -}; -//.... -struct shmid_ds { - struct ipc_perm shm_perm; - size_t shm_segsz; - pid_t shm_lpid; - pid_t shm_cpid; - shmatt_t shm_nattch; - time_t shm_atime; - time_t shm_dtime; - time_t shm_ctime; -}; - -void *shmat(int, const void *, int); -int shmctl(int, int, struct shmid_ds *); -int shmdt(const void *); -int shmget(key_t, size_t, int); - -#endif +#endif /* __CYGSERVER_SHM_H__ */ diff --git a/winsup/cygwin/cygserver_transport.cc b/winsup/cygwin/cygserver_transport.cc index 01f044406..8684a6148 100755 --- a/winsup/cygwin/cygserver_transport.cc +++ b/winsup/cygwin/cygserver_transport.cc @@ -4,89 +4,48 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +/* to allow this to link into cygwin and the .dll, a little magic is needed. */ +#ifdef __OUTSIDE_CYGWIN__ +#include "woutsup.h" +#else +#include "winsup.h" +#endif -#include -#include -#include -#include -#include #include -#include -#include "wincap.h" + +#include "safe_memory.h" + #include "cygwin/cygserver_transport.h" #include "cygwin/cygserver_transport_pipes.h" #include "cygwin/cygserver_transport_sockets.h" -/* to allow this to link into cygwin and the .dll, a little magic is needed. */ -#ifndef __OUTSIDE_CYGWIN__ -#include "winsup.h" -#else -#define debug_printf printf -#endif - /* The factory */ -class transport_layer_base *create_server_transport() +transport_layer_base * +create_server_transport () { - transport_layer_base *temp; - /* currently there is only the base class! */ if (wincap.is_winnt ()) - temp = new transport_layer_pipes (); + return safe_new0 (transport_layer_pipes); else - temp = new transport_layer_sockets (); - return temp; + return safe_new0 (transport_layer_sockets); } - -transport_layer_base::transport_layer_base () -{ - /* should we throw an error of some sort ? */ -} - -void -transport_layer_base::listen () -{ -} - -class transport_layer_base * -transport_layer_base::accept () -{ - return NULL; -} - -void -transport_layer_base::close() -{ -} - -ssize_t -transport_layer_base::read (char *buf, size_t len) -{ - return 0; -} - -ssize_t -transport_layer_base::write (char *buf, size_t len) -{ - return 0; -} - -bool -transport_layer_base::connect () -{ - return false; -} +#ifndef __INSIDE_CYGWIN__ void transport_layer_base::impersonate_client () -{ -} +{} void transport_layer_base::revert_to_self () -{ -} +{} + +#endif /* !__INSIDE_CYGWIN__ */ + +transport_layer_base::~transport_layer_base () +{} diff --git a/winsup/cygwin/cygserver_transport_pipes.cc b/winsup/cygwin/cygserver_transport_pipes.cc index f2221700f..f318a7592 100755 --- a/winsup/cygwin/cygserver_transport_pipes.cc +++ b/winsup/cygwin/cygserver_transport_pipes.cc @@ -4,207 +4,360 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +/* to allow this to link into cygwin and the .dll, a little magic is needed. */ +#ifdef __OUTSIDE_CYGWIN__ +#include "woutsup.h" +#else +#include "winsup.h" +#endif -#include -#include -#include -#include #include -#include + +#include +#include #include -#include "wincap.h" +#include +#include + +#include "cygerrno.h" #include "cygwin/cygserver_transport.h" #include "cygwin/cygserver_transport_pipes.h" -/* to allow this to link into cygwin and the .dll, a little magic is needed. */ -#ifndef __OUTSIDE_CYGWIN__ -#include "winsup.h" -#else -#define DEBUG 0 -#define debug_printf if (DEBUG) printf +#ifndef __INSIDE_CYGWIN__ +#include "cygwin/cygserver.h" #endif -//SECURITY_DESCRIPTOR transport_layer_pipes::sd; -//SECURITY_ATTRIBUTES transport_layer_pipes::sec_none_nih, transport_layer_pipes::sec_all_nih; -//bool transport_layer_pipes::inited = false; +enum + { + MAX_WAIT_NAMED_PIPE_RETRY = 64, + WAIT_NAMED_PIPE_TIMEOUT = 10 // milliseconds + }; -transport_layer_pipes::transport_layer_pipes (HANDLE new_pipe) +#ifndef __INSIDE_CYGWIN__ + +static pthread_once_t pipe_instance_lock_once = PTHREAD_ONCE_INIT; +static CRITICAL_SECTION pipe_instance_lock; +static long pipe_instance = 0; + +static void +initialise_pipe_instance_lock () { - inited = false; //FIXME: allow inited, sd, all_nih_.. to be static members - pipe = new_pipe; - if (inited != true) - init_security(); -}; + assert (pipe_instance == 0); + InitializeCriticalSection (&pipe_instance_lock); +} + +#endif /* !__INSIDE_CYGWIN__ */ + +#ifndef __INSIDE_CYGWIN__ + +transport_layer_pipes::transport_layer_pipes (const HANDLE hPipe) + : _pipe_name (""), + _hPipe (hPipe), + _is_accepted_endpoint (true), + _is_listening_endpoint (false) +{ + assert (_hPipe); + assert (_hPipe != INVALID_HANDLE_VALUE); + + init_security (); +} + +#endif /* !__INSIDE_CYGWIN__ */ transport_layer_pipes::transport_layer_pipes () + : _pipe_name ("\\\\.\\pipe\\cygwin_lpc"), + _hPipe (NULL), + _is_accepted_endpoint (false), + _is_listening_endpoint (false) { - inited = false; - pipe = NULL; - strcpy(pipe_name, "\\\\.\\pipe\\cygwin_lpc"); - if (inited != true) - init_security(); + init_security (); } void -transport_layer_pipes::init_security() +transport_layer_pipes::init_security () { + assert (wincap.has_security ()); + /* FIXME: pthread_once or equivalent needed */ - InitializeSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION); - SetSecurityDescriptorDacl (&sd, TRUE, 0, FALSE); - sec_none_nih.nLength = sec_all_nih.nLength = sizeof (SECURITY_ATTRIBUTES); - sec_none_nih.bInheritHandle = sec_all_nih.bInheritHandle = FALSE; - sec_none_nih.lpSecurityDescriptor = NULL; - sec_all_nih.lpSecurityDescriptor = &sd; - inited = true; + InitializeSecurityDescriptor (&_sd, SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl (&_sd, TRUE, NULL, FALSE); + + _sec_all_nih.nLength = sizeof (SECURITY_ATTRIBUTES); + _sec_all_nih.lpSecurityDescriptor = &_sd; + _sec_all_nih.bInheritHandle = FALSE; } -void +transport_layer_pipes::~transport_layer_pipes () +{ + close (); +} + +#ifndef __INSIDE_CYGWIN__ + +int transport_layer_pipes::listen () { + assert (!_hPipe); + assert (!_is_accepted_endpoint); + assert (!_is_listening_endpoint); + + _is_listening_endpoint = true; + /* no-op */ + return 0; } class transport_layer_pipes * -transport_layer_pipes::accept () +transport_layer_pipes::accept (bool *const recoverable) { - if (pipe) + assert (!_hPipe); + assert (!_is_accepted_endpoint); + assert (_is_listening_endpoint); + + pthread_once (&pipe_instance_lock_once, &initialise_pipe_instance_lock); + + EnterCriticalSection (&pipe_instance_lock); + + // Read: http://www.securityinternals.com/research/papers/namedpipe.php + // See also the Microsoft security bulletins MS00-053 and MS01-031. + + // FIXME: Remove FILE_CREATE_PIPE_INSTANCE. + + const bool first_instance = (pipe_instance == 0); + + const HANDLE accept_pipe = + CreateNamedPipe (_pipe_name, + (PIPE_ACCESS_DUPLEX + | (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0)), + (PIPE_TYPE_BYTE | PIPE_WAIT), + PIPE_UNLIMITED_INSTANCES, + 0, 0, 1000, + &_sec_all_nih); + + const bool duplicate = (accept_pipe == INVALID_HANDLE_VALUE + && pipe_instance == 0 + && GetLastError () == ERROR_ACCESS_DENIED); + + if (accept_pipe != INVALID_HANDLE_VALUE) + InterlockedIncrement (&pipe_instance); + + LeaveCriticalSection (&pipe_instance_lock); + + if (duplicate) { - debug_printf ("Already have a pipe in this %p\n",this); + *recoverable = false; + system_printf ("failed to create named pipe: " + "is the daemon already running?"); return NULL; } - pipe = CreateNamedPipe (pipe_name, - PIPE_ACCESS_DUPLEX, - PIPE_TYPE_BYTE | PIPE_WAIT, - PIPE_UNLIMITED_INSTANCES, - 0, 0, 1000, - &sec_all_nih ); - if (pipe == INVALID_HANDLE_VALUE) + if (accept_pipe == INVALID_HANDLE_VALUE) { - debug_printf ("error creating pipe (%lu)\n.", GetLastError ()); + debug_printf ("error creating pipe (%lu).", GetLastError ()); + *recoverable = true; // FIXME: case analysis? return NULL; } - if ( !ConnectNamedPipe ( pipe, NULL ) && - GetLastError () != ERROR_PIPE_CONNECTED) + assert (accept_pipe); + + if (!ConnectNamedPipe (accept_pipe, NULL) + && GetLastError () != ERROR_PIPE_CONNECTED) { - printf ("error connecting to pipe (%lu)\n.", GetLastError ()); - CloseHandle (pipe); - pipe = NULL; + debug_printf ("error connecting to pipe (%lu)\n.", GetLastError ()); + (void) CloseHandle (accept_pipe); + *recoverable = true; // FIXME: case analysis? return NULL; } - transport_layer_pipes *new_conn = new transport_layer_pipes (pipe); - pipe = NULL; - - return new_conn; + return safe_new (transport_layer_pipes, accept_pipe); } +#endif /* !__INSIDE_CYGWIN__ */ + void -transport_layer_pipes::close() +transport_layer_pipes::close () { - debug_printf ("closing pipe %p\n", pipe); - if (pipe && pipe != INVALID_HANDLE_VALUE) + // verbose: debug_printf ("closing pipe %p", _hPipe); + + if (_hPipe) { - FlushFileBuffers (pipe); - DisconnectNamedPipe (pipe); - CloseHandle (pipe); + assert (_hPipe != INVALID_HANDLE_VALUE); + +#ifndef __INSIDE_CYGWIN__ + + if (_is_accepted_endpoint) + { + (void) FlushFileBuffers (_hPipe); // Blocks until client reads. + (void) DisconnectNamedPipe (_hPipe); + EnterCriticalSection (&pipe_instance_lock); + (void) CloseHandle (_hPipe); + assert (pipe_instance > 0); + InterlockedDecrement (&pipe_instance); + LeaveCriticalSection (&pipe_instance_lock); + } + else + (void) CloseHandle (_hPipe); + +#else /* __INSIDE_CYGWIN__ */ + + assert (!_is_accepted_endpoint); + (void) ForceCloseHandle (_hPipe); + +#endif /* __INSIDE_CYGWIN__ */ + + _hPipe = NULL; } } ssize_t -transport_layer_pipes::read (char *buf, size_t len) +transport_layer_pipes::read (void *const buf, const size_t len) { - debug_printf ("reading from pipe %p\n", pipe); - if (!pipe || pipe == INVALID_HANDLE_VALUE) - return -1; + // verbose: debug_printf ("reading from pipe %p", _hPipe); - DWORD bytes_read; - DWORD rc = ReadFile (pipe, buf, len, &bytes_read, NULL); - if (!rc) + assert (_hPipe); + assert (_hPipe != INVALID_HANDLE_VALUE); + assert (!_is_listening_endpoint); + + DWORD count; + if (!ReadFile (_hPipe, buf, len, &count, NULL)) { - debug_printf ("error reading from pipe (%lu)\n", GetLastError ()); + debug_printf ("error reading from pipe (%lu)", GetLastError ()); + set_errno (EINVAL); // FIXME? return -1; } - return bytes_read; + + return count; } ssize_t -transport_layer_pipes::write (char *buf, size_t len) +transport_layer_pipes::write (void *const buf, const size_t len) { - debug_printf ("writing to pipe %p\n", pipe); - DWORD bytes_written, rc; - if (!pipe || pipe == INVALID_HANDLE_VALUE) - return -1; + // verbose: debug_printf ("writing to pipe %p", _hPipe); - rc = WriteFile (pipe, buf, len, &bytes_written, NULL); - if (!rc) + assert (_hPipe); + assert (_hPipe != INVALID_HANDLE_VALUE); + assert (!_is_listening_endpoint); + + DWORD count; + if (!WriteFile (_hPipe, buf, len, &count, NULL)) { - debug_printf ("error writing to pipe (%lu)\n", GetLastError ()); + debug_printf ("error writing to pipe, error = %lu", GetLastError ()); + set_errno (EINVAL); // FIXME? return -1; } - return bytes_written; + + return count; } -bool +/* + * This routine holds a static variable, assume_cygserver, that is set + * if the transport has good reason to think that cygserver is + * running, i.e. if if successfully connected to it with the previous + * attempt. If this is set, the code tries a lot harder to get a + * connection, making the assumption that any failures are just + * congestion and overloading problems. + */ + +int transport_layer_pipes::connect () { - if (pipe && pipe != INVALID_HANDLE_VALUE) + assert (!_hPipe); + assert (!_is_accepted_endpoint); + assert (!_is_listening_endpoint); + + static bool assume_cygserver = false; + + BOOL rc = TRUE; + int retries = 0; + + while (rc) { - debug_printf ("Already have a pipe in this %p\n",this); - return false; - } + _hPipe = CreateFile (_pipe_name, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &_sec_all_nih, + OPEN_EXISTING, + SECURITY_IMPERSONATION, + NULL); - while (1) - { - pipe = CreateFile (pipe_name, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - &sec_all_nih, - OPEN_EXISTING, - 0, NULL); - - if (pipe != INVALID_HANDLE_VALUE) - /* got the pipe */ - return true; - - if (GetLastError () != ERROR_PIPE_BUSY) + if (_hPipe != INVALID_HANDLE_VALUE) { - debug_printf ("Error opening the pipe (%lu)\n", GetLastError ()); - pipe = NULL; - return false; + assert (_hPipe); +#ifdef __INSIDE_CYGWIN__ + ProtectHandle (_hPipe); +#endif + assume_cygserver = true; + return 0; } - if (!WaitNamedPipe (pipe_name, 20000)) - debug_printf ( "error connecting to server pipe after 20 seconds (%lu)\n", GetLastError () ); - /* We loop here, because the pipe exists but is busy. If it doesn't exist - * the != ERROR_PIPE_BUSY will catch it. + + _hPipe = NULL; + + if (!assume_cygserver && GetLastError () != ERROR_PIPE_BUSY) + { + debug_printf ("Error opening the pipe (%lu)", GetLastError ()); + return -1; + } + + /* Note: `If no instances of the specified named pipe exist, the + * WaitNamedPipe function returns immediately, regardless of the + * time-out value.' Thus the explicit Sleep if the call fails + * with ERROR_FILE_NOT_FOUND. */ + while (retries != MAX_WAIT_NAMED_PIPE_RETRY + && !(rc = WaitNamedPipe (_pipe_name, WAIT_NAMED_PIPE_TIMEOUT))) + { + if (GetLastError () == ERROR_FILE_NOT_FOUND) + Sleep (0); // Give the server a chance. + + retries += 1; + } } + + assert (retries == MAX_WAIT_NAMED_PIPE_RETRY); + + system_printf ("lost connection to cygserver, error = %lu", + GetLastError ()); + + assume_cygserver = false; + + return -1; } +#ifndef __INSIDE_CYGWIN__ + void transport_layer_pipes::impersonate_client () { - debug_printf ("impersonating pipe %p\n", pipe); - if (pipe && pipe != INVALID_HANDLE_VALUE) + assert (_hPipe); + assert (_hPipe != INVALID_HANDLE_VALUE); + assert (_is_accepted_endpoint); + + // verbose: debug_printf ("impersonating pipe %p", _hPipe); + if (_hPipe) { - BOOL rv = ImpersonateNamedPipeClient (pipe); - if (!rv) - debug_printf ("Failed to Impersonate the client, (%lu)\n", GetLastError ()); + assert (_hPipe != INVALID_HANDLE_VALUE); + + if (!ImpersonateNamedPipeClient (_hPipe)) + debug_printf ("Failed to Impersonate the client, (%lu)", + GetLastError ()); } - debug_printf("I am who you are\n"); + // verbose: debug_printf ("I am who you are"); } void transport_layer_pipes::revert_to_self () { + assert (_is_accepted_endpoint); + RevertToSelf (); - debug_printf("I am who I yam\n"); + // verbose: debug_printf ("I am who I yam"); } +#endif /* !__INSIDE_CYGWIN__ */ diff --git a/winsup/cygwin/cygserver_transport_sockets.cc b/winsup/cygwin/cygserver_transport_sockets.cc index a3a98b3e5..6ade14bff 100755 --- a/winsup/cygwin/cygserver_transport_sockets.cc +++ b/winsup/cygwin/cygserver_transport_sockets.cc @@ -4,128 +4,384 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ +/* to allow this to link into cygwin and the .dll, a little magic is needed. */ +#ifdef __OUTSIDE_CYGWIN__ +#include "woutsup.h" +#else +#include "winsup.h" +#endif + +#include +#include +#include + +#include #include #include #include -#include -#include -#include -#include -#include "wincap.h" + #include "cygwin/cygserver_transport.h" #include "cygwin/cygserver_transport_sockets.h" /* to allow this to link into cygwin and the .dll, a little magic is needed. */ #ifndef __OUTSIDE_CYGWIN__ -#include "winsup.h" -extern "C" int -cygwin_socket (int af, int type, int protocol); -extern "C" int -cygwin_connect (int fd, - const struct sockaddr *name, - int namelen); -extern "C" int -cygwin_accept (int fd, struct sockaddr *peer, int *len); -extern "C" int -cygwin_listen (int fd, int backlog); -extern "C" int -cygwin_bind (int fd, const struct sockaddr *my_addr, int addrlen); -#else -#define cygwin_accept(A,B,C) ::accept(A,B,C) -#define cygwin_socket(A,B,C) ::socket(A,B,C) -#define cygwin_listen(A,B) ::listen(A,B) -#define cygwin_bind(A,B,C) ::bind(A,B,C) -#define cygwin_connect(A,B,C) ::connect(A,B,C) -#define debug_printf printf -#endif +extern "C" int cygwin_accept (int fd, struct sockaddr *, int *len); +extern "C" int cygwin_bind (int fd, const struct sockaddr *, int len); +extern "C" int cygwin_connect (int fd, const struct sockaddr *, int len); +extern "C" int cygwin_listen (int fd, int backlog); +extern "C" int cygwin_shutdown (int fd, int how); +extern "C" int cygwin_socket (int af, int type, int protocol); -transport_layer_sockets::transport_layer_sockets (int newfd): fd(newfd) +#else /* __OUTSIDE_CYGWIN__ */ + +#define cygwin_accept(A,B,C) ::accept (A,B,C) +#define cygwin_bind(A,B,C) ::bind (A,B,C) +#define cygwin_connect(A,B,C) ::connect (A,B,C) +#define cygwin_listen(A,B) ::listen (A,B) +#define cygwin_shutdown(A,B) ::shutdown (A,B) +#define cygwin_socket(A,B,C) ::socket (A,B,C) + +#endif /* __OUTSIDE_CYGWIN__ */ + +enum + { + MAX_CONNECT_RETRY = 64 + }; + +transport_layer_sockets::transport_layer_sockets (const int fd) + : _fd (fd), + _addr_len (0), + _is_accepted_endpoint (true), + _is_listening_endpoint (false) { - /* This may not be needed in this constructor - it's only used - * when creating a connection via bind or connect - */ - sockdetails.sa_family = AF_UNIX; - strcpy (sockdetails.sa_data, "/tmp/cygdaemo"); - sdlen = strlen(sockdetails.sa_data) + sizeof(sockdetails.sa_family); -}; + assert (_fd != -1); -transport_layer_sockets::transport_layer_sockets (): fd (-1) -{ - sockdetails.sa_family = AF_UNIX; - strcpy (sockdetails.sa_data, "/tmp/cygdaemo"); - sdlen = strlen(sockdetails.sa_data) + sizeof(sockdetails.sa_family); + memset (&_addr, '\0', sizeof (_addr)); } -void +transport_layer_sockets::transport_layer_sockets () + : _fd (-1), + _addr_len (0), + _is_accepted_endpoint (false), + _is_listening_endpoint (false) +{ + memset (&_addr, '\0', sizeof (_addr)); + + _addr.sun_family = AF_UNIX; + strcpy (_addr.sun_path, "/tmp/cygdaemo"); // FIXME: $TMP? + _addr_len = SUN_LEN (&_addr); +} + +transport_layer_sockets::~transport_layer_sockets () +{ + close (); +} + +#ifndef __INSIDE_CYGWIN__ + +int transport_layer_sockets::listen () { - /* we want a thread pool based approach. */ - if ((fd = cygwin_socket (AF_UNIX, SOCK_STREAM,0)) < 0) - printf ("Socket not created error %d\n", errno); - if (cygwin_bind(fd, &sockdetails, sdlen)) - printf ("Bind doesn't like you. Tsk Tsk. Bind said %d\n", errno); - if (cygwin_listen(fd, 5) < 0) - printf ("And the OS just isn't listening, all it says is %d\n", errno); + assert (_fd == -1); + assert (!_is_accepted_endpoint); + assert (!_is_listening_endpoint); + + debug_printf ("listen () [this = %p]", this); + + struct stat sbuf; + + if (stat (_addr.sun_path, &sbuf) == -1) + { + if (errno != ENOENT) + { + system_printf ("cannot access socket file `%s': %s", + _addr.sun_path, strerror (errno)); + return -1; + } + } + else if (S_ISSOCK (sbuf.st_mode)) + { + // The socket already exists: is a duplicate cygserver running? + + const int newfd = cygwin_socket (AF_UNIX, SOCK_STREAM, 0); + + if (newfd == -1) + { + system_printf ("failed to create UNIX domain socket: %s", + strerror (errno)); + return -1; + } + + if (cygwin_connect (newfd, (struct sockaddr *) &_addr, _addr_len) == 0) + { + system_printf ("the daemon is already running"); + (void) cygwin_shutdown (newfd, SHUT_WR); + char buf[BUFSIZ]; + while (::read (newfd, buf, sizeof (buf)) > 0) + {} + (void) ::close (newfd); + return -1; + } + + if (unlink (_addr.sun_path) == -1) + { + system_printf ("failed to remove `%s': %s", + _addr.sun_path, strerror (errno)); + (void) ::close (newfd); + return -1; + } + } + else + { + system_printf ("cannot create socket `%s': File already exists", + _addr.sun_path); + return -1; + } + + _fd = cygwin_socket (AF_UNIX, SOCK_STREAM, 0); + + if (_fd == -1) + { + system_printf ("failed to create UNIX domain socket: %s", + strerror (errno)); + return -1; + } + + if (cygwin_bind (_fd, (struct sockaddr *) &_addr, _addr_len) == -1) + { + const int saved_errno = errno; + close (); + errno = saved_errno; + system_printf ("failed to bind UNIX domain socket `%s': %s", + _addr.sun_path, strerror (errno)); + return -1; + } + + _is_listening_endpoint = true; // i.e. this really means "have bound". + + if (cygwin_listen (_fd, SOMAXCONN) == -1) + { + const int saved_errno = errno; + close (); + errno = saved_errno; + system_printf ("failed to listen on UNIX domain socket `%s': %s", + _addr.sun_path, strerror (errno)); + return -1; + } + + debug_printf ("0 = listen () [this = %p, fd = %d]", this, _fd); + + return 0; } class transport_layer_sockets * -transport_layer_sockets::accept () +transport_layer_sockets::accept (bool *const recoverable) { - /* FIXME: check we have listened */ - int new_fd; + assert (_fd != -1); + assert (!_is_accepted_endpoint); + assert (_is_listening_endpoint); - if ((new_fd = cygwin_accept(fd, &sockdetails, &sdlen)) < 0) + debug_printf ("accept () [this = %p, fd = %d]", this, _fd); + + struct sockaddr_un client_addr; + socklen_t client_addr_len = sizeof (client_addr); + + const int accept_fd = + cygwin_accept (_fd, (struct sockaddr *) &client_addr, &client_addr_len); + + if (accept_fd == -1) { - printf ("Nup, could' accept. %d\n",errno); + system_printf ("failed to accept connection: %s", strerror (errno)); + switch (errno) + { + case ECONNABORTED: + case EINTR: + case EMFILE: + case ENFILE: + case ENOBUFS: + case ENOMEM: + *recoverable = true; + break; + + default: + *recoverable = false; + break; + } return NULL; } - transport_layer_sockets *new_conn = new transport_layer_sockets (new_fd); - - return new_conn; + debug_printf ("%d = accept () [this = %p, fd = %d]", accept_fd, this, _fd); + return safe_new (transport_layer_sockets, accept_fd); } +#endif /* !__INSIDE_CYGWIN__ */ + void -transport_layer_sockets::close() +transport_layer_sockets::close () { - /* FIXME - are we open? */ - ::close (fd); + debug_printf ("close () [this = %p, fd = %d]", this, _fd); + + if (_is_listening_endpoint) + (void) unlink (_addr.sun_path); + + if (_fd != -1) + { + (void) cygwin_shutdown (_fd, SHUT_WR); + if (!_is_listening_endpoint) + { + char buf[BUFSIZ]; + while (::read (_fd, buf, sizeof (buf)) > 0) + {} + } + (void) ::close (_fd); + _fd = -1; + } + + _is_listening_endpoint = false; } ssize_t -transport_layer_sockets::read (char *buf, size_t len) +transport_layer_sockets::read (void *const buf, const size_t buf_len) { - /* FIXME: are we open? */ - return ::read (fd, buf, len); + assert (_fd != -1); + assert (!_is_listening_endpoint); + + assert (buf); + assert (buf_len > 0); + + // verbose: debug_printf ("read (buf = %p, len = %u) [this = %p, fd = %d]", + // buf, buf_len, this, _fd); + + char *read_buf = static_cast (buf); + size_t read_buf_len = buf_len; + ssize_t res = 0; + + while (read_buf_len != 0 + && (res = ::read (_fd, read_buf, read_buf_len)) > 0) + { + read_buf += res; + read_buf_len -= res; + + assert (read_buf_len >= 0); + } + + if (res != -1) + { + if (res == 0) + errno = EIO; // FIXME? + + res = buf_len - read_buf_len; + } + + if (res != static_cast (buf_len)) + debug_printf ("%d = read (buf = %p, len = %u) [this = %p, fd = %d]: %s", + res, buf, buf_len, this, _fd, + (res == -1 ? strerror (errno) : "EOF")); + else + { + // verbose: debug_printf ("%d = read (buf = %p, len = %u) [this = %p, fd = %d]", + // res, buf, buf_len, this, _fd); + } + + return res; } ssize_t -transport_layer_sockets::write (char *buf, size_t len) +transport_layer_sockets::write (void *const buf, const size_t buf_len) { - /* FIXME: are we open? */ - return ::write (fd, buf, len); + assert (_fd != -1); + assert (!_is_listening_endpoint); + + assert (buf); + assert (buf_len > 0); + + // verbose: debug_printf ("write (buf = %p, len = %u) [this = %p, fd = %d]", + // buf, buf_len, this, _fd); + + char *write_buf = static_cast (buf); + size_t write_buf_len = buf_len; + ssize_t res = 0; + + while (write_buf_len != 0 + && (res = ::write (_fd, write_buf, write_buf_len)) > 0) + { + write_buf += res; + write_buf_len -= res; + + assert (write_buf_len >= 0); + } + + if (res != -1) + { + if (res == 0) + errno = EIO; // FIXME? + + res = buf_len - write_buf_len; + } + + if (res != static_cast (buf_len)) + debug_printf ("%d = write (buf = %p, len = %u) [this = %p, fd = %d]: %s", + res, buf, buf_len, this, _fd, + (res == -1 ? strerror (errno) : "EOF")); + else + { + // verbose: debug_printf ("%d = write (buf = %p, len = %u) [this = %p, fd = %d]", + // res, buf, buf_len, this, _fd); + } + + return res; } -bool +int transport_layer_sockets::connect () { - /* are we already connected? */ - if (fd != -1) - return false; - fd = cygwin_socket (AF_UNIX, SOCK_STREAM, 0); - if (cygwin_connect (fd, &sockdetails, sdlen) < 0) + assert (_fd == -1); + assert (!_is_accepted_endpoint); + assert (!_is_listening_endpoint); + + static bool assume_cygserver = false; + + debug_printf ("connect () [this = %p]", this); + + for (int retries = 0; retries != MAX_CONNECT_RETRY; retries++) { - debug_printf("client connect failure %d\n", errno); - ::close (fd); - return false; + _fd = cygwin_socket (AF_UNIX, SOCK_STREAM, 0); + + if (_fd == -1) + { + system_printf ("failed to create UNIX domain socket: %s", + strerror (errno)); + return -1; + } + + if (cygwin_connect (_fd, (struct sockaddr *) &_addr, _addr_len) == 0) + { + assume_cygserver = true; + debug_printf ("0 = connect () [this = %p, fd = %d]", this, _fd); + return 0; + } + + if (!assume_cygserver || errno != ECONNREFUSED) + { + debug_printf ("failed to connect to server: %s", strerror (errno)); + (void) ::close (_fd); + _fd = -1; + return -1; + } + + (void) ::close (_fd); + _fd = -1; + Sleep (0); // Give the server a chance. } - return true; + + debug_printf ("failed to connect to server: %s", strerror (errno)); + return -1; } diff --git a/winsup/cygwin/dcrt0.cc b/winsup/cygwin/dcrt0.cc index 52b8202ef..701fb140c 100644 --- a/winsup/cygwin/dcrt0.cc +++ b/winsup/cygwin/dcrt0.cc @@ -34,8 +34,6 @@ details. */ #include "cygwin_version.h" #include "dll_init.h" #include "cygthread.h" -#include "cygwin/cygserver_transport.h" -#include "cygwin/cygserver.h" #define MAX_AT_FILE_LEVEL 10 @@ -686,8 +684,6 @@ dll_crt0_1 () /* Initialize signal/subprocess handling. */ sigproc_init (); - cygserver_init (); - /* Connect to tty. */ tty_init (); diff --git a/winsup/cygwin/fhandler_tty.cc b/winsup/cygwin/fhandler_tty.cc index fd83ef70c..9c2bb7f49 100644 --- a/winsup/cygwin/fhandler_tty.cc +++ b/winsup/cygwin/fhandler_tty.cc @@ -24,7 +24,6 @@ details. */ #include "pinfo.h" #include "cygheap.h" #include "shared_info.h" -#include "cygwin/cygserver_transport.h" #include "cygwin/cygserver.h" #include "cygthread.h" @@ -490,8 +489,8 @@ fhandler_tty_slave::open (path_conv *, int flags, mode_t) HANDLE from_master_local, to_master_local; if (!wincap.has_security () || - cygserver_running!=CYGSERVER_OK || - !cygserver_attach_tty ( &from_master_local, &to_master_local)) + cygserver_running == CYGSERVER_UNAVAIL || + !cygserver_attach_tty (&from_master_local, &to_master_local)) { termios_printf ("cannot dup handles via server. using old method."); @@ -547,29 +546,15 @@ fhandler_tty_slave::cygserver_attach_tty (LPHANDLE from_master_ptr, if (!from_master_ptr || !to_master_ptr) return 0; - client_request_attach_tty *request = - new client_request_attach_tty ((DWORD) GetCurrentProcessId (), - (DWORD) get_ttyp ()->master_pid, - (HANDLE) get_ttyp ()->from_master, - (HANDLE) get_ttyp ()->to_master); + client_request_attach_tty req ((DWORD) get_ttyp ()->master_pid, + (HANDLE) get_ttyp ()->from_master, + (HANDLE) get_ttyp ()->to_master); - if (cygserver_request (request) != 0 || - request->header.error_code != 0) + if (req.make_request () == -1 || req.error_code ()) return 0; -/* - struct request_attach_tty req; - INIT_REQUEST (req, CYGSERVER_REQUEST_ATTACH_TTY); - req.pid = GetCurrentProcessId (); - req.master_pid = get_ttyp ()->master_pid; - req.from_master = get_ttyp ()->from_master; - req.to_master = get_ttyp ()->to_master; - if (cygserver_request ((struct request_header*) &req) != 0) - return 0; -*/ - *from_master_ptr = request->from_master (); - *to_master_ptr = request->to_master (); - delete request; + *from_master_ptr = req.from_master (); + *to_master_ptr = req.to_master (); return 1; } diff --git a/winsup/cygwin/include/cygwin/cygserver.h b/winsup/cygwin/include/cygwin/cygserver.h index c7a04929f..d4ea70fbb 100755 --- a/winsup/cygwin/include/cygwin/cygserver.h +++ b/winsup/cygwin/include/cygwin/cygserver.h @@ -13,123 +13,172 @@ details. */ #ifndef _CYGSERVER_H_ #define _CYGSERVER_H_ -#define MAX_REQUEST_SIZE 128 +#ifdef __GNUC__ +#define CYGSERVER_PACKED __attribute__ ((packed)) +#else +#define CYGSERVER_PACKED +#endif #define CYGWIN_SERVER_VERSION_MAJOR 1 #define CYGWIN_SERVER_VERSION_API 1 #define CYGWIN_SERVER_VERSION_MINOR 0 #define CYGWIN_SERVER_VERSION_PATCH 0 - typedef enum { - CYGSERVER_UNKNOWN=0, - CYGSERVER_OK=1, - CYGSERVER_DEAD=2 + CYGSERVER_UNKNOWN = 0, + CYGSERVER_OK, + CYGSERVER_UNAVAIL } cygserver_states; -typedef enum { - CYGSERVER_REQUEST_INVALID = 0, - CYGSERVER_REQUEST_GET_VERSION, - CYGSERVER_REQUEST_ATTACH_TTY, - CYGSERVER_REQUEST_SHUTDOWN, - CYGSERVER_REQUEST_SHM_GET, - CYGSERVER_REQUEST_LAST -} cygserver_request_code; +/*---------------------------------------------------------------------------* + * class client_request + *---------------------------------------------------------------------------*/ -class request_header -{ - public: - ssize_t cb; - cygserver_request_code req_id; - ssize_t error_code; - request_header (cygserver_request_code id, ssize_t ncb) : cb (ncb), req_id (id), error_code (0) {} ; -} -#ifdef __GNUC__ - __attribute__ ((packed)) +class transport_layer_base; + +#ifndef __INSIDE_CYGWIN__ +class process_cache; #endif -; - -extern void cygserver_init (); - -#define INIT_REQUEST(req,id) \ - (req).header.cb = sizeof (req); \ - (req).header.req_id = id; - -struct request_get_version -{ - DWORD major, api, minor, patch; -} -#ifdef __GNUC__ - __attribute__ ((packed)) -#endif -; - -struct request_shutdown -{ - int foo; -} -#ifdef __GNUC__ - __attribute__ ((packed)) -#endif -; - -struct request_attach_tty -{ - DWORD pid, master_pid; - HANDLE from_master, to_master; -} -#ifdef __GNUC__ - __attribute__ ((packed)) -#endif -; class client_request { - public: - client_request (cygserver_request_code id, ssize_t data_size); - virtual void send (transport_layer_base *conn); +protected: + typedef enum { + CYGSERVER_REQUEST_INVALID, + CYGSERVER_REQUEST_GET_VERSION, + CYGSERVER_REQUEST_SHUTDOWN, + CYGSERVER_REQUEST_ATTACH_TTY, + CYGSERVER_REQUEST_SHM, + CYGSERVER_REQUEST_LAST + } request_code_t; + + struct header_t + { + size_t msglen; + union + { + request_code_t request_code; + ssize_t error_code; + }; + + header_t () {}; + header_t (request_code_t, size_t); + } CYGSERVER_PACKED; + +public: #ifndef __INSIDE_CYGWIN__ - virtual void serve (transport_layer_base *conn, class process_cache *cache); + static void handle_request (transport_layer_base *, process_cache *); +#endif + + client_request (request_code_t request_code, + void *buf = NULL, + size_t bufsiz = 0); + virtual ~client_request (); + + request_code_t request_code () const { return _header.request_code; } + + ssize_t error_code () const { return _header.error_code; }; + void error_code (ssize_t error_code) { _header.error_code = error_code; }; + + size_t msglen () const { return _header.msglen; }; + void msglen (size_t len) { _header.msglen = len; }; + + int make_request (); + +protected: + virtual void send (transport_layer_base *); + +private: + header_t _header; + void * const _buf; + const size_t _buflen; + +#ifndef __INSIDE_CYGWIN__ + void handle (transport_layer_base *, process_cache *); + virtual void serve (transport_layer_base *, process_cache *) = 0; #endif - virtual operator struct request_header (); - cygserver_request_code req_id () {return header.req_id;}; - virtual ~client_request(); - request_header header; - char *buffer; }; +/*---------------------------------------------------------------------------* + * class client_request_get_version + *---------------------------------------------------------------------------*/ + class client_request_get_version : public client_request { - public: -#ifndef __INSIDE_CYGWIN__ - virtual void serve (transport_layer_base *conn, class process_cache *cache); -#endif - client_request_get_version::client_request_get_version(); +private: + struct request_get_version + { + DWORD major, api, minor, patch; + } CYGSERVER_PACKED; + +public: + client_request_get_version (); + bool check_version () const; + +private: struct request_get_version version; + +#ifndef __INSIDE_CYGWIN__ + virtual void serve (transport_layer_base *, process_cache *); +#endif }; +/*---------------------------------------------------------------------------* + * class client_request_shutdown + * + * Nb. This whole class is only !__INSIDE_CYGWIN__ since it is used + * solely by cygserver itself. + *---------------------------------------------------------------------------*/ + +#ifndef __INSIDE_CYGWIN__ + class client_request_shutdown : public client_request { - public: -#ifndef __INSIDE_CYGWIN__ - virtual void serve (transport_layer_base *conn, class process_cache *cache); -#endif +public: client_request_shutdown (); + +private: + virtual void serve (transport_layer_base *, process_cache *); }; +#endif /* !__INSIDE_CYGWIN__ */ + +/*---------------------------------------------------------------------------* + * class client_request_attach_tty + *---------------------------------------------------------------------------*/ + class client_request_attach_tty : public client_request { - public: -#ifndef __INSIDE_CYGWIN__ - virtual void serve (transport_layer_base *conn, class process_cache *cache); -#endif +private: + struct request_attach_tty + { + DWORD pid, master_pid; + HANDLE from_master, to_master; + } CYGSERVER_PACKED; + +public: +#ifdef __INSIDE_CYGWIN__ + client_request_attach_tty (DWORD nmaster_pid, + HANDLE nfrom_master, HANDLE nto_master); +#else client_request_attach_tty (); - client_request_attach_tty (DWORD npid, DWORD nmaster_pid, HANDLE nfrom_master, HANDLE nto_master); - HANDLE from_master () {return req.from_master;}; - HANDLE to_master () {return req.to_master;}; +#endif + + HANDLE from_master () const { return req.from_master; }; + HANDLE to_master () const { return req.to_master; }; + +protected: + virtual void send (transport_layer_base *); + +private: struct request_attach_tty req; + +#ifndef __INSIDE_CYGWIN__ + virtual void serve (transport_layer_base *, process_cache *); +#endif }; -extern int cygserver_request (client_request *); +extern bool check_cygserver_available (); +extern void cygserver_init (); -#endif /* _CYGSERVER+H+ */ +#endif /* _CYGSERVER_H_ */ diff --git a/winsup/cygwin/include/cygwin/cygserver_process.h b/winsup/cygwin/include/cygwin/cygserver_process.h index c8ff40b09..25c634e9e 100755 --- a/winsup/cygwin/include/cygwin/cygserver_process.h +++ b/winsup/cygwin/include/cygwin/cygserver_process.h @@ -4,81 +4,161 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ #ifndef _CYGSERVER_PROCESS_ #define _CYGSERVER_PROCESS_ -/* needs threaded_queue.h */ +#include -class process_cleanup:public queue_request +#include "threaded_queue.h" + +class process_cleanup : public queue_request { public: + process_cleanup (class process *const theprocess) + : _process (theprocess) + { + assert (_process); + } + + virtual ~process_cleanup (); + virtual void process (); - process_cleanup (class process *nprocess) : theprocess (nprocess) {}; + private: - class process * theprocess; + class process *const _process; }; -class process_process_param:public queue_process_param -{ - class process_cache *cache; -public: - DWORD request_loop (); - process_process_param ():queue_process_param (true) {}; -}; +class process; class cleanup_routine { + friend class process; + public: - cleanup_routine () : next (NULL) {}; - class cleanup_routine * next; + cleanup_routine (void *const key) + : _key (key), + _next (NULL) + {} + + virtual ~cleanup_routine (); + + bool operator== (const cleanup_routine &rhs) const + { + return _key == rhs._key; + } + + void *key () const { return _key; } + /* MUST BE SYNCHRONOUS */ - virtual void cleanup (long winpid); + virtual void cleanup (class process *) = 0; + +private: + void *const _key; + cleanup_routine *_next; }; +class process_cache; + class process { + friend class process_cache; + friend class process_cleanup; + public: - HANDLE handle (); - long winpid; - process (long); + process (pid_t cygpid, DWORD winpid); ~process (); - DWORD exit_code (); - class process * next; - long refcount; - bool add_cleanup_routine (class cleanup_routine *); - void cleanup (); + + pid_t cygpid () const { return _cygpid; } + DWORD winpid () const { return _winpid; } + HANDLE handle () const { return _hProcess; } + + bool is_active () const { return _exit_status == STILL_ACTIVE; } + + void hold () { EnterCriticalSection (&_access); } + void release () { LeaveCriticalSection (&_access); } + + bool add (cleanup_routine *); + bool remove (const cleanup_routine *); + private: + const pid_t _cygpid; + const DWORD _winpid; + HANDLE _hProcess; + long _cleaning_up; + DWORD _exit_status; // Set in the constructor and in exit_code (). + cleanup_routine *_routines_head; /* used to prevent races-on-delete */ - CRITICAL_SECTION access; - volatile long cleaning_up; - class cleanup_routine *head; - HANDLE thehandle; - DWORD _exit_status; + CRITICAL_SECTION _access; + class process *_next; + + DWORD check_exit_code (); + void cleanup (); }; -class process_cache:public threaded_queue +class process_cache { + // Number of special (i.e., non-process) handles in _wait_array. + // See wait_for_processes () and sync_wait_array () for details. + enum { + SPECIALS_COUNT = 2 + }; + + class submission_loop : public queue_submission_loop + { + public: + submission_loop (process_cache *const cache, threaded_queue *const queue) + : queue_submission_loop (queue, true), + _cache (cache) + { + assert (_cache); + } + + private: + process_cache *const _cache; + + virtual void request_loop (); + }; + + friend class submission_loop; + public: process_cache (unsigned int initial_workers); - virtual ~ process_cache (); - class process *process (long); - /* remove a process from the cache */ - int handle_snapshot (HANDLE *, class process **, ssize_t, int); - void remove_process (class process *); - /* threaded_queue methods */ - void process_requests (); - HANDLE cache_add_trigger; + ~process_cache (); + + class process *process (pid_t cygpid, DWORD winpid); + + bool running () const { return _queue.running (); } + + bool start () { return _queue.start (); } + bool stop () { return _queue.stop (); } private: - virtual void add_task (class process *); - class process *head; - CRITICAL_SECTION cache_write_access; + threaded_queue _queue; + submission_loop _submitter; + + size_t _processes_count; + class process *_processes_head; // A list sorted by winpid. + + // Access to the _wait_array and related fields is not thread-safe, + // since they are used solely by wait_for_processes () and its callees. + + HANDLE _wait_array[MAXIMUM_WAIT_OBJECTS]; + class process *_process_array[MAXIMUM_WAIT_OBJECTS]; + + HANDLE _cache_add_trigger; // Actually both add and remove. + CRITICAL_SECTION _cache_write_access; // Actually both read and write access. + + void wait_for_processes (HANDLE interrupt); + size_t sync_wait_array (HANDLE interrupt); + void check_and_remove_process (const size_t index); + + class process *find (DWORD winpid, class process **previous = NULL); }; #endif /* _CYGSERVER_PROCESS_ */ diff --git a/winsup/cygwin/include/cygwin/cygserver_transport.h b/winsup/cygwin/include/cygwin/cygserver_transport.h index 493c9b530..915f35e66 100755 --- a/winsup/cygwin/include/cygwin/cygserver_transport.h +++ b/winsup/cygwin/include/cygwin/cygserver_transport.h @@ -1,32 +1,39 @@ -/* cygserver.cc +/* cygserver_transport.h Copyright 2001, 2002 Red Hat Inc. Written by Robert Collins - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ #ifndef _CYGSERVER_TRANSPORT_ #define _CYGSERVER_TRANSPORT_ -class transport_layer_base *create_server_transport(); -/* the base class does nothing. */ +class transport_layer_base *create_server_transport (); + class transport_layer_base { - public: - virtual void listen (); - virtual class transport_layer_base * accept (); - virtual void close (); - virtual ssize_t read (char *buf, size_t len); - virtual ssize_t write (char *buf, size_t len); - virtual bool connect(); - virtual void impersonate_client (); - virtual void revert_to_self (); - transport_layer_base (); +public: +#ifndef __INSIDE_CYGWIN__ + virtual int listen () = 0; + virtual class transport_layer_base *accept (bool *recoverable) = 0; +#endif + + virtual void close () = 0; + virtual ssize_t read (void *buf, size_t len) = 0; + virtual ssize_t write (void *buf, size_t len) = 0; + virtual int connect () = 0; + +#ifndef __INSIDE_CYGWIN__ + virtual void impersonate_client (); + virtual void revert_to_self (); +#endif + + virtual ~transport_layer_base (); }; #endif /* _CYGSERVER_TRANSPORT_ */ diff --git a/winsup/cygwin/include/cygwin/cygserver_transport_pipes.h b/winsup/cygwin/include/cygwin/cygserver_transport_pipes.h index 9ebeee2a4..4bea2eb13 100755 --- a/winsup/cygwin/include/cygwin/cygserver_transport_pipes.h +++ b/winsup/cygwin/include/cygwin/cygserver_transport_pipes.h @@ -1,39 +1,53 @@ -/* cygserver.cc +/* cygserver_transport_pipes.h Copyright 2001, 2002 Red Hat Inc. Written by Robert Collins - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ #ifndef _CYGSERVER_TRANSPORT_PIPES_ #define _CYGSERVER_TRANSPORT_PIPES_ + /* Named pipes based transport, for security on NT */ class transport_layer_pipes : public transport_layer_base { - public: - virtual void listen (); - virtual class transport_layer_pipes * accept (); - virtual void close (); - virtual ssize_t read (char *buf, size_t len); - virtual ssize_t write (char *buf, size_t len); - virtual bool connect(); - virtual void impersonate_client (); - virtual void revert_to_self (); - transport_layer_pipes (); +public: +#ifndef __INSIDE_CYGWIN__ + virtual int listen (); + virtual class transport_layer_pipes *accept (bool *recoverable); +#endif - private: - /* for pipe based communications */ - void init_security (); - SECURITY_DESCRIPTOR sd; - SECURITY_ATTRIBUTES sec_none_nih, sec_all_nih; - char pipe_name [MAX_PATH]; - HANDLE pipe; - bool inited; - transport_layer_pipes (HANDLE new_pipe); + virtual void close (); + virtual ssize_t read (void *buf, size_t len); + virtual ssize_t write (void *buf, size_t len); + virtual int connect (); + +#ifndef __INSIDE_CYGWIN__ + virtual void impersonate_client (); + virtual void revert_to_self (); +#endif + + transport_layer_pipes (); + virtual ~transport_layer_pipes (); + +private: + /* for pipe based communications */ + void init_security (); + + //FIXME: allow inited, sd, all_nih_.. to be static members + SECURITY_DESCRIPTOR _sd; + SECURITY_ATTRIBUTES _sec_all_nih; + const char *const _pipe_name; + HANDLE _hPipe; + const bool _is_accepted_endpoint; + bool _is_listening_endpoint; + + transport_layer_pipes (HANDLE hPipe); }; + #endif /* _CYGSERVER_TRANSPORT_PIPES_ */ diff --git a/winsup/cygwin/include/cygwin/cygserver_transport_sockets.h b/winsup/cygwin/include/cygwin/cygserver_transport_sockets.h index 9881ca2bb..d960f9c2c 100755 --- a/winsup/cygwin/include/cygwin/cygserver_transport_sockets.h +++ b/winsup/cygwin/include/cygwin/cygserver_transport_sockets.h @@ -1,33 +1,46 @@ -/* cygserver.cc +/* cygserver_transport_sockets.h Copyright 2001, 2002 Red Hat Inc. Written by Robert Collins - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ #ifndef _CYGSERVER_TRANSPORT_SOCKETS_ #define _CYGSERVER_TRANSPORT_SOCKETS_ + +#include +#include + class transport_layer_sockets : public transport_layer_base { - public: - virtual void listen (); - virtual class transport_layer_sockets * accept (); - virtual void close (); - virtual ssize_t read (char *buf, size_t len); - virtual ssize_t write (char *buf, size_t len); - virtual bool connect(); - transport_layer_sockets (); +public: +#ifndef __INSIDE_CYGWIN__ + virtual int listen (); + virtual class transport_layer_sockets *accept (bool *recoverable); +#endif - private: - /* for socket based communications */ - int fd; - struct sockaddr sockdetails; - int sdlen; - transport_layer_sockets (int newfd); + virtual void close (); + virtual ssize_t read (void *buf, size_t len); + virtual ssize_t write (void *buf, size_t len); + virtual int connect (); + + transport_layer_sockets (); + virtual ~transport_layer_sockets (); + +private: + /* for socket based communications */ + int _fd; + struct sockaddr_un _addr; + socklen_t _addr_len; + const bool _is_accepted_endpoint; + bool _is_listening_endpoint; + + transport_layer_sockets (int fd); }; + #endif /* _CYGSERVER_TRANSPORT_SOCKETS_ */ diff --git a/winsup/cygwin/include/cygwin/ipc.h b/winsup/cygwin/include/cygwin/ipc.h index c718a173a..8a88a1085 100644 --- a/winsup/cygwin/include/cygwin/ipc.h +++ b/winsup/cygwin/include/cygwin/ipc.h @@ -1,6 +1,6 @@ /* sys/ipc.h - Copyright 2001 Red Hat Inc. + Copyright 2001, 2002 Red Hat Inc. Written by Robert Collins This file is part of Cygwin. @@ -9,45 +9,45 @@ This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ +#ifndef _SYS_IPC_H +#define _SYS_IPC_H + #ifdef __cplusplus extern "C" { #endif -#ifndef _SYS_IPC_H -#define _SYS_IPC_H +struct ipc_perm +{ + uid_t uid; /* Owner's user ID. */ + gid_t gid; /* Owner's group ID. */ + uid_t cuid; /* Creator's user ID. */ + gid_t cgid; /* Creator's group ID. */ + mode_t mode; /* Read/write permission. */ + key_t key; +}; -/* sys/types must be included before sys/ipc.h. We aren't meant to automatically - * include it however +/* Mode bits: */ +#define IPC_CREAT 0x0200 /* Create entry if key does not exist. */ +#define IPC_EXCL 0x0400 /* Fail if key exists. */ +#define IPC_NOWAIT 0x0800 /* Error if request must wait. */ -struct ipc_perm { - uid_t uid; - gid_t gid; - uid_t cuid; - gid_t cgid; - mode_t mode; -}; - -/* the mode flags used with the _get functions use the low order 9 bits for a mode - * request +/* Keys: */ -#define IPC_CREAT 0x0200 -#define IPC_EXCL 0x0400 -#define IPC_NOWAIT 0x0800 +#define IPC_PRIVATE ((key_t) 0) /* Private key. */ -/* this is a value that will _never_ be a valid key from ftok */ -#define IPC_PRIVATE -2 +/* Control commands: + */ +#define IPC_RMID 0x1000 /* Remove identifier. */ +#define IPC_SET 0x1001 /* Set options. */ +#define IPC_STAT 0x1002 /* Get options. */ +#define IPC_INFO 0x1003 /* For ipcs(8). */ -/* ctl commands 1000-1fff is ipc reserved */ -#define IPC_RMID 0x1003 -#define IPC_SET 0x1002 -#define IPC_STAT 0x1001 - -key_t ftok(const char *, int); - -#endif /* _SYS_IPC_H */ +key_t ftok (const char *path, int id); #ifdef __cplusplus } #endif + +#endif /* _SYS_IPC_H */ diff --git a/winsup/cygwin/include/cygwin/msg.h b/winsup/cygwin/include/cygwin/msg.h new file mode 100644 index 000000000..14e89f556 --- /dev/null +++ b/winsup/cygwin/include/cygwin/msg.h @@ -0,0 +1,92 @@ +/* sys/msg.h + + Copyright 2002 Red Hat Inc. + Written by Conrad Scott + +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. */ + +#ifndef _SYS_MSG_H +#define _SYS_MSG_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Message operation flags: + */ +#define MSG_NOERROR 0x01 /* No error if big message. */ + +/* Command definitions for the semctl () function: + */ +#define MSG_STAT 0x2000 /* For ipcs(8) */ +#define MSG_INFO 0x2001 /* For ipcs(8) */ + +/* Used for the number of messages in the message queue. + */ +typedef long int msgqnum_t; + +/* Used for the number of bytes allowed in a message queue. + */ +typedef long int msglen_t; + +struct msqid_ds +{ + struct ipc_perm msg_perm; /* Operation permission structure. */ + msglen_t msg_cbytes; /* Number of bytes currently on queue. */ + msgqnum_t msg_qnum; /* Number of messages currently on queue. */ + msglen_t msg_qbytes; /* Maximum number of bytes allowed on queue. */ + pid_t msg_lspid; /* Process ID of last msgsnd (). */ + pid_t msg_lrpid; /* Process ID of last msgrcv (). */ + timestruc_t msg_stim; /* Time of last msgsnd (). */ + timestruc_t msg_rtim; /* Time of last msgrcv (). */ + timestruc_t msg_ctim; /* Time of last change. */ + long msg_spare4[2]; +}; + +#define msg_stime msg_stim.tv_sec +#define msg_rtime msg_rtim.tv_sec +#define msg_ctime msg_ctim.tv_sec + +/* Buffer type for msgctl (IPC_INFO, ...) as used by ipcs(8). + */ +struct msginfo +{ + unsigned long msgpool; /* Maximum number of message bytes, + system wide. */ + unsigned long msgmax; /* Maximum number of bytes per + message. */ + unsigned long msgmnb; /* Maximum number of bytes on any one + message queue. */ + unsigned long msgmni; /* Maximum number of message queues, + system wide. */ + unsigned long msgtql; /* Maximum number of messages, system + wide. */ + unsigned long msg_spare[4]; +}; + +/* Buffer type for msgctl (MSG_INFO, ...) as used by ipcs(8). + */ +struct msg_info +{ + unsigned long msg_ids; /* Number of allocated queues. */ + unsigned long msg_num; /* Number of messages, system wide. */ + unsigned long msg_tot; /* Size in bytes of messages, system wide. */ +}; + +int msgctl (int msqid, int cmd, struct msqid_ds *buf); +int msgget (key_t key, int msgflg); +ssize_t msgrcv (int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); +int msgsnd (int msqid, const void *msgp, size_t msgsz, int msgflg); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_MSG_H */ diff --git a/winsup/cygwin/include/cygwin/sem.h b/winsup/cygwin/include/cygwin/sem.h new file mode 100644 index 000000000..a3ece9f8a --- /dev/null +++ b/winsup/cygwin/include/cygwin/sem.h @@ -0,0 +1,95 @@ +/* sys/sem.h + + Copyright 2002 Red Hat Inc. + Written by Conrad Scott + +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. */ + +#ifndef _SYS_SEM_H +#define _SYS_SEM_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Semaphore operation flags: + */ +#define SEM_UNDO /* Set up adjust on exit entry. */ + +/* Command definitions for the semctl () function: + */ +#define GETNCNT 0x3000 /* Get semncnt. */ +#define GETPID 0x3001 /* Get sempid. */ +#define GETVAL 0x3002 /* Get semval. */ +#define GETALL 0x3003 /* Get all cases of semval. */ +#define GETZCNT 0x3004 /* Get semzcnt. */ +#define SETVAL 0x3005 /* Set semval. */ +#define SETALL 0x3006 /* Set all cases of semval. */ + +#define SEM_STAT 0x3010 /* For ipcs(8). */ +#define SEM_INFO 0x3011 /* For ipcs(8). */ + +struct semid_ds +{ + struct ipc_perm sem_perm; /* Operation permission structure. */ + unsigned short sem_nsems; /* Number of semaphores in set. */ + timestruc_t sem_otim; /* Last semop () time. */ + timestruc_t sem_ctim; /* Last time changed by semctl (). */ + long sem_spare4[2]; +}; + +#define sem_otime sem_otim.tv_sec +#define sem_ctime sem_ctim.tv_sec + +struct sembuf +{ + unsigned short sem_num; /* Semaphore number. */ + short sem_op; /* Semaphore operation. */ + short sem_flg; /* Operation flags. */ +}; + +/* Buffer type for semctl (IPC_INFO, ...) as used by ipcs(8). + */ +struct seminfo +{ + unsigned long semmni; /* Maximum number of unique semaphore + sets, system wide. */ + unsigned long semmns; /* Maximum number of semaphores, + system wide. */ + unsigned long semmsl; /* Maximum number of semaphores per + semaphore set. */ + unsigned long semopm; /* Maximum number of operations per + semop call. */ + unsigned long semmnu; /* Maximum number of undo structures, + system wide. */ + unsigned long semume; /* Maximum number of undo entries per + undo structure. */ + unsigned long semvmx; /* Maximum semaphore value. */ + unsigned long semaem; /* Maximum adjust-on-exit value. */ + unsigned long sem_spare[4]; +}; + +/* Buffer type for semctl (SEM_INFO, ...) as used by ipcs(8). + */ +struct sem_info +{ + unsigned long sem_ids; /* Number of allocated semaphore sets. */ + unsigned long sem_num; /* Number of allocated semaphores. */ +}; + +int semctl (int semid, int semnum, int cmd, ...); +int semget (key_t key, int nsems, int semflg); +int semop (int semid, struct sembuf *sops, size_t nsops); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_SEM_H */ diff --git a/winsup/cygwin/include/cygwin/shm.h b/winsup/cygwin/include/cygwin/shm.h index eb037ba19..b6b2d447c 100644 --- a/winsup/cygwin/include/cygwin/shm.h +++ b/winsup/cygwin/include/cygwin/shm.h @@ -1,6 +1,6 @@ /* sys/shm.h - Copyright 2001 Red Hat Inc. + Copyright 2001, 2002 Red Hat Inc. Written by Robert Collins This file is part of Cygwin. @@ -9,81 +9,86 @@ This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ -#ifdef __cplusplus -extern "C" -{ -#endif - #ifndef _SYS_SHM_H #define _SYS_SHM_H #include -#define SHM_RDONLY 1 -/* 64 Kb was hardcoded for x86. MS states this may change, but we need it in the header - * file. +#ifdef __cplusplus +extern "C" +{ +#endif + +/* 64 Kb was hardcoded for x86. MS states this may change, but we need + * it in the header file. */ -#define SHMLBA 65536 -#define SHM_RND 1 +#define SHMLBA 65536 /* Segment low boundary address multiple. */ -typedef long int shmatt_t; - -#if defined(__INSIDE_CYGWIN__) && defined(__cplusplus) - -class _shmattach { -public: - void *data; - int shmflg; - class _shmattach *next; -}; - -class shmid_ds { -public: - struct ipc_perm shm_perm; - size_t shm_segsz; - pid_t shm_lpid; - pid_t shm_cpid; - shmatt_t shm_nattch; - time_t shm_atime; - time_t shm_dtime; - time_t shm_ctime; - void *mapptr; -}; - -class shmnode { -public: - class shmid_ds * shmds; - int shm_id; - class shmnode *next; - key_t key; - HANDLE filemap; - HANDLE attachmap; - class _shmattach *attachhead; -}; - -#else -/* this is what we return when queried. It has no bitwise correspondence - * the internal structures +/* Shared memory operation flags: */ -struct shmid_ds { - struct ipc_perm shm_perm; - size_t shm_segsz; - pid_t shm_lpid; - pid_t shm_cpid; - shmatt_t shm_nattch; - time_t shm_atime; - time_t shm_dtime; - time_t shm_ctime; +#define SHM_RDONLY 0x01 /* Attach read-only (else read-write). */ +#define SHM_RND 0x02 /* Round attach address to SHMLBA. */ + +/* Command definitions for the semctl () function: + */ +#define SHM_STAT 0x4000 /* For ipcs(8) */ +#define SHM_INFO 0x4001 /* For ipcs(8) */ + +/* Unsigned integer used for the number of current attaches. + */ +typedef unsigned int shmatt_t; + +struct shmid_ds +{ + struct ipc_perm shm_perm; /* Operation permission structure. */ + size_t shm_segsz; /* Size of segment in bytes. */ + pid_t shm_lpid; /* Process ID of last operation. */ + pid_t shm_cpid; /* Process ID of creator. */ + shmatt_t shm_nattch; /* Number of current attaches. */ + timestruc_t shm_atim; /* Time of last shmat (). */ + timestruc_t shm_dtim; /* Time of last shmdt (). */ + timestruc_t shm_ctim; /* Time of last change by shmctl (). */ + long shm_spare4[2]; }; -#endif /* __INSIDE_CYGWIN__ */ -void *shmat(int, const void *, int); -int shmctl(int, int, struct shmid_ds *); -int shmdt(const void *); -int shmget(key_t, size_t, int); +#define shm_atime shm_atim.tv_sec +#define shm_dtime shm_dtim.tv_sec +#define shm_ctime shm_ctim.tv_sec -#endif /* _SYS_SHM_H */ +/* Buffer type for shmctl (IPC_INFO, ...) as used by ipcs(8). + */ +struct shminfo +{ + unsigned long shmmax; /* Maximum size in bytes of a shared + memory segment. */ + unsigned long shmmin; /* Minimum size in bytes of a shared + memory segment. */ + unsigned long shmmni; /* Maximum number of shared memory + segments, system wide. */ + unsigned long shmseg; /* Maximum number of shared memory + segments attached per process. */ + unsigned long shmall; /* Maximum number of bytes of shared + memory, system wide. */ + unsigned long shm_spare[4]; +}; + +/* Buffer type for shmctl (SHM_INFO, ...) as used by ipcs(8). + */ +struct shm_info +{ + unsigned long shm_ids; /* Number of allocated segments. */ + unsigned long shm_tot; /* Size in bytes of allocated segments. */ + unsigned long shm_atts; /* Number of attached segments, system + wide. */ +}; + +void *shmat (int shmid, const void *shmaddr, int shmflg); +int shmctl (int shmid, int cmd, struct shmid_ds *buf); +int shmdt (const void *shmaddr); +int shmget (key_t key, size_t size, int shmflg); #ifdef __cplusplus } #endif + +#endif /* _SYS_SHM_H */ diff --git a/winsup/cygwin/msg.cc b/winsup/cygwin/msg.cc new file mode 100644 index 000000000..c76fd8ee7 --- /dev/null +++ b/winsup/cygwin/msg.cc @@ -0,0 +1,48 @@ +/* msg.cc: Single unix specification IPC interface for Cygwin. + + Copyright 2002 Red Hat, Inc. + + Written by Conrad Scott . + +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 + +#include "cygerrno.h" + +extern "C" int +msgctl (int msqid, int cmd, struct msqid_ds *buf) +{ + set_errno (ENOSYS); + return -1; +} + +extern "C" int +msgget (key_t key, int msgflg) +{ + set_errno (ENOSYS); + return -1; +} + +extern "C" ssize_t +msgrcv (int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg) +{ + set_errno (ENOSYS); + return -1; +} + +extern "C" int +msgsnd (int msqid, const void *msgp, size_t msgsz, int msgflg) +{ + set_errno (ENOSYS); + return -1; +} diff --git a/winsup/cygwin/safe_memory.h b/winsup/cygwin/safe_memory.h new file mode 100644 index 000000000..b5f3d95d6 --- /dev/null +++ b/winsup/cygwin/safe_memory.h @@ -0,0 +1,53 @@ +/* safe_memory.h + + Copyright 2002 Red Hat, Inc. + +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. */ + +#ifndef __SAFE_MEMORY_H__ +#define __SAFE_MEMORY_H__ + +/*****************************************************************************/ + +/* Temporary hack to get around the thread-unsafe new/delete in cygwin + * gcc 2.95.3. This should all be binned at the first opportunity, + * e.g. gcc 3.1 or sooner. + * + * The trick here is to do contruction via malloc(3) and then the + * placement new operator, and destruction via an explicit call to the + * destructor and then free(3). + */ + +#include + +inline void *operator new (size_t, void *__p) throw () { return __p; } + +#define safe_new0(T) (new (malloc (sizeof (T))) T) + +#ifdef NEW_MACRO_VARARGS + +#define safe_new(T, ...) \ + (new (malloc (sizeof (T))) T (__VA_ARGS__)) + +#else /* !NEW_MACRO_VARARGS */ + +#define safe_new(T, args...) \ + (new (malloc (sizeof (T))) T (## args)) + +#endif /* !NEW_MACRO_VARARGS */ + +template void +safe_delete (T *const object) +{ + if (object) + { + object->~T (); + free (object); + } +} + +#endif /* __SAFE_MEMORY_H__ */ diff --git a/winsup/cygwin/sem.cc b/winsup/cygwin/sem.cc new file mode 100644 index 000000000..63aba8e80 --- /dev/null +++ b/winsup/cygwin/sem.cc @@ -0,0 +1,41 @@ +/* sem.cc: Single unix specification IPC interface for Cygwin. + + Copyright 2002 Red Hat, Inc. + + Written by Conrad Scott . + +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 + +#include "cygerrno.h" + +extern "C" int +semctl (int semid, int semnum, int cmd, ...) +{ + set_errno (ENOSYS); + return -1; +} + +extern "C" int +semget (key_t key, int nsems, int semflg) +{ + set_errno (ENOSYS); + return -1; +} + +extern "C" int +semop (int semid, struct sembuf *sops, size_t nsops) +{ + set_errno (ENOSYS); + return -1; +} diff --git a/winsup/cygwin/shm.cc b/winsup/cygwin/shm.cc index 400f204d8..94a86e2a8 100644 --- a/winsup/cygwin/shm.cc +++ b/winsup/cygwin/shm.cc @@ -1,8 +1,9 @@ -/* shm.cc: Single unix specification IPC interface for Cygwin +/* shm.cc: Single unix specification IPC interface for Cygwin. -Copyright 2001, 2002 Red Hat, Inc. + Copyright 2002 Red Hat, Inc. -Originally written by Robert Collins + Written by Conrad Scott . + Based on code by Robert Collins . This file is part of Cygwin. @@ -11,548 +12,682 @@ Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ #include "winsup.h" -#include + +#include + +#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 +#include + +#include "cygerrno.h" +#include "safe_memory.h" +#include "sigproc.h" + +#include "cygserver_ipc.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. +/*---------------------------------------------------------------------------* + * class client_shmmgr + * + * A singleton class. + *---------------------------------------------------------------------------*/ + +#define shmmgr (client_shmmgr::instance ()) + +class client_shmmgr +{ +private: + class segment_t + { + public: + const int shmid; + const void *const shmaddr; + const int shmflg; + HANDLE hFileMap; // Updated by fixup_shms_after_fork (). + + segment_t *next; + + segment_t (const int shmid, const void *const shmaddr, const int shmflg, + const HANDLE hFileMap) + : shmid (shmid), shmaddr (shmaddr), shmflg (shmflg), hFileMap (hFileMap), + next (NULL) + {} + }; + +public: + static client_shmmgr & instance (); + + void *shmat (int shmid, const void *, int shmflg); + int shmctl (int shmid, int cmd, struct shmid_ds *); + int shmdt (const void *); + int shmget (key_t, size_t, int shmflg); + + int fixup_shms_after_fork (); + +private: + static NO_COPY client_shmmgr *_instance; + + CRITICAL_SECTION _segments_lock; + static segment_t *_segments_head; // List of attached segs by shmaddr. + + static long _shmat_cnt; // No. of attached segs; for info. only. + + client_shmmgr (); + ~client_shmmgr (); + + // Undefined (as this class is a singleton): + client_shmmgr (const client_shmmgr &); + client_shmmgr & operator= (const client_shmmgr &); + + segment_t *find (const void *, segment_t **previous = NULL); + + void *attach (int shmid, const void *, int shmflg, HANDLE & hFileMap); + + segment_t *new_segment (int shmid, const void *, int shmflg, HANDLE); +}; + +/* static */ NO_COPY client_shmmgr *client_shmmgr::_instance; + +/* The following two variables must be inherited by child processes + * since they are used by fixup_shms_after_fork () to re-attach to the + * parent's shm segments. */ -extern "C" size_t -getsystemallocgranularity () +/* static */ client_shmmgr::segment_t *client_shmmgr::_segments_head; +/* static */ long client_shmmgr::_shmat_cnt; + +/*---------------------------------------------------------------------------* + * client_shmmgr::instance () + *---------------------------------------------------------------------------*/ + +client_shmmgr & +client_shmmgr::instance () { - SYSTEM_INFO sysinfo; - static size_t buffer_offset = 0; - if (buffer_offset) - return buffer_offset; - GetSystemInfo (&sysinfo); - buffer_offset = sysinfo.dwAllocationGranularity; - return buffer_offset; + if (!_instance) + _instance = safe_new0 (client_shmmgr); + + assert (_instance); + + return *_instance; } -client_request_shm::client_request_shm (int ntype, int nshm_id): -client_request (CYGSERVER_REQUEST_SHM_GET, sizeof (parameters)) +/*---------------------------------------------------------------------------* + * client_shmmgr::shmat () + *---------------------------------------------------------------------------*/ + +void * +client_shmmgr::shmat (const int shmid, + const void *const shmaddr, + const int shmflg) { - buffer = (char *) ¶meters; - parameters.in.shm_id = nshm_id; - parameters.in.type = SHM_REATTACH; - parameters.in.pid = GetCurrentProcessId (); + syscall_printf ("shmat (shmid = %d, shmaddr = %p, shmflg = 0%o)", + shmid, shmaddr, shmflg); + + EnterCriticalSection (&_segments_lock); + + HANDLE hFileMap = NULL; + + void *const ptr = attach (shmid, shmaddr, shmflg, hFileMap); + + if (ptr) + new_segment (shmid, ptr, shmflg, hFileMap); + + LeaveCriticalSection (&_segments_lock); + + if (ptr) + syscall_printf ("%p = shmat (shmid = %d, shmaddr = %p, shmflg = 0%o)", + ptr, shmid, shmaddr, shmflg); + // else + // See the syscall_printf in client_shmmgr::attach (). + + return (ptr ? ptr : (void *) -1); } -client_request_shm::client_request_shm (int ntype, int nshm_id, pid_t npid): -client_request (CYGSERVER_REQUEST_SHM_GET, sizeof (parameters)) +/*---------------------------------------------------------------------------* + * client_shmmgr::shmctl () + *---------------------------------------------------------------------------*/ + +int +client_shmmgr::shmctl (const int shmid, + const int cmd, + struct shmid_ds *const buf) { - buffer = (char *) ¶meters; - parameters.in.shm_id = nshm_id; - parameters.in.type = ntype; - parameters.in.pid = npid; -} + syscall_printf ("shmctl (shmid = %d, cmd = 0x%x, buf = %p)", + shmid, cmd, buf); -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); -} + // Check parameters and set up in parameters as required. -static shmnode *shm_head = NULL; + const struct shmid_ds *in_buf = 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) + switch (cmd) { - 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) + case IPC_SET: + if (__check_invalid_read_ptr_errno (buf, sizeof (struct shmid_ds))) { - /* don't worry about handle cleanup, we're dying! */ - system_printf ("failed to reattach to shm control file view %x\n", - tempnode); - return 1; + syscall_printf (("-1 [EFAULT] = " + "shmctl (shmid = %d, cmd = 0x%x, buf = %p)"), + shmid, cmd, buf); + set_errno (EFAULT); + return -1; } - tempnode->shmds = (class shmid_ds *) newshmds; - tempnode->shmds->mapptr = newshmds; - _shmattach *attachnode = tempnode->attachhead; - while (attachnode) + in_buf = buf; + break; + + case IPC_STAT: + case SHM_STAT: + if (__check_null_invalid_struct_errno (buf, sizeof (struct shmid_ds))) { - 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; + syscall_printf (("-1 [EFAULT] = " + "shmctl (shmid = %d, cmd = 0x%x, buf = %p)"), + shmid, cmd, buf); + set_errno (EFAULT); + return -1; } - tempnode = tempnode->next; - } - return 0; -} + break; -/* 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))) + case IPC_INFO: + if (__check_null_invalid_struct_errno (buf, sizeof (struct shminfo))) { - delete req; - set_errno (ENOSYS); /* daemon communication failed */ - return (void *) -1; + syscall_printf (("-1 [EFAULT] = " + "shmctl (shmid = %d, cmd = 0x%x, buf = %p)"), + shmid, cmd, buf); + set_errno (EFAULT); + return -1; } + break; - if (req->header.error_code) /* shm_get failed in the daemon */ + case SHM_INFO: + if (__check_null_invalid_struct_errno (buf, sizeof (struct shm_info))) { - set_errno (req->header.error_code); - delete req; - return (void *) -1; + syscall_printf (("-1 [EFAULT] = " + "shmctl (shmid = %d, cmd = 0x%x, buf = %p)"), + shmid, cmd, buf); + set_errno (EFAULT); + 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 (void *) -1; - + break; } - // class shmid_ds *shm = tempnode->shmds; + // Create and issue the command. - if (shmaddr) + client_request_shm request (shmid, cmd, in_buf); + + if (request.make_request () == -1 || request.error_code ()) { - //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); + syscall_printf (("-1 [%d] = " + "shmctl (shmid = %d, cmd = 0x%x, buf = %p)"), + request.error_code (), shmid, cmd, buf); + set_errno (request.error_code ()); 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; + // Some commands require special processing for their out parameters. - 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; - } + int result = 0; 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; + *buf = request.ds (); 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); - - } + case IPC_INFO: + *(struct shminfo *) buf = request.shminfo (); break; - case IPC_SET: - default: + + case SHM_STAT: // ipcs(8) i'face. + result = request.shmid (); + *buf = request.ds (); + break; + + case SHM_INFO: // ipcs(8) i'face. + result = request.shmid (); + *(struct shm_info *) buf = request.shm_info (); + break; + } + + syscall_printf ("%d = shmctl (shmid = %d, cmd = 0x%x, buf = %p)", + result, shmid, cmd, buf); + + return result; +} + +/*---------------------------------------------------------------------------* + * client_shmmgr::shmdt () + * + * According to Posix, the only error condition for this system call + * is EINVAL if shmaddr is not the address of the start of an attached + * shared memory segment. Given that, all other errors just generate + * tracing noise. + *---------------------------------------------------------------------------*/ + +int +client_shmmgr::shmdt (const void *const shmaddr) +{ + syscall_printf ("shmdt (shmaddr = %p)", shmaddr); + + EnterCriticalSection (&_segments_lock); + + segment_t *previous = NULL; + + segment_t *const segptr = find (shmaddr, &previous); + + if (!segptr) + { + LeaveCriticalSection (&_segments_lock); + syscall_printf ("-1 [EINVAL] = shmdt (shmaddr = %p)", shmaddr); set_errno (EINVAL); return -1; } + + assert (previous ? previous->next == segptr : _segments_head == segptr); + + if (previous) + previous->next = segptr->next; + else + _segments_head = segptr->next; + + LeaveCriticalSection (&_segments_lock); + + const long cnt = InterlockedDecrement (&_shmat_cnt); + assert (cnt >= 0); + + if (!UnmapViewOfFile ((void *) shmaddr)) + syscall_printf (("failed to unmap view " + "[shmid = %d, handle = %p, shmaddr = %p]:" + "%E"), + segptr->shmid, segptr->hFileMap, shmaddr); + + assert (segptr->hFileMap); + + if (!CloseHandle (segptr->hFileMap)) + syscall_printf (("failed to close file map handle " + "[shmid = %d, handle = %p]: %E"), + segptr->shmid, segptr->hFileMap); + + client_request_shm request (segptr->shmid); + + if (request.make_request () == -1 || request.error_code ()) + syscall_printf ("shmdt request failed [shmid = %d, handle = %p]: %s", + segptr->shmid, segptr->hFileMap, + strerror (request.error_code ())); + + safe_delete (segptr); + + syscall_printf ("0 = shmdt (shmaddr = %p)", shmaddr); + 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 - */ +/*---------------------------------------------------------------------------* + * client_shmmgr::shmget () + *---------------------------------------------------------------------------*/ -/* 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) +int +client_shmmgr::shmget (const key_t key, const size_t size, const 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); + syscall_printf ("shmget (key = 0x%016X, size = %u, shmflg = 0%o)", + key, size, shmflg); - if (key == (key_t) - 1) + client_request_shm request (key, size, shmflg); + + if (request.make_request () == -1 || request.error_code ()) { - set_errno (ENOENT); + syscall_printf (("-1 [%d] = " + "shmget (key = 0x%016X, size = %u, shmflg = 0%o)"), + request.error_code (), + key, size, shmflg); + set_errno (request.error_code ()); return -1; } - /* FIXME: enter the checking for existing keys mutex. This mutex _must_ be system wide - * to prevent races on shmget. - */ + syscall_printf (("%d = shmget (key = 0x%016X, size = %u, shmflg = 0%o)"), + request.shmid (), + key, size, shmflg); - /* 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 + return request.shmid (); +} + +/*---------------------------------------------------------------------------* + * client_shmmgr::fixup_shms_after_fork () + * + * The hFileMap handles are non-inheritable: so they have to be + * re-acquired from cygserver. + * + * Nb. This routine need not be thread-safe as it is only called at startup. + *---------------------------------------------------------------------------*/ + +int +client_shmmgr::fixup_shms_after_fork () +{ + debug_printf ("re-attaching to shm segments: %d attached", _shmat_cnt); + + { + int length = 0; + for (segment_t *segptr = _segments_head; segptr; segptr = segptr->next) + length += 1; + + if (_shmat_cnt != length) + { + system_printf (("state inconsistent: " + "_shmat_cnt = %d, length of segments list = %d"), + _shmat_cnt, length); + return 1; + } + } + + for (segment_t *segptr = _segments_head; segptr; segptr = segptr->next) + if (!attach (segptr->shmid, + segptr->shmaddr, + segptr->shmflg & ~SHM_RND, + segptr->hFileMap)) + { + system_printf ("fatal error re-attaching to shm segment %d", + segptr->shmid); + return 1; + } + + if (_shmat_cnt) + debug_printf ("re-attached all %d shm segments", _shmat_cnt); + + return 0; +} + +/*---------------------------------------------------------------------------* + * client_shmmgr::client_shmmgr () + *---------------------------------------------------------------------------*/ + +client_shmmgr::client_shmmgr () +{ + InitializeCriticalSection (&_segments_lock); +} + +/*---------------------------------------------------------------------------* + * client_shmmgr::~client_shmmgr () + *---------------------------------------------------------------------------*/ + +client_shmmgr::~client_shmmgr () +{ + DeleteCriticalSection (&_segments_lock); +} + +/*---------------------------------------------------------------------------* + * client_shmmgr::find () + *---------------------------------------------------------------------------*/ + +client_shmmgr::segment_t * +client_shmmgr::find (const void *const shmaddr, segment_t **previous) +{ + if (previous) + *previous = NULL; + + for (segment_t *segptr = _segments_head; segptr; segptr = segptr->next) + if (segptr->shmaddr == shmaddr) + return segptr; + else if (segptr->shmaddr > shmaddr) // The list is sorted by shmaddr. + return NULL; + else if (previous) + *previous = segptr; + + return NULL; +} + +/*---------------------------------------------------------------------------* + * client_shmmgr::attach () + * + * The body of shmat (), also used by fixup_shms_after_fork (). + *---------------------------------------------------------------------------*/ + +void * +client_shmmgr::attach (const int shmid, + const void *shmaddr, + const int shmflg, + HANDLE & hFileMap) +{ + client_request_shm request (shmid, shmflg); + + if (request.make_request () == -1 || request.error_code ()) + { + syscall_printf (("-1 [%d] = " + "shmat (shmid = %d, shmaddr = %p, shmflg = 0%o)"), + request.error_code (), shmid, shmaddr, shmflg); + set_errno (request.error_code ()); + return NULL; + } + + int result = 0; + + const DWORD access = (shmflg & SHM_RDONLY) ? FILE_MAP_READ : FILE_MAP_WRITE; + + if (shmaddr && (shmflg & SHM_RND)) + shmaddr = (char *) shmaddr - ((ssize_t) shmaddr % SHMLBA); + + void *const ptr = + MapViewOfFileEx (request.hFileMap (), access, 0, 0, 0, (void *) shmaddr); + + if (!ptr) + { + syscall_printf (("failed to map view " + "[shmid = %d, handle = %p, shmaddr = %p]: %E"), + shmid, request.hFileMap (), shmaddr); + result = EINVAL; // FIXME + } + else if (shmaddr && ptr != shmaddr) + { + syscall_printf (("failed to map view at requested address " + "[shmid = %d, handle = %p]: " + "requested address = %p, mapped address = %p"), + shmid, request.hFileMap (), + shmaddr, ptr); + result = EINVAL; // FIXME + } + + if (result != 0) + { + if (!CloseHandle (request.hFileMap ())) + syscall_printf (("failed to close file map handle " + "[shmid = %d, handle = %p]: %E"), + shmid, request.hFileMap ()); + + client_request_shm dt_req (shmid); + + if (dt_req.make_request () == -1 || dt_req.error_code ()) + syscall_printf ("shmdt request failed [shmid = %d, handle = %p]: %s", + shmid, request.hFileMap (), + strerror (dt_req.error_code ())); + + set_errno (result); + return NULL; + } + + hFileMap = request.hFileMap (); + return ptr; +} + +/*---------------------------------------------------------------------------* + * client_shmmgr::new_segment () + * + * Allocate a new segment for the given shmid, file map and address + * and insert into the segment map. + *---------------------------------------------------------------------------*/ + +client_shmmgr::segment_t * +client_shmmgr::new_segment (const int shmid, + const void *const shmaddr, + const int shmflg, + const HANDLE hFileMap) +{ + assert (ipc_ext2int_subsys (shmid) == IPC_SHMOP); + assert (hFileMap); + assert (shmaddr); + + segment_t *previous = NULL; // Insert pointer. + + const segment_t *const tmp = find (shmaddr, &previous); + + assert (!tmp); + assert (previous \ + ? (!previous->next || previous->next->shmaddr > shmaddr) \ + : (!_segments_head || _segments_head->shmaddr > shmaddr)); + + segment_t *const segptr = + safe_new (segment_t, shmid, shmaddr, shmflg, hFileMap); + + assert (segptr); + + if (previous) + { + segptr->next = previous->next; + previous->next = segptr; + } + else + { + segptr->next = _segments_head; + _segments_head = segptr; + } + + const long cnt = InterlockedIncrement (&_shmat_cnt); + assert (cnt > 0); + + return segptr; +} + +/*---------------------------------------------------------------------------* + * shmat () + *---------------------------------------------------------------------------*/ + +extern "C" void * +shmat (const int shmid, const void *const shmaddr, const int shmflg) +{ + sigframe thisframe (mainthread); + return shmmgr.shmat (shmid, shmaddr, shmflg); +} + +/*---------------------------------------------------------------------------* + * shmctl () + *---------------------------------------------------------------------------*/ + +extern "C" int +shmctl (const int shmid, const int cmd, struct shmid_ds *const buf) +{ + sigframe thisframe (mainthread); + return shmmgr.shmctl (shmid, cmd, buf); +} + +/*---------------------------------------------------------------------------* + * shmdt () + *---------------------------------------------------------------------------*/ + +extern "C" int +shmdt (const void *const shmaddr) +{ + sigframe thisframe (mainthread); + return shmmgr.shmdt (shmaddr); +} + +/*---------------------------------------------------------------------------* + * shmget () + *---------------------------------------------------------------------------*/ + +extern "C" int +shmget (const key_t key, const size_t size, const int shmflg) +{ + sigframe thisframe (mainthread); + return shmmgr.shmget (key, size, shmflg); +} + +/*---------------------------------------------------------------------------* + * fixup_shms_after_fork () + *---------------------------------------------------------------------------*/ + +int __stdcall +fixup_shms_after_fork () +{ + return shmmgr.fixup_shms_after_fork (); +} + +/*---------------------------------------------------------------------------* + * client_request_shm::client_request_shm () + *---------------------------------------------------------------------------*/ + +client_request_shm::client_request_shm (const int shmid, const int shmflg) + : client_request (CYGSERVER_REQUEST_SHM, &_parameters, sizeof (_parameters)) +{ + _parameters.in.shmop = SHMOP_shmat; + + _parameters.in.shmid = shmid; + _parameters.in.shmflg = shmflg; + + _parameters.in.cygpid = getpid (); + _parameters.in.winpid = GetCurrentProcessId (); + _parameters.in.uid = geteuid (); + _parameters.in.gid = getegid (); + + msglen (sizeof (_parameters.in)); +} + +/*---------------------------------------------------------------------------* + * client_request_shm::client_request_shm () + *---------------------------------------------------------------------------*/ + +client_request_shm::client_request_shm (const int shmid, + const int cmd, + const struct shmid_ds *const buf) + : client_request (CYGSERVER_REQUEST_SHM, &_parameters, sizeof (_parameters)) +{ + _parameters.in.shmop = SHMOP_shmctl; + + _parameters.in.shmid = shmid; + _parameters.in.cmd = cmd; + if (buf) + _parameters.in.ds = *buf; + + _parameters.in.cygpid = getpid (); + _parameters.in.winpid = GetCurrentProcessId (); + _parameters.in.uid = geteuid (); + _parameters.in.gid = getegid (); + + msglen (sizeof (_parameters.in)); +} + +/*---------------------------------------------------------------------------* + * client_request_shm::client_request_shm () + *---------------------------------------------------------------------------*/ + +client_request_shm::client_request_shm (const int shmid) + : client_request (CYGSERVER_REQUEST_SHM, &_parameters, sizeof (_parameters)) +{ + _parameters.in.shmop = SHMOP_shmdt; + + _parameters.in.shmid = shmid; + + _parameters.in.cygpid = getpid (); + _parameters.in.winpid = GetCurrentProcessId (); + _parameters.in.uid = geteuid (); + _parameters.in.gid = getegid (); + + msglen (sizeof (_parameters.in)); +} + +/*---------------------------------------------------------------------------* + * client_request_shm::client_request_shm () + *---------------------------------------------------------------------------*/ + +client_request_shm::client_request_shm (const key_t key, + const size_t size, + const int shmflg) + : client_request (CYGSERVER_REQUEST_SHM, &_parameters, sizeof (_parameters)) +{ + _parameters.in.shmop = SHMOP_shmget; + + _parameters.in.key = key; + _parameters.in.size = size; + _parameters.in.shmflg = shmflg; + + _parameters.in.cygpid = getpid (); + _parameters.in.winpid = GetCurrentProcessId (); + _parameters.in.uid = geteuid (); + _parameters.in.gid = getegid (); + + msglen (sizeof (_parameters.in)); } diff --git a/winsup/cygwin/threaded_queue.cc b/winsup/cygwin/threaded_queue.cc index 321fa1612..ba0fe4178 100755 --- a/winsup/cygwin/threaded_queue.cc +++ b/winsup/cygwin/threaded_queue.cc @@ -4,247 +4,405 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +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 #include -#include "wincap.h" #include "threaded_queue.h" -#define DEBUG 1 -#define debug_printf if (DEBUG) printf + +/*****************************************************************************/ + +/* queue_request */ + +queue_request::~queue_request () +{} + +/*****************************************************************************/ /* threaded_queue */ -DWORD WINAPI -worker_function (LPVOID LpParam) +threaded_queue::threaded_queue (const size_t initial_workers) + : _workers_count (0), + _running (false), + _submitters_head (NULL), + _requests_count (0), + _requests_head (NULL), + _requests_sem (NULL) { - class threaded_queue *queue = (class threaded_queue *) LpParam; - class queue_request *request; - /* FIXME use a threadsafe pop instead for speed? */ - while (queue->active) + InitializeCriticalSection (&_queue_lock); + + // This semaphore's count is the number of requests on the queue. + // The maximum count (129792) is calculated as MAXIMUM_WAIT_OBJECTS + // multiplied by max. threads per process (2028?), which is (a few) + // more requests than could ever be pending with the current design. + + _requests_sem = CreateSemaphore (NULL, // SECURITY_ATTRIBUTES + 0, // Initial count + 129792, // Maximum count + NULL); // Anonymous + + if (!_requests_sem) { - EnterCriticalSection (&queue->queuelock); - while (!queue->request && queue->active) - { - LeaveCriticalSection (&queue->queuelock); - DWORD rc = WaitForSingleObject (queue->event, INFINITE); - if (rc == WAIT_FAILED) - { - printf ("Wait for event failed\n"); - queue->running--; - ExitThread (0); - } - EnterCriticalSection (&queue->queuelock); - } - if (!queue->active) - { - queue->running--; - LeaveCriticalSection (&queue->queuelock); - ExitThread (0); - } - /* not needed, but it is efficient */ - request = - (class queue_request *) InterlockedExchangePointer (&queue->request, - queue->request-> - next); - LeaveCriticalSection (&queue->queuelock); - request->process (); - delete request; + system_printf (("failed to create the request queue semaphore, " + "error = %lu"), + GetLastError ()); + abort (); } - queue->running--; - ExitThread (0); + + create_workers (initial_workers); } -void -threaded_queue::create_workers () +threaded_queue::~threaded_queue () { - InitializeCriticalSection (&queuelock); - if ((event = CreateEvent (NULL, FALSE, FALSE, NULL)) == NULL) - { - printf ("Failed to create event queue (%lu), terminating\n", - GetLastError ()); - exit (1); - } - active = true; + if (_running) + stop (); - /* FIXME: Use a stack pair and create threads on the fly whenever - * we have to to service a request. - */ - for (unsigned int i = 0; i < initial_workers; i++) + debug_printf ("deleting all pending queue requests"); + queue_request *reqptr = _requests_head; + while (reqptr) { - HANDLE hThread; - DWORD tid; - hThread = CreateThread (NULL, 0, worker_function, this, 0, &tid); - if (hThread == NULL) - { - printf ("Failed to create thread (%lu), terminating\n", - GetLastError ()); - exit (1); - } - CloseHandle (hThread); - running++; + queue_request *const ptr = reqptr; + reqptr = reqptr->_next; + safe_delete (ptr); } -} -void -threaded_queue::cleanup () -{ - /* harvest the threads */ - active = false; - /* kill the request processing loops */ - queue_process_param *reqloop; - /* make sure we don't race with a incoming request creation */ - EnterCriticalSection (&queuelock); - reqloop = - (queue_process_param *) InterlockedExchangePointer (&process_head, NULL); - while (reqloop) - { - queue_process_param *t = reqloop; - reqloop = reqloop->next; - delete t; - } - LeaveCriticalSection (&queuelock); - if (!running) - return; - printf ("Waiting for current queue threads to terminate\n"); - for (int n = running; n; n--) - PulseEvent (event); - while (running) - sleep (1); - DeleteCriticalSection (&queuelock); - CloseHandle (event); -} - -/* FIXME: return success or failure */ -void -threaded_queue::add (queue_request * therequest) -{ - /* safe to not "Try" because workers don't hog this, they wait on the event - */ - EnterCriticalSection (&queuelock); - if (!running) - { - printf ("No worker threads to handle request!\n"); - } - if (!request) - request = therequest; - else - { - /* add to the queue end. */ - queue_request *listrequest = request; - while (listrequest->next) - listrequest = listrequest->next; - listrequest->next = therequest; - } - PulseEvent (event); - LeaveCriticalSection (&queuelock); + DeleteCriticalSection (&_queue_lock); + if (_requests_sem) + (void) CloseHandle (_requests_sem); } /* FIXME: return success or failure rather than quitting */ void -threaded_queue::process_requests (queue_process_param * params, - threaded_queue_thread_function * - request_loop) +threaded_queue::add_submission_loop (queue_submission_loop *const submitter) { - if (params->start (request_loop, this) == false) - exit (1); - params->next = - (queue_process_param *) InterlockedExchangePointer (&process_head, - params); -} + assert (this); + assert (submitter); + assert (submitter->_queue == this); + assert (!submitter->_next); -/* queue_process_param */ -/* How does a constructor return an error? */ -queue_process_param::queue_process_param (bool ninterruptible):running (false), shutdown (false), -interruptible -(ninterruptible) -{ - if (!interruptible) - return; - debug_printf ("creating an interruptible processing thread\n"); - if ((interrupt = CreateEvent (NULL, FALSE, FALSE, NULL)) == NULL) - { - printf ("Failed to create interrupt event (%lu), terminating\n", - GetLastError ()); - exit (1); - } -} + submitter->_next = + TInterlockedExchangePointer (&_submitters_head, submitter); -queue_process_param::~queue_process_param () -{ - if (running) - stop (); - if (!interruptible) - return; - CloseHandle (interrupt); + if (_running) + submitter->start (); } bool - queue_process_param::start (threaded_queue_thread_function * request_loop, - threaded_queue * thequeue) +threaded_queue::start () { - queue = thequeue; - hThread = CreateThread (NULL, 0, request_loop, this, 0, &tid); - if (hThread) + EnterCriticalSection (&_queue_lock); + const bool was_running = _running; + _running = true; + queue_submission_loop *loopptr = _submitters_head; + LeaveCriticalSection (&_queue_lock); + + if (!was_running) { - running = true; - return true; + debug_printf ("starting all queue submission loops"); + + while (loopptr) + { + queue_submission_loop *const ptr = loopptr; + loopptr = loopptr->_next; + ptr->start (); + } } - printf ("Failed to create thread (%lu), terminating\n", GetLastError ()); - return false; + + return was_running; } -void -queue_process_param::stop () +bool +threaded_queue::stop () { - if (interruptible) + EnterCriticalSection (&_queue_lock); + const bool was_running = _running; + _running = false; + queue_submission_loop *loopptr = _submitters_head; + LeaveCriticalSection (&_queue_lock); + + if (was_running) { - InterlockedExchange (&shutdown, true); - PulseEvent (interrupt); - /* Wait up to 50 ms for the thread to exit. If it doesn't _and_ we get - * scheduled again, we print an error and exit. We _should_ loop or - * try resignalling. We don't want to hand here though... - */ - int n = 5; - while (n-- && WaitForSingleObject (hThread, 1000) == WAIT_TIMEOUT); - if (!n) + debug_printf ("stopping all queue submission loops"); + while (loopptr) { - printf ("Process thread didn't shutdown cleanly after 200ms!\n"); - exit (1); + queue_submission_loop *const ptr = loopptr; + loopptr = loopptr->_next; + ptr->stop (); } - else - running = false; + + ReleaseSemaphore (_requests_sem, _workers_count, NULL); + while (_workers_count) + { + debug_printf (("waiting for worker threads to terminate: " + "%lu still running"), + _workers_count); + Sleep (1000); + } + debug_printf ("all worker threads have terminated"); } + + return was_running; +} + +/* FIXME: return success or failure */ +void +threaded_queue::add (queue_request *const therequest) +{ + assert (this); + assert (therequest); + assert (!therequest->_next); + + if (!_workers_count) + { + system_printf ("warning: no worker threads to handle request!"); + // FIXME: And then what? + } + + EnterCriticalSection (&_queue_lock); + if (!_requests_head) + _requests_head = therequest; else { - printf ("killing request loop thread %ld\n", tid); - int rc; - if (!(rc = TerminateThread (hThread, 0))) - { - printf ("error shutting down request loop worker thread\n"); - } - running = false; + /* Add to the queue end. */ + queue_request *reqptr = _requests_head; + for (; reqptr->_next; reqptr = reqptr->_next) + {} + assert (reqptr); + assert (!reqptr->_next); + reqptr->_next = therequest; } - CloseHandle (hThread); + + _requests_count += 1; + assert (_requests_count > 0); + LeaveCriticalSection (&_queue_lock); + + (void) ReleaseSemaphore (_requests_sem, 1, NULL); } -/* queue_request */ -queue_request::queue_request ():next (NULL) +/*static*/ DWORD WINAPI +threaded_queue::start_routine (const LPVOID lpParam) { + class threaded_queue *const queue = (class threaded_queue *) lpParam; + assert (queue); + + queue->worker_loop (); + + const long count = InterlockedDecrement (&queue->_workers_count); + assert (count >= 0); + + if (queue->_running) + debug_printf ("worker loop has exited; thread about to terminate"); + + return 0; +} + +/* Called from the constructor: so no need to be thread-safe until the + * worker threads start to be created; thus the interlocked increment + * of the `_workers_count' field. + */ + +void +threaded_queue::create_workers (const size_t initial_workers) +{ + assert (initial_workers > 0); + + for (unsigned int i = 0; i != initial_workers; i++) + { + const long count = InterlockedIncrement (&_workers_count); + assert (count > 0); + + DWORD tid; + const HANDLE hThread = + CreateThread (NULL, 0, start_routine, this, 0, &tid); + + if (!hThread) + { + system_printf ("failed to create thread, error = %lu", + GetLastError ()); + abort (); + } + + (void) CloseHandle (hThread); + } } void -queue_request::process (void) +threaded_queue::worker_loop () { - printf ("\n**********************************************\n" - "Oh no! we've hit the base queue_request process() function, and this indicates a coding\n" - "fault !!!\n" "***********************************************\n"); + while (true) + { + const DWORD rc = WaitForSingleObject (_requests_sem, INFINITE); + if (rc == WAIT_FAILED) + { + system_printf ("wait for request semaphore failed, error = %lu", + GetLastError ()); + return; + } + assert (rc == WAIT_OBJECT_0); + + EnterCriticalSection (&_queue_lock); + if (!_running) + { + LeaveCriticalSection (&_queue_lock); + return; + } + + assert (_requests_head); + queue_request *const reqptr = _requests_head; + _requests_head = reqptr->_next; + + _requests_count -= 1; + assert (_requests_count >= 0); + LeaveCriticalSection (&_queue_lock); + + assert (reqptr); + reqptr->process (); + safe_delete (reqptr); + } } + +/*****************************************************************************/ + +/* queue_submission_loop */ + +queue_submission_loop::queue_submission_loop (threaded_queue *const queue, + const bool ninterruptible) + : _running (false), + _interrupt_event (NULL), + _queue (queue), + _interruptible (ninterruptible), + _hThread (NULL), + _tid (0), + _next (NULL) +{ + if (_interruptible) + { + // verbose: debug_printf ("creating an interruptible processing thread"); + + _interrupt_event = CreateEvent (NULL, // SECURITY_ATTRIBUTES + FALSE, // Auto-reset + FALSE, // Initially non-signalled + NULL); // Anonymous + + if (!_interrupt_event) + { + system_printf ("failed to create interrupt event, error = %lu", + GetLastError ()); + abort (); + } + } +} + +queue_submission_loop::~queue_submission_loop () +{ + if (_running) + stop (); + if (_interrupt_event) + (void) CloseHandle (_interrupt_event); + if (_hThread) + (void) CloseHandle (_hThread); +} + +bool +queue_submission_loop::start () +{ + assert (this); + assert (!_hThread); + + const bool was_running = _running; + + if (!was_running) + { + _running = true; + + _hThread = CreateThread (NULL, 0, start_routine, this, 0, &_tid); + if (!_hThread) + { + system_printf ("failed to create thread, error = %lu", + GetLastError ()); + abort (); + } + } + + return was_running; +} + +bool +queue_submission_loop::stop () +{ + assert (this); + assert (_hThread && _hThread != INVALID_HANDLE_VALUE); + + const bool was_running = _running; + + if (_running) + { + _running = false; + + if (_interruptible) + { + assert (_interrupt_event + && _interrupt_event != INVALID_HANDLE_VALUE); + + SetEvent (_interrupt_event); + + if (WaitForSingleObject (_hThread, 1000) == WAIT_TIMEOUT) + { + system_printf (("request loop thread %lu failed to shutdown " + "when asked politely: about to get heavy"), + _tid); + + if (!TerminateThread (_hThread, 0)) + { + system_printf (("failed to kill request loop thread %lu" + ", error = %lu"), + _tid, GetLastError ()); + abort (); + } + } + } + else + { + // FIXME: could wait to see if the request loop notices that + // the submission loop is no longer running and shuts down + // voluntarily. + + debug_printf ("killing request loop thread %lu", _tid); + + if (!TerminateThread (_hThread, 0)) + system_printf (("failed to kill request loop thread %lu" + ", error = %lu"), + _tid, GetLastError ()); + } + } + + return was_running; +} + +/*static*/ DWORD WINAPI +queue_submission_loop::start_routine (const LPVOID lpParam) +{ + class queue_submission_loop *const submission_loop = + (class queue_submission_loop *) lpParam; + assert (submission_loop); + + submission_loop->request_loop (); + + debug_printf ("submission loop has exited; thread about to terminate"); + + submission_loop->stop (); + + return 0; +} + +/*****************************************************************************/ diff --git a/winsup/cygwin/threaded_queue.h b/winsup/cygwin/threaded_queue.h index d72541e22..5b6fddc42 100755 --- a/winsup/cygwin/threaded_queue.h +++ b/winsup/cygwin/threaded_queue.h @@ -4,64 +4,124 @@ Written by Robert Collins - This file is part of Cygwin. +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. */ +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ #ifndef _THREADED_QUEUE_ #define _THREADED_QUEUE_ +/*****************************************************************************/ + /* a specific request */ class queue_request { - public: - class queue_request *next; - virtual void process (); - queue_request(); +public: + queue_request *_next; + + queue_request () : _next (NULL) {} + virtual ~queue_request (); + + virtual void process () = 0; }; - -typedef DWORD WINAPI threaded_queue_thread_function (LPVOID); -/* parameters for a request finding and submitting loop */ - -class queue_process_param -{ - public: - bool start (threaded_queue_thread_function *, class threaded_queue *); - void stop (); - bool running; - long int shutdown; - class queue_process_param * next; - class threaded_queue *queue; - queue_process_param (bool ninterruptible); - ~queue_process_param (); - bool interruptible; - HANDLE interrupt; - HANDLE hThread; - DWORD tid; -}; +/*****************************************************************************/ /* a queue to allocate requests from n submission loops to x worker threads */ +class queue_submission_loop; + class threaded_queue { - public: - CRITICAL_SECTION queuelock; - HANDLE event; - bool active; - queue_request * request; - unsigned int initial_workers; - unsigned int running; - void create_workers (); - void cleanup (); - void add (queue_request *); - void process_requests (queue_process_param *, threaded_queue_thread_function *); - threaded_queue () : active (false), request (NULL), initial_workers (1), running (0), process_head (NULL) {}; - private: - queue_request *process_head; +public: + threaded_queue (size_t initial_workers = 1); + ~threaded_queue (); + + void add_submission_loop (queue_submission_loop *); + + bool running () const { return _running; } + + bool start (); + bool stop (); + + void add (queue_request *); + +private: + long _workers_count; + bool _running; + + queue_submission_loop *_submitters_head; + + long _requests_count; // Informational only. + queue_request *_requests_head; + + CRITICAL_SECTION _queue_lock; + HANDLE _requests_sem; // == _requests_count + + static DWORD WINAPI start_routine (LPVOID /* this */); + + void create_workers (size_t initial_workers); + void worker_loop (); }; +/*****************************************************************************/ + +/* parameters for a request finding and submitting loop */ + +class queue_submission_loop +{ + friend class threaded_queue; + +public: + queue_submission_loop (threaded_queue *, bool ninterruptible); + virtual ~queue_submission_loop (); + + bool start (); + bool stop (); + + threaded_queue *queue () { return _queue; }; + +protected: + bool _running; + HANDLE _interrupt_event; + threaded_queue *const _queue; + +private: + bool _interruptible; + HANDLE _hThread; + DWORD _tid; + queue_submission_loop *_next; + + static DWORD WINAPI start_routine (LPVOID /* this */); + virtual void request_loop () = 0; +}; + +#ifdef __cplusplus + +/*---------------------------------------------------------------------------* + * Some type-safe versions of the various interlocked functions. + *---------------------------------------------------------------------------*/ + +template T * +TInterlockedExchangePointer (T **lvalue, T *rvalue) +{ + return reinterpret_cast + (InterlockedExchangePointer (reinterpret_cast (lvalue), + reinterpret_cast (rvalue))); +} + +template T * +TInterlockedCompareExchangePointer (T **lvalue, T *rvalue1, T *rvalue2) +{ + return reinterpret_cast + (InterlockedCompareExchangePointer (reinterpret_cast (lvalue), + reinterpret_cast (rvalue1), + reinterpret_cast (rvalue2))); +} + +#endif /* __cplusplus */ + #endif /* _THREADED_QUEUE_ */ diff --git a/winsup/cygwin/tty.cc b/winsup/cygwin/tty.cc index b564d6620..982bfab57 100644 --- a/winsup/cygwin/tty.cc +++ b/winsup/cygwin/tty.cc @@ -22,7 +22,6 @@ details. */ #include "dtable.h" #include "cygheap.h" #include "pinfo.h" -#include "cygwin/cygserver_transport.h" #include "cygwin/cygserver.h" #include "shared_info.h" #include "cygthread.h" @@ -405,10 +404,17 @@ tty::common_init (fhandler_pty_master *ptym) /* FIXME: we shold NOT set the security wide open when the daemon is running */ - if (wincap.has_security () && cygserver_running != CYGSERVER_OK && - (SetKernelObjectSecurity (hMainProc, DACL_SECURITY_INFORMATION, - get_null_sd ()) == FALSE)) - system_printf ("Can't set process security, %E"); + if (wincap.has_security ()) + { + if (cygserver_running == CYGSERVER_UNKNOWN) + cygserver_init (); + + if (cygserver_running != CYGSERVER_OK + && !SetKernelObjectSecurity (hMainProc, + DACL_SECURITY_INFORMATION, + get_null_sd ())) + system_printf ("Can't set process security, %E"); + } /* Create synchronisation events */ diff --git a/winsup/cygwin/woutsup.h b/winsup/cygwin/woutsup.h new file mode 100644 index 000000000..c048f1c19 --- /dev/null +++ b/winsup/cygwin/woutsup.h @@ -0,0 +1,110 @@ +/* woutsup.h: for Cygwin code compiled outside the DLL (i.e. cygserver). + + Copyright 2002 Red Hat, Inc. + +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. */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef __INSIDE_CYGWIN__ +#error "woutsup.h is not for code being compiled inside the dll" +#endif + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif + +#if _WIN32_WINNT < 0x0500 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif + +#define WIN32_LEAN_AND_MEAN 1 +#define _WINGDI_H +#define _WINUSER_H +#define _WINNLS_H +#define _WINVER_H +#define _WINNETWK_H +#define _WINSVC_H +#include +#include +#include +#undef _WINGDI_H +#undef _WINUSER_H +#undef _WINNLS_H +#undef _WINVER_H +#undef _WINNETWK_H +#undef _WINSVC_H + +#include "wincap.h" + +/* The one function we use from winuser.h most of the time */ +extern "C" DWORD WINAPI GetLastError (void); + +extern int cygserver_running; + +#if !defined(__STDC_VERSION__) || __STDC_VERSION__ >= 199900L +#define NEW_MACRO_VARARGS +#endif + +/* + * A reproduction of the macros. This allows code that + * runs both inside and outside the Cygwin DLL to use the same macros + * for logging messages. + */ + +extern "C" void __cygserver__printf (const char *, const char *, ...); + +#ifdef NEW_MACRO_VARARGS + +#define system_printf(...) \ + do \ + { \ + __cygserver__printf (__PRETTY_FUNCTION__, __VA_ARGS__); \ + } while (false) + +#define __noop_printf(...) do {;} while (false) + +#else /* !NEW_MACRO_VARARGS */ + +#define system_printf(args...) \ + do \ + { \ + __cygserver__printf (__PRETTY_FUNCTION__, ## args); \ + } while (false) + +#define __noop_printf(args...) do {;} while (false) + +#endif /* !NEW_MACRO_VARARGS */ + +#ifdef DEBUGGING +#define debug_printf system_printf +#define paranoid_printf system_printf +#define select_printf system_printf +#define sigproc_printf system_printf +#define syscall_printf system_printf +#define termios_printf system_printf +#define wm_printf system_printf +#define minimal_printf system_printf +#define malloc_printf system_printf +#define thread_printf system_printf +#else +#define debug_printf __noop_printf +#define paranoid_printf __noop_printf +#define select_printf __noop_printf +#define sigproc_printf __noop_printf +#define syscall_printf __noop_printf +#define termios_printf __noop_printf +#define wm_printf __noop_printf +#define minimal_printf __noop_printf +#define malloc_printf __noop_printf +#define thread_printf __noop_printf +#endif + +#include "safe_memory.h"