diff options
| author | crupest <crupest@outlook.com> | 2023-02-06 00:09:57 +0800 | 
|---|---|---|
| committer | crupest <crupest@outlook.com> | 2023-02-06 00:09:57 +0800 | 
| commit | a207604826d5b4eb1a230adc2c201fdc478b2678 (patch) | |
| tree | 65af79b53ef392e8ddaff2c09ad22f47dccfffb6 /works | |
| parent | 82ca9e11965bce800ed5d500ab9c7dbc9ac3aa77 (diff) | |
| download | crupest-a207604826d5b4eb1a230adc2c201fdc478b2678.tar.gz crupest-a207604826d5b4eb1a230adc2c201fdc478b2678.tar.bz2 crupest-a207604826d5b4eb1a230adc2c201fdc478b2678.zip | |
import(life): Add linux-run.
Diffstat (limited to 'works')
| -rw-r--r-- | works/life/linux-run/.gitignore | 2 | ||||
| -rw-r--r-- | works/life/linux-run/Makefile | 16 | ||||
| -rw-r--r-- | works/life/linux-run/linux-run-test-bin.cpp | 8 | ||||
| -rw-r--r-- | works/life/linux-run/linux-run-test.cpp | 26 | ||||
| -rw-r--r-- | works/life/linux-run/linux-run.cpp | 200 | ||||
| -rw-r--r-- | works/life/linux-run/linux-run.h | 43 | 
6 files changed, 295 insertions, 0 deletions
| diff --git a/works/life/linux-run/.gitignore b/works/life/linux-run/.gitignore new file mode 100644 index 0000000..6c314b3 --- /dev/null +++ b/works/life/linux-run/.gitignore @@ -0,0 +1,2 @@ +linux-run-test +linux-run-test-bin diff --git a/works/life/linux-run/Makefile b/works/life/linux-run/Makefile new file mode 100644 index 0000000..7392db6 --- /dev/null +++ b/works/life/linux-run/Makefile @@ -0,0 +1,16 @@ +.PHONY: all test clean + +all: linux-run-test linux-run-test-bin + +linux-run-test: linux-run.h linux-run.cpp linux-run-test.cpp +	g++ linux-run.cpp linux-run-test.cpp -o linux-run-test + +linux-run-test-bin: linux-run-test-bin.cpp +	g++ linux-run-test-bin.cpp -o linux-run-test-bin + + +test: linux-run-test linux-run-test-bin +	./linux-run-test + +clean: +	rm linux-run-test linux-run-test-bin diff --git a/works/life/linux-run/linux-run-test-bin.cpp b/works/life/linux-run/linux-run-test-bin.cpp new file mode 100644 index 0000000..3f7bd94 --- /dev/null +++ b/works/life/linux-run/linux-run-test-bin.cpp @@ -0,0 +1,8 @@ +#include <iostream> +#include <unistd.h> + +int main() { +    sleep(5); +    std::cout << "Hello world!\n"; +    return 0; +} diff --git a/works/life/linux-run/linux-run-test.cpp b/works/life/linux-run/linux-run-test.cpp new file mode 100644 index 0000000..fe2486b --- /dev/null +++ b/works/life/linux-run/linux-run-test.cpp @@ -0,0 +1,26 @@ +#include "linux-run.h" + +#include <iostream> +#include <stdexcept> + +int main() { +  using namespace linux_run; +  RunOptions options; +  options.timeout_in_second = 1; + +  bool caught_timeout_error = false; + +  try { +    run("./linux-run-test-bin", {}, options); +  } catch (const TimeoutError &e) { +    caught_timeout_error = true; +  } + +  if (!caught_timeout_error) { +    throw std::runtime_error("Test failed. TimeoutError was not thrown."); +  } else { +    std::cout << "Test succeeded. TimeoutError caught.\n"; +  } + +  return 0; +} diff --git a/works/life/linux-run/linux-run.cpp b/works/life/linux-run/linux-run.cpp new file mode 100644 index 0000000..e656e55 --- /dev/null +++ b/works/life/linux-run/linux-run.cpp @@ -0,0 +1,200 @@ +#include "linux-run.h" + +#include <functional> +#include <stdexcept> +#include <string> +#include <vector> + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <string.h> // for strerror_r (GNU extension) +#include <sys/signalfd.h> +#include <sys/wait.h> +#include <unistd.h> + +namespace linux_run { +static std::string get_errno_message() { +  char buffer[100]; +  strerror_r(errno, buffer, 100); +  return std::string(buffer); +} + +static std::string get_errno_message(int custom_errno) { +  char buffer[100]; +  strerror_r(custom_errno, buffer, 100); +  return std::string(buffer); +} + +static void create_signal_fd(int *pfd) { +  sigset_t sigset; +  sigemptyset(&sigset); +  sigaddset(&sigset, SIGCHLD); +  if (sigprocmask(SIG_BLOCK, &sigset, nullptr) == -1) { +    throw std::runtime_error("Failed to block SIGCHLD. Reason: " + +                             get_errno_message()); +  } + +  sigemptyset(&sigset); +  sigaddset(&sigset, SIGCHLD); +  int fd = signalfd(-1, &sigset, 0); +  if (fd == -1) { +    throw std::runtime_error("Failed to create SIGCHLD fd. Reason: " + +                             get_errno_message()); +  } +  *pfd = fd; +} + +static void destroy_signal_fd(int fd) { +  if (close(fd) == -1) { +    throw std::runtime_error("Failed to close SIGCHLD fd. Reason: " + +                             get_errno_message()); +  } + +  sigset_t sigset; +  sigemptyset(&sigset); +  sigaddset(&sigset, SIGCHLD); +  if (sigprocmask(SIG_UNBLOCK, &sigset, nullptr) == -1) { +    throw std::runtime_error("Failed to unblock SIGCHLD. Reason: " + +                             get_errno_message()); +  } +} + +static char ** +convert_string_vector_to_argv(const std::string &program, +                              const std::vector<std::string> &arguments) { +  char **argv; +  argv = new char *[arguments.size() + 2]; +  argv[0] = new char[program.size() + 1]; +  strcpy(argv[0], program.c_str()); +  for (int i = 0; i < arguments.size(); i++) { +    const auto &argument = arguments[i]; +    argv[i + 1] = new char[argument.size() + 1]; +    strcpy(argv[i + 1], argument.c_str()); +  } +  argv[arguments.size() + 1] = nullptr; +  return argv; +} + +static void redirect_file(int old_file_descriptor, const std::string &path, +                          int open_file_flags) { +  if (path.empty()) +    return; +  int file_descriptor = open(path.c_str(), open_file_flags); +  if (file_descriptor == -1) +    throw std::runtime_error( +        "Failed to open " + path + +        " for redirection. Reason: " + get_errno_message()); +  int dup_r = dup2(file_descriptor, old_file_descriptor); +  if (dup_r == -1) +    throw std::runtime_error( +        "Failed to dup file descriptor for redirection. Reason: " + +        get_errno_message()); +  int close_r = close(file_descriptor); +  if (close_r == -1) +    throw std::runtime_error( +        "Failed to close old file descriptor for redirection. Reason: " + +        get_errno_message()); +} + +void run(const std::string &program, std::vector<std::string> arguments, +         RunOptions options) { + +  int sigchld_fd; +  create_signal_fd(&sigchld_fd); + +  pid_t pid = fork(); +  if (pid == 0) { +    destroy_signal_fd(sigchld_fd); + +    redirect_file(STDIN_FILENO, options.stdin_file_path, O_RDONLY); +    redirect_file(STDOUT_FILENO, options.stdout_file_path, +                  O_WRONLY | O_CREAT | O_TRUNC); +    auto argv = convert_string_vector_to_argv(program, arguments); +    int r = execvp(program.c_str(), argv); +    throw std::runtime_error("Failed to exec: " + get_errno_message()); +  } +  if (pid == -1) { +    throw std::runtime_error("Failed to fork: " + get_errno_message()); +  } + +  struct pollfd sigchld_poll; +  sigchld_poll.fd = sigchld_fd; +  sigchld_poll.events = POLLIN; +  sigchld_poll.revents = 0; + +  int timeout_value = +      options.timeout_in_second > 0 ? options.timeout_in_second * 1000 : -1; + +  while (true) { +    int poll_return = poll(&sigchld_poll, 1, timeout_value); + +    if (poll_return == 0) { +      throw TimeoutError("Timeout to run command."); +    } else if (poll_return == -1) { +      if (errno == EINTR) { +        continue; +      } else { +        throw std::runtime_error("Failed to poll. Reason: " + +                                 get_errno_message()); +      } +    } else if (poll_return == 1) { +      break; +    } else { +      throw std::runtime_error("Poll returns a wired value."); +    } +  } + +  signalfd_siginfo sig_info; + +  if (sigchld_poll.revents & POLLIN) { +    size_t read_size = sizeof(signalfd_siginfo); +    int read_return = read(sigchld_fd, &sig_info, read_size); +    if (read_return == -1) { +      throw std::runtime_error("Failed to read signal fd. Reason: " + +                               get_errno_message()); +    } else if (read_return != read_size) { +      throw std::runtime_error("Failed to read signal fd because the return " +                               "byte count does not match request."); +    } +  } else { +    throw std::runtime_error("SIGCHLD fd is not readable after poll."); +  } + +  switch (sig_info.ssi_code) { +  case CLD_EXITED: { +    if (options.stop_reason) { +      *options.stop_reason = StopReason::Exited; +    } +    int exit_code = sig_info.ssi_status; +    if (options.exit_code) { +      *options.exit_code = exit_code; +    } +    if (options.check_exit_code) { +      if (exit_code != 0) { +        throw ExitCodeError("Command exit with nonzero value."); +      } +    } +  } break; +  case CLD_KILLED: { +    if (options.stop_reason) { +      *options.stop_reason = StopReason::Killed; +    } +  } break; +  default: +    throw std::runtime_error("Unsupported sig_info code."); +  } + +  if (options.before_reap_callback) { +    options.before_reap_callback(pid); +  } + +  // Reap child. +  if (waitpid(pid, nullptr, 0) == -1) { +    throw std::runtime_error("Failed to reap child process. Reason: " + +                             get_errno_message()); +  } +} + +} // namespace linux_run diff --git a/works/life/linux-run/linux-run.h b/works/life/linux-run/linux-run.h new file mode 100644 index 0000000..d953115 --- /dev/null +++ b/works/life/linux-run/linux-run.h @@ -0,0 +1,43 @@ +#ifndef LINUX_RUN_H +#define LINUX_RUN_H + +#include <functional> +#include <stdexcept> +#include <string> +#include <vector> + +namespace linux_run { + +enum StopReason { Exited, Killed }; + +struct RunOptions { +  RunOptions() +      : timeout_in_second(0), check_exit_code(true), stop_reason(nullptr), +        exit_code(nullptr) {} + +  int timeout_in_second; +  bool check_exit_code; +  std::string stdin_file_path; +  std::string stdout_file_path; +  StopReason *stop_reason; +  int *exit_code; +  // Before reap so you can get final information of the process. +  std::function<void(int pid)> before_reap_callback; +}; + +class TimeoutError : std::runtime_error { +public: +  using runtime_error::runtime_error; +}; + +class ExitCodeError : std::runtime_error { +public: +  using runtime_error::runtime_error; +}; + +void run(const std::string &program, std::vector<std::string> arguments, +         RunOptions options); + +} // namespace linux_run + +#endif | 
