| /* | |
| * Copyright (C) 2019 The Android Open Source Project | |
| * | |
| * 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 "perfetto/ext/base/subprocess.h" | |
| #include <thread> | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| #include <windows.h> | |
| #else | |
| #include <signal.h> | |
| #include <sys/stat.h> | |
| #include <unistd.h> | |
| #endif | |
| #include "perfetto/base/time.h" | |
| #include "perfetto/ext/base/file_utils.h" | |
| #include "perfetto/ext/base/pipe.h" | |
| #include "perfetto/ext/base/temp_file.h" | |
| #include "test/gtest_and_gmock.h" | |
| namespace perfetto { | |
| namespace base { | |
| namespace { | |
| std::string GetOutput(const Subprocess& p) { | |
| std::string output = p.output(); | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| size_t pos = 0; | |
| while ((pos = output.find("\r\n", pos)) != std::string::npos) | |
| output.erase(pos, 1); | |
| #endif | |
| return output; | |
| } | |
| std::string GenLargeString() { | |
| std::string contents; | |
| for (int i = 0; i < 4096; i++) { | |
| contents += "very long text " + std::to_string(i) + "\n"; | |
| } | |
| // Make sure that |contents| is > the default pipe buffer on Linux (4 pages). | |
| PERFETTO_DCHECK(contents.size() > 4096 * 4); | |
| return contents; | |
| } | |
| TEST(SubprocessTest, InvalidPath) { | |
| Subprocess p({"/usr/bin/invalid_1337"}); | |
| EXPECT_FALSE(p.Call()); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| EXPECT_EQ(p.returncode(), ERROR_FILE_NOT_FOUND); | |
| #else | |
| EXPECT_EQ(p.returncode(), 128); | |
| EXPECT_EQ(GetOutput(p), "execve() failed\n"); | |
| #endif | |
| } | |
| TEST(SubprocessTest, StdoutOnly) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", "(echo skip_err 1>&2) && echo out_only"}); | |
| #else | |
| Subprocess p({"sh", "-c", "(echo skip_err >&2); echo out_only"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kBuffer; | |
| p.args.stderr_mode = Subprocess::OutputMode::kDevNull; | |
| EXPECT_TRUE(p.Call()); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_EQ(GetOutput(p), "out_only\n"); | |
| } | |
| TEST(SubprocessTest, StderrOnly) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", "(echo err_only>&2) && echo skip_out"}); | |
| #else | |
| Subprocess p({"sh", "-c", "(echo err_only >&2); echo skip_out"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kDevNull; | |
| p.args.stderr_mode = Subprocess::OutputMode::kBuffer; | |
| EXPECT_TRUE(p.Call()); | |
| EXPECT_EQ(GetOutput(p), "err_only\n"); | |
| } | |
| TEST(SubprocessTest, BothStdoutAndStderr) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", "echo out&&(echo err>&2)&&echo out2"}); | |
| #else | |
| Subprocess p({"sh", "-c", "echo out; (echo err >&2); echo out2"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kBuffer; | |
| p.args.stderr_mode = Subprocess::OutputMode::kBuffer; | |
| EXPECT_TRUE(p.Call()); | |
| EXPECT_EQ(GetOutput(p), "out\nerr\nout2\n"); | |
| } | |
| TEST(SubprocessTest, CatInputModeDevNull) { | |
| std::string ignored_input = "ignored input"; | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/C", "findstr . || exit 0"}); | |
| #else | |
| Subprocess p({"cat", "-"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kBuffer; | |
| p.args.input = ignored_input; | |
| p.args.stdin_mode = Subprocess::InputMode::kDevNull; | |
| EXPECT_TRUE(p.Call()); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_EQ(GetOutput(p), ""); | |
| } | |
| TEST(SubprocessTest, BothStdoutAndStderrInputModeDevNull) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", "echo out&&(echo err>&2)&&echo out2"}); | |
| #else | |
| Subprocess p({"sh", "-c", "echo out; (echo err >&2); echo out2"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kBuffer; | |
| p.args.stderr_mode = Subprocess::OutputMode::kBuffer; | |
| p.args.stdin_mode = Subprocess::InputMode::kDevNull; | |
| EXPECT_TRUE(p.Call()); | |
| EXPECT_EQ(GetOutput(p), "out\nerr\nout2\n"); | |
| } | |
| TEST(SubprocessTest, AllDevNull) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", "(exit 1)"}); | |
| #else | |
| Subprocess p({"false"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kDevNull; | |
| p.args.stderr_mode = Subprocess::OutputMode::kDevNull; | |
| p.args.stdin_mode = Subprocess::InputMode::kDevNull; | |
| EXPECT_FALSE(p.Call()); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_EQ(p.returncode(), 1); | |
| } | |
| TEST(SubprocessTest, BinTrue) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", "(exit 0)"}); | |
| #else | |
| Subprocess p({"true"}); | |
| #endif | |
| EXPECT_TRUE(p.Call()); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_EQ(p.returncode(), 0); | |
| } | |
| TEST(SubprocessTest, BinFalse) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", "(exit 1)"}); | |
| #else | |
| Subprocess p({"false"}); | |
| #endif | |
| EXPECT_FALSE(p.Call()); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_EQ(p.returncode(), 1); | |
| } | |
| TEST(SubprocessTest, Echo) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", "echo|set /p ignored_var=foobar"}); | |
| #else | |
| Subprocess p({"echo", "-n", "foobar"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kBuffer; | |
| EXPECT_TRUE(p.Call()); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_EQ(p.returncode(), 0); | |
| EXPECT_EQ(GetOutput(p), "foobar"); | |
| } | |
| TEST(SubprocessTest, FeedbackLongInput) { | |
| std::string contents = GenLargeString(); | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/C", "findstr ."}); | |
| #else | |
| Subprocess p({"cat", "-"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kBuffer; | |
| p.args.input = contents; | |
| EXPECT_TRUE(p.Call()); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_EQ(p.returncode(), 0); | |
| EXPECT_EQ(GetOutput(p), contents); | |
| } | |
| TEST(SubprocessTest, CatLargeFile) { | |
| std::string contents = GenLargeString(); | |
| TempFile tf = TempFile::Create(); | |
| WriteAll(tf.fd(), contents.data(), contents.size()); | |
| FlushFile(tf.fd()); | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", ("type \"" + tf.path() + "\"").c_str()}); | |
| #else | |
| Subprocess p({"cat", tf.path().c_str()}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kBuffer; | |
| EXPECT_TRUE(p.Call()); | |
| EXPECT_EQ(GetOutput(p), contents); | |
| } | |
| TEST(SubprocessTest, Timeout) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"ping", "127.0.0.1", "-n", "60"}); | |
| p.args.stdout_mode = Subprocess::OutputMode::kDevNull; | |
| #else | |
| Subprocess p({"sleep", "60"}); | |
| #endif | |
| EXPECT_FALSE(p.Call(/*timeout_ms=*/1)); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_TRUE(p.timed_out()); | |
| } | |
| TEST(SubprocessTest, TimeoutNotHit) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"ping", "127.0.0.1", "-n", "1"}); | |
| p.args.stdout_mode = Subprocess::OutputMode::kDevNull; | |
| #else | |
| Subprocess p({"sleep", "0.01"}); | |
| #endif | |
| EXPECT_TRUE(p.Call(/*timeout_ms=*/100000)); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| } | |
| TEST(SubprocessTest, TimeoutStopOutput) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", "FOR /L %N IN () DO @echo stuff>NUL"}); | |
| #else | |
| Subprocess p({"sh", "-c", "while true; do echo stuff; done"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kDevNull; | |
| EXPECT_FALSE(p.Call(/*timeout_ms=*/10)); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_TRUE(p.timed_out()); | |
| } | |
| TEST(SubprocessTest, ExitBeforeReadingStdin) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"ping", "127.0.0.1", "-n", "1"}); | |
| #else | |
| // 'sh -c' is to avoid closing stdin (sleep closes it before sleeping). | |
| Subprocess p({"sh", "-c", "sleep 0.01"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kDevNull; | |
| p.args.stderr_mode = Subprocess::OutputMode::kDevNull; | |
| p.args.input = GenLargeString(); | |
| EXPECT_TRUE(p.Call()); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_EQ(p.returncode(), 0); | |
| } | |
| TEST(SubprocessTest, StdinWriteStall) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"ping", "127.0.0.1", "-n", "10"}); | |
| #else | |
| // 'sh -c' is to avoid closing stdin (sleep closes it before sleeping). | |
| // This causes a situation where the write on the stdin will stall because | |
| // nobody reads it and the pipe buffer fills up. In this situation we should | |
| // still handle the timeout properly. | |
| Subprocess p({"sh", "-c", "sleep 10"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kDevNull; | |
| p.args.stderr_mode = Subprocess::OutputMode::kDevNull; | |
| p.args.input = GenLargeString(); | |
| EXPECT_FALSE(p.Call(/*timeout_ms=*/10)); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_TRUE(p.timed_out()); | |
| } | |
| TEST(SubprocessTest, StartAndWait) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"ping", "127.0.0.1", "-n", "1000"}); | |
| #else | |
| Subprocess p({"sleep", "1000"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kDevNull; | |
| p.Start(); | |
| EXPECT_EQ(p.Poll(), Subprocess::kRunning); | |
| p.KillAndWaitForTermination(); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_EQ(p.Poll(), Subprocess::kTerminated); | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| EXPECT_EQ(p.returncode(), static_cast<int>(STATUS_CONTROL_C_EXIT)); | |
| #else | |
| EXPECT_EQ(p.returncode(), static_cast<int>(128 + SIGKILL)); | |
| #endif | |
| } | |
| TEST(SubprocessTest, PollBehavesProperly) { | |
| Pipe pipe = Pipe::Create(); | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", "(exit 0)"}); | |
| #else | |
| Subprocess p({"true"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kFd; | |
| p.args.out_fd = std::move(pipe.wr); | |
| p.Start(); | |
| // Wait for EOF (which really means the child process has terminated). | |
| std::string ignored; | |
| ReadPlatformHandle(*pipe.rd, &ignored); | |
| // The kernel takes some time to detect the termination of the process. The | |
| // best thing we can do here is check that we detect the termination within | |
| // some reasonable time. | |
| auto start_ms = GetWallTimeMs(); | |
| while (p.Poll() != Subprocess::kTerminated) { | |
| auto elapsed_ms = GetWallTimeMs() - start_ms; | |
| ASSERT_LT(elapsed_ms, TimeMillis(10000)); | |
| std::this_thread::sleep_for(TimeMillis(5)); | |
| } | |
| // At this point Poll() must detect the termination. | |
| EXPECT_EQ(p.Poll(), Subprocess::kTerminated); | |
| EXPECT_EQ(p.returncode(), 0); | |
| } | |
| TEST(SubprocessTest, Wait) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"cmd", "/c", "echo exec_done && FOR /L %N IN () DO @echo>NUL"}); | |
| #else | |
| Subprocess p({"sh", "-c", "echo exec_done; while true; do true; done"}); | |
| #endif | |
| p.args.stdout_mode = Subprocess::OutputMode::kBuffer; | |
| p.Start(); | |
| // Wait for the fork()+exec() to complete. | |
| while (p.output().find("exec_done") == std::string::npos) { | |
| EXPECT_FALSE(p.Wait(1 /*ms*/)); | |
| EXPECT_EQ(p.status(), Subprocess::kRunning); | |
| } | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| ScopedPlatformHandle proc_handle(::OpenProcess( | |
| PROCESS_TERMINATE, /*inherit=*/false, static_cast<DWORD>(p.pid()))); | |
| ASSERT_TRUE(proc_handle); | |
| ASSERT_TRUE(::TerminateProcess(*proc_handle, DBG_CONTROL_BREAK)); | |
| #else | |
| kill(p.pid(), SIGBUS); | |
| #endif | |
| EXPECT_TRUE(p.Wait(30000 /*ms*/)); // We shouldn't hit this. | |
| EXPECT_TRUE(p.Wait()); // Should be a no-op. | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| EXPECT_EQ(p.returncode(), static_cast<int>(DBG_CONTROL_BREAK)); | |
| #else | |
| EXPECT_EQ(p.returncode(), 128 + SIGBUS); | |
| #endif | |
| } | |
| TEST(SubprocessTest, KillOnDtor) { | |
| auto is_process_alive = [](PlatformProcessId pid) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| DWORD ignored = 0; | |
| return ProcessIdToSessionId(static_cast<DWORD>(pid), &ignored); | |
| #else | |
| // We use kill(SIGWINCH) as a way to tell if the process is still alive by | |
| // looking at the kill(2) return value. SIGWINCH is one of the few signals | |
| // that has default ignore disposition. | |
| return kill(pid, SIGWINCH) == 0; | |
| #endif | |
| }; | |
| PlatformProcessId pid; | |
| { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess p({"ping", "127.0.0.1", "-n", "1000"}); | |
| #else | |
| Subprocess p({"sleep", "1000"}); | |
| #endif | |
| p.Start(); | |
| pid = p.pid(); | |
| EXPECT_TRUE(is_process_alive(pid)); | |
| } | |
| // Both on Windows and Linux, kill can take some time to free up the pid. | |
| bool alive = true; | |
| for (int attempt = 0; attempt < 1000 && alive; attempt++) { | |
| alive = is_process_alive(pid); | |
| std::this_thread::sleep_for(TimeMillis(5)); | |
| } | |
| EXPECT_FALSE(alive); | |
| } | |
| // Regression test for b/162505491. | |
| TEST(SubprocessTest, MoveOperators) { | |
| { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| Subprocess initial({"ping", "127.0.0.1", "-n", "100"}); | |
| #else | |
| Subprocess initial = Subprocess({"sleep", "10000"}); | |
| #endif | |
| initial.args.stdout_mode = Subprocess::OutputMode::kDevNull; | |
| initial.Start(); | |
| Subprocess moved(std::move(initial)); | |
| EXPECT_EQ(moved.Poll(), Subprocess::kRunning); | |
| EXPECT_EQ(initial.Poll(), Subprocess::kNotStarted); | |
| // Check that reuse works | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| initial = Subprocess({"cmd", "/c", "echo|set /p ignored_var=hello"}); | |
| #else | |
| initial = Subprocess({"echo", "-n", "hello"}); | |
| #endif | |
| initial.args.stdout_mode = Subprocess::OutputMode::kBuffer; | |
| initial.Start(); | |
| initial.Wait(/*timeout_ms=*/5000); | |
| EXPECT_EQ(initial.status(), Subprocess::kTerminated); | |
| EXPECT_EQ(initial.returncode(), 0); | |
| EXPECT_EQ(initial.output(), "hello"); | |
| } | |
| std::vector<Subprocess> v; | |
| for (int i = 0; i < 10; i++) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| v.emplace_back(Subprocess({"ping", "127.0.0.1", "-n", "10"})); | |
| #else | |
| v.emplace_back(Subprocess({"sleep", "10"})); | |
| #endif | |
| v.back().args.stdout_mode = Subprocess::OutputMode::kDevNull; | |
| v.back().Start(); | |
| } | |
| for (auto& p : v) | |
| EXPECT_EQ(p.Poll(), Subprocess::kRunning); | |
| } | |
| // posix_entrypoint_for_testing is not supported on Windows. | |
| #if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| // Test the case of passing a lambda in |entrypoint| but no cmd.c | |
| TEST(SubprocessTest, Entrypoint) { | |
| Subprocess p; | |
| p.args.input = "ping\n"; | |
| p.args.stdout_mode = Subprocess::OutputMode::kBuffer; | |
| p.args.posix_entrypoint_for_testing = [] { | |
| char buf[32]{}; | |
| PERFETTO_CHECK(fgets(buf, sizeof(buf), stdin)); | |
| PERFETTO_CHECK(strcmp(buf, "ping\n") == 0); | |
| printf("pong\n"); | |
| fflush(stdout); | |
| _exit(42); | |
| }; | |
| EXPECT_FALSE(p.Call()); | |
| EXPECT_EQ(p.returncode(), 42); | |
| EXPECT_EQ(GetOutput(p), "pong\n"); | |
| } | |
| // Test the case of passing both a lambda entrypoint and a process to exec. | |
| TEST(SubprocessTest, EntrypointAndExec) { | |
| base::Pipe pipe1 = base::Pipe::Create(); | |
| base::Pipe pipe2 = base::Pipe::Create(); | |
| int pipe1_wr = *pipe1.wr; | |
| int pipe2_wr = *pipe2.wr; | |
| Subprocess p({"echo", "123"}); | |
| p.args.stdout_mode = Subprocess::OutputMode::kBuffer; | |
| p.args.preserve_fds.push_back(pipe2_wr); | |
| p.args.posix_entrypoint_for_testing = [pipe1_wr, pipe2_wr] { | |
| base::ignore_result(write(pipe1_wr, "fail", 4)); | |
| base::ignore_result(write(pipe2_wr, "pass", 4)); | |
| }; | |
| p.Start(); | |
| pipe1.wr.reset(); | |
| pipe2.wr.reset(); | |
| char buf[8]; | |
| EXPECT_LE(read(*pipe1.rd, buf, sizeof(buf)), 0); | |
| EXPECT_EQ(read(*pipe2.rd, buf, sizeof(buf)), 4); | |
| buf[4] = '\0'; | |
| EXPECT_STREQ(buf, "pass"); | |
| EXPECT_TRUE(p.Wait()); | |
| EXPECT_EQ(p.status(), Subprocess::kTerminated); | |
| EXPECT_EQ(GetOutput(p), "123\n"); | |
| } | |
| #endif | |
| } // namespace | |
| } // namespace base | |
| } // namespace perfetto |