localipc-win.cpp revision b0c051f82d721656f056283809e25b538e4a961c
/* $Id$ */
/** @file
* IPRT - Local IPC, Windows Implementation Using Named Pipes.
*/
/*
* Copyright (C) 2008-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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 *
*******************************************************************************/
/*
* 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
#endif
#include <Windows.h>
#include <sddl.h>
#include <iprt/critsect.h>
#include <iprt/localipc.h>
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** Pipe prefix string. */
#define RTLOCALIPC_WIN_PREFIX "\\\\.\\pipe\\IPRT-"
*
* 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 \
#define RTLOCALIPC_WIN_SDDL_SERVER \
#define RTLOCALIPC_WIN_SDDL_CLIENT \
// SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED ";;" SDDL_GENERIC_ALL ";;;" SDDL_PERSONAL_SELF SDDL_ACE_END \
// SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED ";CIOI;" SDDL_GENERIC_ALL ";;;" SDDL_CREATOR_OWNER SDDL_ACE_END
// SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED ";;" "0x0012019b" ";;;" SDDL_EVERYONE SDDL_ACE_END
// SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED ";;" SDDL_FILE_ALL ";;;" SDDL_LOCAL_SYSTEM SDDL_ACE_END
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* Local IPC service instance, Windows.
*/
typedef struct RTLOCALIPCSERVERINT
{
/** The magic (RTLOCALIPCSERVER_MAGIC). */
/** The creation flags. */
/** Critical section protecting the structure. */
/** The number of references to the instance.
* @remarks The reference counting isn't race proof. */
/** Indicates that there is a pending cancel request. */
bool volatile fCancelled;
/** The name pipe handle. */
/** The handle to the event object we're using for overlapped I/O. */
/** The overlapped I/O structure. */
/** The pipe name. */
char szName[1];
/** Pointer to a local IPC server instance (Windows). */
typedef RTLOCALIPCSERVERINT *PRTLOCALIPCSERVERINT;
/**
* Local IPC session instance, Windows.
*/
typedef struct RTLOCALIPCSESSIONINT
{
/** The magic (RTLOCALIPCSESSION_MAGIC). */
/** Critical section protecting the structure. */
/** The number of references to the instance.
* @remarks The reference counting isn't race proof. */
/** Set if there is already pending I/O. */
bool fIOPending;
/** 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;
/** The name pipe handle. */
/** The handle to the event object we're using for overlapped I/O. */
/** The overlapped I/O structure. */
/** Bounce buffer for writes. */
/** Amount of used buffer space. */
/** Amount of allocated buffer space. */
/** Buffer for the zero byte read.
* Used in RTLocalIpcSessionWaitForData(). */
/** Pointer to a local IPC session instance (Windows). */
typedef RTLOCALIPCSESSIONINT *PRTLOCALIPCSESSIONINT;
typedef BOOL WINAPI FNCONVERTSTRINGSECURITYDESCRIPTORTOSECURITYDESCRIPTOR(LPCTSTR, DWORD, PSECURITY_DESCRIPTOR, PULONG);
*PFNCONVERTSTRINGSECURITYDESCRIPTORTOSECURITYDESCRIPTOR; /* No, nobody fell on the keyboard, really! */
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
/**
* 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().
*/
{
/** @todo Stuff this into RTInitOnce? Later. */
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
/*
* 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.
*/
if (RT_SUCCESS(rc))
{
&pSecDesc,
NULL))
{
}
}
}
else
{
/* Windows OSes < W2K SP2 not supported for now, bail out. */
/** @todo Implement me! */
}
if (hAdvApi32 != NIL_RTLDRMOD)
if (RT_SUCCESS(rc))
{
}
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 pszFullPipeName The full named pipe name.
* @param fFirst Set on the first call (from RTLocalIpcServerCreate), otherwise clear.
* Governs the FILE_FLAG_FIRST_PIPE_INSTANCE flag.
*/
static int rtLocalIpcServerWinCreatePipeInstance(PHANDLE phNmPipe, const char *pszFullPipeName, bool fFirst)
{
if (RT_SUCCESS(rc))
{
bool fSupportsFirstInstance = false;
{
if ( /* Vista+. */
/* Windows XP+. */
&& OSInfoEx.dwMinorVersion > 0)
/* Windows 2000. */
&& OSInfoEx.dwMinorVersion == 0
{
/* Requires at least W2K (5.0) SP2+. This is non-fatal. */
fSupportsFirstInstance = true;
}
}
if (fFirst && fSupportsFirstInstance)
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 */
if (hNmPipe != INVALID_HANDLE_VALUE)
{
}
else
}
return rc;
}
RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszName, uint32_t fFlags)
{
/*
* Basic parameter validation.
*/
AssertReturn((fFlags & RTLOCALIPC_FLAGS_MULTI_SESSION), VERR_INVALID_PARAMETER); /** @todo Implement !RTLOCALIPC_FLAGS_MULTI_SESSION */
/*
* Allocate and initialize the instance data.
*/
if (!pThis)
return VERR_NO_MEMORY;
pThis->fCancelled = false;
if (RT_SUCCESS(rc))
{
{
if (RT_SUCCESS(rc))
{
return VINF_SUCCESS;
}
}
else
}
return rc;
}
/**
* Call when the reference count reaches 0.
* Caller owns the critsect.
* @param pThis The instance to destroy.
*/
{
}
{
/*
* Validate input.
*/
if (hServer == NIL_RTLOCALIPCSERVER)
return VINF_SUCCESS;
/*
* Cancel any thread currently busy using the server,
* leaving the cleanup to it.
*/
{
}
else
return VINF_SUCCESS;
}
{
/*
* Validate input.
*/
/*
* Enter the critsect before inspecting the object further.
*/
int rc;
if (pThis->fCancelled)
{
pThis->fCancelled = false;
rc = VERR_CANCELLED;
}
else
{
/*
* Try connect a client. We need to use overlapped I/O here because
* of the cancellation done by RTLocalIpcServerCancel and RTLocalIpcServerDestroy.
*/
if ( !fRc
&& dwErr == ERROR_IO_PENDING)
{
}
{
/*
* 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)
{
if (RT_SUCCESS(rc))
{
}
else
{
/*
* We failed to create a new instance for the server, disconnect
* the client and fail. Don't try service the client here.
*/
}
}
else
}
else
{
/*
* Cancelled or destroyed.
*
* Cancel the overlapped io if it didn't complete (must be done
* in the this thread) or disconnect the client.
*/
if ( fRc
|| dwErr == ERROR_PIPE_CONNECTED)
else if (dwErr == ERROR_IO_PENDING)
else
rc = VERR_CANCELLED;
}
else
}
return rc;
}
{
/*
* Validate input.
*/
/*
* Enter the critical section, then set the cancellation flag
*/
if (RT_SUCCESS(rc))
{
}
return rc;
}
/**
* Create a session instance.
*
* @returns IPRT status code.
*
* @param phClientSession Where to store the session handle on success.
* @param hNmPipeSession The named pipe handle. This will be consumed by this session, meaning on failure
* to create the session it will be closed.
*/
{
int rc;
/*
* Allocate and initialize the session instance data.
*/
if (pThis)
{
pThis->fCancelled = false;
pThis->fIOPending = false;
pThis->fZeroByteRead = false;
if (RT_SUCCESS(rc))
{
{
*phClientSession = pThis;
return VINF_SUCCESS;
}
/* bail out */
}
}
else
rc = VERR_NO_MEMORY;
return rc;
}
RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *pszName, uint32_t fFlags)
{
if (!pThis)
return VERR_NO_MEMORY;
pThis->fIOPending = false;
pThis->fZeroByteRead = false;
pThis->fCancelled = false;
pThis->cbBounceBufAlloc = 0;
pThis->cbBounceBufUsed = 0;
if (RT_SUCCESS(rc))
{
{
if (RT_SUCCESS(rc))
{
char *pszPipe;
{
GENERIC_READ /* read and write access */
0, /* no sharing */
&SecAttrs, /* lpSecurityAttributes */
OPEN_EXISTING, /* opens existing pipe */
FILE_FLAG_OVERLAPPED, /* default attributes */
NULL); /* no template file */
if (hPipe != INVALID_HANDLE_VALUE)
{
return VINF_SUCCESS;
}
else
}
else
rc = VERR_NO_MEMORY;
}
}
else
}
return rc;
}
/**
* Call when the reference count reaches 0.
* Caller owns the critsect.
* @param pThis The instance to destroy.
*/
{
}
{
/*
* Validate input.
*/
if (hSession == NIL_RTLOCALIPCSESSION)
return VINF_SUCCESS;
/*
* Cancel any thread currently busy using the session,
* leaving the cleanup to it.
*/
{
}
else
return VINF_SUCCESS;
}
RTDECL(int) RTLocalIpcSessionRead(RTLOCALIPCSESSION hSession, void *pvBuffer, size_t cbBuffer, size_t *pcbRead)
{
/* pcbRead is optional. */
if (RT_SUCCESS(rc))
{
/* No concurrent readers, sorry. */
{
/*
* If pcbRead is non-NULL this indicates the maximum number of bytes to read.
* If pcbRead is NULL then this is the exact number of bytes to read.
*/
size_t cbTotalRead = 0;
while (cbToRead > 0)
{
/*
* 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.
*/
pThis->fIOPending = true;
rc = VINF_SUCCESS;
else if (GetLastError() == ERROR_IO_PENDING)
{
rc = VINF_SUCCESS;
else
{
}
}
else
{
}
pThis->fIOPending = false;
if (RT_FAILURE(rc))
break;
/* Advance. */
cbTotalRead += cbRead;
}
if (pcbRead)
{
*pcbRead = cbTotalRead;
if ( RT_FAILURE(rc)
&& cbTotalRead
&& rc != VERR_INVALID_POINTER)
rc = VINF_SUCCESS;
}
}
else
}
return rc;
}
/**
* Common worker for handling I/O completion.
*
* This is used by RTLocalIpcSessionClose and RTLocalIpcSessionWrite.
*
* @returns IPRT status code.
* @param pThis The pipe instance handle.
*/
{
int rc;
if (dwRc == WAIT_OBJECT_0)
{
{
for (;;)
{
{
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);
{
if (dwErr == ERROR_IO_PENDING)
rc = VINF_TRY_AGAIN;
else
{
pThis->fIOPending = false;
if (dwErr == ERROR_NO_DATA)
else
}
break;
}
}
}
else
{
pThis->fIOPending = false;
}
}
else if (dwRc == WAIT_TIMEOUT)
rc = VINF_TRY_AGAIN;
else
{
pThis->fIOPending = false;
if (dwRc == WAIT_ABANDONED)
else
}
return rc;
}
RTDECL(int) RTLocalIpcSessionWrite(RTLOCALIPCSESSION hSession, const void *pvBuffer, size_t cbBuffer)
{
if (RT_SUCCESS(rc))
{
/* No concurrent writers, sorry. */
{
/*
* If I/O is pending, wait for it to complete.
*/
if (pThis->fIOPending)
{
while (rc == VINF_TRY_AGAIN)
{
}
}
if (RT_SUCCESS(rc))
{
/*
* Try write everything.
* No bounce buffering, cUsers protects us.
*/
size_t cbTotalWritten = 0;
while (cbBuffer > 0)
{
pThis->fIOPending = true;
if (fRc)
{
rc = VINF_SUCCESS;
}
else
{
if (dwErr == ERROR_IO_PENDING)
{
if (dwRc == WAIT_OBJECT_0)
{
rc = VINF_SUCCESS;
else
}
else if (dwRc == WAIT_TIMEOUT)
rc = VERR_TIMEOUT;
else if (dwRc == WAIT_ABANDONED)
else
}
else if (dwErr == ERROR_NO_DATA)
else
}
pThis->fIOPending = false;
if (RT_FAILURE(rc))
break;
/* Advance. */
}
}
}
else
}
return rc;
}
{
/* No flushing on Windows needed since RTLocalIpcSessionWrite will block until
* all data was written (or an error occurred). */
/** @todo Implement this as soon as we want an explicit asynchronous version of
* RTLocalIpcSessionWrite on Windows. */
return VINF_SUCCESS;
}
{
if (RT_FAILURE(rc))
return rc;
{
if (pThis->fIOPending)
else
{
/* Peek at the pipe buffer and see how many bytes it contains. */
if ( fRc
&& cbAvailable)
{
rc = VINF_SUCCESS;
break;
}
else if (!fRc)
{
break;
}
/* Start a zero byte read operation that we can wait on. */
if (cMillies == 0)
{
rc = VERR_TIMEOUT;
break;
}
{
rc = VINF_SUCCESS;
if (iLoop > 10)
}
else if (GetLastError() == ERROR_IO_PENDING)
{
pThis->fIOPending = true;
pThis->fZeroByteRead = true;
}
else
}
if (RT_FAILURE(rc))
break;
/*
* Check for timeout.
*/
if ( cMillies != RT_INDEFINITE_WAIT
&& ( hWait != INVALID_HANDLE_VALUE
|| iLoop > 10)
)
{
{
rc = VERR_TIMEOUT;
break;
}
}
/*
* Wait.
*/
if (hWait != INVALID_HANDLE_VALUE)
{
if (dwRc == WAIT_OBJECT_0)
rc = VINF_SUCCESS;
else if (dwRc == WAIT_TIMEOUT)
rc = VERR_TIMEOUT;
else if (dwRc == WAIT_ABANDONED)
else
if ( RT_FAILURE(rc)
return rc;
if (pThis->fZeroByteRead)
{
pThis->fIOPending = false;
if (rc != VINF_SUCCESS)
{
}
if ( !fRc
&& RT_SUCCESS(rc))
{
if (dwRc == ERROR_OPERATION_ABORTED)
rc = VERR_CANCELLED;
else
}
}
if (RT_FAILURE(rc))
break;
}
}
if (RT_SUCCESS(rc))
return rc;
}
{
/*
* Enter the critical section, then set the cancellation flag
*/
if (RT_SUCCESS(rc))
{
}
return rc;
}
{
return VERR_NOT_SUPPORTED;
}
{
return VERR_NOT_SUPPORTED;
}
{
return VERR_NOT_SUPPORTED;
}