From 747f0f5084c6e1b4c1bde0216ff01ce4310a3ba3 Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Sat, 20 Jan 2024 09:22:37 +0100 Subject: process posix impl --- source/kit/_lib.c | 2 + source/kit/process.h | 26 +++- source/kit/process.posix.c | 308 +++++++++++++++++++++++++++++++++++++++++++++ source/kit/process.win32.c | 4 + source/kit/status.h | 11 ++ 5 files changed, 348 insertions(+), 3 deletions(-) diff --git a/source/kit/_lib.c b/source/kit/_lib.c index 3b5d97a..0f7cc19 100644 --- a/source/kit/_lib.c +++ b/source/kit/_lib.c @@ -10,6 +10,8 @@ #include "atomic.win32.c" #include "threads.posix.c" #include "threads.win32.c" +#include "process.posix.c" +#include "process.win32.c" #include "shared_memory.posix.c" #include "shared_memory.win32.c" #include "xml.c" diff --git a/source/kit/process.h b/source/kit/process.h index 4a56055..f0aa200 100644 --- a/source/kit/process.h +++ b/source/kit/process.h @@ -7,14 +7,31 @@ #include "types.h" #include "string_ref.h" +#if !defined(_WIN32) || defined(__CYGWIN__) +# include +#else +#endif + #ifdef __cplusplus extern "C" { #endif +enum { + KIT_PROCESS_NO_ARGUMENTS = 1, + KIT_PROCESS_NO_ENVIRONMENT = (1 << 1), + KIT_PROCESS_NO_PIPES = (1 << 2), +}; + typedef struct { s32 status; - i32 exit_code; -#if defined(_WIN32) && !defined(__CYGWIN__) + u8 exit_code; +#if !defined(_WIN32) || defined(__CYGWIN__) + b8 _ready; + b8 _running; + pid_t _id; + i32 _stdin; + i32 _stdout; + i32 _stderr; #else #endif } kit_process_t; @@ -28,18 +45,21 @@ typedef KIT_AR(kit_str_t) kit_process_args_t; typedef KIT_AR(kit_process_env_var_t) kit_process_env_t; typedef struct { + u32 flags; kit_str_t file_name; kit_process_args_t command_line; kit_process_env_t environment; kit_str_t working_directory; } kit_process_info_t; -s32 kit_process_init(kit_process_t *p, kit_process_info_t info); +s32 kit_process_init(kit_process_t *p, kit_process_info_t info); +void kit_process_cleanup(kit_process_t *p); i64 kit_process_write_stdin(kit_process_t *p, kit_str_t in_data); i64 kit_process_read_stdout(kit_process_t *p, kit_str_t out_data); i64 kit_process_read_stderr(kit_process_t *p, kit_str_t out_data); s32 kit_process_terminate(kit_process_t *p); +b8 kit_process_alive(kit_process_t *p); s32 kit_process_wait(kit_process_t *p); #ifdef __cplusplus diff --git a/source/kit/process.posix.c b/source/kit/process.posix.c index aaf2a9f..b4039e6 100644 --- a/source/kit/process.posix.c +++ b/source/kit/process.posix.c @@ -1 +1,309 @@ #include "process.h" + +#if !defined(_WIN32) || defined(__CYGWIN__) +# include "status.h" + +# include +# include +# include +# include + +char *kit_process_argv_null_[] = { "", NULL }; +char *kit_process_env_null_[] = { NULL }; + +char **kit_init_argv_(kit_process_args_t args, u32 flags) { + // TODO + // + + if ((flags & KIT_PROCESS_NO_ARGUMENTS) != 0) + return kit_process_argv_null_; + + return NULL; +} + +char **kit_init_envp_(kit_process_env_t env, u32 flags) { + // TODO + // + + if ((flags & KIT_PROCESS_NO_ENVIRONMENT) != 0) + return kit_process_env_null_; + + return NULL; +} + +s32 kit_process_init(kit_process_t *p, kit_process_info_t info) { + assert(p != NULL); + assert(info.working_directory.size == 0 || + info.working_directory.values != NULL); + + if (p == NULL || (info.working_directory.size != 0 && + info.working_directory.values == NULL)) + return KIT_ERROR_INVALID_ARGUMENT; + + memset(p, 0, sizeof *p); + + p->_stdin = -1; + p->_stdout = -1; + p->_stderr = -1; + + signal(SIGCHLD, SIG_IGN); + signal(SIGALRM, SIG_IGN); // pipes + + i32 pipe_in[2]; + i32 pipe_out[2]; + i32 pipe_err[2]; + + if ((info.flags & KIT_PROCESS_NO_PIPES) == 0) { + if (pipe(pipe_in) == -1) { + assert(0); + return KIT_ERROR_PIPE_FAILED; + } + + if (pipe(pipe_out) == -1) { + assert(0); + close(pipe_in[0]); + close(pipe_in[1]); + return KIT_ERROR_PIPE_FAILED; + } + + if (pipe(pipe_err) == -1) { + assert(0); + close(pipe_in[0]); + close(pipe_in[1]); + close(pipe_out[0]); + close(pipe_out[1]); + return KIT_ERROR_PIPE_FAILED; + } + + // non-blocking writing for stdout, stderr + if (fcntl(pipe_out[1], F_SETFL, O_NONBLOCK) == -1 || + fcntl(pipe_err[1], F_SETFL, O_NONBLOCK) == -1) { + assert(0); + close(pipe_in[0]); + close(pipe_in[1]); + close(pipe_out[0]); + close(pipe_out[1]); + close(pipe_err[0]); + close(pipe_err[1]); + return KIT_ERROR_PIPE_FAILED; + } + } + + pid_t id = fork(); + + // NOTE + // We can safely use BS because there's only one thread after + // fork. + // + + switch (id) { + case -1: return KIT_ERROR_FORK_FAILED; + + case 0: + // Child process + // + + if ((info.flags & KIT_PROCESS_NO_PIPES) == 0) { + // Redirect IO + if (dup2(pipe_in[0], STDIN_FILENO) == -1 || + dup2(pipe_out[1], STDOUT_FILENO) == -1 || + dup2(pipe_err[1], STDERR_FILENO) == -1) { + assert(0); + close(pipe_in[0]); + close(pipe_in[1]); + close(pipe_out[0]); + close(pipe_out[1]); + close(pipe_err[0]); + close(pipe_err[1]); + return KIT_ERROR_DUP2_FAILED; + } + + // Close pipes + close(pipe_in[0]); + close(pipe_in[1]); + close(pipe_out[0]); + close(pipe_out[1]); + close(pipe_err[0]); + close(pipe_err[1]); + } + + // Change working directory + if (info.working_directory.size != 0 && + chdir(BS(info.working_directory)) == -1) { + assert(0); + return KIT_ERROR_CHDIR_FAILED; + } + + execve(BS(info.file_name), + kit_init_argv_(info.command_line, info.flags), + kit_init_envp_(info.environment, info.flags)); + // Doesn't return on success + + return KIT_ERROR_EXECVE_FAILED; + + default: + // Parent process + // + + p->status = KIT_OK; + p->_ready = 1; + p->_running = 1; + p->_id = id; + + if ((info.flags & KIT_PROCESS_NO_PIPES) == 0) { + p->_stdin = pipe_in[1]; + p->_stdout = pipe_out[0]; + p->_stderr = pipe_err[0]; + + // Close unused pipes + close(pipe_in[0]); + close(pipe_out[1]); + close(pipe_err[1]); + } + } + + return KIT_OK; +} + +void kit_process_cleanup(kit_process_t *p) { + assert(p != NULL); + assert(p->_ready); + + if (p == NULL || !p->_ready) + return; + + if (p->_stdin != -1) + close(p->_stdin); + if (p->_stdout != -1) + close(p->_stdout); + if (p->_stderr != -1) + close(p->_stderr); + + memset(p, 0, sizeof *p); +} + +i64 kit_process_write_stdin(kit_process_t *p, kit_str_t in_data) { + assert(p != NULL && (in_data.size == 0 || in_data.values != NULL) && + p->_running); + + if (p == NULL || (in_data.size != 0 && in_data.values == NULL)) + return KIT_ERROR_INVALID_ARGUMENT; + if (in_data.size == 0 || !p->_running || p->_stdin == -1) + return 0; + + i64 n = write(p->_stdin, in_data.values, in_data.size); + + assert(n >= 0); + if (n < 0) + return 0; + + return n; +} + +i64 kit_process_read_stdout(kit_process_t *p, kit_str_t out_data) { + assert(p != NULL && + (out_data.size == 0 || out_data.values != NULL) && + p->_ready); + + if (p == NULL || (out_data.size != 0 && out_data.values == NULL)) + return KIT_ERROR_INVALID_ARGUMENT; + if (out_data.size == 0 || !p->_ready || p->_stdout == -1) + return 0; + + i64 n = read(p->_stdout, out_data.values, out_data.size); + + assert(n >= 0); + if (n < 0) + return 0; + + return n; +} + +i64 kit_process_read_stderr(kit_process_t *p, kit_str_t out_data) { + assert(p != NULL && + (out_data.size == 0 || out_data.values != NULL) && + p->_ready); + + if (p == NULL || (out_data.size != 0 && out_data.values == NULL)) + return KIT_ERROR_INVALID_ARGUMENT; + if (out_data.size == 0 || !p->_ready || p->_stderr == -1) + return 0; + + i64 n = read(p->_stderr, out_data.values, out_data.size); + + assert(n >= 0); + if (n < 0) + return 0; + + return n; +} + +s32 kit_process_terminate(kit_process_t *p) { + assert(p != NULL && p->_running); + if (p == NULL || !p->_running) + return KIT_ERROR_INVALID_ARGUMENT; + + if (kill(p->_id, SIGTERM) == -1) + return KIT_ERROR_KILL_FAILED; + + return KIT_OK; +} + +b8 kit_process_alive(kit_process_t *p) { + assert(p != NULL); + if (p == NULL || p->status != KIT_OK) + return 0; + + if (!p->_running) + return 0; + + int status; + + pid_t id = waitpid(p->_id, &status, WNOHANG); + + if (id == -1) { + p->status = KIT_ERROR_WAITPID_FAILED; + return 0; + } + + if (id == 0) + return 1; + + if (WIFEXITED(status)) { + p->exit_code = WEXITSTATUS(status); + p->_running = 0; + return 0; + } + + return 1; +} + +s32 kit_process_wait(kit_process_t *p) { + assert(p != NULL); + if (p == NULL) + return KIT_ERROR_INVALID_ARGUMENT; + + if (p->status != KIT_OK) + return p->status; + if (!p->_running) + return KIT_OK; + + for (;;) { + int status; + + pid_t id = waitpid(p->_id, &status, 0); + + if (id == -1) + return KIT_ERROR_WAITPID_FAILED; + + if (WIFEXITED(status)) { + p->exit_code = WEXITSTATUS(status); + p->_running = 0; + break; + } + } + + return KIT_OK; +} + +#endif diff --git a/source/kit/process.win32.c b/source/kit/process.win32.c index aaf2a9f..ebff7b1 100644 --- a/source/kit/process.win32.c +++ b/source/kit/process.win32.c @@ -1 +1,5 @@ #include "process.h" + +#if defined(_WIN32) && !defined(__CYGWIN__) + +#endif diff --git a/source/kit/status.h b/source/kit/status.h index 9d1eb70..7ea1c4d 100644 --- a/source/kit/status.h +++ b/source/kit/status.h @@ -1,6 +1,10 @@ #ifndef KIT_STATUS_H #define KIT_STATUS_H +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + enum { KIT_OK = 0, KIT_PARSING_FAILED = 1, @@ -25,6 +29,13 @@ enum { KIT_ERROR_SYNC_FAILED = (1 << 19), KIT_ERROR_CLOSE_FAILED = (1 << 20), KIT_ERROR_RESOURCE_UNAVAILABLE = (1 << 21), + KIT_ERROR_FORK_FAILED = (1 << 22), + KIT_ERROR_EXECVE_FAILED = (1 << 23), + KIT_ERROR_WAITPID_FAILED = (1 << 24), + KIT_ERROR_PIPE_FAILED = (1 << 25), + KIT_ERROR_DUP2_FAILED = (1 << 26), + KIT_ERROR_CHDIR_FAILED = (1 << 27), + KIT_ERROR_KILL_FAILED = (1 << 28), KIT_ERROR_INTERNAL = (1 << 30), KIT_ERROR_NOT_IMPLEMENTED = -1, }; -- cgit v1.2.3