diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Runtime/r3/win/localipc-win.cpp | 1645 |
1 files changed, 1645 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r3/win/localipc-win.cpp b/src/VBox/Runtime/r3/win/localipc-win.cpp new file mode 100644 index 00000000..e4ce5274 --- /dev/null +++ b/src/VBox/Runtime/r3/win/localipc-win.cpp @@ -0,0 +1,1645 @@ +/* $Id: localipc-win.cpp $ */ +/** @file + * IPRT - Local IPC, Windows Implementation Using Named Pipes. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_LOCALIPC +/* + * We have to force NT 5.0 here because of + * ConvertStringSecurityDescriptorToSecurityDescriptor. Note that because of + * FILE_FLAG_FIRST_PIPE_INSTANCE this code actually requires W2K SP2+. + */ +#ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0500 /* for ConvertStringSecurityDescriptorToSecurityDescriptor */ +#elif _WIN32_WINNT < 0x0500 +# undef _WIN32_WINNT +# define _WIN32_WINNT 0x0500 +#endif +#define UNICODE /* For the SDDL_ strings. */ +#include <iprt/win/windows.h> +#include <sddl.h> + +#include "internal/iprt.h" +#include <iprt/localipc.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/ldr.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/utf16.h> + +#include "internal/magics.h" +#include "internal-r3-win.h" + + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Pipe prefix string. */ +#define RTLOCALIPC_WIN_PREFIX L"\\\\.\\pipe\\IPRT-" + +/** DACL for block all network access and local users other than the creator/owner. + * + * ACE format: (ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_sid) + * + * Note! FILE_GENERIC_WRITE (SDDL_FILE_WRITE) is evil here because it includes + * the FILE_CREATE_PIPE_INSTANCE(=FILE_APPEND_DATA) flag. Thus the hardcoded + * value 0x0012019b in the client ACE. The server-side still needs + * setting FILE_CREATE_PIPE_INSTANCE although. + * It expands to: + * 0x00000001 - FILE_READ_DATA + * 0x00000008 - FILE_READ_EA + * 0x00000080 - FILE_READ_ATTRIBUTES + * 0x00020000 - READ_CONTROL + * 0x00100000 - SYNCHRONIZE + * 0x00000002 - FILE_WRITE_DATA + * 0x00000010 - FILE_WRITE_EA + * 0x00000100 - FILE_WRITE_ATTRIBUTES + * = 0x0012019b (client) + * + (only for server): + * 0x00000004 - FILE_CREATE_PIPE_INSTANCE + * = 0x0012019f + * + * @todo Triple check this! + * @todo EVERYONE -> AUTHENTICATED USERS or something more appropriate? + * @todo Have trouble allowing the owner FILE_CREATE_PIPE_INSTANCE access, so for now I'm hacking + * it just to get progress - the service runs as local system. + * The CREATOR OWNER and PERSONAL SELF works (the former is only involved in inheriting + * it seems, which is why it won't work. The latter I've no idea about. Perhaps the solution + * is to go the annoying route of OpenProcessToken, QueryTokenInformation, + * ConvertSidToStringSid and then use the result... Suggestions are very welcome + */ +#define RTLOCALIPC_WIN_SDDL_BASE \ + SDDL_DACL SDDL_DELIMINATOR \ + SDDL_ACE_BEGIN SDDL_ACCESS_DENIED L";;" SDDL_GENERIC_ALL L";;;" SDDL_NETWORK SDDL_ACE_END \ + SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" SDDL_FILE_ALL L";;;" SDDL_LOCAL_SYSTEM SDDL_ACE_END + +#define RTLOCALIPC_WIN_SDDL_SERVER \ + RTLOCALIPC_WIN_SDDL_BASE \ + SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019f" L";;;" SDDL_EVERYONE SDDL_ACE_END + +#define RTLOCALIPC_WIN_SDDL_CLIENT \ + RTLOCALIPC_WIN_SDDL_BASE \ + SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019b" L";;;" SDDL_EVERYONE SDDL_ACE_END + +// SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" SDDL_GENERIC_ALL L";;;" SDDL_PERSONAL_SELF SDDL_ACE_END \ +// SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";CIOI;" SDDL_GENERIC_ALL L";;;" SDDL_CREATOR_OWNER SDDL_ACE_END +// SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019b" L";;;" SDDL_EVERYONE SDDL_ACE_END +// SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" SDDL_FILE_ALL L";;;" SDDL_LOCAL_SYSTEM SDDL_ACE_END + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Local IPC service instance, Windows. + */ +typedef struct RTLOCALIPCSERVERINT +{ + /** The magic (RTLOCALIPCSERVER_MAGIC). */ + uint32_t u32Magic; + /** The creation flags. */ + uint32_t fFlags; + /** Critical section protecting the structure. */ + RTCRITSECT CritSect; + /** The number of references to the instance. + * @remarks The reference counting isn't race proof. */ + uint32_t volatile cRefs; + /** Indicates that there is a pending cancel request. */ + bool volatile fCancelled; + /** The named pipe handle. */ + HANDLE hNmPipe; + /** The handle to the event object we're using for overlapped I/O. */ + HANDLE hEvent; + /** The overlapped I/O structure. */ + OVERLAPPED OverlappedIO; + /** The full pipe name (variable length). */ + RTUTF16 wszName[1]; +} RTLOCALIPCSERVERINT; +/** Pointer to a local IPC server instance (Windows). */ +typedef RTLOCALIPCSERVERINT *PRTLOCALIPCSERVERINT; + + +/** + * Local IPC session instance, Windows. + * + * This is a named pipe and we should probably merge the pipe code with this to + * save work and code duplication. + */ +typedef struct RTLOCALIPCSESSIONINT +{ + /** The magic (RTLOCALIPCSESSION_MAGIC). */ + uint32_t u32Magic; + /** Critical section protecting the structure. */ + RTCRITSECT CritSect; + /** The number of references to the instance. + * @remarks The reference counting isn't race proof. */ + uint32_t volatile cRefs; + /** Set if the zero byte read that the poll code using is pending. */ + bool fZeroByteRead; + /** Indicates that there is a pending cancel request. */ + bool volatile fCancelled; + /** Set if this is the server side, clear if the client. */ + bool fServerSide; + /** The named pipe handle. */ + HANDLE hNmPipe; + struct + { + RTTHREAD hActiveThread; + /** The handle to the event object we're using for overlapped I/O. */ + HANDLE hEvent; + /** The overlapped I/O structure. */ + OVERLAPPED OverlappedIO; + } + /** Overlapped reads. */ + Read, + /** Overlapped writes. */ + Write; +#if 0 /* Non-blocking writes are not yet supported. */ + /** Bounce buffer for writes. */ + uint8_t *pbBounceBuf; + /** Amount of used buffer space. */ + size_t cbBounceBufUsed; + /** Amount of allocated buffer space. */ + size_t cbBounceBufAlloc; +#endif + /** Buffer for the zero byte read. + * Used in RTLocalIpcSessionWaitForData(). */ + uint8_t abBuf[8]; +} RTLOCALIPCSESSIONINT; +/** Pointer to a local IPC session instance (Windows). */ +typedef RTLOCALIPCSESSIONINT *PRTLOCALIPCSESSIONINT; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static bool volatile g_fResolvedApis = false; +/** advapi32.dll API ConvertStringSecurityDescriptorToSecurityDescriptorW. */ +static decltype(ConvertStringSecurityDescriptorToSecurityDescriptorW) *g_pfnSSDLToSecDescW = NULL; + + +/** + * Builds and allocates the security descriptor required for securing the local pipe. + * + * @return IPRT status code. + * @param ppDesc Where to store the allocated security descriptor on success. + * Must be free'd using LocalFree(). + * @param fServer Whether it's for a server or client instance. + */ +static int rtLocalIpcServerWinAllocSecurityDescriptior(PSECURITY_DESCRIPTOR *ppDesc, bool fServer) +{ + /* + * Resolve the API the first time around. + */ + if (!g_fResolvedApis) + { + g_pfnSSDLToSecDescW = (decltype(g_pfnSSDLToSecDescW))RTLdrGetSystemSymbol("advapi32.dll", "ConvertStringSecurityDescriptorToSecurityDescriptorW"); + ASMCompilerBarrier(); + g_fResolvedApis = true; + } + + int rc; + PSECURITY_DESCRIPTOR pSecDesc = NULL; + if (g_pfnSSDLToSecDescW) + { + /* + * We'll create a security descriptor from a SDDL that denies + * access to network clients (this is local IPC after all), it + * makes some further restrictions to prevent non-authenticated + * users from screwing around. + */ + PCRTUTF16 pwszSDDL = fServer ? RTLOCALIPC_WIN_SDDL_SERVER : RTLOCALIPC_WIN_SDDL_CLIENT; + if (g_pfnSSDLToSecDescW(pwszSDDL, SDDL_REVISION_1, &pSecDesc, NULL)) + { + AssertPtr(pSecDesc); + *ppDesc = pSecDesc; + return VINF_SUCCESS; + } + + rc = RTErrConvertFromWin32(GetLastError()); + } + else + { + /* Windows OSes < W2K SP2 not supported for now, bail out. */ + /** @todo Implement me! */ + rc = VERR_NOT_SUPPORTED; + } + return rc; +} + + +/** + * Creates a named pipe instance. + * + * This is used by both RTLocalIpcServerCreate and RTLocalIpcServerListen. + * + * @return IPRT status code. + * @param phNmPipe Where to store the named pipe handle on success. + * This will be set to INVALID_HANDLE_VALUE on failure. + * @param pwszPipeName The named pipe name, full, UTF-16 encoded. + * @param fFirst Set on the first call (from RTLocalIpcServerCreate), + * otherwise clear. Governs the + * FILE_FLAG_FIRST_PIPE_INSTANCE flag. + */ +static int rtLocalIpcServerWinCreatePipeInstance(PHANDLE phNmPipe, PCRTUTF16 pwszPipeName, bool fFirst) +{ + *phNmPipe = INVALID_HANDLE_VALUE; + + PSECURITY_DESCRIPTOR pSecDesc; + int rc = rtLocalIpcServerWinAllocSecurityDescriptior(&pSecDesc, fFirst /* Server? */); + if (RT_SUCCESS(rc)) + { + SECURITY_ATTRIBUTES SecAttrs; + SecAttrs.nLength = sizeof(SECURITY_ATTRIBUTES); + SecAttrs.lpSecurityDescriptor = pSecDesc; + SecAttrs.bInheritHandle = FALSE; + + DWORD fOpenMode = PIPE_ACCESS_DUPLEX + | PIPE_WAIT + | FILE_FLAG_OVERLAPPED; + if ( fFirst + && ( g_enmWinVer >= kRTWinOSType_XP + || ( g_enmWinVer == kRTWinOSType_2K + && g_WinOsInfoEx.wServicePackMajor >= 2) ) ) + fOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE; /* Introduced with W2K SP2 */ + + HANDLE hNmPipe = CreateNamedPipeW(pwszPipeName, /* lpName */ + fOpenMode, /* dwOpenMode */ + PIPE_TYPE_BYTE, /* dwPipeMode */ + PIPE_UNLIMITED_INSTANCES, /* nMaxInstances */ + PAGE_SIZE, /* nOutBufferSize (advisory) */ + PAGE_SIZE, /* nInBufferSize (ditto) */ + 30*1000, /* nDefaultTimeOut = 30 sec */ + &SecAttrs); /* lpSecurityAttributes */ + LocalFree(pSecDesc); + if (hNmPipe != INVALID_HANDLE_VALUE) + *phNmPipe = hNmPipe; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + + return rc; +} + + +/** + * Validates the user specified name. + * + * @returns IPRT status code. + * @param pszName The name to validate. + * @param pcwcFullName Where to return the UTF-16 length of the full name. + * @param fNative Whether it's a native name or a portable name. + */ +static int rtLocalIpcWinValidateName(const char *pszName, size_t *pcwcFullName, bool fNative) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertReturn(*pszName, VERR_INVALID_NAME); + + if (!fNative) + { + size_t cwcName = RT_ELEMENTS(RTLOCALIPC_WIN_PREFIX) - 1; + for (;;) + { + char ch = *pszName++; + if (!ch) + break; + AssertReturn(!RT_C_IS_CNTRL(ch), VERR_INVALID_NAME); + AssertReturn((unsigned)ch < 0x80, VERR_INVALID_NAME); + AssertReturn(ch != '\\', VERR_INVALID_NAME); + AssertReturn(ch != '/', VERR_INVALID_NAME); + cwcName++; + } + *pcwcFullName = cwcName; + } + else + { + int rc = RTStrCalcUtf16LenEx(pszName, RTSTR_MAX, pcwcFullName); + AssertRCReturn(rc, rc); + } + + return VINF_SUCCESS; +} + + +/** + * Constructs the full pipe name as UTF-16. + * + * @returns IPRT status code. + * @param pszName The user supplied name. ASSUMES reasonable length + * for now, so no long path prefixing needed. + * @param pwszFullName The output buffer. + * @param cwcFullName The output buffer size excluding the terminator. + * @param fNative Whether the user supplied name is a native or + * portable one. + */ +static int rtLocalIpcWinConstructName(const char *pszName, PRTUTF16 pwszFullName, size_t cwcFullName, bool fNative) +{ + if (!fNative) + { + static RTUTF16 const s_wszPrefix[] = RTLOCALIPC_WIN_PREFIX; + Assert(cwcFullName * sizeof(RTUTF16) > sizeof(s_wszPrefix)); + memcpy(pwszFullName, s_wszPrefix, sizeof(s_wszPrefix)); + cwcFullName -= RT_ELEMENTS(s_wszPrefix) - 1; + pwszFullName += RT_ELEMENTS(s_wszPrefix) - 1; + } + return RTStrToUtf16Ex(pszName, RTSTR_MAX, &pwszFullName, cwcFullName + 1, NULL); +} + + +RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszName, uint32_t fFlags) +{ + /* + * Validate parameters. + */ + AssertPtrReturn(phServer, VERR_INVALID_POINTER); + *phServer = NIL_RTLOCALIPCSERVER; + AssertReturn(!(fFlags & ~RTLOCALIPC_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + size_t cwcFullName; + int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + if (RT_SUCCESS(rc)) + { + /* + * Allocate and initialize the instance data. + */ + size_t cbThis = RT_UOFFSETOF_DYN(RTLOCALIPCSERVERINT, wszName[cwcFullName + 1]); + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)RTMemAllocVar(cbThis); + AssertReturn(pThis, VERR_NO_MEMORY); + + pThis->u32Magic = RTLOCALIPCSERVER_MAGIC; + pThis->cRefs = 1; /* the one we return */ + pThis->fCancelled = false; + + rc = rtLocalIpcWinConstructName(pszName, pThis->wszName, cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + pThis->hEvent = CreateEvent(NULL /*lpEventAttributes*/, TRUE /*bManualReset*/, + FALSE /*bInitialState*/, NULL /*lpName*/); + if (pThis->hEvent != NULL) + { + RT_ZERO(pThis->OverlappedIO); + pThis->OverlappedIO.Internal = STATUS_PENDING; + pThis->OverlappedIO.hEvent = pThis->hEvent; + + rc = rtLocalIpcServerWinCreatePipeInstance(&pThis->hNmPipe, pThis->wszName, true /* fFirst */); + if (RT_SUCCESS(rc)) + { + *phServer = pThis; + return VINF_SUCCESS; + } + + BOOL fRc = CloseHandle(pThis->hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + + int rc2 = RTCritSectDelete(&pThis->CritSect); + AssertRC(rc2); + } + } + RTMemFree(pThis); + } + return rc; +} + + +/** + * Retains a reference to the server instance. + * + * @returns + * @param pThis The server instance. + */ +DECLINLINE(void) rtLocalIpcServerRetain(PRTLOCALIPCSERVERINT pThis) +{ + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2 && cRefs); NOREF(cRefs); +} + + +/** + * Call when the reference count reaches 0. + * + * Caller owns the critsect. + * + * @returns VINF_OBJECT_DESTROYED + * @param pThis The instance to destroy. + */ +DECL_NO_INLINE(static, int) rtLocalIpcServerWinDestroy(PRTLOCALIPCSERVERINT pThis) +{ + Assert(pThis->u32Magic == ~RTLOCALIPCSERVER_MAGIC); + pThis->u32Magic = ~RTLOCALIPCSERVER_MAGIC; + + BOOL fRc = CloseHandle(pThis->hNmPipe); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + pThis->hNmPipe = INVALID_HANDLE_VALUE; + + fRc = CloseHandle(pThis->hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + pThis->hEvent = NULL; + + RTCritSectLeave(&pThis->CritSect); + RTCritSectDelete(&pThis->CritSect); + + RTMemFree(pThis); + return VINF_OBJECT_DESTROYED; +} + + +/** + * Server instance destructor. + * + * @returns VINF_OBJECT_DESTROYED + * @param pThis The server instance. + */ +DECL_NO_INLINE(static, int) rtLocalIpcServerDtor(PRTLOCALIPCSERVERINT pThis) +{ + RTCritSectEnter(&pThis->CritSect); + return rtLocalIpcServerWinDestroy(pThis); +} + + +/** + * Releases a reference to the server instance. + * + * @returns VINF_SUCCESS if only release, VINF_OBJECT_DESTROYED if destroyed. + * @param pThis The server instance. + */ +DECLINLINE(int) rtLocalIpcServerRelease(PRTLOCALIPCSERVERINT pThis) +{ + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2); + if (!cRefs) + return rtLocalIpcServerDtor(pThis); + return VINF_SUCCESS; +} + + +/** + * Releases a reference to the server instance and leaves the critsect. + * + * @returns VINF_SUCCESS if only release, VINF_OBJECT_DESTROYED if destroyed. + * @param pThis The server instance. + */ +DECLINLINE(int) rtLocalIpcServerReleaseAndUnlock(PRTLOCALIPCSERVERINT pThis) +{ + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2); + if (!cRefs) + return rtLocalIpcServerWinDestroy(pThis); + return RTCritSectLeave(&pThis->CritSect); +} + + + +RTDECL(int) RTLocalIpcServerDestroy(RTLOCALIPCSERVER hServer) +{ + /* + * Validate input. + */ + if (hServer == NIL_RTLOCALIPCSERVER) + return VINF_SUCCESS; + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE); + + /* + * Cancel any thread currently busy using the server, + * leaving the cleanup to it. + */ + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, ~RTLOCALIPCSERVER_MAGIC, RTLOCALIPCSERVER_MAGIC), VERR_WRONG_ORDER); + + RTCritSectEnter(&pThis->CritSect); + + /* Cancel everything. */ + ASMAtomicUoWriteBool(&pThis->fCancelled, true); + if (pThis->cRefs > 1) + { + BOOL fRc = SetEvent(pThis->hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + } + + return rtLocalIpcServerReleaseAndUnlock(pThis); +} + + +RTDECL(int) RTLocalIpcServerListen(RTLOCALIPCSERVER hServer, PRTLOCALIPCSESSION phClientSession) +{ + /* + * Validate input. + */ + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(phClientSession, VERR_INVALID_POINTER); + + /* + * Enter the critsect before inspecting the object further. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, rc); + + rtLocalIpcServerRetain(pThis); + if (!pThis->fCancelled) + { + ResetEvent(pThis->hEvent); + + RTCritSectLeave(&pThis->CritSect); + + /* + * Try connect a client. We need to use overlapped I/O here because + * of the cancellation done by RTLocalIpcServerCancel and RTLocalIpcServerDestroy. + */ + SetLastError(NO_ERROR); + BOOL fRc = ConnectNamedPipe(pThis->hNmPipe, &pThis->OverlappedIO); + DWORD dwErr = fRc ? NO_ERROR : GetLastError(); + if ( !fRc + && dwErr == ERROR_IO_PENDING) + { + WaitForSingleObject(pThis->hEvent, INFINITE); + DWORD dwIgnored; + fRc = GetOverlappedResult(pThis->hNmPipe, &pThis->OverlappedIO, &dwIgnored, FALSE /* bWait*/); + dwErr = fRc ? NO_ERROR : GetLastError(); + } + + RTCritSectEnter(&pThis->CritSect); + if ( !pThis->fCancelled /* Event signalled but not cancelled? */ + && pThis->u32Magic == RTLOCALIPCSERVER_MAGIC) + { + /* + * Still alive, some error or an actual client. + * + * If it's the latter we'll have to create a new pipe instance that + * replaces the current one for the server. The current pipe instance + * will be assigned to the client session. + */ + if ( fRc + || dwErr == ERROR_PIPE_CONNECTED) + { + HANDLE hNmPipe; + rc = rtLocalIpcServerWinCreatePipeInstance(&hNmPipe, pThis->wszName, false /* fFirst */); + if (RT_SUCCESS(rc)) + { + HANDLE hNmPipeSession = pThis->hNmPipe; /* consumed */ + pThis->hNmPipe = hNmPipe; + rc = rtLocalIpcWinCreateSession(phClientSession, hNmPipeSession); + } + else + { + /* + * We failed to create a new instance for the server, disconnect + * the client and fail. Don't try service the client here. + */ + fRc = DisconnectNamedPipe(pThis->hNmPipe); + AssertMsg(fRc, ("%d\n", GetLastError())); + } + } + else + rc = RTErrConvertFromWin32(dwErr); + } + else + { + /* + * Cancelled. + * + * Cancel the overlapped io if it didn't complete (must be done + * in the this thread) or disconnect the client. + */ + Assert(pThis->fCancelled); + if ( fRc + || dwErr == ERROR_PIPE_CONNECTED) + fRc = DisconnectNamedPipe(pThis->hNmPipe); + else if (dwErr == ERROR_IO_PENDING) + fRc = CancelIo(pThis->hNmPipe); + else + fRc = TRUE; + AssertMsg(fRc, ("%d\n", GetLastError())); + rc = VERR_CANCELLED; + } + } + else + { + /*pThis->fCancelled = false; - Terrible interface idea. Add API to clear fCancelled if ever required. */ + rc = VERR_CANCELLED; + } + rtLocalIpcServerReleaseAndUnlock(pThis); + return rc; +} + + +RTDECL(int) RTLocalIpcServerCancel(RTLOCALIPCSERVER hServer) +{ + /* + * Validate input. + */ + PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE); + + /* + * Enter the critical section, then set the cancellation flag + * and signal the event (to wake up anyone in/at WaitForSingleObject). + */ + rtLocalIpcServerRetain(pThis); + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + ASMAtomicUoWriteBool(&pThis->fCancelled, true); + + BOOL fRc = SetEvent(pThis->hEvent); + if (fRc) + rc = VINF_SUCCESS; + else + { + DWORD dwErr = GetLastError(); + AssertMsgFailed(("dwErr=%u\n", dwErr)); + rc = RTErrConvertFromWin32(dwErr); + } + + rtLocalIpcServerReleaseAndUnlock(pThis); + } + else + rtLocalIpcServerRelease(pThis); + return rc; +} + + +/** + * Create a session instance for a new server client or a client connect. + * + * @returns IPRT status code. + * + * @param ppSession Where to store the session handle on success. + * @param hNmPipeSession The named pipe handle if server calling, + * INVALID_HANDLE_VALUE if client connect. This will + * be consumed by this session, meaning on failure to + * create the session it will be closed. + */ +static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession) +{ + AssertPtr(ppSession); + + /* + * Allocate and initialize the session instance data. + */ + int rc; + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->u32Magic = RTLOCALIPCSESSION_MAGIC; + pThis->cRefs = 1; /* our ref */ + pThis->fCancelled = false; + pThis->fZeroByteRead = false; + pThis->fServerSide = hNmPipeSession != INVALID_HANDLE_VALUE; + pThis->hNmPipe = hNmPipeSession; +#if 0 /* Non-blocking writes are not yet supported. */ + pThis->pbBounceBuf = NULL; + pThis->cbBounceBufAlloc = 0; + pThis->cbBounceBufUsed = 0; +#endif + rc = RTCritSectInit(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + pThis->Read.hEvent = CreateEvent(NULL /*lpEventAttributes*/, TRUE /*bManualReset*/, + FALSE /*bInitialState*/, NULL /*lpName*/); + if (pThis->Read.hEvent != NULL) + { + pThis->Read.OverlappedIO.Internal = STATUS_PENDING; + pThis->Read.OverlappedIO.hEvent = pThis->Read.hEvent; + pThis->Read.hActiveThread = NIL_RTTHREAD; + + pThis->Write.hEvent = CreateEvent(NULL /*lpEventAttributes*/, TRUE /*bManualReset*/, + FALSE /*bInitialState*/, NULL /*lpName*/); + if (pThis->Write.hEvent != NULL) + { + pThis->Write.OverlappedIO.Internal = STATUS_PENDING; + pThis->Write.OverlappedIO.hEvent = pThis->Write.hEvent; + pThis->Write.hActiveThread = NIL_RTTHREAD; + + *ppSession = pThis; + return VINF_SUCCESS; + } + + CloseHandle(pThis->Read.hEvent); + } + + /* bail out */ + rc = RTErrConvertFromWin32(GetLastError()); + RTCritSectDelete(&pThis->CritSect); + } + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + if (hNmPipeSession != INVALID_HANDLE_VALUE) + { + BOOL fRc = CloseHandle(hNmPipeSession); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + } + return rc; +} + + +RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *pszName, uint32_t fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(phSession, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTLOCALIPC_C_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + + size_t cwcFullName; + int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + if (RT_SUCCESS(rc)) + { + /* + * Create a session (shared with server client session creation). + */ + PRTLOCALIPCSESSIONINT pThis; + rc = rtLocalIpcWinCreateSession(&pThis, INVALID_HANDLE_VALUE); + if (RT_SUCCESS(rc)) + { + /* + * Try open the pipe. + */ + PSECURITY_DESCRIPTOR pSecDesc; + rc = rtLocalIpcServerWinAllocSecurityDescriptior(&pSecDesc, false /*fServer*/); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwszFullName = RTUtf16Alloc((cwcFullName + 1) * sizeof(RTUTF16)); + if (pwszFullName) + rc = rtLocalIpcWinConstructName(pszName, pwszFullName, cwcFullName, + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + else + rc = VERR_NO_UTF16_MEMORY; + if (RT_SUCCESS(rc)) + { + SECURITY_ATTRIBUTES SecAttrs; + SecAttrs.nLength = sizeof(SECURITY_ATTRIBUTES); + SecAttrs.lpSecurityDescriptor = pSecDesc; + SecAttrs.bInheritHandle = FALSE; + + HANDLE hPipe = CreateFileW(pwszFullName, + GENERIC_READ | GENERIC_WRITE, + 0 /*no sharing*/, + &SecAttrs, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL /*no template handle*/); + if (hPipe != INVALID_HANDLE_VALUE) + { + pThis->hNmPipe = hPipe; + + LocalFree(pSecDesc); + RTUtf16Free(pwszFullName); + + /* + * We're done! + */ + *phSession = pThis; + return VINF_SUCCESS; + } + + rc = RTErrConvertFromWin32(GetLastError()); + } + + RTUtf16Free(pwszFullName); + LocalFree(pSecDesc); + } + + /* destroy the session handle. */ + CloseHandle(pThis->Read.hEvent); + CloseHandle(pThis->Write.hEvent); + RTCritSectDelete(&pThis->CritSect); + + RTMemFree(pThis); + } + } + return rc; +} + + +/** + * Cancells all pending I/O operations, forcing the methods to return with + * VERR_CANCELLED (unless they've got actual data to return). + * + * Used by RTLocalIpcSessionCancel and RTLocalIpcSessionClose. + * + * @returns IPRT status code. + * @param pThis The client session instance. + */ +static int rtLocalIpcWinCancel(PRTLOCALIPCSESSIONINT pThis) +{ + ASMAtomicUoWriteBool(&pThis->fCancelled, true); + + /* + * Call CancelIo since this call cancels both read and write oriented operations. + */ + if ( pThis->fZeroByteRead + || pThis->Read.hActiveThread != NIL_RTTHREAD + || pThis->Write.hActiveThread != NIL_RTTHREAD) + CancelIo(pThis->hNmPipe); + + /* + * Set both event semaphores. + */ + BOOL fRc = SetEvent(pThis->Read.hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + fRc = SetEvent(pThis->Write.hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + + return VINF_SUCCESS; +} + + +/** + * Retains a reference to the session instance. + * + * @param pThis The client session instance. + */ +DECLINLINE(void) rtLocalIpcSessionRetain(PRTLOCALIPCSESSIONINT pThis) +{ + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2 && cRefs); NOREF(cRefs); +} + + +RTDECL(uint32_t) RTLocalIpcSessionRetain(RTLOCALIPCSESSION hSession) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2 && cRefs); + return cRefs; +} + + +/** + * Call when the reference count reaches 0. + * + * Caller owns the critsect. + * + * @returns VINF_OBJECT_DESTROYED + * @param pThis The instance to destroy. + */ +DECL_NO_INLINE(static, int) rtLocalIpcSessionWinDestroy(PRTLOCALIPCSESSIONINT pThis) +{ + BOOL fRc = CloseHandle(pThis->hNmPipe); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + pThis->hNmPipe = INVALID_HANDLE_VALUE; + + fRc = CloseHandle(pThis->Write.hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); + pThis->Write.hEvent = NULL; + + fRc = CloseHandle(pThis->Read.hEvent); + AssertMsg(fRc, ("%d\n", GetLastError())); + pThis->Read.hEvent = NULL; + + int rc2 = RTCritSectLeave(&pThis->CritSect); AssertRC(rc2); + RTCritSectDelete(&pThis->CritSect); + + RTMemFree(pThis); + return VINF_OBJECT_DESTROYED; +} + + +/** + * Releases a reference to the session instance and unlock it. + * + * @returns VINF_SUCCESS or VINF_OBJECT_DESTROYED as appropriate. + * @param pThis The session instance. + */ +DECLINLINE(int) rtLocalIpcSessionReleaseAndUnlock(PRTLOCALIPCSESSIONINT pThis) +{ + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2); + if (!cRefs) + return rtLocalIpcSessionWinDestroy(pThis); + + int rc2 = RTCritSectLeave(&pThis->CritSect); AssertRC(rc2); + Log(("rtLocalIpcSessionReleaseAndUnlock: %u refs left\n", cRefs)); + return VINF_SUCCESS; +} + + +RTDECL(uint32_t) RTLocalIpcSessionRelease(RTLOCALIPCSESSION hSession) +{ + if (hSession == NIL_RTLOCALIPCSESSION) + return 0; + + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < UINT32_MAX / 2); + if (cRefs) + Log(("RTLocalIpcSessionRelease: %u refs left\n", cRefs)); + else + { + RTCritSectEnter(&pThis->CritSect); + rtLocalIpcSessionWinDestroy(pThis); + } + return cRefs; +} + + +RTDECL(int) RTLocalIpcSessionClose(RTLOCALIPCSESSION hSession) +{ + /* + * Validate input. + */ + if (hSession == NIL_RTLOCALIPCSESSION) + return VINF_SUCCESS; + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + /* + * Invalidate the instance, cancel all outstanding I/O and drop our reference. + */ + RTCritSectEnter(&pThis->CritSect); + rtLocalIpcWinCancel(pThis); + return rtLocalIpcSessionReleaseAndUnlock(pThis); +} + + +/** + * Handles WaitForSingleObject return value when waiting for a zero byte read. + * + * The zero byte read is started by the RTLocalIpcSessionWaitForData method and + * left pending when the function times out. This saves us the problem of + * CancelIo messing with all active I/O operations and the trouble of restarting + * the zero byte read the next time the method is called. However should + * RTLocalIpcSessionRead be called after a failed RTLocalIpcSessionWaitForData + * call, the zero byte read will still be pending and it must wait for it to + * complete before the OVERLAPPEDIO structure can be reused. + * + * Thus, both functions will do WaitForSingleObject and share this routine to + * handle the outcome. + * + * @returns IPRT status code. + * @param pThis The session instance. + * @param rcWait The WaitForSingleObject return code. + */ +static int rtLocalIpcWinGetZeroReadResult(PRTLOCALIPCSESSIONINT pThis, DWORD rcWait) +{ + int rc; + DWORD cbRead = 42; + if (rcWait == WAIT_OBJECT_0) + { + if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, !pThis->fCancelled /*fWait*/)) + { + Assert(cbRead == 0); + rc = VINF_SUCCESS; + pThis->fZeroByteRead = false; + } + else if (pThis->fCancelled) + rc = VERR_CANCELLED; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + { + /* We try get the result here too, just in case we're lucky, but no waiting. */ + DWORD dwErr = GetLastError(); + if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, FALSE /*fWait*/)) + { + Assert(cbRead == 0); + rc = VINF_SUCCESS; + pThis->fZeroByteRead = false; + } + else if (rcWait == WAIT_TIMEOUT) + rc = VERR_TIMEOUT; + else if (rcWait == WAIT_ABANDONED) + rc = VERR_INVALID_HANDLE; + else + rc = RTErrConvertFromWin32(dwErr); + } + return rc; +} + + +RTDECL(int) RTLocalIpcSessionRead(RTLOCALIPCSESSION hSession, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + /* pcbRead is optional. */ + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (pThis->Read.hActiveThread == NIL_RTTHREAD) + { + pThis->Read.hActiveThread = RTThreadSelf(); + + size_t cbTotalRead = 0; + while (cbToRead > 0) + { + DWORD cbRead = 0; + if (!pThis->fCancelled) + { + /* + * Wait for pending zero byte read, if necessary. + * Note! It cannot easily be cancelled due to concurrent current writes. + */ + if (!pThis->fZeroByteRead) + { /* likely */ } + else + { + RTCritSectLeave(&pThis->CritSect); + DWORD rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, RT_MS_1MIN); + RTCritSectEnter(&pThis->CritSect); + + rc = rtLocalIpcWinGetZeroReadResult(pThis, rcWait); + if (RT_SUCCESS(rc) || rc == VERR_TIMEOUT) + continue; + break; + } + + /* + * Kick of a an overlapped read. It should return immediately if + * there is bytes in the buffer. If not, we'll cancel it and see + * what we get back. + */ + rc = ResetEvent(pThis->Read.OverlappedIO.hEvent); Assert(rc == TRUE); + RTCritSectLeave(&pThis->CritSect); + + if (ReadFile(pThis->hNmPipe, pvBuf, + cbToRead <= ~(DWORD)0 ? (DWORD)cbToRead : ~(DWORD)0, + &cbRead, &pThis->Read.OverlappedIO)) + { + RTCritSectEnter(&pThis->CritSect); + rc = VINF_SUCCESS; + } + else if (GetLastError() == ERROR_IO_PENDING) + { + WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, INFINITE); + + RTCritSectEnter(&pThis->CritSect); + if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, TRUE /*fWait*/)) + rc = VINF_SUCCESS; + else + { + if (pThis->fCancelled) + rc = VERR_CANCELLED; + else + rc = RTErrConvertFromWin32(GetLastError()); + break; + } + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailedBreak(("%Rrc\n", rc)); + } + } + else + { + rc = VERR_CANCELLED; + break; + } + + /* Advance. */ + cbToRead -= cbRead; + cbTotalRead += cbRead; + pvBuf = (uint8_t *)pvBuf + cbRead; + } + + if (pcbRead) + { + *pcbRead = cbTotalRead; + if ( RT_FAILURE(rc) + && cbTotalRead + && rc != VERR_INVALID_POINTER) + rc = VINF_SUCCESS; + } + + pThis->Read.hActiveThread = NIL_RTTHREAD; + } + else + rc = VERR_WRONG_ORDER; + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +RTDECL(int) RTLocalIpcSessionReadNB(RTLOCALIPCSESSION hSession, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + *pcbRead = 0; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (pThis->Read.hActiveThread == NIL_RTTHREAD) + { + pThis->Read.hActiveThread = RTThreadSelf(); + + for (;;) + { + DWORD cbRead = 0; + if (!pThis->fCancelled) + { + /* + * Wait for pending zero byte read, if necessary. + * Note! It cannot easily be cancelled due to concurrent current writes. + */ + if (!pThis->fZeroByteRead) + { /* likely */ } + else + { + RTCritSectLeave(&pThis->CritSect); + DWORD rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, 0); + RTCritSectEnter(&pThis->CritSect); + + rc = rtLocalIpcWinGetZeroReadResult(pThis, rcWait); + if (RT_SUCCESS(rc)) + continue; + + if (rc == VERR_TIMEOUT) + rc = VINF_TRY_AGAIN; + break; + } + + /* + * Figure out how much we can read (cannot try and cancel here + * like in the anonymous pipe code). + */ + DWORD cbAvailable; + if (PeekNamedPipe(pThis->hNmPipe, NULL, 0, NULL, &cbAvailable, NULL)) + { + if (cbAvailable == 0 || cbToRead == 0) + { + *pcbRead = 0; + rc = VINF_TRY_AGAIN; + break; + } + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + break; + } + if (cbAvailable > cbToRead) + cbAvailable = (DWORD)cbToRead; + + /* + * Kick of a an overlapped read. It should return immediately, so we + * don't really need to leave the critsect here. + */ + rc = ResetEvent(pThis->Read.OverlappedIO.hEvent); Assert(rc == TRUE); + if (ReadFile(pThis->hNmPipe, pvBuf, cbAvailable, &cbRead, &pThis->Read.OverlappedIO)) + { + *pcbRead = cbRead; + rc = VINF_SUCCESS; + } + else if (GetLastError() == ERROR_IO_PENDING) + { + DWORD rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, 0); + if (rcWait == WAIT_TIMEOUT) + { + RTCritSectLeave(&pThis->CritSect); + rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, INFINITE); + RTCritSectEnter(&pThis->CritSect); + } + if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, TRUE /*fWait*/)) + { + *pcbRead = cbRead; + rc = VINF_SUCCESS; + } + else + { + if (pThis->fCancelled) + rc = VERR_CANCELLED; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + AssertMsgFailedBreak(("%Rrc\n", rc)); + } + } + else + rc = VERR_CANCELLED; + break; + } + + pThis->Read.hActiveThread = NIL_RTTHREAD; + } + else + rc = VERR_WRONG_ORDER; + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +#if 0 /* Non-blocking writes are not yet supported. */ +/** + * Common worker for handling I/O completion. + * + * This is used by RTLocalIpcSessionClose and RTLocalIpcSessionWrite. + * + * @returns IPRT status code. + * @param pThis The pipe instance handle. + */ +static int rtLocalIpcSessionWriteCheckCompletion(PRTLOCALIPCSESSIONINT pThis) +{ + int rc; + DWORD rcWait = WaitForSingleObject(pThis->OverlappedIO.hEvent, 0); + if (rcWait == WAIT_OBJECT_0) + { + DWORD cbWritten = 0; + if (GetOverlappedResult(pThis->hNmPipe, &pThis->OverlappedIO, &cbWritten, TRUE)) + { + for (;;) + { + if (cbWritten >= pThis->cbBounceBufUsed) + { + pThis->fIOPending = false; + rc = VINF_SUCCESS; + break; + } + + /* resubmit the remainder of the buffer - can this actually happen? */ + memmove(&pThis->pbBounceBuf[0], &pThis->pbBounceBuf[cbWritten], pThis->cbBounceBufUsed - cbWritten); + rc = ResetEvent(pThis->OverlappedIO.hEvent); Assert(rc == TRUE); + if (!WriteFile(pThis->hNmPipe, pThis->pbBounceBuf, (DWORD)pThis->cbBounceBufUsed, + &cbWritten, &pThis->OverlappedIO)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_IO_PENDING) + rc = VINF_TRY_AGAIN; + else + { + pThis->fIOPending = false; + if (dwErr == ERROR_NO_DATA) + rc = VERR_BROKEN_PIPE; + else + rc = RTErrConvertFromWin32(dwErr); + } + break; + } + Assert(cbWritten > 0); + } + } + else + { + pThis->fIOPending = false; + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else if (rcWait == WAIT_TIMEOUT) + rc = VINF_TRY_AGAIN; + else + { + pThis->fIOPending = false; + if (rcWait == WAIT_ABANDONED) + rc = VERR_INVALID_HANDLE; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + return rc; +} +#endif + + +RTDECL(int) RTLocalIpcSessionWrite(RTLOCALIPCSESSION hSession, const void *pvBuf, size_t cbToWrite) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbToWrite, VERR_INVALID_PARAMETER); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (pThis->Write.hActiveThread == NIL_RTTHREAD) + { + pThis->Write.hActiveThread = RTThreadSelf(); + + /* + * Try write everything. No bounce buffering necessary. + */ + size_t cbTotalWritten = 0; + while (cbToWrite > 0) + { + DWORD cbWritten = 0; + if (!pThis->fCancelled) + { + BOOL fRc = ResetEvent(pThis->Write.OverlappedIO.hEvent); Assert(fRc == TRUE); + RTCritSectLeave(&pThis->CritSect); + + DWORD const cbToWriteInThisIteration = cbToWrite <= ~(DWORD)0 ? (DWORD)cbToWrite : ~(DWORD)0; + fRc = WriteFile(pThis->hNmPipe, pvBuf, cbToWriteInThisIteration, &cbWritten, &pThis->Write.OverlappedIO); + if (fRc) + rc = VINF_SUCCESS; + else + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_IO_PENDING) + { + DWORD rcWait = WaitForSingleObject(pThis->Write.OverlappedIO.hEvent, INFINITE); + if (rcWait == WAIT_OBJECT_0) + { + if (GetOverlappedResult(pThis->hNmPipe, &pThis->Write.OverlappedIO, &cbWritten, TRUE /*fWait*/)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else if (rcWait == WAIT_TIMEOUT) + rc = VERR_TIMEOUT; + else if (rcWait == WAIT_ABANDONED) + rc = VERR_INVALID_HANDLE; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else if (dwErr == ERROR_NO_DATA) + rc = VERR_BROKEN_PIPE; + else + rc = RTErrConvertFromWin32(dwErr); + } + + if (cbWritten > cbToWriteInThisIteration) /* paranoia^3 */ + cbWritten = cbToWriteInThisIteration; + + RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + break; + } + else + { + rc = VERR_CANCELLED; + break; + } + + /* Advance. */ + pvBuf = (char const *)pvBuf + cbWritten; + cbTotalWritten += cbWritten; + cbToWrite -= cbWritten; + } + + pThis->Write.hActiveThread = NIL_RTTHREAD; + } + else + rc = VERR_WRONG_ORDER; + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +RTDECL(int) RTLocalIpcSessionFlush(RTLOCALIPCSESSION hSession) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->Write.hActiveThread == NIL_RTTHREAD) + { + /* No flushing on Windows needed since RTLocalIpcSessionWrite will block until + * all data was written (or an error occurred). */ + /** @todo r=bird: above comment is misinformed. + * Implement this as soon as we want an explicit asynchronous version of + * RTLocalIpcSessionWrite on Windows. */ + rc = VINF_SUCCESS; + } + else + rc = VERR_WRONG_ORDER; + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +RTDECL(int) RTLocalIpcSessionWaitForData(RTLOCALIPCSESSION hSession, uint32_t cMillies) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + uint64_t const msStart = RTTimeMilliTS(); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (pThis->Read.hActiveThread == NIL_RTTHREAD) + { + pThis->Read.hActiveThread = RTThreadSelf(); + + /* + * Wait loop. + */ + for (unsigned iLoop = 0;; iLoop++) + { + /* + * Check for cancellation before we continue. + */ + if (!pThis->fCancelled) + { /* likely */ } + else + { + rc = VERR_CANCELLED; + break; + } + + /* + * Prep something we can wait on. + */ + HANDLE hWait = INVALID_HANDLE_VALUE; + if (pThis->fZeroByteRead) + hWait = pThis->Read.OverlappedIO.hEvent; + else + { + /* Peek at the pipe buffer and see how many bytes it contains. */ + DWORD cbAvailable; + if ( PeekNamedPipe(pThis->hNmPipe, NULL, 0, NULL, &cbAvailable, NULL) + && cbAvailable) + { + rc = VINF_SUCCESS; + break; + } + + /* Start a zero byte read operation that we can wait on. */ + if (cMillies == 0) + { + rc = VERR_TIMEOUT; + break; + } + BOOL fRc = ResetEvent(pThis->Read.OverlappedIO.hEvent); Assert(fRc == TRUE); NOREF(fRc); + DWORD cbRead = 0; + if (ReadFile(pThis->hNmPipe, pThis->abBuf, 0 /*cbToRead*/, &cbRead, &pThis->Read.OverlappedIO)) + { + rc = VINF_SUCCESS; + if (iLoop > 10) + RTThreadYield(); + } + else if (GetLastError() == ERROR_IO_PENDING) + { + pThis->fZeroByteRead = true; + hWait = pThis->Read.OverlappedIO.hEvent; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + if (RT_FAILURE(rc)) + break; + } + + /* + * Check for timeout. + */ + DWORD cMsMaxWait = INFINITE; /* (MSC maybe used uninitialized) */ + if (cMillies == RT_INDEFINITE_WAIT) + cMsMaxWait = INFINITE; + else if ( hWait != INVALID_HANDLE_VALUE + || iLoop > 10) + { + uint64_t cMsElapsed = RTTimeMilliTS() - msStart; + if (cMsElapsed <= cMillies) + cMsMaxWait = cMillies - (uint32_t)cMsElapsed; + else if (iLoop == 0) + cMsMaxWait = cMillies ? 1 : 0; + else + { + rc = VERR_TIMEOUT; + break; + } + } + + /* + * Wait and collect the result. + */ + if (hWait != INVALID_HANDLE_VALUE) + { + RTCritSectLeave(&pThis->CritSect); + + DWORD rcWait = WaitForSingleObject(hWait, cMsMaxWait); + + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc2); + + rc = rtLocalIpcWinGetZeroReadResult(pThis, rcWait); + break; + } + } + + pThis->Read.hActiveThread = NIL_RTTHREAD; + } + + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +RTDECL(int) RTLocalIpcSessionCancel(RTLOCALIPCSESSION hSession) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + /* + * Enter the critical section, then set the cancellation flag + * and signal the event (to wake up anyone in/at WaitForSingleObject). + */ + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + rc = rtLocalIpcWinCancel(pThis); + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +RTDECL(int) RTLocalIpcSessionQueryProcess(RTLOCALIPCSESSION hSession, PRTPROCESS pProcess) +{ + RT_NOREF_PV(hSession); RT_NOREF_PV(pProcess); + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTLocalIpcSessionQueryUserId(RTLOCALIPCSESSION hSession, PRTUID pUid) +{ + RT_NOREF_PV(hSession); RT_NOREF_PV(pUid); + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTLocalIpcSessionQueryGroupId(RTLOCALIPCSESSION hSession, PRTGID pGid) +{ + RT_NOREF_PV(hSession); RT_NOREF_PV(pGid); + return VERR_NOT_SUPPORTED; +} + |