Chromium Code Reviews| 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..2be2da49f071db5da455c122349938da45d4dae2 |
| --- /dev/null |
| +++ b/util/test/multiprocess_exec.cc |
| @@ -0,0 +1,141 @@ |
| +// 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/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; |
| + } |
| + |
| + // 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 test |
| + // messages directed to stdout go to the right place. Since stdin, stdout, and |
|
Robert Sesek
2014/09/02 21:45:14
Don't test messages go to stderr?
Mark Mentovai
2014/09/02 22:47:47
rsesek wrote:
|
| + // stderr are known to be 0, 1, and 2, and no other file descriptors are |
| + // needed in the child, use the next available file descriptor, 3, for this |
|
Robert Sesek
2014/09/02 21:45:14
Are you really guaranteed that no other fd occupie
|
| + // purpose. 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(). |
| + const int kDupOrigStdoutFd = 3; |
| + rv = HANDLE_EINTR(dup2(STDOUT_FILENO, kDupOrigStdoutFd)); |
| + ASSERT_EQ(kDupOrigStdoutFd, rv) << ErrnoMessage("dup2"); |
| + |
| + rv = fcntl(kDupOrigStdoutFd, 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(kDupOrigStdoutFd + 1); |
| + |
| + // 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. |
| + |
| + // 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(kDupOrigStdoutFd, STDOUT_FILENO)); |
| + IGNORE_EINTR(close(kDupOrigStdoutFd)); |
| + |
| + FAIL() << ErrnoMessage("execv") << ": " << argv_[0]; |
|
Robert Sesek
2014/09/02 21:45:14
Seems like a case for ScopedNoReturn.
Mark Mentovai
2014/09/02 22:47:47
rsesek wrote:
|
| +} |
| + |
| +} // namespace test |
| +} // namespace crashpad |