diff options
Diffstat (limited to 'ipc/glue/IPCStreamUtils.cpp')
-rw-r--r-- | ipc/glue/IPCStreamUtils.cpp | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/ipc/glue/IPCStreamUtils.cpp b/ipc/glue/IPCStreamUtils.cpp new file mode 100644 index 000000000..3bb351184 --- /dev/null +++ b/ipc/glue/IPCStreamUtils.cpp @@ -0,0 +1,495 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "IPCStreamUtils.h" + +#include "nsIIPCSerializableInputStream.h" + +#include "mozilla/Assertions.h" +#include "mozilla/dom/nsIContentChild.h" +#include "mozilla/dom/PContentParent.h" +#include "mozilla/dom/File.h" +#include "mozilla/ipc/FileDescriptorSetChild.h" +#include "mozilla/ipc/FileDescriptorSetParent.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/PBackgroundParent.h" +#include "mozilla/ipc/SendStream.h" +#include "mozilla/Unused.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIPipe.h" +#include "nsStreamUtils.h" + +namespace mozilla { +namespace ipc { + +namespace { + +// These serialization and cleanup functions could be externally exposed. For +// now, though, keep them private to encourage use of the safer RAII +// AutoIPCStream class. + +template<typename M> +void +SerializeInputStreamWithFdsChild(nsIInputStream* aStream, + IPCStream& aValue, + M* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + + // First attempt simple stream serialization + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aStream); + if (!serializable) { + MOZ_CRASH("Input stream is not serializable!"); + } + + aValue = InputStreamParamsWithFds(); + InputStreamParamsWithFds& streamWithFds = + aValue.get_InputStreamParamsWithFds(); + + AutoTArray<FileDescriptor, 4> fds; + serializable->Serialize(streamWithFds.stream(), fds); + + if (streamWithFds.stream().type() == InputStreamParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } + + if (fds.IsEmpty()) { + streamWithFds.optionalFds() = void_t(); + } else { + PFileDescriptorSetChild* fdSet = + aManager->SendPFileDescriptorSetConstructor(fds[0]); + for (uint32_t i = 1; i < fds.Length(); ++i) { + Unused << fdSet->SendAddFileDescriptor(fds[i]); + } + + streamWithFds.optionalFds() = fdSet; + } +} + +template<typename M> +void +SerializeInputStreamWithFdsParent(nsIInputStream* aStream, + IPCStream& aValue, + M* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + + // First attempt simple stream serialization + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aStream); + if (!serializable) { + MOZ_CRASH("Input stream is not serializable!"); + } + + aValue = InputStreamParamsWithFds(); + InputStreamParamsWithFds& streamWithFds = + aValue.get_InputStreamParamsWithFds(); + + AutoTArray<FileDescriptor, 4> fds; + serializable->Serialize(streamWithFds.stream(), fds); + + if (streamWithFds.stream().type() == InputStreamParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } + + streamWithFds.optionalFds() = void_t(); + if (!fds.IsEmpty()) { + PFileDescriptorSetParent* fdSet = + aManager->SendPFileDescriptorSetConstructor(fds[0]); + for (uint32_t i = 1; i < fds.Length(); ++i) { + if (NS_WARN_IF(!fdSet->SendAddFileDescriptor(fds[i]))) { + Unused << PFileDescriptorSetParent::Send__delete__(fdSet); + fdSet = nullptr; + break; + } + } + + if (fdSet) { + streamWithFds.optionalFds() = fdSet; + } + } +} + +template<typename M> +void +SerializeInputStream(nsIInputStream* aStream, IPCStream& aValue, M* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + + // If a stream is known to be larger than 1MB, prefer sending it in chunks. + const uint64_t kTooLargeStream = 1024 * 1024; + + // First attempt simple stream serialization + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aStream); + uint64_t expectedLength = + serializable ? serializable->ExpectedSerializedLength().valueOr(0) : 0; + if (serializable && expectedLength < kTooLargeStream) { + SerializeInputStreamWithFdsChild(aStream, aValue, aManager); + return; + } + + // As a fallback, attempt to stream the data across using a SendStream + // actor. For blocking streams, create a nonblocking pipe instead, + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream); + if (!asyncStream) { + const uint32_t kBufferSize = 32768; // matches SendStream buffer size. + nsCOMPtr<nsIAsyncOutputStream> sink; + DebugOnly<nsresult> rv = NS_NewPipe2(getter_AddRefs(asyncStream), + getter_AddRefs(sink), + true, + false, + kBufferSize, + UINT32_MAX); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + + rv = NS_AsyncCopy(aStream, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, kBufferSize); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + MOZ_ASSERT(asyncStream); + aValue = SendStreamChild::Create(asyncStream, aManager); + + if (!aValue.get_PSendStreamChild()) { + MOZ_CRASH("SendStream creation failed!"); + } +} + +template<typename M> +void +SerializeInputStream(nsIInputStream* aStream, OptionalIPCStream& aValue, + M* aManager) +{ + if (!aStream) { + aValue = void_t(); + return; + } + + aValue = IPCStream(); + SerializeInputStream(aStream, aValue.get_IPCStream(), + aManager); +} + +void +CleanupIPCStream(IPCStream& aValue, bool aConsumedByIPC) +{ + if (aValue.type() == IPCStream::T__None) { + return; + } + + if (aValue.type() == IPCStream::TInputStreamParamsWithFds) { + + InputStreamParamsWithFds& streamWithFds = + aValue.get_InputStreamParamsWithFds(); + + // Cleanup file descriptors if necessary + if (streamWithFds.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetChild) { + + AutoTArray<FileDescriptor, 4> fds; + + auto fdSetActor = static_cast<FileDescriptorSetChild*>( + streamWithFds.optionalFds().get_PFileDescriptorSetChild()); + MOZ_ASSERT(fdSetActor); + + // FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we + // unconditionally forget them here. The fds themselves are auto-closed in + // ~FileDescriptor since they originated in this process. + fdSetActor->ForgetFileDescriptors(fds); + + if (!aConsumedByIPC) { + Unused << fdSetActor->Send__delete__(fdSetActor); + } + + } else if (streamWithFds.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetParent) { + + AutoTArray<FileDescriptor, 4> fds; + + auto fdSetActor = static_cast<FileDescriptorSetParent*>( + streamWithFds.optionalFds().get_PFileDescriptorSetParent()); + MOZ_ASSERT(fdSetActor); + + // FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we + // unconditionally forget them here. The fds themselves are auto-closed in + // ~FileDescriptor since they originated in this process. + fdSetActor->ForgetFileDescriptors(fds); + + if (!aConsumedByIPC) { + Unused << fdSetActor->Send__delete__(fdSetActor); + } + } + + return; + } + + MOZ_ASSERT(aValue.type() == IPCStream::TPSendStreamChild); + + auto sendStream = + static_cast<SendStreamChild*>(aValue.get_PSendStreamChild()); + + if (!aConsumedByIPC) { + sendStream->StartDestroy(); + return; + } + + // If the SendStream was taken to be sent to the parent, then we need to + // start it before forgetting about it. + sendStream->Start(); +} + +void +CleanupIPCStream(OptionalIPCStream& aValue, bool aConsumedByIPC) +{ + if (aValue.type() == OptionalIPCStream::Tvoid_t) { + return; + } + + CleanupIPCStream(aValue.get_IPCStream(), aConsumedByIPC); +} + +} // anonymous namespace + +already_AddRefed<nsIInputStream> +DeserializeIPCStream(const IPCStream& aValue) +{ + if (aValue.type() == IPCStream::TPSendStreamParent) { + auto sendStream = + static_cast<SendStreamParent*>(aValue.get_PSendStreamParent()); + return sendStream->TakeReader(); + } + + // Note, we explicitly do not support deserializing the PSendStream actor on + // the child side. It can only be sent from child to parent. + MOZ_ASSERT(aValue.type() == IPCStream::TInputStreamParamsWithFds); + + const InputStreamParamsWithFds& streamWithFds = + aValue.get_InputStreamParamsWithFds(); + + AutoTArray<FileDescriptor, 4> fds; + if (streamWithFds.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetParent) { + + auto fdSetActor = static_cast<FileDescriptorSetParent*>( + streamWithFds.optionalFds().get_PFileDescriptorSetParent()); + MOZ_ASSERT(fdSetActor); + + fdSetActor->ForgetFileDescriptors(fds); + MOZ_ASSERT(!fds.IsEmpty()); + + if (!fdSetActor->Send__delete__(fdSetActor)) { + // child process is gone, warn and allow actor to clean up normally + NS_WARNING("Failed to delete fd set actor."); + } + } else if (streamWithFds.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetChild) { + + auto fdSetActor = static_cast<FileDescriptorSetChild*>( + streamWithFds.optionalFds().get_PFileDescriptorSetChild()); + MOZ_ASSERT(fdSetActor); + + fdSetActor->ForgetFileDescriptors(fds); + MOZ_ASSERT(!fds.IsEmpty()); + + Unused << fdSetActor->Send__delete__(fdSetActor); + } + + return DeserializeInputStream(streamWithFds.stream(), fds); +} + +already_AddRefed<nsIInputStream> +DeserializeIPCStream(const OptionalIPCStream& aValue) +{ + if (aValue.type() == OptionalIPCStream::Tvoid_t) { + return nullptr; + } + + return DeserializeIPCStream(aValue.get_IPCStream()); +} + +namespace { + +void +AssertValidValueToTake(const IPCStream& aVal) +{ + MOZ_ASSERT(aVal.type() == IPCStream::TPSendStreamChild || + aVal.type() == IPCStream::TInputStreamParamsWithFds); +} + +void +AssertValidValueToTake(const OptionalIPCStream& aVal) +{ + MOZ_ASSERT(aVal.type() == OptionalIPCStream::Tvoid_t || + aVal.type() == OptionalIPCStream::TIPCStream); + if (aVal.type() == OptionalIPCStream::TIPCStream) { + AssertValidValueToTake(aVal.get_IPCStream()); + } +} + +} // anonymous namespace + +AutoIPCStream::AutoIPCStream() + : mInlineValue(void_t()) + , mValue(nullptr) + , mOptionalValue(&mInlineValue) + , mTaken(false) +{ +} + +AutoIPCStream::AutoIPCStream(IPCStream& aTarget) + : mInlineValue(void_t()) + , mValue(&aTarget) + , mOptionalValue(nullptr) + , mTaken(false) +{ +} + +AutoIPCStream::AutoIPCStream(OptionalIPCStream& aTarget) + : mInlineValue(void_t()) + , mValue(nullptr) + , mOptionalValue(&aTarget) + , mTaken(false) +{ + *mOptionalValue = void_t(); +} + +AutoIPCStream::~AutoIPCStream() +{ + MOZ_ASSERT(mValue || mOptionalValue); + if (mValue && IsSet()) { + CleanupIPCStream(*mValue, mTaken); + } else { + CleanupIPCStream(*mOptionalValue, mTaken); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, dom::nsIContentChild* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStream(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStream(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, PBackgroundChild* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStream(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStream(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, dom::PContentParent* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStreamWithFdsParent(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStreamWithFdsParent(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, PBackgroundParent* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStreamWithFdsParent(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStreamWithFdsParent(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +bool +AutoIPCStream::IsSet() const +{ + MOZ_ASSERT(mValue || mOptionalValue); + if (mValue) { + return mValue->type() != IPCStream::T__None; + } else { + return mOptionalValue->type() != OptionalIPCStream::Tvoid_t && + mOptionalValue->get_IPCStream().type() != IPCStream::T__None; + } +} + +IPCStream& +AutoIPCStream::TakeValue() +{ + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(IsSet()); + + mTaken = true; + + if (mValue) { + AssertValidValueToTake(*mValue); + return *mValue; + } + + IPCStream& value = + mOptionalValue->get_IPCStream(); + + AssertValidValueToTake(value); + return value; +} + +OptionalIPCStream& +AutoIPCStream::TakeOptionalValue() +{ + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!mValue); + MOZ_ASSERT(mOptionalValue); + mTaken = true; + AssertValidValueToTake(*mOptionalValue); + return *mOptionalValue; +} + +} // namespace ipc +} // namespace mozilla |