//
// Copyright (C) 2012 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 "update_engine/common/subprocess.h"

#include <fcntl.h>
#include <poll.h>
#include <sys/types.h>
#include <unistd.h>

#include <set>
#include <string>
#include <vector>

#include <base/bind.h>
#include <base/files/scoped_temp_dir.h>
#include <base/location.h>
#include <base/message_loop/message_loop.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <brillo/message_loops/base_message_loop.h>
#include <brillo/message_loops/message_loop.h>
#include <brillo/message_loops/message_loop_utils.h>
#include <brillo/strings/string_utils.h>
#include <brillo/unittest_utils.h>
#include <gtest/gtest.h>

#include "update_engine/common/test_utils.h"
#include "update_engine/common/utils.h"

using base::TimeDelta;
using brillo::MessageLoop;
using std::string;
using std::vector;

namespace {

#ifdef __ANDROID__
#define kBinPath "/system/bin"
#define kUsrBinPath "/system/bin"
#else
#define kBinPath "/bin"
#define kUsrBinPath "/usr/bin"
#endif  // __ANDROID__

}  // namespace

namespace chromeos_update_engine {

class SubprocessTest : public ::testing::Test {
 protected:
  void SetUp() override {
    loop_.SetAsCurrent();
    async_signal_handler_.Init();
    subprocess_.Init(&async_signal_handler_);
  }

