// Copyright 2014 The Crashpad Authors // // 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 "test/multiprocess.h" #include #include #include #include "gtest/gtest.h" #include "test/gtest_death.h" #include "util/file/file_io.h" namespace crashpad { namespace test { namespace { class TestMultiprocess final : public Multiprocess { public: TestMultiprocess() : Multiprocess() {} TestMultiprocess(const TestMultiprocess&) = delete; TestMultiprocess& operator=(const TestMultiprocess&) = delete; ~TestMultiprocess() {} private: // Multiprocess: void MultiprocessParent() override { FileHandle read_handle = ReadPipeHandle(); char c; CheckedReadFileExactly(read_handle, &c, 1); EXPECT_EQ(c, 'M'); pid_t pid; CheckedReadFileExactly(read_handle, &pid, sizeof(pid)); EXPECT_EQ(ChildPID(), pid); c = 'm'; CheckedWriteFile(WritePipeHandle(), &c, 1); // The child will close its end of the pipe and exit. Make sure that the // parent sees EOF. CheckedReadFileAtEOF(read_handle); } void MultiprocessChild() override { FileHandle write_handle = WritePipeHandle(); char c = 'M'; CheckedWriteFile(write_handle, &c, 1); pid_t pid = getpid(); CheckedWriteFile(write_handle, &pid, sizeof(pid)); CheckedReadFileExactly(ReadPipeHandle(), &c, 1); EXPECT_EQ(c, 'm'); } }; TEST(Multiprocess, Multiprocess) { TestMultiprocess multiprocess; multiprocess.Run(); } class TestMultiprocessUnclean final : public Multiprocess { public: enum TerminationType { kExitSuccess = 0, kExitFailure, kExit2, kAbort, }; explicit TestMultiprocessUnclean(TerminationType type) : Multiprocess(), type_(type) { if (type_ == kAbort) { SetExpectedChildTermination(kTerminationSignal, SIGABRT); } else { SetExpectedChildTermination(kTerminationNormal, ExitCode()); } } TestMultiprocessUnclean(const TestMultiprocessUnclean&) = delete; TestMultiprocessUnclean& operator=(const TestMultiprocessUnclean&) = delete; ~TestMultiprocessUnclean() {} private: int ExitCode() const { return type_; } // Multiprocess: void MultiprocessParent() override { } void MultiprocessChild() override { if (type_ == kAbort) { abort(); } else { _exit(ExitCode()); } } TerminationType type_; }; TEST(Multiprocess, SuccessfulExit) { TestMultiprocessUnclean multiprocess(TestMultiprocessUnclean::kExitSuccess); multiprocess.Run(); } TEST(Multiprocess, UnsuccessfulExit) { TestMultiprocessUnclean multiprocess(TestMultiprocessUnclean::kExitFailure); multiprocess.Run(); } TEST(Multiprocess, Exit2) { TestMultiprocessUnclean multiprocess(TestMultiprocessUnclean::kExit2); multiprocess.Run(); } TEST(Multiprocess, AbortSignal) { TestMultiprocessUnclean multiprocess(TestMultiprocessUnclean::kAbort); multiprocess.Run(); } class TestMultiprocessClosePipe final : public Multiprocess { public: enum WhoCloses { kParentCloses = 0, kChildCloses, }; enum WhatCloses { kReadCloses = 0, kWriteCloses, kReadAndWriteClose, }; TestMultiprocessClosePipe(WhoCloses who_closes, WhatCloses what_closes) : Multiprocess(), who_closes_(who_closes), what_closes_(what_closes) { // Fails under "threadsafe" mode on macOS 10.11. GTEST_FLAG_SET(death_test_style, "fast"); } TestMultiprocessClosePipe(const TestMultiprocessClosePipe&) = delete; TestMultiprocessClosePipe& operator=(const TestMultiprocessClosePipe&) = delete; ~TestMultiprocessClosePipe() {} private: void VerifyInitial() { ASSERT_NE(ReadPipeHandle(), -1); ASSERT_NE(WritePipeHandle(), -1); } // Verifies that the partner process did what it was supposed to do. This must // only be called when who_closes_ names the partner process, not this // process. // // If the partner was supposed to close its write pipe, the read pipe will be // checked to ensure that it shows end-of-file. // // If the partner was supposed to close its read pipe, the write pipe will be // checked to ensure that a checked write causes death. This can only be done // if the partner also provides some type of signal when it has closed its // read pipe, which is done in the form of it closing its write pipe, causing // the read pipe in this process to show end-of-file. void VerifyPartner() { if (what_closes_ == kWriteCloses) { CheckedReadFileAtEOF(ReadPipeHandle()); } else if (what_closes_ == kReadAndWriteClose) { CheckedReadFileAtEOF(ReadPipeHandle()); char c = '\0'; // This will raise SIGPIPE. If fatal (the normal case), that will cause // process termination. If SIGPIPE is being handled somewhere, the write // will still fail and set errno to EPIPE, and CheckedWriteFile() will // abort execution. Regardless of how SIGPIPE is handled, the process will // be terminated. Because the actual termination mechanism is not known, // no regex can be specified. EXPECT_DEATH_CHECK(CheckedWriteFile(WritePipeHandle(), &c, 1), ""); } } void Close() { switch (what_closes_) { case kReadCloses: CloseReadPipe(); EXPECT_NE(WritePipeHandle(), -1); EXPECT_DEATH_CHECK(ReadPipeHandle(), "fd"); break; case kWriteCloses: CloseWritePipe(); EXPECT_NE(ReadPipeHandle(), -1); EXPECT_DEATH_CHECK(WritePipeHandle(), "fd"); break; case kReadAndWriteClose: CloseReadPipe(); CloseWritePipe(); EXPECT_DEATH_CHECK(ReadPipeHandle(), "fd"); EXPECT_DEATH_CHECK(WritePipeHandle(), "fd"); break; } } // Multiprocess: void MultiprocessParent() override { ASSERT_NO_FATAL_FAILURE(VerifyInitial()); if (who_closes_ == kParentCloses) { Close(); } else { VerifyPartner(); } } void MultiprocessChild() override { ASSERT_NO_FATAL_FAILURE(VerifyInitial()); if (who_closes_ == kChildCloses) { Close(); } else { VerifyPartner(); } } WhoCloses who_closes_; WhatCloses what_closes_; }; TEST(MultiprocessDeathTest, ParentClosesReadPipe) { TestMultiprocessClosePipe multiprocess( TestMultiprocessClosePipe::kParentCloses, TestMultiprocessClosePipe::kReadCloses); multiprocess.Run(); } TEST(MultiprocessDeathTest, ParentClosesWritePipe) { TestMultiprocessClosePipe multiprocess( TestMultiprocessClosePipe::kParentCloses, TestMultiprocessClosePipe::kWriteCloses); multiprocess.Run(); } TEST(MultiprocessDeathTest, ParentClosesReadAndWritePipe) { TestMultiprocessClosePipe multiprocess( TestMultiprocessClosePipe::kParentCloses, TestMultiprocessClosePipe::kReadAndWriteClose); multiprocess.Run(); } TEST(MultiprocessDeathTest, ChildClosesReadPipe) { TestMultiprocessClosePipe multiprocess( TestMultiprocessClosePipe::kChildCloses, TestMultiprocessClosePipe::kReadCloses); multiprocess.Run(); } TEST(MultiprocessDeathTest, ChildClosesWritePipe) { TestMultiprocessClosePipe multiprocess( TestMultiprocessClosePipe::kChildCloses, TestMultiprocessClosePipe::kWriteCloses); multiprocess.Run(); } TEST(MultiprocessDeathTest, ChildClosesReadAndWritePipe) { TestMultiprocessClosePipe multiprocess( TestMultiprocessClosePipe::kChildCloses, TestMultiprocessClosePipe::kReadAndWriteClose); multiprocess.Run(); } } // namespace } // namespace test } // namespace crashpad