// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/zygote_manager.h" #include #include #include #include #include #include "base/eintr_wrapper.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/process_util.h" #include "base/scoped_ptr.h" #include "base/string_util.h" #include "testing/gtest/include/gtest/gtest.h" using file_util::Delete; using file_util::WriteFile; using file_util::ReadFileToString; using file_util::GetCurrentDirectory; #if defined(OS_LINUX) // ZygoteManager is only used on Linux at the moment typedef testing::Test ZygoteManagerTest; TEST_F(ZygoteManagerTest, Ping) { base::ZygoteManager zm; scoped_ptr< std::vector > new_argv; new_argv.reset(zm.Start()); EXPECT_TRUE(new_argv.get() == NULL); // Measure round trip time base::TimeDelta delta; EXPECT_EQ(zm.Ping(&delta), true); EXPECT_LT(delta.InMilliseconds(), 5000); } TEST_F(ZygoteManagerTest, SpawnChild) { base::ZygoteManager zm; const int kDummyChildExitCode = 39; scoped_ptr< std::vector > new_argv; new_argv.reset(zm.Start()); if (new_argv.get() == NULL) { // original process // Launch a child process std::vector myargs; myargs.push_back(std::string("0thArg")); myargs.push_back(std::string("1stArg")); base::file_handle_mapping_vector no_files; pid_t child = zm.LongFork(myargs, no_files); EXPECT_NE(child, -1); EXPECT_NE(child, 0); LOG(INFO) << "child pid " << child; // ZygoteManager doesn't support waiting for exit status int status; int err = HANDLE_EINTR(waitpid(child, &status, 0)); EXPECT_EQ(-1, err); EXPECT_EQ(ECHILD, errno); } else { LOG(INFO) << "Hello from child!"; // child process std::string arg0(new_argv.get()->at(0)); std::string arg1(new_argv.get()->at(1)); EXPECT_EQ(arg0, std::string("0thArg")); EXPECT_EQ(arg1, std::string("1stArg")); exit(kDummyChildExitCode); } } TEST_F(ZygoteManagerTest, MapFile) { base::ZygoteManager zm; const int kDummyChildExitCode = 39; const int kSpecialFDSlot = 5; scoped_ptr< std::vector > new_argv; new_argv.reset(zm.Start()); if (new_argv.get() == NULL) { // original process // Launch a child process std::vector myargs; myargs.push_back(std::string("0thArg")); myargs.push_back(std::string("1stArg")); base::file_handle_mapping_vector fds_to_map; int fd = open("/tmp/zygote_manager_unittest.tmp", O_CREAT|O_RDWR|O_TRUNC, 0644); fds_to_map.push_back(std::pair(fd, kSpecialFDSlot)); pid_t child = zm.LongFork(myargs, fds_to_map); EXPECT_NE(child, -1); EXPECT_NE(child, 0); // FIXME: really wait for child sleep(3); char buf[100]; // Expect fd to be seeked to end, so reading without seeking should fail off_t loc = lseek(fd, 0L, SEEK_CUR); EXPECT_EQ(3, loc); memset(buf, 0, sizeof(buf)); int nread = read(fd, buf, 5); EXPECT_EQ(0, nread); // Try again from beginning lseek(fd, 0L, SEEK_SET); memset(buf, 0, sizeof(buf)); nread = read(fd, buf, 2); EXPECT_EQ(2, nread); EXPECT_EQ(strncmp(buf, "hi", 2), 0); close(fd); } else { // child process // Write three bytes; this happens to seek the file descriptor to the end. int nwritten = write(kSpecialFDSlot, "hi\n", 3); EXPECT_EQ(3, nwritten); exit(kDummyChildExitCode); } } TEST_F(ZygoteManagerTest, OpenFile) { base::ZygoteManager zm; scoped_ptr< std::vector > new_argv; new_argv.reset(zm.Start()); EXPECT_EQ(NULL, new_argv.get()); const char kSomeText[] = "foobar\n"; // Verify that we disallow nonabsolute paths. FilePath badfilepath(FilePath::kCurrentDirectory); badfilepath = badfilepath.AppendASCII("zygote_manager_test.pak"); ASSERT_FALSE(badfilepath.IsAbsolute()); EXPECT_NE(-1, WriteFile(badfilepath, kSomeText, strlen(kSomeText))); std::string badfilename = WideToASCII(badfilepath.ToWStringHack()); int fd = zm.OpenFile(badfilename); EXPECT_EQ(-1, fd); EXPECT_TRUE(Delete(badfilepath, false)); // Verify that we disallow non-plain files. ASSERT_TRUE(GetCurrentDirectory(&badfilepath)); badfilepath = badfilepath.AppendASCII("zygote_manager_test.pak"); std::string badfilenameA = WideToASCII(badfilepath.ToWStringHack()); EXPECT_EQ(0, mkfifo(badfilenameA.c_str(), 0644)); badfilename = WideToASCII(badfilepath.ToWStringHack()); fd = zm.OpenFile(badfilename); ASSERT_EQ(-1, fd); EXPECT_TRUE(Delete(badfilepath, false)); // Verify that we disallow files not ending in .pak. ASSERT_TRUE(GetCurrentDirectory(&badfilepath)); badfilepath = badfilepath.AppendASCII("zygote_manager_test.tmp"); ASSERT_NE(-1, WriteFile(badfilepath, kSomeText, strlen(kSomeText))); badfilename = WideToASCII(badfilepath.ToWStringHack()); fd = zm.OpenFile(badfilename); ASSERT_EQ(-1, fd); EXPECT_TRUE(Delete(badfilepath, false)); // Verify that we disallow files in /etc badfilepath = FilePath(FILE_PATH_LITERAL("/")); badfilepath = badfilepath.AppendASCII("etc"); badfilepath = badfilepath.AppendASCII("hosts"); EXPECT_TRUE(badfilepath.IsAbsolute()); badfilename = WideToASCII(badfilepath.ToWStringHack()); fd = zm.OpenFile(badfilename); ASSERT_EQ(-1, fd); // Verify that we disallow files in /dev badfilepath = FilePath(FILE_PATH_LITERAL("/")); badfilepath = badfilepath.AppendASCII("dev"); badfilepath = badfilepath.AppendASCII("tty"); EXPECT_TRUE(badfilepath.IsAbsolute()); badfilename = WideToASCII(badfilepath.ToWStringHack()); fd = zm.OpenFile(badfilename); ASSERT_EQ(-1, fd); // Verify that we allow absolute paths with filename ending in .pak, // and that we can open them a second time even if they were // deleted after we opened them the first time. // Because of our restrictive filename checks, can't put // test file in /tmp, so put it in current directory. FilePath goodfilepath; ASSERT_TRUE(GetCurrentDirectory(&goodfilepath)); goodfilepath = goodfilepath.AppendASCII("zygote_manager_test.pak"); ASSERT_NE(-1, WriteFile(goodfilepath, kSomeText, strlen(kSomeText))); std::string goodfilename = WideToASCII(goodfilepath.ToWStringHack()); for (int i = 0; i < 2; i++) { fd = zm.OpenFile(goodfilename); ASSERT_NE(-1, fd); char buf[sizeof(kSomeText)]; // Can't use read because it depends on file position. // (In practice these files are mmapped.) int nread = pread(fd, buf, strlen(kSomeText), 0); ASSERT_EQ(strlen(kSomeText), static_cast(nread)); EXPECT_EQ(0, strncmp(buf, kSomeText, strlen(kSomeText))); EXPECT_EQ(0, close(fd)); // oddly, our Delete returns true for nonexistant files. EXPECT_EQ(true, Delete(goodfilepath, false)); } } TEST_F(ZygoteManagerTest, ChildOpenFile) { base::ZygoteManager zm; const int kDummyChildExitCode = 39; const char kSomeText[] = "foobar\n"; FilePath resultfilepath(FILE_PATH_LITERAL("/tmp")); resultfilepath = resultfilepath.AppendASCII("zygote_manager_test_result.tmp"); EXPECT_EQ(true, Delete(resultfilepath, false)); scoped_ptr< std::vector > new_argv; new_argv.reset(zm.Start()); if (new_argv.get() == NULL) { // original process // Launch a child process std::vector myargs; base::file_handle_mapping_vector no_files; pid_t child = zm.LongFork(myargs, no_files); EXPECT_NE(child, -1); EXPECT_NE(child, 0); LOG(INFO) << "child pid " << child; // Wait for resultfile to be created std::string result; int nloops = 0; while (!ReadFileToString(resultfilepath, &result)) { sleep(1); ++nloops; ASSERT_NE(10, nloops); } ASSERT_EQ(result, std::string(kSomeText)); EXPECT_EQ(true, Delete(resultfilepath, false)); } else { LOG(INFO) << "Hello from child!"; FilePath goodfilepath; ASSERT_TRUE(GetCurrentDirectory(&goodfilepath)); goodfilepath = goodfilepath.AppendASCII("zygote_manager_test.pak"); ASSERT_NE(-1, WriteFile(goodfilepath, kSomeText, strlen(kSomeText))); std::string goodfilename = WideToASCII(goodfilepath.ToWStringHack()); int fd = zm.OpenFile(goodfilename); EXPECT_EQ(true, Delete(goodfilepath, false)); ASSERT_NE(-1, fd); char buf[sizeof(kSomeText)]; // Can't use read because it depends on file position. // (In practice these files are mmapped.) int nread = pread(fd, buf, strlen(kSomeText), 0); ASSERT_EQ(strlen(kSomeText), static_cast(nread)); EXPECT_EQ(0, strncmp(buf, kSomeText, strlen(kSomeText))); EXPECT_EQ(0, close(fd)); ASSERT_NE(-1, WriteFile(resultfilepath, kSomeText, strlen(kSomeText))); exit(kDummyChildExitCode); } } #endif