  base::MessageLoopForIO base_loop_;
  brillo::BaseMessageLoop loop_{&base_loop_};
  brillo::AsynchronousSignalHandler async_signal_handler_;
  Subprocess subprocess_;
};

namespace {

void ExpectedResults(int expected_return_code,
                     const string& expected_output,
                     int return_code,
                     const string& output) {
  EXPECT_EQ(expected_return_code, return_code);
  EXPECT_EQ(expected_output, output);
  MessageLoop::current()->BreakLoop();
}

void ExpectedEnvVars(int return_code, const string& output) {
  EXPECT_EQ(0, return_code);
  const std::set<string> allowed_envs = {"LD_LIBRARY_PATH", "PATH"};
  for (const string& key_value : brillo::string_utils::Split(output, "\n")) {
    auto key_value_pair =
        brillo::string_utils::SplitAtFirst(key_value, "=", true);
    EXPECT_NE(allowed_envs.end(), allowed_envs.find(key_value_pair.first));
  }
  MessageLoop::current()->BreakLoop();
}

void ExpectedDataOnPipe(const Subprocess* subprocess,
                        pid_t* pid,
                        int child_fd,
                        const string& child_fd_data,
                        int expected_return_code,
                        int return_code,
                        const string& /* output */) {
  EXPECT_EQ(expected_return_code, return_code);

  // Verify that we can read the data from our end of |child_fd|.
  int fd = subprocess->GetPipeFd(*pid, child_fd);
  EXPECT_NE(-1, fd);
  vector<char> buf(child_fd_data.size() + 1);
  EXPECT_EQ(static_cast<ssize_t>(child_fd_data.size()),
            HANDLE_EINTR(read(fd, buf.data(), buf.size())));
  EXPECT_EQ(child_fd_data,
            string(buf.begin(), buf.begin() + child_fd_data.size()));

  MessageLoop::current()->BreakLoop();
}

}  // namespace

TEST_F(SubprocessTest, IsASingleton) {
  EXPECT_EQ(&subprocess_, &Subprocess::Get());
}

TEST_F(SubprocessTest, InactiveInstancesDontChangeTheSingleton) {
  std::unique_ptr<Subprocess> another_subprocess(new Subprocess());
  EXPECT_EQ(&subprocess_, &Subprocess::Get());
  another_subprocess.reset();
  EXPECT_EQ(&subprocess_, &Subprocess::Get());
}

TEST_F(SubprocessTest, SimpleTest) {
  EXPECT_TRUE(subprocess_.Exec({kBinPath "/false"},
                               base::Bind(&ExpectedResults, 1, "")));
  loop_.Run();
}

TEST_F(SubprocessTest, EchoTest) {
  EXPECT_TRUE(subprocess_.Exec(
      {kBinPath "/sh", "-c", "echo this is stdout; echo this is stderr >&2"},
      base::Bind(&ExpectedResults, 0, "this is stdout\nthis is stderr\n")));
  loop_.Run();
}

TEST_F(SubprocessTest, StderrNotIncludedInOutputTest) {
  EXPECT_TRUE(subprocess_.ExecFlags(
      {kBinPath "/sh", "-c", "echo on stdout; echo on stderr >&2"},
      0,
      {},
      base::Bind(&ExpectedResults, 0, "on stdout\n")));
  loop_.Run();
}

TEST_F(SubprocessTest, PipeRedirectFdTest) {
  pid_t pid;
  pid = subprocess_.ExecFlags(
      {kBinPath "/sh", "-c", "echo on pipe >&3"},
      0,
      {3},
      base::Bind(&ExpectedDataOnPipe, &subprocess_, &pid, 3, "on pipe\n", 0));
  EXPECT_NE(0, pid);

  // Wrong file descriptor values should return -1.
  EXPECT_EQ(-1, subprocess_.GetPipeFd(pid, 123));
  loop_.Run();
  // Calling GetPipeFd() after the callback runs is invalid.
  EXPECT_EQ(-1, subprocess_.GetPipeFd(pid, 3));
}

// Test that a pipe file descriptor open in the parent is not open in the child.
TEST_F(SubprocessTest, PipeClosedWhenNotRedirectedTest) {
  brillo::ScopedPipe pipe;

  // test_subprocess will return with the errno of fstat, which should be EBADF
  // if the passed file descriptor is closed in the child.
  const vector<string> cmd = {
      test_utils::GetBuildArtifactsPath("test_subprocess"),
      "fstat",
      std::to_string(pipe.writer)};
  EXPECT_TRUE(subprocess_.ExecFlags(
      cmd, 0, {}, base::Bind(&ExpectedResults, EBADF, "")));
  loop_.Run();
}

TEST_F(SubprocessTest, EnvVarsAreFiltered) {
  EXPECT_TRUE(
      subprocess_.Exec({kUsrBinPath "/env"}, base::Bind(&ExpectedEnvVars)));
  loop_.Run();
}

TEST_F(SubprocessTest, SynchronousTrueSearchsOnPath) {
  int rc = -1;
  EXPECT_TRUE(Subprocess::SynchronousExecFlags(
      {"true"}, Subprocess::kSearchPath, &rc, nullptr));
  EXPECT_EQ(0, rc);
}

TEST_F(SubprocessTest, SynchronousEchoTest) {
  vector<string> cmd = {
      kBinPath "/sh", "-c", "echo -n stdout-here; echo -n stderr-there >&2"};
  int rc = -1;
  string stdout;
  ASSERT_TRUE(Subprocess::SynchronousExec(cmd, &rc, &stdout));
  EXPECT_EQ(0, rc);
  EXPECT_EQ("stdout-herestderr-there", stdout);
}

TEST_F(SubprocessTest, SynchronousEchoNoOutputTest) {
  int rc = -1;
  ASSERT_TRUE(Subprocess::SynchronousExec(
      {kBinPath "/sh", "-c", "echo test"}, &rc, nullptr));
  EXPECT_EQ(0, rc);
}

namespace {
void CallbackBad(int return_code, const string& output) {
  ADD_FAILURE() << "should never be called.";
}
}  // namespace

// Test that you can cancel a program that's already running.
TEST_F(SubprocessTest, CancelTest) {
  base::ScopedTempDir tempdir;
  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
  string fifo_path = tempdir.GetPath().Append("fifo").value();
  EXPECT_EQ(0, mkfifo(fifo_path.c_str(), 0666));

  // Start a process, make sure it is running and try to cancel it. We write
  // two bytes to the fifo, the first one marks that the program is running and
  // the second one marks that the process waited for a timeout and was not
  // killed. We should read the first byte but not the second one.
  vector<string> cmd = {
      kBinPath "/sh",
      "-c",
      base::StringPrintf(
          // The 'sleep' launched below could be left behind as an orphaned
          // process when the 'sh' process is terminated by SIGTERM. As a
          // remedy, trap SIGTERM and kill the 'sleep' process, which requires
          // launching 'sleep' in background and then waiting for it.
          "cleanup() { kill \"${sleep_pid}\"; exit 0; }; "
          "trap cleanup TERM; "
          "sleep 60 & "
          "sleep_pid=$!; "
          "printf X >\"%s\"; "
          "wait; "
          "printf Y >\"%s\"; "
          "exit 1",
          fifo_path.c_str(),
          fifo_path.c_str())};
  uint32_t tag = Subprocess::Get().Exec(cmd, base::Bind(&CallbackBad));
  EXPECT_NE(0U, tag);

  int fifo_fd = HANDLE_EINTR(open(fifo_path.c_str(), O_RDONLY));
  EXPECT_GE(fifo_fd, 0);

  loop_.WatchFileDescriptor(FROM_HERE,
                            fifo_fd,
                            MessageLoop::WatchMode::kWatchRead,
                            false,
                            base::Bind(
                                [](int fifo_fd, uint32_t tag) {
                                  char c;
                                  EXPECT_EQ(1,
                                            HANDLE_EINTR(read(fifo_fd, &c, 1)));
                                  EXPECT_EQ('X', c);
                                  LOG(INFO) << "Killing tag " << tag;
                                  Subprocess::Get().KillExec(tag);
                                },
                                fifo_fd,
                                tag));

  // This test would leak a callback that runs when the child process exits
  // unless we wait for it to run.
  brillo::MessageLoopRunUntil(
      &loop_, TimeDelta::FromSeconds(120), base::Bind([] {
        return Subprocess::Get().subprocess_records_.empty();
      }));
  EXPECT_TRUE(Subprocess::Get().subprocess_records_.empty());
  // Check that there isn't anything else to read from the pipe.
  char c;
  EXPECT_EQ(0, HANDLE_EINTR(read(fifo_fd, &c, 1)));
  IGNORE_EINTR(close(fifo_fd));
}

}  // namespace chromeos_update_engine