#include "../include/Program.hpp" #include #include #include #include #include #include #include #include #include #include #define READ_END 0 #define WRITE_END 1 struct ThreadProgram { ReadProgram read_program; bool killed; }; static std::unordered_map thread_current_program; static std::mutex thread_current_program_mutex; class CurrentThreadProgram { public: CurrentThreadProgram() { std::lock_guard lock(thread_current_program_mutex); ThreadProgram thread_program; thread_program.read_program.pid = -1; thread_program.read_program.read_fd = -1; thread_program.killed = false; thread_current_program[std::this_thread::get_id()] = std::move(thread_program); } ~CurrentThreadProgram() { std::lock_guard lock(thread_current_program_mutex); thread_current_program.erase(std::this_thread::get_id()); } void set(ReadProgram read_program) { std::lock_guard lock(thread_current_program_mutex); auto it = thread_current_program.find(std::this_thread::get_id()); if(it != thread_current_program.end()) it->second.read_program = std::move(read_program); } void clear() { std::lock_guard lock(thread_current_program_mutex); auto it = thread_current_program.find(std::this_thread::get_id()); if(it != thread_current_program.end()) { it->second.read_program.pid = -1; it->second.read_program.read_fd = -1; } } void kill_in_thread(const std::thread::id &thread_id) { std::lock_guard lock(thread_current_program_mutex); auto it = thread_current_program.find(thread_id); if(it != thread_current_program.end()) { if(it->second.read_program.read_fd != -1) close(it->second.read_program.read_fd); if(it->second.read_program.pid != -1) kill(it->second.read_program.pid, SIGTERM); it->second.killed = true; } } bool is_killed() { std::lock_guard lock(thread_current_program_mutex); auto it = thread_current_program.find(std::this_thread::get_id()); if(it != thread_current_program.end()) return it->second.killed; return false; } }; thread_local CurrentThreadProgram current_thread_program; int exec_program_pipe(const char **args, ReadProgram *read_program) { read_program->pid = -1; read_program->read_fd = -1; /* 1 arguments */ if(args[0] == NULL) return -1; if(current_thread_program.is_killed()) return -1; int fd[2]; if(pipe(fd) == -1) { perror("Failed to open pipe"); return -2; } pid_t pid = vfork(); if(pid == -1) { perror("Failed to vfork"); close(fd[READ_END]); close(fd[WRITE_END]); return -3; } else if(pid == 0) { /* child */ 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, int buffer_size) { ReadProgram read_program; int res = exec_program_pipe(args, &read_program); if(res != 0) return res; int result = 0; int status; int exit_status; assert(buffer_size >= 1 && buffer_size <= 65536); char *buffer = (char*)alloca(buffer_size + 1); for(;;) { ssize_t bytes_read = read(read_program.read_fd, buffer, buffer_size); 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; } buffer[bytes_read] = '\0'; if(output_callback) { result = output_callback(buffer, bytes_read, userdata); if(result != 0) break; } } 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: program_clear_current_thread(); close(read_program.read_fd); return result; } int wait_program(pid_t process_id) { int status; if(waitpid(process_id, &status, 0) == -1) { int err = -errno; perror("waitpid failed"); return err; } 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) { int err = -errno; perror("waitpid failed"); *status = err; 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 pid = vfork(); if(pid == -1) { int err = errno; perror("Failed to vfork"); return -err; } else if(pid == 0) { /* child */ if(result_process_id) { execvp(args[0], (char* const*)args); perror("execvp"); _exit(127); } else { setsid(); signal(SIGHUP, SIG_IGN); // Daemonize child to make the parent the init process which will reap the zombie child pid_t second_child = vfork(); 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; else waitpid(pid, nullptr, 0); } return 0; } void program_clear_current_thread() { current_thread_program.clear(); } void program_kill_in_thread(std::thread::id thread_id) { current_thread_program.kill_in_thread(thread_id); } bool program_is_dead_in_thread(std::thread::id thread_id) { std::lock_guard lock(thread_current_program_mutex); auto it = thread_current_program.find(thread_id); if(it != thread_current_program.end()) return it->second.killed; return false; } bool program_is_dead_in_current_thread() { return current_thread_program.is_killed(); }