/*
 * 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
