aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2023-02-06 00:09:57 +0800
committercrupest <crupest@outlook.com>2023-02-06 00:09:57 +0800
commita207604826d5b4eb1a230adc2c201fdc478b2678 (patch)
tree65af79b53ef392e8ddaff2c09ad22f47dccfffb6
parent82ca9e11965bce800ed5d500ab9c7dbc9ac3aa77 (diff)
downloadcrupest-a207604826d5b4eb1a230adc2c201fdc478b2678.tar.gz
crupest-a207604826d5b4eb1a230adc2c201fdc478b2678.tar.bz2
crupest-a207604826d5b4eb1a230adc2c201fdc478b2678.zip
import(life): Add linux-run.
-rw-r--r--works/life/linux-run/.gitignore2
-rw-r--r--works/life/linux-run/Makefile16
-rw-r--r--works/life/linux-run/linux-run-test-bin.cpp8
-rw-r--r--works/life/linux-run/linux-run-test.cpp26
-rw-r--r--works/life/linux-run/linux-run.cpp200
-rw-r--r--works/life/linux-run/linux-run.h43
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