pipe-win.cpp revision 42ee6d03355fb68926435ec4feb8e5293f391344
/* $Id$ */
/** @file
* IPRT - Anonymous Pipes, Windows Implementation.
*/
/*
* Copyright (C) 2010 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 *
*******************************************************************************/
#include <Windows.h>
#include <iprt/critsect.h>
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** The pipe buffer size we prefer. */
#define RTPIPE_NT_SIZE _64K
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
typedef struct RTPIPEINTERNAL
{
/** Magic value (RTPIPE_MAGIC). */
/** The pipe handle. */
/** Set if this is the read end, clear if it's the write end. */
bool fRead;
/** 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;
/** Set if the pipe is broken. */
bool fBrokenPipe;
/** Set if we've promised that the handle is writable. */
bool fPromisedWritable;
/** Usage counter. */
/** The overlapped I/O structure we use. */
/** Bounce buffer for writes. */
/** Amount of used buffer space. */
/** Amount of allocated buffer space. */
/** The handle of the poll set currently polling on this pipe.
* We can only have one poller at the time (lazy bird). */
/** Critical section protecting the above members.
/** Buffer for the zero byte read. */
/* from ntdef.h */
/* from ntddk.h */
typedef struct _IO_STATUS_BLOCK {
union {
};
typedef enum _FILE_INFORMATION_CLASS {
FilePipeInformation = 23,
FilePipeLocalInformation = 24,
/* from ntifs.h */
typedef struct _FILE_PIPE_LOCAL_INFORMATION {
#define FILE_PIPE_DISCONNECTED_STATE 0x00000001
#define FILE_PIPE_LISTENING_STATE 0x00000002
#define FILE_PIPE_CONNECTED_STATE 0x00000003
#define FILE_PIPE_CLOSING_STATE 0x00000004
#define FILE_PIPE_INBOUND 0x00000000
#define FILE_PIPE_OUTBOUND 0x00000001
#define FILE_PIPE_FULL_DUPLEX 0x00000002
#define FILE_PIPE_CLIENT_END 0x00000000
#define FILE_PIPE_SERVER_END 0x00000001
extern "C" NTSYSAPI NTSTATUS WINAPI NtQueryInformationFile(HANDLE, PIO_STATUS_BLOCK, PVOID, LONG, FILE_INFORMATION_CLASS);
/**
* Wrapper for getting FILE_PIPE_LOCAL_INFORMATION via the NT API.
*
* @param pThis The pipe.
* @param pInfo The info structure.
*/
{
NTSTATUS rcNt = NtQueryInformationFile(pThis->hPipe, &Ios, pInfo, sizeof(*pInfo), FilePipeLocalInformation);
return rcNt >= 0;
}
{
/*
* Create the read end of the pipe.
*/
int rc;
for (;;)
{
static volatile uint32_t g_iNextPipe = 0;
char szName[128];
RTStrPrintf(szName, sizeof(szName), "\\\\.\\pipe\\iprt-pipe-%u-%u", RTProcSelf(), ASMAtomicIncU32(&g_iNextPipe));
if (fFlags & RTPIPE_C_INHERIT_READ)
{
}
#endif
#ifdef PIPE_REJECT_REMOTE_CLIENTS
#endif
hPipeR = CreateNamedPipeA(szName, dwOpenMode, dwPipeMode, 1 /*nMaxInstances*/, RTPIPE_NT_SIZE, RTPIPE_NT_SIZE,
#ifdef PIPE_REJECT_REMOTE_CLIENTS
{
hPipeR = CreateNamedPipeA(szName, dwOpenMode, dwPipeMode, 1 /*nMaxInstances*/, RTPIPE_NT_SIZE, RTPIPE_NT_SIZE,
}
#endif
{
hPipeR = CreateNamedPipeA(szName, dwOpenMode, dwPipeMode, 1 /*nMaxInstances*/, RTPIPE_NT_SIZE, RTPIPE_NT_SIZE,
}
#endif
if (hPipeR != INVALID_HANDLE_VALUE)
{
/*
* Connect to the pipe (the write end).
* We add FILE_READ_ATTRIBUTES here to make sure we can query the
* pipe state later on.
*/
if (fFlags & RTPIPE_C_INHERIT_WRITE)
{
}
0 /*dwShareMode*/,
OPEN_EXISTING /* dwCreationDisposition */,
FILE_FLAG_OVERLAPPED /*dwFlagsAndAttributes*/,
NULL /*hTemplateFile*/);
if (hPipeW != INVALID_HANDLE_VALUE)
break;
dwErr = GetLastError();
}
else
dwErr = GetLastError();
return RTErrConvertFromWin32(dwErr);
/* else: try again with a new name */
}
/*
* Create the two handles.
*/
if (pThisR)
{
if (pThisW)
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
{
{
//pThisR->fIOPending = false;
//pThisW->fIOPending = false;
//pThisR->fZeroByteRead = false;
//pThisW->fZeroByteRead = false;
//pThisR->fBrokenPipe = false;
//pThisW->fBrokenPipe = false;
//pThisW->fPromisedWritable= false;
//pThisR->fPromisedWritable= false;
//pThisR->cUsers = 0;
//pThisW->cUsers = 0;
//pThisR->pbBounceBuf = NULL;
//pThisW->pbBounceBuf = NULL;
//pThisR->cbBounceBufUsed = 0;
//pThisW->cbBounceBufUsed = 0;
//pThisR->cbBounceBufAlloc= 0;
//pThisW->cbBounceBufAlloc= 0;
*phPipeRead = pThisR;
*phPipeWrite = pThisW;
return VINF_SUCCESS;
}
}
}
}
}
else
rc = VERR_NO_MEMORY;
}
else
rc = VERR_NO_MEMORY;
return rc;
}
/**
* Common worker for handling I/O completion.
*
* This is used by RTPipeClose, RTPipeWrite and RTPipeWriteBlocking.
*
* @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 (GetLastError() == ERROR_IO_PENDING)
rc = VINF_TRY_AGAIN;
else
{
pThis->fIOPending = false;
if (GetLastError() == ERROR_NO_DATA)
else
if (rc == VERR_BROKEN_PIPE)
pThis->fBrokenPipe = true;
}
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;
}
{
if (pThis == NIL_RTPIPE)
return VINF_SUCCESS;
/*
* Do the cleanup.
*/
AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, ~RTPIPE_MAGIC, RTPIPE_MAGIC), VERR_INVALID_HANDLE);
return VINF_SUCCESS;
}
{
/*
* Get and validate the pipe handle info.
*/
return RTErrConvertFromWin32(GetLastError());
return RTErrConvertFromWin32(GetLastError());
/*
* Looks kind of OK, create a handle so we can try rtPipeQueryInfo on it
* and see if we need to duplicate it to make that call work.
*/
if (!pThis)
return VERR_NO_MEMORY;
if (RT_SUCCESS(rc))
{
{
//pThis->fIOPending = false;
//pThis->fZeroByteRead = false;
//pThis->fBrokenPipe = false;
//pThisR->fPromisedWritable= false;
//pThis->cUsers = 0;
//pThis->pbBounceBuf = NULL;
//pThis->cbBounceBufUsed = 0;
//pThis->cbBounceBufAlloc= 0;
rc = VINF_SUCCESS;
else
{
0 /*dwOptions*/))
{
rc = VINF_SUCCESS;
else
{
}
}
else
}
if (RT_SUCCESS(rc))
{
/*
* Verify the pipe state and correct the inheritability.
*/
if ( RT_SUCCESS(rc)
&& hNative2 == INVALID_HANDLE_VALUE
HANDLE_FLAG_INHERIT /*dwMask*/,
{
}
if (RT_SUCCESS(rc))
{
/*
* Ok, we're good!
*/
if (hNative2 != INVALID_HANDLE_VALUE)
return VINF_SUCCESS;
}
}
/* Bail out. */
if (hNative2 != INVALID_HANDLE_VALUE)
}
}
return rc;
}
{
}
{
if (RT_SUCCESS(rc))
{
/* No concurrent readers, sorry. */
{
/*
* 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.
*/
if ( cbToRead == 0
{
rc = VINF_SUCCESS;
}
else if (GetLastError() == ERROR_IO_PENDING)
{
pThis->fIOPending = true;
{
rc = VINF_SUCCESS;
}
else if (GetLastError() == ERROR_OPERATION_ABORTED)
{
*pcbRead = 0;
rc = VINF_TRY_AGAIN;
}
else
pThis->fIOPending = false;
}
else
if (rc == VERR_BROKEN_PIPE)
pThis->fBrokenPipe = true;
}
else
}
return rc;
}
{
if (RT_SUCCESS(rc))
{
/* No concurrent readers, sorry. */
{
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 (rc == VERR_BROKEN_PIPE)
pThis->fBrokenPipe = true;
if (pcbRead)
{
*pcbRead = cbTotalRead;
if ( RT_FAILURE(rc)
&& cbTotalRead
&& rc != VERR_INVALID_POINTER)
rc = VINF_SUCCESS;
}
}
else
}
return rc;
}
{
if (RT_SUCCESS(rc))
{
/* No concurrent readers, sorry. */
{
/* If I/O is pending, check if it has completed. */
if (pThis->fIOPending)
else
rc = VINF_SUCCESS;
if (rc == VINF_SUCCESS)
{
/* Adjust the number of bytes to write to fit into the current
buffer quota, unless we've promised stuff in RTPipeSelectOne.
WriteQuotaAvailable better not be zero when it shouldn't!! */
if ( !pThis->fPromisedWritable
&& cbToWrite > 0
{
/** @todo fixme: To get the pipe writing support to work the
* block below needs to be commented out until a
* way is found to address the problem of the incorrectly
* set field Info.WriteQuotaAvailable. */
#if 0
&& Info.OutboundQuota != 0
)
{
if (!cbToWrite)
rc = VINF_TRY_AGAIN;
}
#endif
}
pThis->fPromisedWritable = false;
/* Do the bounce buffering. */
{
if (cbToWrite > RTPIPE_NT_SIZE)
if (pv)
{
}
else
rc = VERR_NO_MEMORY;
}
else if (cbToWrite > RTPIPE_NT_SIZE)
{
/* Submit the write. */
{
*pcbWritten = cbWritten;
rc = VINF_SUCCESS;
}
else if (GetLastError() == ERROR_IO_PENDING)
{
*pcbWritten = cbToWrite;
pThis->fIOPending = true;
rc = VINF_SUCCESS;
}
else if (GetLastError() == ERROR_NO_DATA)
else
}
else if (RT_SUCCESS(rc))
*pcbWritten = 0;
}
else if (RT_SUCCESS(rc))
*pcbWritten = 0;
if (rc == VERR_BROKEN_PIPE)
pThis->fBrokenPipe = true;
}
else
}
return rc;
}
RTDECL(int) RTPipeWriteBlocking(RTPIPE hPipe, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten)
{
if (RT_SUCCESS(rc))
{
/* No concurrent readers, sorry. */
{
/*
* If I/O is pending, wait for it to complete.
*/
if (pThis->fIOPending)
{
while (rc == VINF_TRY_AGAIN)
{
}
}
if (RT_SUCCESS(rc))
{
pThis->fPromisedWritable = false;
/*
* Try write everything.
* No bounce buffering, cUsers protects us.
*/
size_t cbTotalWritten = 0;
while (cbToWrite > 0)
{
pThis->fIOPending = true;
rc = VINF_SUCCESS;
else if (GetLastError() == ERROR_IO_PENDING)
{
rc = VINF_SUCCESS;
else
}
else if (GetLastError() == ERROR_NO_DATA)
else
pThis->fIOPending = false;
if (RT_FAILURE(rc))
break;
/* advance */
}
if (pcbWritten)
{
if ( RT_FAILURE(rc)
&& rc != VERR_INVALID_POINTER)
rc = VINF_SUCCESS;
}
}
if (rc == VERR_BROKEN_PIPE)
pThis->fBrokenPipe = true;
}
else
}
return rc;
#if 1
return VERR_NOT_IMPLEMENTED;
#else
if (RT_SUCCESS(rc))
{
size_t cbTotalWritten = 0;
while (cbToWrite > 0)
{
if (cbWritten < 0)
{
break;
}
/* advance */
}
if (pcbWritten)
{
if ( RT_FAILURE(rc)
&& rc != VERR_INVALID_POINTER)
rc = VINF_SUCCESS;
}
}
return rc;
#endif
}
{
{
if (rc == VERR_BROKEN_PIPE)
pThis->fBrokenPipe = true;
return rc;
}
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. */
&& cbAvailable > 0)
{
rc = VINF_SUCCESS;
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
}
}
else
{
if (pThis->fIOPending)
{
if (RT_FAILURE(rc))
break;
}
if (pThis->fIOPending)
else
{
{
/* Check for broken pipe. */
{
break;
}
/* Check for available write buffer space. */
else if (Info.WriteQuotaAvailable > 0)
{
pThis->fPromisedWritable = false;
rc = VINF_SUCCESS;
break;
}
/* delayed buffer alloc or timeout: phony promise
later: See if we still can associate a semaphore with
the pipe, like on OS/2. */
else if ( Info.OutboundQuota == 0
|| cMillies)
{
pThis->fPromisedWritable = true;
rc = VINF_SUCCESS;
break;
}
}
else
{
pThis->fPromisedWritable = true;
rc = VINF_SUCCESS;
break;
}
}
}
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 (RT_FAILURE(rc))
break;
}
}
if (rc == VERR_BROKEN_PIPE)
pThis->fBrokenPipe = true;
return rc;
}
{
if (RT_FAILURE(rc))
return rc;
DWORD cbAvailable = 0;
else
return rc;
}
/**
* Internal RTPollSetAdd helper that returns the handle that should be added to
* the pollset.
*
* @returns Valid handle on success, INVALID_HANDLE_VALUE on failure.
* @param hPipe The pipe handle.
* @param fEvents The events we're polling for.
* @param ph where to put the primary handle.
*/
{
/* Later: Try register an event handle with the pipe like on OS/2, there is
a file control for doing this obviously intended for the OS/2 subsys.
The question is whether this still exists on Vista and W7. */
return VINF_SUCCESS;
}
/**
* Checks for pending events.
*
* @returns Event mask or 0.
* @param pThis The pipe handle.
* @param fEvents The desired events.
*/
{
uint32_t fRetEvents = 0;
if (pThis->fBrokenPipe)
{
if (!pThis->fIOPending)
{
{
if ( (fEvents & RTPOLL_EVT_READ)
&& cbAvailable > 0)
}
else
{
if (GetLastError() == ERROR_BROKEN_PIPE)
pThis->fBrokenPipe = true;
}
}
}
else
{
if (pThis->fIOPending)
{
if (pThis->fBrokenPipe)
}
if ( !pThis->fIOPending
&& !fRetEvents)
{
{
/* Check for broken pipe. */
{
pThis->fBrokenPipe = true;
}
/* Check if there is available buffer space. */
if ( !fRetEvents
&& (fEvents & RTPOLL_EVT_WRITE)
&& ( Info.WriteQuotaAvailable > 0
|| Info.OutboundQuota == 0)
)
}
else if (fEvents & RTPOLL_EVT_WRITE)
}
}
return fRetEvents;
}
/**
* Internal RTPoll helper that polls the pipe handle and, if @a fNoWait is
* clear, starts whatever actions we've got running during the poll call.
*
* @returns 0 if no pending events, actions initiated if @a fNoWait is clear.
* Event mask (in @a fEvents) and no actions if the handle is ready
* already.
* UINT32_MAX (asserted) if the pipe handle is busy in I/O or a
* different poll set.
*
* @param hPipe The pipe handle.
* @param hPollSet The poll set handle (for access checks).
* @param fEvents The events we're polling for.
* @param fFinalEntry Set if this is the final entry for this handle
* in this poll set. This can be used for dealing
* with duplicate entries.
* @param fNoWait Set if it's a zero-wait poll call. Clear if
* we'll wait for an event to occur.
*/
uint32_t rtPipePollStart(RTPIPE hPipe, RTPOLLSET hPollSet, uint32_t fEvents, bool fFinalEntry, bool fNoWait)
{
/** @todo All this polling code could be optimized to make fewer system
* calls; like for instance the ResetEvent calls. */
/* Check that this is the only current use of this pipe. */
{
/* Check what the current events are. */
if ( !fRetEvents
&& !fNoWait)
{
/* Make sure the event semaphore has been reset. */
if (!pThis->fIOPending)
{
}
/* Kick off the zero byte read thing if applicable. */
if ( !pThis->fIOPending
&& (fEvents & RTPOLL_EVT_READ)
)
{
else if (GetLastError() == ERROR_IO_PENDING)
{
pThis->fIOPending = true;
pThis->fZeroByteRead = true;
}
else
}
/* If we're still set for the waiting, record the poll set and
mark the pipe used. */
if (!fRetEvents)
{
}
}
}
else
{
AssertFailed();
}
return fRetEvents;
}
/**
* Called after a WaitForMultipleObjects returned in order to check for pending
* events and stop whatever actions that rtPipePollStart() initiated.
*
* @returns Event mask or 0.
*
* @param hPipe The pipe handle.
* @param fEvents The events we're polling for.
* @param fFinalEntry Set if this is the final entry for this handle
* in this poll set. This can be used for dealing
* with duplicate entries. Only keep in mind that
* this method is called in reverse order, so the
* first call will have this set (when the entire
* set was processed).
* @param fHarvestEvents Set if we should check for pending events.
*/
{
AssertPtrReturn(pThis, 0);
AssertRCReturn(rc, 0);
/* Cancel the zero byte read. */
uint32_t fRetEvents = 0;
if (pThis->fZeroByteRead)
{
&& GetLastError() != ERROR_OPERATION_ABORTED)
pThis->fIOPending = false;
pThis->fZeroByteRead = false;
}
/* harvest events. */
/* update counters. */
return fRetEvents;
}