Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1243)

Unified Diff: util/test/mac/mach_multiprocess.cc

Issue 482483002: Add MachMultiprocess and its test (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@bootstrap
Patch Set: Update documentation Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « util/test/mac/mach_multiprocess.h ('k') | util/test/mac/mach_multiprocess_test.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: util/test/mac/mach_multiprocess.cc
diff --git a/util/test/mac/mach_multiprocess.cc b/util/test/mac/mach_multiprocess.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1b3e9d2fa1ed8ba9d0cb48da7a674dd00ac41dc3
--- /dev/null
+++ b/util/test/mac/mach_multiprocess.cc
@@ -0,0 +1,323 @@
+// Copyright 2014 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "util/test/mac/mach_multiprocess.h"
+
+#include <AvailabilityMacros.h>
+#include <bsm/libbsm.h>
+#include <servers/bootstrap.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include <string>
+
+#include "base/auto_reset.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/mac/scoped_mach_port.h"
+#include "base/rand_util.h"
+#include "base/strings/stringprintf.h"
+#include "gtest/gtest.h"
+#include "util/mach/bootstrap.h"
+#include "util/test/errors.h"
+#include "util/test/mac/mach_errors.h"
+
+namespace {
+
+class ScopedNotReached {
+ public:
+ ScopedNotReached() {}
+ ~ScopedNotReached() { abort(); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedNotReached);
+};
+
+} // namespace
+
+namespace crashpad {
+namespace test {
+
+using namespace testing;
+
+MachMultiprocess::MachMultiprocess()
+ : child_pid_(0),
+ pipe_fd_(-1),
+ local_port_(MACH_PORT_NULL),
+ remote_port_(MACH_PORT_NULL),
+ child_task_(MACH_PORT_NULL) {
+}
+
+void MachMultiprocess::Run() {
+ int pipe_fds[2];
+ int rv = pipe(pipe_fds);
+ ASSERT_EQ(0, rv) << ErrnoMessage("pipe");
+
+ base::ScopedFD read_pipe(pipe_fds[0]);
+ base::ScopedFD write_pipe(pipe_fds[1]);
+
+ // Set up the parent port and register it with the bootstrap server before
+ // forking, so that it’s guaranteed to be there when the child attempts to
+ // look it up.
+ std::string service_name = "com.googlecode.crashpad.test.mach_multiprocess.";
+ for (int index = 0; index < 16; ++index) {
+ service_name.append(1, base::RandInt('A', 'Z'));
+ }
+
+ mach_port_t local_port;
+ kern_return_t kr =
+ BootstrapCheckIn(bootstrap_port, service_name, &local_port);
+ ASSERT_EQ(BOOTSTRAP_SUCCESS, kr)
+ << BootstrapErrorMessage(kr, "bootstrap_check_in");
+ base::mac::ScopedMachReceiveRight local_port_owner(local_port);
+
+ pid_t pid = fork();
+ ASSERT_GE(pid, 0) << ErrnoMessage("fork");
+
+ // The “hello” message contains a send right to the child process’ task port.
+ struct SendHelloMessage : public mach_msg_base_t {
+ mach_msg_port_descriptor_t port_descriptor;
+ };
+
+ struct ReceiveHelloMessage : public SendHelloMessage {
+ mach_msg_audit_trailer_t audit_trailer;
+ };
+
+ if (pid > 0) {
Robert Sesek 2014/08/19 17:27:51 Split this into private helper routines DoParent()
Mark Mentovai 2014/08/20 00:08:48 rsesek wrote:
+ // Parent.
+ base::AutoReset<pid_t> reset_child_pid(&child_pid_, pid);
+
+ // The parent uses the read end of the pipe.
+ write_pipe.reset();
+ base::AutoReset<int> reset_pipe_fd(&pipe_fd_, read_pipe.get());
+
+ base::AutoReset<mach_port_t> reset_local_port(&local_port_,
+ local_port_owner);
+
+ ReceiveHelloMessage message = {};
+
+ kr = mach_msg(&message.header,
+ MACH_RCV_MSG |
+ MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) |
+ MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT),
+ 0,
+ sizeof(message),
+ local_port_,
+ MACH_MSG_TIMEOUT_NONE,
+ MACH_PORT_NULL);
+ ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg");
+
+ // Comb through the entire message, checking every field against its
Robert Sesek 2014/08/19 17:27:51 This might be excessive, but that's fine.
+ // expected value.
+ EXPECT_EQ(MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MOVE_SEND) |
+ MACH_MSGH_BITS_COMPLEX,
+ message.header.msgh_bits);
+ ASSERT_EQ(sizeof(SendHelloMessage), message.header.msgh_size);
+ EXPECT_EQ(local_port_, message.header.msgh_local_port);
+ ASSERT_EQ(1u, message.body.msgh_descriptor_count);
+ EXPECT_EQ(static_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_MOVE_SEND),
+ message.port_descriptor.disposition);
+ ASSERT_EQ(static_cast<mach_msg_descriptor_type_t>(MACH_MSG_PORT_DESCRIPTOR),
+ message.port_descriptor.type);
+ ASSERT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0),
+ message.audit_trailer.msgh_trailer_type);
+ ASSERT_EQ(sizeof(message.audit_trailer),
+ message.audit_trailer.msgh_trailer_size);
+ EXPECT_EQ(0u, message.audit_trailer.msgh_seqno);
+
+ // Check the audit trailer’s values for sanity.
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
+ uid_t audit_auid;
+ uid_t audit_euid;
+ gid_t audit_egid;
+ uid_t audit_ruid;
+ gid_t audit_rgid;
+ pid_t audit_pid;
+ au_asid_t audit_asid;
+ audit_token_to_au32(message.audit_trailer.msgh_audit,
+ &audit_auid,
+ &audit_euid,
+ &audit_egid,
+ &audit_ruid,
+ &audit_rgid,
+ &audit_pid,
+ &audit_asid,
+ NULL);
+#else
+ uid_t audit_auid = audit_token_to_auid(message.audit_trailer.msgh_audit);
+ uid_t audit_euid = audit_token_to_euid(message.audit_trailer.msgh_audit);
+ gid_t audit_egid = audit_token_to_egid(message.audit_trailer.msgh_audit);
+ uid_t audit_ruid = audit_token_to_ruid(message.audit_trailer.msgh_audit);
+ gid_t audit_rgid = audit_token_to_rgid(message.audit_trailer.msgh_audit);
+ pid_t audit_pid = audit_token_to_pid(message.audit_trailer.msgh_audit);
+ au_asid_t audit_asid =
+ audit_token_to_asid(message.audit_trailer.msgh_audit);
+#endif
+ EXPECT_EQ(geteuid(), audit_euid);
+ EXPECT_EQ(getegid(), audit_egid);
+ EXPECT_EQ(getuid(), audit_ruid);
+ EXPECT_EQ(getgid(), audit_rgid);
+ ASSERT_EQ(pid, audit_pid);
+
+ auditinfo_addr_t audit_info;
+ rv = getaudit_addr(&audit_info, sizeof(audit_info));
+ ASSERT_EQ(0, rv) << ErrnoMessage("getaudit_addr");
+ EXPECT_EQ(audit_info.ai_auid, audit_auid);
+ EXPECT_EQ(audit_info.ai_asid, audit_asid);
+
+ // Retrieve the remote port from the message header, and the child’s task
+ // port from the message body.
+ base::mac::ScopedMachSendRight remote_port_owner(
+ message.header.msgh_remote_port);
+ base::mac::ScopedMachSendRight child_task_owner(
+ message.port_descriptor.name);
+ base::AutoReset<mach_port_t> reset_remote_port(&remote_port_,
+ remote_port_owner);
+ base::AutoReset<mach_port_t> reset_child_task(&child_task_,
+ child_task_owner);
+
+ // Verify that the child’s task port is what it purports to be.
+ int mach_pid;
+ kr = pid_for_task(child_task_, &mach_pid);
+ ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "pid_for_task");
+ ASSERT_EQ(pid, mach_pid);
+
+ Parent();
+
+ remote_port_ = MACH_PORT_NULL;
Robert Sesek 2014/08/19 17:27:51 Do you need to do this? Won't the AutoReset do it
Mark Mentovai 2014/08/20 00:08:48 rsesek wrote:
+ local_port_ = MACH_PORT_NULL;
+ remote_port_owner.reset();
+ local_port_owner.reset();
+
+ pipe_fd_ = -1;
+ read_pipe.reset();
+
+ int status;
+ pid_t wait_pid = waitpid(pid, &status, 0);
+ ASSERT_EQ(pid, wait_pid) << ErrnoMessage("waitpid");
+ if (status != 0) {
+ std::string message;
+ if (WIFEXITED(status)) {
+ message = base::StringPrintf("Child exited with code %d",
+ WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ message = base::StringPrintf("Child terminated by signal %d (%s) %s",
+ WTERMSIG(status),
+ strsignal(WTERMSIG(status)),
+ WCOREDUMP(status) ? " (core dumped)" : "");
+ }
+ ASSERT_EQ(0, status) << message;
+ }
+ } else {
+ // Child.
+ ScopedNotReached must_not_leave_this_scope;
+
+ // local_port is not valid in the forked child process.
+ ignore_result(local_port_owner.release());
+ local_port = MACH_PORT_NULL;
+
+ // The child uses the write end of the pipe.
+ read_pipe.reset();
+ base::AutoReset<int> reset_pipe_fd(&pipe_fd_, write_pipe.get());
+
+ kr = mach_port_allocate(
+ mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &local_port);
+ ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_allocate");
+ local_port_owner.reset(local_port);
+ base::AutoReset<mach_port_t> reset_local_port(&local_port_,
+ local_port_owner);
+
+ // The remote port can be obtained from the bootstrap server.
+ mach_port_t remote_port;
+ kr = bootstrap_look_up(bootstrap_port, service_name.c_str(), &remote_port);
Robert Sesek 2014/08/19 17:27:51 This doesn't require that stupid const_cast<>?
Mark Mentovai 2014/08/20 00:08:48 rsesek wrote:
+ ASSERT_EQ(BOOTSTRAP_SUCCESS, kr)
+ << BootstrapErrorMessage(kr, "bootstrap_look_up");
+ base::mac::ScopedMachSendRight remote_port_owner(remote_port);
+ base::AutoReset<mach_port_t> reset_remote_port(&remote_port_,
+ remote_port_owner);
+
+ // The “hello” message will provide the parent with its remote port, a send
+ // right to the child task’s local port receive right. It will also carry a
+ // send right to the child task’s task port.
+ SendHelloMessage message = {};
+ message.header.msgh_bits =
+ MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND) |
+ MACH_MSGH_BITS_COMPLEX;
+ message.header.msgh_size = sizeof(message);
+ message.header.msgh_remote_port = remote_port_;
+ message.header.msgh_local_port = local_port_;
+ message.body.msgh_descriptor_count = 1;
+ message.port_descriptor.name = mach_task_self();
+ message.port_descriptor.disposition = MACH_MSG_TYPE_COPY_SEND;
+ message.port_descriptor.type = MACH_MSG_PORT_DESCRIPTOR;
+
+ kr = mach_msg(&message.header,
+ MACH_SEND_MSG,
+ message.header.msgh_size,
+ 0,
+ MACH_PORT_NULL,
+ MACH_MSG_TIMEOUT_NONE,
+ MACH_PORT_NULL);
+ ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg");
+
+ Child();
+
+ remote_port_ = MACH_PORT_NULL;
+ local_port_ = MACH_PORT_NULL;
+ remote_port_owner.reset();
+ local_port_owner.reset();
+
+ pipe_fd_ = -1;
+ write_pipe.reset();
+
+ if (Test::HasFailure()) {
+ // Trigger the ScopedNotReached destructor.
+ return;
+ }
+
+ exit(0);
+ }
+}
+
+MachMultiprocess::~MachMultiprocess() {
+}
+
+pid_t MachMultiprocess::ChildPID() const {
+ DCHECK_NE(child_pid_, 0);
+ return child_pid_;
+}
+
+int MachMultiprocess::PipeFD() const {
+ DCHECK_NE(pipe_fd_, -1);
+ return pipe_fd_;
+}
+
+mach_port_t MachMultiprocess::LocalPort() const {
+ DCHECK_NE(local_port_, static_cast<mach_port_t>(MACH_PORT_NULL));
+ return local_port_;
+}
+
+mach_port_t MachMultiprocess::RemotePort() const {
+ DCHECK_NE(remote_port_, static_cast<mach_port_t>(MACH_PORT_NULL));
+ return remote_port_;
+}
+
+mach_port_t MachMultiprocess::ChildTask() const {
+ DCHECK_NE(child_task_, static_cast<mach_port_t>(MACH_PORT_NULL));
+ return child_task_;
+}
+
+} // namespace test
+} // namespace crashpad
« no previous file with comments | « util/test/mac/mach_multiprocess.h ('k') | util/test/mac/mach_multiprocess_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698