Index: util/test/multiprocess_exec.cc |
diff --git a/util/test/multiprocess_exec.cc b/util/test/multiprocess_exec.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4cad7e0de2fdf161f218f79797591b86ea63a8a1 |
--- /dev/null |
+++ b/util/test/multiprocess_exec.cc |
@@ -0,0 +1,145 @@ |
+// 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/multiprocess_exec.h" |
+ |
+#include <fcntl.h> |
+#include <stdio.h> |
+#include <stdlib.h> |
+ |
+#include "base/posix/eintr_wrapper.h" |
+#include "gtest/gtest.h" |
+#include "util/misc/scoped_forbid_return.h" |
+#include "util/test/errors.h" |
+#include "util/test/posix/close_multiple.h" |
+ |
+namespace crashpad { |
+namespace test { |
+ |
+using namespace testing; |
+ |
+MultiprocessExec::MultiprocessExec() |
+ : Multiprocess(), |
+ command_(), |
+ arguments_(), |
+ argv_() { |
+} |
+ |
+void MultiprocessExec::SetChildCommand( |
+ const std::string& command, const std::vector<std::string>* arguments) { |
+ command_ = command; |
+ if (arguments) { |
+ arguments_ = *arguments; |
+ } else { |
+ arguments_.clear(); |
+ } |
+} |
+ |
+MultiprocessExec::~MultiprocessExec() { |
+} |
+ |
+void MultiprocessExec::PreFork() { |
+ Multiprocess::PreFork(); |
+ if (testing::Test::HasFatalFailure()) { |
+ return; |
+ } |
+ |
+ ASSERT_FALSE(command_.empty()); |
+ |
+ // Build up the argv vector. This is done in PreFork() instead of |
+ // MultiprocessChild() because although the result is only needed in the child |
+ // process, building it is a hazardous operation in that process. |
+ ASSERT_TRUE(argv_.empty()); |
+ |
+ argv_.push_back(command_.c_str()); |
+ for (const std::string& argument : arguments_) { |
+ argv_.push_back(argument.c_str()); |
+ } |
+ argv_.push_back(NULL); |
+} |
+ |
+void MultiprocessExec::MultiprocessChild() { |
+ // Make sure that stdin, stdout, and stderr are FDs 0, 1, and 2, respectively. |
+ // All FDs above this will be closed. |
+ COMPILE_ASSERT(STDIN_FILENO == 0, stdin_must_be_fd_0); |
+ COMPILE_ASSERT(STDOUT_FILENO == 1, stdout_must_be_fd_1); |
+ COMPILE_ASSERT(STDERR_FILENO == 2, stderr_must_be_fd_2); |
+ |
+ // Move the read pipe to stdin. |
+ int read_fd = ReadPipeFD(); |
+ ASSERT_NE(read_fd, STDIN_FILENO); |
+ ASSERT_NE(read_fd, STDOUT_FILENO); |
+ ASSERT_EQ(STDIN_FILENO, fileno(stdin)); |
+ |
+ int rv = fpurge(stdin); |
+ ASSERT_EQ(0, rv) << ErrnoMessage("fpurge"); |
+ |
+ rv = HANDLE_EINTR(dup2(read_fd, STDIN_FILENO)); |
+ ASSERT_EQ(STDIN_FILENO, rv) << ErrnoMessage("dup2"); |
+ |
+ // Move the write pipe to stdout. |
+ int write_fd = WritePipeFD(); |
+ ASSERT_NE(write_fd, STDIN_FILENO); |
+ ASSERT_NE(write_fd, STDOUT_FILENO); |
+ ASSERT_EQ(STDOUT_FILENO, fileno(stdout)); |
+ |
+ // Make a copy of the original stdout file descriptor so that in case there’s |
+ // an execv() failure, the original stdout can be restored so that gtest |
+ // messages directed to stdout go to the right place. Mark it as |
+ // close-on-exec, so that the child won’t see it after a successful exec(), |
+ // but it will still be available in this process after an unsuccessful |
+ // exec(). |
+ int dup_orig_stdout_fd = dup(STDOUT_FILENO); |
+ ASSERT_GE(dup_orig_stdout_fd, 0) << ErrnoMessage("dup"); |
+ |
+ rv = fcntl(dup_orig_stdout_fd, F_SETFD, FD_CLOEXEC); |
+ ASSERT_NE(rv, -1) << ErrnoMessage("fcntl"); |
+ |
+ rv = HANDLE_EINTR(fflush(stdout)); |
+ ASSERT_EQ(0, rv) << ErrnoMessage("fflush"); |
+ |
+ rv = HANDLE_EINTR(dup2(write_fd, STDOUT_FILENO)); |
+ ASSERT_EQ(STDOUT_FILENO, rv) << ErrnoMessage("dup2"); |
+ |
+ CloseMultipleNowOrOnExec(STDERR_FILENO + 1, dup_orig_stdout_fd); |
+ |
+ // Start the new program, replacing this one. execv() has a weird declaration |
+ // where its argv argument is declared as char* const*. In reality, the |
+ // implementation behaves as if the argument were const char* const*, and this |
+ // behavior is required by the standard. See |
+ // http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html |
+ // (search for “constant”). |
+ execv(argv_[0], const_cast<char* const*>(&argv_[0])); |
+ |
+ // This should not normally be reached. Getting here means that execv() |
+ // failed. |
+ |
+ // Be sure not to return until FAIL() is reached. |
+ ScopedForbidReturn forbid_return; |
+ |
+ // Put the original stdout back. Close the copy of the write pipe FD that’s |
+ // currently on stdout first, so that in case the dup2() that restores the |
+ // original stdout fails, stdout isn’t left attached to the pipe when the |
+ // FAIL() statement executes. |
+ HANDLE_EINTR(fflush(stdout)); |
+ IGNORE_EINTR(close(STDOUT_FILENO)); |
+ HANDLE_EINTR(dup2(dup_orig_stdout_fd, STDOUT_FILENO)); |
+ IGNORE_EINTR(close(dup_orig_stdout_fd)); |
+ |
+ forbid_return.Disarm(); |
+ FAIL() << ErrnoMessage("execv") << ": " << argv_[0]; |
+} |
+ |
+} // namespace test |
+} // namespace crashpad |