aboutsummaryrefslogtreecommitdiff
path: root/store/works/life/linux-run/linux-run.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'store/works/life/linux-run/linux-run.cpp')
-rw-r--r--store/works/life/linux-run/linux-run.cpp215
1 files changed, 215 insertions, 0 deletions
diff --git a/store/works/life/linux-run/linux-run.cpp b/store/works/life/linux-run/linux-run.cpp
new file mode 100644
index 0000000..0c3ab15
--- /dev/null
+++ b/store/works/life/linux-run/linux-run.cpp
@@ -0,0 +1,215 @@
+#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) {
+ kill(pid, SIGKILL);
+
+ if (options.stop_reason) {
+ *options.stop_reason = StopReason::Killed;
+ }
+
+ if (options.before_reap_callback) {
+ options.before_reap_callback(pid);
+ }
+
+ // Reap child process.
+ if (waitpid(pid, nullptr, 0) == -1) {
+ throw std::runtime_error("Failed to reap child process. Reason: " +
+ get_errno_message());
+ }
+ 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