/* transport_pipes.cc Written by Robert Collins This file is part of Cygwin. This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ /* to allow this to link into cygwin and the .dll, a little magic is needed. */ #ifdef __OUTSIDE_CYGWIN__ #include "woutsup.h" #include #else #include "winsup.h" #endif #include #include #include #include #include #include #include #include "cygerrno.h" #include "transport.h" #include "transport_pipes.h" #ifndef __INSIDE_CYGWIN__ #include "cygserver.h" #include "cygserver_ipc.h" #else #include "security.h" #endif #ifdef __INSIDE_CYGWIN__ #define SET_ERRNO(err) set_errno (err) #else #define SET_ERRNO(err) errno = (err) #endif enum { MAX_WAIT_NAMED_PIPE_RETRY = 64, WAIT_NAMED_PIPE_TIMEOUT = 10 // milliseconds }; #ifndef __INSIDE_CYGWIN__ transport_layer_pipes::transport_layer_pipes (const HANDLE hPipe) : _hPipe (hPipe), _is_accepted_endpoint (true), _is_listening_endpoint (false) { assert (_hPipe); assert (_hPipe != INVALID_HANDLE_VALUE); _pipe_name[0] = L'\0'; } #endif /* !__INSIDE_CYGWIN__ */ transport_layer_pipes::transport_layer_pipes () : _hPipe (NULL), _is_accepted_endpoint (false), _is_listening_endpoint (false) { wchar_t cyg_instkey[18]; wchar_t *p = wcpcpy (_pipe_name, PIPE_NAME_PREFIX); if (!cygwin_internal (CW_GET_INSTKEY, cyg_instkey)) wcpcpy (wcpcpy (p, cyg_instkey), PIPE_NAME_SUFFIX); } transport_layer_pipes::~transport_layer_pipes () { close (); } #ifndef __INSIDE_CYGWIN__ static HANDLE listen_pipe; static HANDLE connect_pipe; int transport_layer_pipes::listen () { assert (!_hPipe); assert (!_is_accepted_endpoint); assert (!_is_listening_endpoint); _is_listening_endpoint = true; debug ("Try to create named pipe: %ls", _pipe_name); /* We have to create the first instance of the listening pipe here, and we also have to create at least one instance of the client side to avoid a race condition. See https://cygwin.com/ml/cygwin/2012-11/threads.html#00144 */ listen_pipe = CreateNamedPipeW (_pipe_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, 1000, &sec_all_nih); if (listen_pipe != INVALID_HANDLE_VALUE) { connect_pipe = CreateFileW (_pipe_name, GENERIC_READ | GENERIC_WRITE, 0, &sec_all_nih, OPEN_EXISTING, 0, NULL); if (connect_pipe == INVALID_HANDLE_VALUE) { CloseHandle (listen_pipe); listen_pipe = INVALID_HANDLE_VALUE; } } if (listen_pipe == INVALID_HANDLE_VALUE) { system_printf ("failed to create named pipe: " "is the daemon already running?"); return -1; } return 0; } class transport_layer_pipes * transport_layer_pipes::accept (bool *const recoverable) { assert (!_hPipe); assert (!_is_accepted_endpoint); assert (_is_listening_endpoint); debug ("Try to create named pipe instance: %ls", _pipe_name); const HANDLE accept_pipe = CreateNamedPipeW (_pipe_name, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, 1000, &sec_all_nih); if (accept_pipe == INVALID_HANDLE_VALUE) { debug_printf ("error creating pipe (%u).", GetLastError ()); *recoverable = true; // FIXME: case analysis? return NULL; } if (!ConnectNamedPipe (accept_pipe, NULL) && GetLastError () != ERROR_PIPE_CONNECTED) { debug_printf ("error connecting to pipe (%u)", GetLastError ()); (void) CloseHandle (accept_pipe); *recoverable = true; // FIXME: case analysis? return NULL; } return new transport_layer_pipes (accept_pipe); } #endif /* !__INSIDE_CYGWIN__ */ void transport_layer_pipes::close () { // verbose: debug_printf ("closing pipe %p", _hPipe); if (_hPipe) { assert (_hPipe != INVALID_HANDLE_VALUE); #ifndef __INSIDE_CYGWIN__ if (_is_accepted_endpoint) { (void) FlushFileBuffers (_hPipe); // Blocks until client reads. (void) DisconnectNamedPipe (_hPipe); (void) CloseHandle (_hPipe); } 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 (void *const buf, const size_t len) { // verbose: debug_printf ("reading from pipe %p", _hPipe); 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 (%u)", GetLastError ()); SET_ERRNO (EINVAL); // FIXME? return -1; } return count; } ssize_t transport_layer_pipes::write (void *const buf, const size_t len) { // verbose: debug_printf ("writing to pipe %p", _hPipe); 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, error = %u", GetLastError ()); SET_ERRNO (EINVAL); // FIXME? return -1; } return count; } /* * 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 () { assert (!_hPipe); assert (!_is_accepted_endpoint); assert (!_is_listening_endpoint); static bool assume_cygserver = false; BOOL rc = TRUE; int retries = 0; debug_printf ("Try to connect to named pipe: %W", _pipe_name); while (rc) { _hPipe = CreateFileW (_pipe_name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sec_all_nih, OPEN_EXISTING, SECURITY_IMPERSONATION, NULL); if (_hPipe != INVALID_HANDLE_VALUE) { assert (_hPipe); #ifdef __INSIDE_CYGWIN__ ProtectHandle (_hPipe); #endif assume_cygserver = true; return 0; } _hPipe = NULL; if (!assume_cygserver && GetLastError () != ERROR_PIPE_BUSY) { debug_printf ("Error opening the pipe (%u)", 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 = WaitNamedPipeW (_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 = %u", GetLastError ()); assume_cygserver = false; return -1; } #ifndef __INSIDE_CYGWIN__ bool transport_layer_pipes::impersonate_client () { assert (_hPipe); assert (_hPipe != INVALID_HANDLE_VALUE); assert (_is_accepted_endpoint); if (_hPipe && !ImpersonateNamedPipeClient (_hPipe)) { debug_printf ("Failed to Impersonate client, (%u)", GetLastError ()); return false; } return true; } bool transport_layer_pipes::revert_to_self () { assert (_is_accepted_endpoint); if (!RevertToSelf ()) { debug_printf ("Failed to RevertToSelf, (%u)", GetLastError ()); return false; } return true; } #endif /* !__INSIDE_CYGWIN__ */