aboutsummaryrefslogtreecommitdiff
path: root/src/Program.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-10-30 16:47:40 +0100
committerdec05eba <dec05eba@protonmail.com>2020-10-30 18:30:46 +0100
commit928f2525c29929de0c2ab520f48c82b5cb882aa7 (patch)
treee97130909f65e7835e4740824e9f38f58e4c0fbe /src/Program.cpp
parente422322650f9cb057937182987071438f9c79e84 (diff)
Matrix: re-add /logout, cancel task immediately
Cancel video download when pressing escape, other fixes..
Diffstat (limited to 'src/Program.cpp')
-rw-r--r--src/Program.cpp246
1 files changed, 246 insertions, 0 deletions
diff --git a/src/Program.cpp b/src/Program.cpp
new file mode 100644
index 0000000..136a494
--- /dev/null
+++ b/src/Program.cpp
@@ -0,0 +1,246 @@
+#include "../include/Program.hpp"
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/prctl.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unordered_map>
+#include <mutex>
+#include <signal.h>
+
+#define READ_END 0
+#define WRITE_END 1
+
+class CurrentThreadProgram {
+public:
+ void set(ReadProgram read_program) {
+ std::lock_guard<std::mutex> lock(mutex);
+ thread_current_program[std::this_thread::get_id()] = read_program;
+ }
+
+ void clear() {
+ std::lock_guard<std::mutex> lock(mutex);
+ thread_current_program.erase(std::this_thread::get_id());
+ }
+
+ void kill_in_thread(const std::thread::id &thread_id) {
+ std::lock_guard<std::mutex> lock(mutex);
+ auto it = thread_current_program.find(thread_id);
+ if(it != thread_current_program.end()) {
+ close(it->second.read_fd);
+ kill(it->second.pid, SIGTERM);
+ }
+ }
+private:
+ std::unordered_map<std::thread::id, ReadProgram> thread_current_program;
+ std::mutex mutex;
+};
+
+static CurrentThreadProgram current_thread_program;
+
+int exec_program_pipe(const char **args, ReadProgram *read_program) {
+ /* 1 arguments */
+ if(args[0] == NULL)
+ return -1;
+
+ int fd[2];
+ if(pipe(fd) == -1) {
+ perror("Failed to open pipe");
+ return -2;
+ }
+
+ pid_t parent_pid = getpid();
+
+ pid_t pid = fork();
+ if(pid == -1) {
+ perror("Failed to fork");
+ close(fd[READ_END]);
+ close(fd[WRITE_END]);
+ return -3;
+ } else if(pid == 0) { /* child */
+ if(prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) {
+ perror("prctl(PR_SET_PDEATHSIG, SIGTERM) failed");
+ _exit(127);
+ }
+
+ /* Test if the parent died before the above call to prctl */
+ if(getppid() != parent_pid)
+ _exit(127);
+
+ dup2(fd[WRITE_END], STDOUT_FILENO);
+ close(fd[READ_END]);
+ close(fd[WRITE_END]);
+
+ execvp(args[0], (char* const*)args);
+ perror("execvp");
+ _exit(127);
+ } else { /* parent */
+ close(fd[WRITE_END]);
+ read_program->pid = pid;
+ read_program->read_fd = fd[READ_END];
+ current_thread_program.set(*read_program);
+ return 0;
+ }
+}
+
+int exec_program(const char **args, ProgramOutputCallback output_callback, void *userdata) {
+ ReadProgram read_program;
+ int res = exec_program_pipe(args, &read_program);
+ if(res != 0)
+ return res;
+
+ int result = 0;
+ int status;
+ int exit_status;
+
+ char buffer[4097];
+
+ for(;;) {
+ ssize_t bytes_read = read(read_program.read_fd, buffer, sizeof(buffer) - 1);
+ if(bytes_read == 0) {
+ break;
+ } else if(bytes_read == -1) {
+ int err = errno;
+ fprintf(stderr, "Failed to read from pipe to program %s, error: %s\n", args[0], strerror(err));
+ result = -err;
+ break;
+ }
+
+ //check if running. Also do the same in download_to_json
+
+ buffer[bytes_read] = '\0';
+ if(output_callback) {
+ result = output_callback(buffer, bytes_read, userdata);
+ if(result != 0)
+ break;
+ }
+ }
+ program_clear_current_thread();
+
+ if(result != 0)
+ kill(read_program.pid, SIGTERM);
+
+ if(waitpid(read_program.pid, &status, 0) == -1) {
+ perror("waitpid failed");
+ result = -5;
+ goto cleanup;
+ }
+
+ if(!WIFEXITED(status)) {
+ result = -4;
+ goto cleanup;
+ }
+
+ exit_status = WEXITSTATUS(status);
+ if(exit_status != 0) {
+ fprintf(stderr, "Failed to execute program (");
+ const char **arg = args;
+ while(*arg) {
+ if(arg != args)
+ fputc(' ', stderr);
+ fprintf(stderr, "'%s'", *arg);
+ ++arg;
+ }
+ fprintf(stderr, "), exit status %d\n", exit_status);
+ result = -exit_status;
+ }
+
+ cleanup:
+ close(read_program.read_fd);
+ return result;
+}
+
+int wait_program(pid_t process_id) {
+ int status;
+ if(waitpid(process_id, &status, 0) == -1) {
+ perror("waitpid failed");
+ return -errno;
+ }
+
+ if(!WIFEXITED(status))
+ return -4;
+
+ return WEXITSTATUS(status);
+}
+
+int wait_program_non_blocking(pid_t process_id, int *status) {
+ int s;
+ int wait_result = waitpid(process_id, &s, WNOHANG);
+ if(wait_result == -1) {
+ perror("waitpid failed");
+ *status = -errno;
+ return 0;
+ } else if(wait_result == 0) {
+ /* the child process is still running */
+ *status = 0;
+ return 0;
+ }
+
+ if(!WIFEXITED(s)) {
+ *status = -4;
+ return 0;
+ }
+
+ *status = WEXITSTATUS(s);
+ return 1;
+}
+
+int exec_program_async(const char **args, pid_t *result_process_id) {
+ /* 1 arguments */
+ if(args[0] == NULL)
+ return -1;
+
+ pid_t parent_pid = getpid();
+
+ pid_t pid = fork();
+ if(pid == -1) {
+ int err = errno;
+ perror("Failed to fork");
+ return -err;
+ } else if(pid == 0) { /* child */
+ if(result_process_id) {
+ if(prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) {
+ perror("prctl(PR_SET_PDEATHSIG, SIGTERM) failed");
+ _exit(127);
+ }
+
+ /* Test if the parent died before the above call to prctl */
+ if(getppid() != parent_pid)
+ _exit(127);
+
+ execvp(args[0], (char* const*)args);
+ perror("execvp");
+ _exit(127);
+ } else {
+ setsid();
+ signal(SIGHUP, SIG_IGN);
+
+ // TODO: Still creates zombie??? find a fix!
+
+ // Daemonize child to make the parent the init process which will reap the zombie child
+ pid_t second_child = fork();
+ if(second_child == 0) { // child
+ execvp(args[0], (char* const*)args);
+ perror("execvp");
+ _exit(127);
+ } else if(second_child != -1) {
+ _exit(0);
+ }
+ }
+ } else { /* parent */
+ if(result_process_id)
+ *result_process_id = pid;
+ }
+ return 0;
+}
+
+void program_clear_current_thread() {
+ current_thread_program.clear();
+}
+
+void program_kill_in_thread(const std::thread::id &thread_id) {
+ current_thread_program.kill_in_thread(thread_id);
+}