| /* |
| * 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::kBuffer; |
| p.args.stderr_mode = Subprocess::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::kDevNull; |
| p.args.stderr_mode = Subprocess::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::kBuffer; |
| p.args.stderr_mode = Subprocess::kBuffer; |
| EXPECT_TRUE(p.Call()); |
| EXPECT_EQ(GetOutput(p), "out\nerr\nout2\n"); |
| } |
| |
| 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::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::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::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::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::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::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::kDevNull; |
| p.args.stderr_mode = Subprocess::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::kDevNull; |
| p.args.stderr_mode = Subprocess::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::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::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::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::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=*/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::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::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 |