// Copyright 2018 The gVisor Authors.
//
// 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 <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <string>
#include <utility>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/time/time.h"
#include "test/util/logging.h"
#include "test/util/multiprocess_util.h"
#include "test/util/test_util.h"
#include "test/util/time_util.h"

DEFINE_bool(vfork_test_child, false,
            "If true, run the VforkTest child workload.");

namespace gvisor {
namespace testing {

namespace {

// We don't test with raw CLONE_VFORK to avoid interacting with glibc's use of
// TLS.
//
// Even with vfork(2), we must be careful to do little more in the child than
// call execve(2). We use the simplest sleep function possible, though this is
// still precarious, as we're officially only allowed to call execve(2) and
// _exit(2).
constexpr absl::Duration kChildDelay = absl::Seconds(10);

// Exit code for successful child subprocesses. We don't want to use 0 since
// it's too common, and an execve(2) failure causes the child to exit with the
// errno, so kChildExitCode is chosen to be an unlikely errno:
constexpr int kChildExitCode = 118;  // ENOTNAM: Not a XENIX named type file

int64_t MonotonicNow() {
  struct timespec now;
  TEST_PCHECK(clock_gettime(CLOCK_MONOTONIC, &now) == 0);
  return now.tv_sec * 1000000000ll + now.tv_nsec;
}

TEST(VforkTest, ParentStopsUntilChildExits) {
  const auto test = [] {
    // N.B. Run the test in a single-threaded subprocess because
    // vfork is not safe in a multi-threaded process.

    const int64_t start = MonotonicNow();

    pid_t pid = vfork();
    if (pid == 0) {
      SleepSafe(kChildDelay);
      _exit(kChildExitCode);
    }
    TEST_PCHECK_MSG(pid > 0, "vfork failed");
    MaybeSave();

    const int64_t end = MonotonicNow();

    absl::Duration dur = absl::Nanoseconds(end - start);

    TEST_CHECK(dur >= kChildDelay);

    int status = 0;
    TEST_PCHECK(RetryEINTR(waitpid)(pid, &status, 0));
    TEST_CHECK(WIFEXITED(status));
    TEST_CHECK(WEXITSTATUS(status) == kChildExitCode);
  };

  EXPECT_THAT(InForkedProcess(test), IsPosixErrorOkAndHolds(0));
}

TEST(VforkTest, ParentStopsUntilChildExecves_NoRandomSave) {
  ExecveArray const owned_child_argv = {"/proc/self/exe", "--vfork_test_child"};
  char* const* const child_argv = owned_child_argv.get();

  const auto test = [&] {
    const int64_t start = MonotonicNow();

    pid_t pid = vfork();
    if (pid == 0) {
      SleepSafe(kChildDelay);
      execve(child_argv[0], child_argv, /* envp = */ nullptr);
      _exit(errno);
    }
    // Don't attempt save/restore until after recording end_time,
    // since the test expects an upper bound on the time spent
    // stopped.
    int saved_errno = errno;
    const int64_t end = MonotonicNow();
    errno = saved_errno;
    TEST_PCHECK_MSG(pid > 0, "vfork failed");
    MaybeSave();

    absl::Duration dur = absl::Nanoseconds(end - start);

    // The parent should resume execution after execve, but before
    // the post-execve test child exits.
    TEST_CHECK(dur >= kChildDelay);
    TEST_CHECK(dur <= 2 * kChildDelay);

    int status = 0;
    TEST_PCHECK(RetryEINTR(waitpid)(pid, &status, 0));
    TEST_CHECK(WIFEXITED(status));
    TEST_CHECK(WEXITSTATUS(status) == kChildExitCode);
  };

  EXPECT_THAT(InForkedProcess(test), IsPosixErrorOkAndHolds(0));
}

// A vfork child does not unstop the parent a second time when it exits after
// exec.
TEST(VforkTest, ExecedChildExitDoesntUnstopParent_NoRandomSave) {
  ExecveArray const owned_child_argv = {"/proc/self/exe", "--vfork_test_child"};
  char* const* const child_argv = owned_child_argv.get();

  const auto test = [&] {
    pid_t pid1 = vfork();
    if (pid1 == 0) {
      execve(child_argv[0], child_argv, /* envp = */ nullptr);
      _exit(errno);
    }
    TEST_PCHECK_MSG(pid1 > 0, "vfork failed");
    MaybeSave();

    // pid1 exec'd and is now sleeping.
    SleepSafe(kChildDelay / 2);

    const int64_t start = MonotonicNow();

    pid_t pid2 = vfork();
    if (pid2 == 0) {
      SleepSafe(kChildDelay);
      _exit(kChildExitCode);
    }
    TEST_PCHECK_MSG(pid2 > 0, "vfork failed");
    MaybeSave();

    const int64_t end = MonotonicNow();

    absl::Duration dur = absl::Nanoseconds(end - start);

    // The parent should resume execution only after pid2 exits, not
    // when pid1 exits.
    TEST_CHECK(dur >= kChildDelay);

    int status = 0;
    TEST_PCHECK(RetryEINTR(waitpid)(pid1, &status, 0));
    TEST_CHECK(WIFEXITED(status));
    TEST_CHECK(WEXITSTATUS(status) == kChildExitCode);

    TEST_PCHECK(RetryEINTR(waitpid)(pid2, &status, 0));
    TEST_CHECK(WIFEXITED(status));
    TEST_CHECK(WEXITSTATUS(status) == kChildExitCode);
  };

  EXPECT_THAT(InForkedProcess(test), IsPosixErrorOkAndHolds(0));
}

int RunChild() {
  SleepSafe(kChildDelay);
  return kChildExitCode;
}

}  // namespace

}  // namespace testing
}  // namespace gvisor

int main(int argc, char** argv) {
  gvisor::testing::TestInit(&argc, &argv);

  if (FLAGS_vfork_test_child) {
    return gvisor::testing::RunChild();
  }

  return RUN_ALL_TESTS();
}