diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /netwerk/socket/nsNamedPipeIOLayer.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | netwerk/socket/nsNamedPipeIOLayer.cpp | 864 |
1 files changed, 864 insertions, 0 deletions
diff --git a/netwerk/socket/nsNamedPipeIOLayer.cpp b/netwerk/socket/nsNamedPipeIOLayer.cpp new file mode 100644 index 0000000000..d229f0e23e --- /dev/null +++ b/netwerk/socket/nsNamedPipeIOLayer.cpp @@ -0,0 +1,864 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNamedPipeIOLayer.h" + +#include <algorithm> +#include <utility> + +#include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Unused.h" +#include "mozilla/net/DNS.h" +#include "nsISupportsImpl.h" +#include "nsNamedPipeService.h" +#include "nsNativeCharsetUtils.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsSocketTransportService2.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nspr.h" +#include "private/pprio.h" + +namespace mozilla { +namespace net { + +static mozilla::LazyLogModule gNamedPipeLog("NamedPipeWin"); +#define LOG_NPIO_DEBUG(...) \ + MOZ_LOG(gNamedPipeLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) +#define LOG_NPIO_ERROR(...) \ + MOZ_LOG(gNamedPipeLog, mozilla::LogLevel::Error, (__VA_ARGS__)) + +PRDescIdentity nsNamedPipeLayerIdentity; +static PRIOMethods nsNamedPipeLayerMethods; + +class NamedPipeInfo final : public nsINamedPipeDataObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSINAMEDPIPEDATAOBSERVER + + explicit NamedPipeInfo(); + + nsresult Connect(const nsAString& aPath); + nsresult Disconnect(); + + /** + * Both blocking/non-blocking mode are supported in this class. + * The default mode is non-blocking mode, however, the client may change its + * mode to blocking mode during hand-shaking (e.g. nsSOCKSSocketInfo). + * + * In non-blocking mode, |Read| and |Write| should be called by clients only + * when |GetPollFlags| reports data availability. That is, the client calls + * |GetPollFlags| with |PR_POLL_READ| and/or |PR_POLL_WRITE| set, and + * according to the flags that set, |GetPollFlags| will check buffers status + * and decide corresponding actions: + * + * ------------------------------------------------------------------- + * | | data in buffer | empty buffer | + * |---------------+-------------------------+-----------------------| + * | PR_POLL_READ | out: PR_POLL_READ | DoRead/DoReadContinue | + * |---------------+-------------------------+-----------------------| + * | PR_POLL_WRITE | DoWrite/DoWriteContinue | out: PR_POLL_WRITE | + * ------------------------------------------+------------------------ + * + * |DoRead| and |DoWrite| initiate read/write operations asynchronously, and + * the |DoReadContinue| and |DoWriteContinue| are used to check the amount + * of the data are read/written to/from buffers. + * + * The output parameter and the return value of |GetPollFlags| are identical + * because we don't rely on the low-level select function to wait for data + * availability, we instead use nsNamedPipeService to poll I/O completeness. + * + * When client get |PR_POLL_READ| or |PR_POLL_WRITE| from |GetPollFlags|, + * they are able to use |Read| or |Write| to access the data in the buffer, + * and this is supposed to be very fast because no network traffic is + * involved. + * + * In blocking mode, the flow is quite similar to non-blocking mode, but + * |DoReadContinue| and |DoWriteContinue| are never been used since the + * operations are done synchronously, which could lead to slow responses. + */ + int32_t Read(void* aBuffer, int32_t aSize); + int32_t Write(const void* aBuffer, int32_t aSize); + + // Like Read, but doesn't remove data in internal buffer. + uint32_t Peek(void* aBuffer, int32_t aSize); + + // Number of bytes available to read in internal buffer. + int32_t Available() const; + + // Flush write buffer + // + // @return whether the buffer has been flushed + bool Sync(uint32_t aTimeout); + void SetNonblocking(bool nonblocking); + + bool IsConnected() const; + bool IsNonblocking() const; + HANDLE GetHandle() const; + + // Initiate and check current status for read/write operations. + int16_t GetPollFlags(int16_t aInFlags, int16_t* aOutFlags); + + private: + virtual ~NamedPipeInfo(); + + /** + * DoRead/DoWrite starts a read/write call synchronously or asynchronously + * depending on |mNonblocking|. In blocking mode, they return when the action + * has been done and in non-blocking mode it returns the number of bytes that + * were read/written if the operation is done immediately. If it takes some + * time to finish the operation, zero is returned and + * DoReadContinue/DoWriteContinue must be called to get async I/O result. + */ + int32_t DoRead(); + int32_t DoReadContinue(); + int32_t DoWrite(); + int32_t DoWriteContinue(); + + /** + * There was a write size limitation of named pipe, + * see https://support.microsoft.com/en-us/kb/119218 for more information. + * The limitation no longer exists, so feel free to change the value. + */ + static const uint32_t kBufferSize = 65536; + + nsCOMPtr<nsINamedPipeService> mNamedPipeService; + + HANDLE mPipe; // the handle to the named pipe. + OVERLAPPED mReadOverlapped; // used for asynchronous read operations. + OVERLAPPED mWriteOverlapped; // used for asynchronous write operations. + + uint8_t mReadBuffer[kBufferSize]; // octets read from pipe. + + /** + * These indicates the [begin, end) position of the data in the buffer. + */ + DWORD mReadBegin; + DWORD mReadEnd; + + bool mHasPendingRead; // previous asynchronous read is not finished yet. + + uint8_t mWriteBuffer[kBufferSize]; // octets to be written to pipe. + + /** + * These indicates the [begin, end) position of the data in the buffer. + */ + DWORD mWriteBegin; // how many bytes are already written. + DWORD mWriteEnd; // valid amount of data in the buffer. + + bool mHasPendingWrite; // previous asynchronous write is not finished yet. + + /** + * current blocking mode is non-blocking or not, accessed only in socket + * thread. + */ + bool mNonblocking; + + Atomic<DWORD> mErrorCode; // error code from Named Pipe Service. +}; + +NS_IMPL_ISUPPORTS(NamedPipeInfo, nsINamedPipeDataObserver) + +NamedPipeInfo::NamedPipeInfo() + : mNamedPipeService(NamedPipeService::GetOrCreate()), + mPipe(INVALID_HANDLE_VALUE), + mReadBegin(0), + mReadEnd(0), + mHasPendingRead(false), + mWriteBegin(0), + mWriteEnd(0), + mHasPendingWrite(false), + mNonblocking(true), + mErrorCode(0) { + MOZ_ASSERT(mNamedPipeService); + + ZeroMemory(&mReadOverlapped, sizeof(OVERLAPPED)); + ZeroMemory(&mWriteOverlapped, sizeof(OVERLAPPED)); +} + +NamedPipeInfo::~NamedPipeInfo() { MOZ_ASSERT(!mPipe); } + +// nsINamedPipeDataObserver + +NS_IMETHODIMP +NamedPipeInfo::OnDataAvailable(uint32_t aBytesTransferred, void* aOverlapped) { + DebugOnly<bool> isOnPipeServiceThread; + MOZ_ASSERT(NS_SUCCEEDED(mNamedPipeService->IsOnCurrentThread( + &isOnPipeServiceThread)) && + isOnPipeServiceThread); + + if (aOverlapped == &mReadOverlapped) { + LOG_NPIO_DEBUG("[%s] %p read %d bytes", __func__, this, aBytesTransferred); + } else if (aOverlapped == &mWriteOverlapped) { + LOG_NPIO_DEBUG("[%s] %p write %d bytes", __func__, this, aBytesTransferred); + } else { + MOZ_ASSERT(false, "invalid callback"); + mErrorCode = ERROR_INVALID_DATA; + return NS_ERROR_FAILURE; + } + + mErrorCode = ERROR_SUCCESS; + + // dispatch an empty event to trigger STS thread + gSocketTransportService->Dispatch( + NS_NewRunnableFunction("NamedPipeInfo::OnDataAvailable", [] {}), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +NS_IMETHODIMP +NamedPipeInfo::OnError(uint32_t aError, void* aOverlapped) { + DebugOnly<bool> isOnPipeServiceThread; + MOZ_ASSERT(NS_SUCCEEDED(mNamedPipeService->IsOnCurrentThread( + &isOnPipeServiceThread)) && + isOnPipeServiceThread); + + LOG_NPIO_ERROR("[%s] error code=%d", __func__, aError); + mErrorCode = aError; + + // dispatch an empty event to trigger STS thread + gSocketTransportService->Dispatch( + NS_NewRunnableFunction("NamedPipeInfo::OnError", [] {}), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +// Named pipe operations + +nsresult NamedPipeInfo::Connect(const nsAString& aPath) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + HANDLE pipe = + CreateFileW(PromiseFlatString(aPath).get(), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, nullptr); + + if (pipe == INVALID_HANDLE_VALUE) { + LOG_NPIO_ERROR("[%p] CreateFile error (%lu)", this, GetLastError()); + return NS_ERROR_FAILURE; + } + + DWORD pipeMode = PIPE_READMODE_MESSAGE; + if (!SetNamedPipeHandleState(pipe, &pipeMode, nullptr, nullptr)) { + LOG_NPIO_ERROR("[%p] SetNamedPipeHandleState error (%lu)", this, + GetLastError()); + CloseHandle(pipe); + return NS_ERROR_FAILURE; + } + + nsresult rv = mNamedPipeService->AddDataObserver(pipe, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseHandle(pipe); + return rv; + } + + HANDLE readEvent = CreateEventA(nullptr, TRUE, TRUE, "NamedPipeRead"); + if (NS_WARN_IF(!readEvent || readEvent == INVALID_HANDLE_VALUE)) { + CloseHandle(pipe); + return NS_ERROR_FAILURE; + } + + HANDLE writeEvent = CreateEventA(nullptr, TRUE, TRUE, "NamedPipeWrite"); + if (NS_WARN_IF(!writeEvent || writeEvent == INVALID_HANDLE_VALUE)) { + CloseHandle(pipe); + CloseHandle(readEvent); + return NS_ERROR_FAILURE; + } + + mPipe = pipe; + mReadOverlapped.hEvent = readEvent; + mWriteOverlapped.hEvent = writeEvent; + return NS_OK; +} + +nsresult NamedPipeInfo::Disconnect() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + nsresult rv = mNamedPipeService->RemoveDataObserver(mPipe, this); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + mPipe = nullptr; + + if (mReadOverlapped.hEvent && + mReadOverlapped.hEvent != INVALID_HANDLE_VALUE) { + CloseHandle(mReadOverlapped.hEvent); + mReadOverlapped.hEvent = nullptr; + } + + if (mWriteOverlapped.hEvent && + mWriteOverlapped.hEvent != INVALID_HANDLE_VALUE) { + CloseHandle(mWriteOverlapped.hEvent); + mWriteOverlapped.hEvent = nullptr; + } + + return rv; +} + +int32_t NamedPipeInfo::Read(void* aBuffer, int32_t aSize) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + int32_t bytesRead = Peek(aBuffer, aSize); + + if (bytesRead > 0) { + mReadBegin += bytesRead; + } + + return bytesRead; +} + +int32_t NamedPipeInfo::Write(const void* aBuffer, int32_t aSize) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(mWriteBegin <= mWriteEnd); + + if (!IsConnected()) { + // pipe unconnected + PR_SetError(PR_NOT_CONNECTED_ERROR, 0); + return -1; + } + + if (mWriteBegin == mWriteEnd) { + mWriteBegin = mWriteEnd = 0; + } + + int32_t bytesToWrite = + std::min<int32_t>(aSize, sizeof(mWriteBuffer) - mWriteEnd); + MOZ_ASSERT(bytesToWrite >= 0); + + if (bytesToWrite == 0) { + PR_SetError(IsNonblocking() ? PR_WOULD_BLOCK_ERROR : PR_IO_PENDING_ERROR, + 0); + return -1; + } + + memcpy(&mWriteBuffer[mWriteEnd], aBuffer, bytesToWrite); + mWriteEnd += bytesToWrite; + + /** + * Triggers internal write operation by calling |GetPollFlags|. + * This is required for callers that use blocking I/O because they don't call + * |GetPollFlags| to write data, but this also works for non-blocking I/O. + */ + int16_t outFlag; + GetPollFlags(PR_POLL_WRITE, &outFlag); + + return bytesToWrite; +} + +uint32_t NamedPipeInfo::Peek(void* aBuffer, int32_t aSize) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(mReadBegin <= mReadEnd); + + if (!IsConnected()) { + // pipe unconnected + PR_SetError(PR_NOT_CONNECTED_ERROR, 0); + return -1; + } + + /** + * If there's nothing in the read buffer, try to trigger internal read + * operation by calling |GetPollFlags|. This is required for callers that + * use blocking I/O because they don't call |GetPollFlags| to read data, + * but this also works for non-blocking I/O. + */ + if (!Available()) { + int16_t outFlag; + GetPollFlags(PR_POLL_READ, &outFlag); + + if (!(outFlag & PR_POLL_READ)) { + PR_SetError(IsNonblocking() ? PR_WOULD_BLOCK_ERROR : PR_IO_PENDING_ERROR, + 0); + return -1; + } + } + + // Available() can't return more than what fits to the buffer at the read + // offset. + int32_t bytesRead = std::min<int32_t>(aSize, Available()); + MOZ_ASSERT(bytesRead >= 0); + MOZ_ASSERT(mReadBegin + bytesRead <= mReadEnd); + memcpy(aBuffer, &mReadBuffer[mReadBegin], bytesRead); + return bytesRead; +} + +int32_t NamedPipeInfo::Available() const { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(mReadBegin <= mReadEnd); + MOZ_ASSERT(mReadEnd - mReadBegin <= 0x7FFFFFFF); // no more than int32_max + return mReadEnd - mReadBegin; +} + +bool NamedPipeInfo::Sync(uint32_t aTimeout) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (!mHasPendingWrite) { + return true; + } + return WaitForSingleObject(mWriteOverlapped.hEvent, aTimeout) == + WAIT_OBJECT_0; +} + +void NamedPipeInfo::SetNonblocking(bool nonblocking) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + mNonblocking = nonblocking; +} + +bool NamedPipeInfo::IsConnected() const { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + return mPipe && mPipe != INVALID_HANDLE_VALUE; +} + +bool NamedPipeInfo::IsNonblocking() const { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + return mNonblocking; +} + +HANDLE +NamedPipeInfo::GetHandle() const { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + return mPipe; +} + +int16_t NamedPipeInfo::GetPollFlags(int16_t aInFlags, int16_t* aOutFlags) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + *aOutFlags = 0; + + if (aInFlags & PR_POLL_READ) { + int32_t bytesToRead = 0; + if (mReadBegin < mReadEnd) { // data in buffer and is ready to be read + bytesToRead = Available(); + } else if (mHasPendingRead) { // nonblocking I/O and has pending task + bytesToRead = DoReadContinue(); + } else { // read bufer is empty. + bytesToRead = DoRead(); + } + + if (bytesToRead > 0) { + *aOutFlags |= PR_POLL_READ; + } else if (bytesToRead < 0) { + *aOutFlags |= PR_POLL_ERR; + } + } + + if (aInFlags & PR_POLL_WRITE) { + int32_t bytesWritten = 0; + if (mHasPendingWrite) { // nonblocking I/O and has pending task. + bytesWritten = DoWriteContinue(); + } else if (mWriteBegin < mWriteEnd) { // data in buffer, ready to write + bytesWritten = DoWrite(); + } else { // write buffer is empty. + *aOutFlags |= PR_POLL_WRITE; + } + + if (bytesWritten < 0) { + *aOutFlags |= PR_POLL_ERR; + } else if (bytesWritten && !mHasPendingWrite && mWriteBegin == mWriteEnd) { + *aOutFlags |= PR_POLL_WRITE; + } + } + + return *aOutFlags; +} + +// @return: data has been read and is available +int32_t NamedPipeInfo::DoRead() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(!mHasPendingRead); + MOZ_ASSERT(mReadBegin == mReadEnd); // the buffer should be empty + + mReadBegin = 0; + mReadEnd = 0; + + BOOL success = ReadFile(mPipe, mReadBuffer, sizeof(mReadBuffer), &mReadEnd, + IsNonblocking() ? &mReadOverlapped : nullptr); + + if (success) { + LOG_NPIO_DEBUG("[%s][%p] %lu bytes read", __func__, this, mReadEnd); + return mReadEnd; + } + + switch (GetLastError()) { + case ERROR_MORE_DATA: // has more data to read + mHasPendingRead = true; + return DoReadContinue(); + + case ERROR_IO_PENDING: // read is pending + mHasPendingRead = true; + break; + + default: + LOG_NPIO_ERROR("[%s] ReadFile failed (%lu)", __func__, GetLastError()); + Disconnect(); + PR_SetError(PR_IO_ERROR, 0); + return -1; + } + + return 0; +} + +int32_t NamedPipeInfo::DoReadContinue() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(mHasPendingRead); + MOZ_ASSERT(mReadBegin == 0 && mReadEnd == 0); + + BOOL success; + success = GetOverlappedResult(mPipe, &mReadOverlapped, &mReadEnd, FALSE); + if (success) { + mHasPendingRead = false; + if (mReadEnd == 0) { + Disconnect(); + PR_SetError(PR_NOT_CONNECTED_ERROR, 0); + return -1; + } + + LOG_NPIO_DEBUG("[%s][%p] %lu bytes read", __func__, this, mReadEnd); + return mReadEnd; + } + + switch (GetLastError()) { + case ERROR_MORE_DATA: + mHasPendingRead = false; + LOG_NPIO_DEBUG("[%s][%p] %lu bytes read", __func__, this, mReadEnd); + return mReadEnd; + case ERROR_IO_INCOMPLETE: // still in progress + break; + default: + LOG_NPIO_ERROR("[%s]: GetOverlappedResult failed (%lu)", __func__, + GetLastError()); + Disconnect(); + PR_SetError(PR_IO_ERROR, 0); + return -1; + } + + return 0; +} + +int32_t NamedPipeInfo::DoWrite() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(!mHasPendingWrite); + MOZ_ASSERT(mWriteBegin < mWriteEnd); + + DWORD bytesWritten = 0; + BOOL success = + WriteFile(mPipe, &mWriteBuffer[mWriteBegin], mWriteEnd - mWriteBegin, + &bytesWritten, IsNonblocking() ? &mWriteOverlapped : nullptr); + + if (success) { + mWriteBegin += bytesWritten; + LOG_NPIO_DEBUG("[%s][%p] %lu bytes written", __func__, this, bytesWritten); + return bytesWritten; + } + + if (GetLastError() != ERROR_IO_PENDING) { + LOG_NPIO_ERROR("[%s] WriteFile failed (%lu)", __func__, GetLastError()); + Disconnect(); + PR_SetError(PR_IO_ERROR, 0); + return -1; + } + + mHasPendingWrite = true; + + return 0; +} + +int32_t NamedPipeInfo::DoWriteContinue() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(mHasPendingWrite); + + DWORD bytesWritten = 0; + BOOL success = + GetOverlappedResult(mPipe, &mWriteOverlapped, &bytesWritten, FALSE); + + if (!success) { + if (GetLastError() == ERROR_IO_INCOMPLETE) { + // still in progress + return 0; + } + + LOG_NPIO_ERROR("[%s] GetOverlappedResult failed (%lu)", __func__, + GetLastError()); + Disconnect(); + PR_SetError(PR_IO_ERROR, 0); + return -1; + } + + mHasPendingWrite = false; + mWriteBegin += bytesWritten; + LOG_NPIO_DEBUG("[%s][%p] %lu bytes written", __func__, this, bytesWritten); + return bytesWritten; +} + +static inline NamedPipeInfo* GetNamedPipeInfo(PRFileDesc* aFd) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_DIAGNOSTIC_ASSERT(aFd); + MOZ_DIAGNOSTIC_ASSERT(aFd->secret); + MOZ_DIAGNOSTIC_ASSERT(PR_GetLayersIdentity(aFd) == nsNamedPipeLayerIdentity); + + if (!aFd || !aFd->secret || + PR_GetLayersIdentity(aFd) != nsNamedPipeLayerIdentity) { + LOG_NPIO_ERROR("cannot get named pipe info"); + return nullptr; + } + + return reinterpret_cast<NamedPipeInfo*>(aFd->secret); +} + +static PRStatus nsNamedPipeConnect(PRFileDesc* aFd, const PRNetAddr* aAddr, + PRIntervalTime aTimeout) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return PR_FAILURE; + } + + nsAutoString path; + if (NS_FAILED(NS_CopyNativeToUnicode(nsDependentCString(aAddr->local.path), + path))) { + PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); + return PR_FAILURE; + } + if (NS_WARN_IF(NS_FAILED(info->Connect(path)))) { + return PR_FAILURE; + } + + return PR_SUCCESS; +} + +static PRStatus nsNamedPipeConnectContinue(PRFileDesc* aFd, PRInt16 aOutFlags) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + return PR_SUCCESS; +} + +static PRStatus nsNamedPipeClose(PRFileDesc* aFd) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (aFd->secret && PR_GetLayersIdentity(aFd) == nsNamedPipeLayerIdentity) { + RefPtr<NamedPipeInfo> info = dont_AddRef(GetNamedPipeInfo(aFd)); + info->Disconnect(); + aFd->secret = nullptr; + aFd->identity = PR_INVALID_IO_LAYER; + } + + MOZ_ASSERT(!aFd->lower); + PR_Free(aFd); // PRFileDescs are allocated with PR_Malloc(). + + return PR_SUCCESS; +} + +static PRInt32 nsNamedPipeSend(PRFileDesc* aFd, const void* aBuffer, + PRInt32 aAmount, PRIntn aFlags, + PRIntervalTime aTimeout) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + Unused << aFlags; + Unused << aTimeout; + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + return info->Write(aBuffer, aAmount); +} + +static PRInt32 nsNamedPipeRecv(PRFileDesc* aFd, void* aBuffer, PRInt32 aAmount, + PRIntn aFlags, PRIntervalTime aTimeout) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + Unused << aTimeout; + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + + if (aFlags) { + if (aFlags != PR_MSG_PEEK) { + PR_SetError(PR_UNKNOWN_ERROR, 0); + return -1; + } + return info->Peek(aBuffer, aAmount); + } + + return info->Read(aBuffer, aAmount); +} + +static inline PRInt32 nsNamedPipeRead(PRFileDesc* aFd, void* aBuffer, + PRInt32 aAmount) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + return info->Read(aBuffer, aAmount); +} + +static inline PRInt32 nsNamedPipeWrite(PRFileDesc* aFd, const void* aBuffer, + PRInt32 aAmount) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + return info->Write(aBuffer, aAmount); +} + +static PRInt32 nsNamedPipeAvailable(PRFileDesc* aFd) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + return static_cast<PRInt32>(info->Available()); +} + +static PRInt64 nsNamedPipeAvailable64(PRFileDesc* aFd) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + return static_cast<PRInt64>(info->Available()); +} + +static PRStatus nsNamedPipeSync(PRFileDesc* aFd) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return PR_FAILURE; + } + return info->Sync(0) ? PR_SUCCESS : PR_FAILURE; +} + +static PRInt16 nsNamedPipePoll(PRFileDesc* aFd, PRInt16 aInFlags, + PRInt16* aOutFlags) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return 0; + } + return info->GetPollFlags(aInFlags, aOutFlags); +} + +// FIXME: remove socket option functions? +static PRStatus nsNamedPipeGetSocketOption(PRFileDesc* aFd, + PRSocketOptionData* aData) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + MOZ_ASSERT(aFd); + MOZ_ASSERT(aData); + + switch (aData->option) { + case PR_SockOpt_Nonblocking: + aData->value.non_blocking = + GetNamedPipeInfo(aFd)->IsNonblocking() ? PR_TRUE : PR_FALSE; + break; + case PR_SockOpt_Keepalive: + aData->value.keep_alive = PR_TRUE; + break; + case PR_SockOpt_NoDelay: + aData->value.no_delay = PR_TRUE; + break; + default: + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return PR_FAILURE; + } + + return PR_SUCCESS; +} + +static PRStatus nsNamedPipeSetSocketOption(PRFileDesc* aFd, + const PRSocketOptionData* aData) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + MOZ_ASSERT(aFd); + MOZ_ASSERT(aData); + + switch (aData->option) { + case PR_SockOpt_Nonblocking: + GetNamedPipeInfo(aFd)->SetNonblocking(aData->value.non_blocking); + break; + case PR_SockOpt_Keepalive: + case PR_SockOpt_NoDelay: + break; + default: + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return PR_FAILURE; + } + + return PR_SUCCESS; +} + +static void Initialize() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + static bool initialized = false; + if (initialized) { + return; + } + + nsNamedPipeLayerIdentity = PR_GetUniqueIdentity("Named Pipe layer"); + nsNamedPipeLayerMethods = *PR_GetDefaultIOMethods(); + nsNamedPipeLayerMethods.close = nsNamedPipeClose; + nsNamedPipeLayerMethods.read = nsNamedPipeRead; + nsNamedPipeLayerMethods.write = nsNamedPipeWrite; + nsNamedPipeLayerMethods.available = nsNamedPipeAvailable; + nsNamedPipeLayerMethods.available64 = nsNamedPipeAvailable64; + nsNamedPipeLayerMethods.fsync = nsNamedPipeSync; + nsNamedPipeLayerMethods.connect = nsNamedPipeConnect; + nsNamedPipeLayerMethods.recv = nsNamedPipeRecv; + nsNamedPipeLayerMethods.send = nsNamedPipeSend; + nsNamedPipeLayerMethods.poll = nsNamedPipePoll; + nsNamedPipeLayerMethods.getsocketoption = nsNamedPipeGetSocketOption; + nsNamedPipeLayerMethods.setsocketoption = nsNamedPipeSetSocketOption; + nsNamedPipeLayerMethods.connectcontinue = nsNamedPipeConnectContinue; + + initialized = true; +} + +bool IsNamedPipePath(const nsACString& aPath) { + return StringBeginsWith(aPath, "\\\\.\\pipe\\"_ns); +} + +PRFileDesc* CreateNamedPipeLayer() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + Initialize(); + + PRFileDesc* layer = + PR_CreateIOLayerStub(nsNamedPipeLayerIdentity, &nsNamedPipeLayerMethods); + if (NS_WARN_IF(!layer)) { + LOG_NPIO_ERROR("CreateNamedPipeLayer() failed."); + return nullptr; + } + + RefPtr<NamedPipeInfo> info = new NamedPipeInfo(); + layer->secret = reinterpret_cast<PRFilePrivate*>(info.forget().take()); + + return layer; +} + +} // namespace net +} // namespace mozilla |