// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.

#define _GNU_SOURCE

#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

#include "process-tools.h"

int SwitchToEuid() {
  int uid = getuid();
  int euid = geteuid();
  if (uid != euid) {
    CHECK_CALL(setreuid(euid, euid));
  }
  return euid;
}

int SwitchToEgid() {
  int gid = getgid();
  int egid = getegid();
  if (gid != egid) {
    CHECK_CALL(setregid(egid, egid));
  }
  return egid;
}

void Redirect(const char *target_path, int fd, const char *name) {
  if (target_path != NULL && strcmp(target_path, "-") != 0) {
    int fd_out;
    const int flags = O_WRONLY | O_CREAT | O_TRUNC | O_APPEND;
    CHECK_CALL(fd_out = open(target_path, flags, 0666));
    CHECK_CALL(dup2(fd_out, fd));
    CHECK_CALL(close(fd_out));
  }
}

void RedirectStdout(const char *stdout_path) {
  Redirect(stdout_path, STDOUT_FILENO, "stdout");
}

void RedirectStderr(const char *stderr_path) {
  Redirect(stderr_path, STDERR_FILENO, "stderr");
}

void KillEverything(int pgrp, bool gracefully, double graceful_kill_delay) {
  if (gracefully) {
    kill(-pgrp, SIGTERM);

    // Round up fractional seconds in this polling implementation.
    int kill_delay = (int)(ceil(graceful_kill_delay));

    // If the process is still alive, give it some time to die gracefully.
    while (kill_delay-- > 0 && kill(-pgrp, 0) == 0) {
      sleep(1);
    }
  }

  kill(-pgrp, SIGKILL);
}

void HandleSignal(int sig, void (*handler)(int)) {
  struct sigaction sa = {.sa_handler = handler};
  CHECK_CALL(sigemptyset(&sa.sa_mask));
  CHECK_CALL(sigaction(sig, &sa, NULL));
}

void UnHandle(int sig) { HandleSignal(sig, SIG_DFL); }

void ClearSignalMask() {
  // Use an empty signal mask for the process.
  sigset_t empty_sset;
  CHECK_CALL(sigemptyset(&empty_sset));
  CHECK_CALL(sigprocmask(SIG_SETMASK, &empty_sset, NULL));

  // Set the default signal handler for all signals.
  for (int i = 1; i < NSIG; ++i) {
    if (i == SIGKILL || i == SIGSTOP) {
      continue;
    }
    struct sigaction sa = {.sa_handler = SIG_DFL};
    CHECK_CALL(sigemptyset(&sa.sa_mask));
    // Ignore possible errors, because we might not be allowed to set the
    // handler for certain signals, but we still want to try.
    sigaction(i, &sa, NULL);
  }
}

void SetTimeout(double timeout_secs) {
  if (timeout_secs <= 0) {
    return;
  }

  double int_val, fraction_val;
  fraction_val = modf(timeout_secs, &int_val);

  struct itimerval timer = {.it_interval.tv_sec = 0,
                            .it_interval.tv_usec = 0,
                            .it_value.tv_sec = (long)int_val,
                            .it_value.tv_usec = (long)(fraction_val * 1e6)};

  CHECK_CALL(setitimer(ITIMER_REAL, &timer, NULL));
}

int WaitChild(pid_t pid, const char *name) {
  int err, status;

  do {
    err = waitpid(pid, &status, 0);
  } while (err == -1 && errno == EINTR);

  if (err == -1) {
    DIE("wait on %s (pid %d) failed\n", name, pid);
  }

  return status;
}
