diff options
Diffstat (limited to 'ipc/glue/MiniTransceiver.cpp')
-rw-r--r-- | ipc/glue/MiniTransceiver.cpp | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/ipc/glue/MiniTransceiver.cpp b/ipc/glue/MiniTransceiver.cpp new file mode 100644 index 0000000000..933a727f62 --- /dev/null +++ b/ipc/glue/MiniTransceiver.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 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 "mozilla/ipc/MiniTransceiver.h" +#include "chrome/common/ipc_message.h" +#include "chrome/common/ipc_message_utils.h" +#include "base/eintr_wrapper.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Sprintf.h" +#include "mozilla/ScopeExit.h" +#include "nsDebug.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <string.h> +#include <errno.h> + +namespace mozilla::ipc { + +static const size_t kMaxIOVecSize = 64; +static const size_t kMaxDataSize = 8 * 1024; +static const size_t kMaxNumFds = 16; + +MiniTransceiver::MiniTransceiver(int aFd, DataBufferClear aDataBufClear) + : mFd(aFd), +#ifdef DEBUG + mState(STATE_NONE), +#endif + mDataBufClear(aDataBufClear) { +} + +namespace { + +/** + * Initialize the IO vector for sending data and the control buffer for sending + * FDs. + */ +static void InitMsgHdr(msghdr* aHdr, int aIOVSize, size_t aMaxNumFds) { + aHdr->msg_name = nullptr; + aHdr->msg_namelen = 0; + aHdr->msg_flags = 0; + + // Prepare the IO vector to receive the content of message. + auto* iov = new iovec[aIOVSize]; + aHdr->msg_iov = iov; + aHdr->msg_iovlen = aIOVSize; + + // Prepare the control buffer to receive file descriptors. + auto* cbuf = new char[CMSG_SPACE(sizeof(int) * aMaxNumFds)]; + aHdr->msg_control = cbuf; + aHdr->msg_controllen = CMSG_SPACE(sizeof(int) * aMaxNumFds); +} + +/** + * Delete resources allocated by InitMsgHdr(). + */ +static void DeinitMsgHdr(msghdr* aHdr) { + delete aHdr->msg_iov; + delete static_cast<char*>(aHdr->msg_control); +} + +} // namespace + +void MiniTransceiver::PrepareFDs(msghdr* aHdr, IPC::Message& aMsg) { + // Set control buffer to send file descriptors of the Message. + size_t num_fds = aMsg.attached_handles_.Length(); + + cmsghdr* cmsg = CMSG_FIRSTHDR(aHdr); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds); + for (size_t i = 0; i < num_fds; ++i) { + reinterpret_cast<int*>(CMSG_DATA(cmsg))[i] = + aMsg.attached_handles_[i].get(); + } + + // This number will be sent in the header of the message. So, we + // can check it at the other side. + aMsg.header()->num_handles = num_fds; +} + +size_t MiniTransceiver::PrepareBuffers(msghdr* aHdr, IPC::Message& aMsg) { + // Set iovec to send for all buffers of the Message. + iovec* iov = aHdr->msg_iov; + size_t iovlen = 0; + size_t bytes_to_send = 0; + for (Pickle::BufferList::IterImpl iter(aMsg.Buffers()); !iter.Done(); + iter.Advance(aMsg.Buffers(), iter.RemainingInSegment())) { + char* data = iter.Data(); + size_t size = iter.RemainingInSegment(); + iov[iovlen].iov_base = data; + iov[iovlen].iov_len = size; + iovlen++; + MOZ_ASSERT(iovlen <= kMaxIOVecSize); + bytes_to_send += size; + } + MOZ_ASSERT(bytes_to_send <= kMaxDataSize); + aHdr->msg_iovlen = iovlen; + + return bytes_to_send; +} + +bool MiniTransceiver::Send(IPC::Message& aMsg) { +#ifdef DEBUG + if (mState == STATE_SENDING) { + MOZ_CRASH( + "STATE_SENDING: It violates of request-response and no concurrent " + "rules"); + } + mState = STATE_SENDING; +#endif + + auto clean_fdset = MakeScopeExit([&] { aMsg.attached_handles_.Clear(); }); + + size_t num_fds = aMsg.attached_handles_.Length(); + msghdr hdr{}; + InitMsgHdr(&hdr, kMaxIOVecSize, num_fds); + + UniquePtr<msghdr, decltype(&DeinitMsgHdr)> uniq(&hdr, &DeinitMsgHdr); + + PrepareFDs(&hdr, aMsg); + DebugOnly<size_t> bytes_to_send = PrepareBuffers(&hdr, aMsg); + + ssize_t bytes_written = HANDLE_EINTR(sendmsg(mFd, &hdr, 0)); + + if (bytes_written < 0) { + char error[128]; + SprintfLiteral(error, "sendmsg: %s", strerror(errno)); + NS_WARNING(error); + return false; + } + MOZ_ASSERT(bytes_written == (ssize_t)bytes_to_send, + "The message is too big?!"); + + return true; +} + +unsigned MiniTransceiver::RecvFDs(msghdr* aHdr, int* aAllFds, + unsigned aMaxFds) { + if (aHdr->msg_controllen == 0) { + return 0; + } + + unsigned num_all_fds = 0; + for (cmsghdr* cmsg = CMSG_FIRSTHDR(aHdr); cmsg; + cmsg = CMSG_NXTHDR(aHdr, cmsg)) { + MOZ_ASSERT(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS, + "Accept only SCM_RIGHTS to receive file descriptors"); + + unsigned payload_sz = cmsg->cmsg_len - CMSG_LEN(0); + MOZ_ASSERT(payload_sz % sizeof(int) == 0); + + // Add fds to |aAllFds| + unsigned num_part_fds = payload_sz / sizeof(int); + int* part_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + MOZ_ASSERT(num_all_fds + num_part_fds <= aMaxFds); + + memcpy(aAllFds + num_all_fds, part_fds, num_part_fds * sizeof(int)); + num_all_fds += num_part_fds; + } + return num_all_fds; +} + +bool MiniTransceiver::RecvData(char* aDataBuf, size_t aBufSize, + uint32_t* aMsgSize, int* aFdsBuf, + unsigned aMaxFds, unsigned* aNumFds) { + msghdr hdr; + InitMsgHdr(&hdr, 1, aMaxFds); + + UniquePtr<msghdr, decltype(&DeinitMsgHdr)> uniq(&hdr, &DeinitMsgHdr); + + // The buffer to collect all fds received from the socket. + int* all_fds = aFdsBuf; + unsigned num_all_fds = 0; + + size_t total_readed = 0; + uint32_t msgsz = 0; + while (msgsz == 0 || total_readed < msgsz) { + // Set IO vector with the begin of the unused buffer. + hdr.msg_iov->iov_base = aDataBuf + total_readed; + hdr.msg_iov->iov_len = (msgsz == 0 ? aBufSize : msgsz) - total_readed; + + // Read the socket + ssize_t bytes_readed = HANDLE_EINTR(recvmsg(mFd, &hdr, 0)); + if (bytes_readed <= 0) { + // Closed or error! + return false; + } + total_readed += bytes_readed; + MOZ_ASSERT(total_readed <= aBufSize); + + if (msgsz == 0) { + // Parse the size of the message. + // Get 0 if data in the buffer is no enough to get message size. + msgsz = IPC::Message::MessageSize(aDataBuf, aDataBuf + total_readed); + } + + num_all_fds += RecvFDs(&hdr, all_fds + num_all_fds, aMaxFds - num_all_fds); + } + + *aMsgSize = msgsz; + *aNumFds = num_all_fds; + return true; +} + +bool MiniTransceiver::Recv(UniquePtr<IPC::Message>& aMsg) { +#ifdef DEBUG + if (mState == STATE_RECEIVING) { + MOZ_CRASH( + "STATE_RECEIVING: It violates of request-response and no concurrent " + "rules"); + } + mState = STATE_RECEIVING; +#endif + + UniquePtr<char[]> databuf = MakeUnique<char[]>(kMaxDataSize); + uint32_t msgsz = 0; + int all_fds[kMaxNumFds]; + unsigned num_all_fds = 0; + + if (!RecvData(databuf.get(), kMaxDataSize, &msgsz, all_fds, kMaxDataSize, + &num_all_fds)) { + return false; + } + + // Create Message from databuf & all_fds. + UniquePtr<IPC::Message> msg = MakeUnique<IPC::Message>(databuf.get(), msgsz); + nsTArray<UniqueFileHandle> handles(num_all_fds); + for (unsigned i = 0; i < num_all_fds; ++i) { + handles.AppendElement(UniqueFileHandle(all_fds[i])); + } + msg->SetAttachedFileHandles(std::move(handles)); + + if (mDataBufClear == DataBufferClear::AfterReceiving) { + // Avoid content processes from reading the content of + // messages. + memset(databuf.get(), 0, msgsz); + } + + MOZ_ASSERT(msg->header()->num_handles == msg->attached_handles_.Length(), + "The number of file descriptors in the header is different from" + " the number actually received"); + + aMsg = std::move(msg); + return true; +} + +} // namespace mozilla::ipc |