From 8237fae60f22a490db09242552831d743faa1003 Mon Sep 17 00:00:00 2001 From: Max Wash Date: Sat, 13 Jun 2026 14:48:19 +0100 Subject: [PATCH] fx.diagnostics: add linux support for starting processes --- .../include/fx/diagnostics/process.h | 5 + fx.diagnostics/sys/apple/process.c | 26 +- fx.diagnostics/sys/linux/process.c | 493 ++++++++++++++++++ fx.diagnostics/test/redirect.c | 29 +- 4 files changed, 540 insertions(+), 13 deletions(-) create mode 100644 fx.diagnostics/sys/linux/process.c diff --git a/fx.diagnostics/include/fx/diagnostics/process.h b/fx.diagnostics/include/fx/diagnostics/process.h index 8ddf4c4..9b43835 100644 --- a/fx.diagnostics/include/fx/diagnostics/process.h +++ b/fx.diagnostics/include/fx/diagnostics/process.h @@ -1,6 +1,7 @@ #ifndef FX_DIAGNOSTICS_PROCESS_H_ #define FX_DIAGNOSTICS_PROCESS_H_ +#include #include #include @@ -18,16 +19,20 @@ typedef struct fx_process_start_info { const char *proc_file_name; const char **proc_args; size_t proc_args_count; + const fx_hashtable *proc_environment; } fx_process_start_info; FX_API fx_type_id fx_process_get_type(void); FX_API fx_process *fx_process_create(const fx_process_start_info *info); +FX_API fx_process *fx_process_get_self(void); + FX_API fx_status fx_process_start(fx_process *proc); FX_API fx_status fx_process_wait(fx_process *proc, int *out_result); FX_API fx_status fx_process_kill(fx_process *proc); +FX_API const fx_hashtable *fx_process_get_environment(const fx_process *proc); FX_API fx_stream *fx_process_get_stdin(fx_process *proc); FX_API fx_stream *fx_process_get_stdout(fx_process *proc); FX_API fx_stream *fx_process_get_stderr(fx_process *proc); diff --git a/fx.diagnostics/sys/apple/process.c b/fx.diagnostics/sys/apple/process.c index 3a99d2e..6610253 100644 --- a/fx.diagnostics/sys/apple/process.c +++ b/fx.diagnostics/sys/apple/process.c @@ -84,7 +84,6 @@ static fx_status process_start(struct fx_process_p *proc) if (proc->proc_redirect_stdout) { fx_iostream_unref(out_r); fd = fx_iostream_steal_os_handle(out_w); - fprintf(stderr, "dup2(%d, %d)\n", fd, 1); dup2(fd, 1); close(fd); } @@ -92,17 +91,28 @@ static fx_status process_start(struct fx_process_p *proc) if (proc->proc_redirect_stderr) { fx_iostream_unref(err_r); fd = fx_iostream_steal_os_handle(err_w); - fprintf(stderr, "dup2(%d, %d)\n", fd, 2); dup2(fd, 2); close(fd); } - const char *args[] = { - "cowsay", - NULL, - }; - printf("exec '%s'\n", fx_string_get_cstr(proc->proc_exec_path)); - execv(fx_string_get_cstr(proc->proc_exec_path), (char *const *)args); + const char **argv = NULL; + size_t argc = 0; + + if (proc->proc_args) { + argc = fx_array_get_size(proc->proc_args); + } + + if (argc) { + argv = calloc(argc + 1, sizeof(char *)); + + for (size_t i = 0; i < argc; i++) { + const fx_value *arg_value + = fx_array_get_ref(proc->proc_args, i); + fx_value_get_cstr(arg_value, &argv[i]); + } + } + + execv(fx_string_get_cstr(proc->proc_exec_path), (char *const *)argv); exit(127); cleanup: diff --git a/fx.diagnostics/sys/linux/process.c b/fx.diagnostics/sys/linux/process.c new file mode 100644 index 0000000..782c944 --- /dev/null +++ b/fx.diagnostics/sys/linux/process.c @@ -0,0 +1,493 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static fx_process *self = NULL; + +extern const char **environ; + +FX_API fx_type_id fx_process_iterator_get_type(); + +FX_DECLARE_TYPE(fx_process_iterator); + +FX_TYPE_CLASS_DECLARATION_BEGIN(fx_process_iterator) +FX_TYPE_CLASS_DECLARATION_END(fx_process_iterator) + +struct fx_process_p { + fx_string *proc_exec_path; + fx_array *proc_args; + bool proc_redirect_stdin, proc_redirect_stdout, proc_redirect_stderr; + fx_stream *proc_stdin, *proc_stdout, *proc_stderr; + pid_t proc_id; + fx_hashtable *proc_environment; +}; + +struct fx_process_iterator_p { +}; + +/*** PRIVATE FUNCTIONS ********************************************************/ + +static fx_status process_start(struct fx_process_p *proc) +{ + fx_iostream *in_r = NULL, *in_w = NULL; + fx_iostream *out_r = NULL, *out_w = NULL; + fx_iostream *err_r = NULL, *err_w = NULL; + fx_status status = FX_SUCCESS; + + if (proc->proc_redirect_stdin) { + status = fx_pipe_create(&in_r, &in_w); + if (!FX_OK(status)) { + goto cleanup; + } + } + + if (proc->proc_redirect_stdout) { + status = fx_pipe_create(&out_r, &out_w); + if (!FX_OK(status)) { + goto cleanup; + } + } + + if (proc->proc_redirect_stderr) { + status = fx_pipe_create(&err_r, &err_w); + if (!FX_OK(status)) { + goto cleanup; + } + } + + pid_t pid = fork(); + + if (pid < 0) { + status = FX_ERR_INVALID_ARGUMENT; + goto cleanup; + } + + if (pid > 0) { + proc->proc_id = pid; + proc->proc_stdin = fx_iostream_ref(in_w); + proc->proc_stdout = fx_iostream_ref(out_r); + proc->proc_stderr = fx_iostream_ref(err_r); + status = FX_SUCCESS; + goto cleanup; + } + + int fd = -1; + if (proc->proc_redirect_stdin) { + fx_iostream_unref(in_w); + fd = fx_iostream_steal_os_handle(in_r); + int err = dup2(fd, 0); + close(fd); + } + + if (proc->proc_redirect_stdout) { + fx_iostream_unref(out_r); + fd = fx_iostream_steal_os_handle(out_w); + dup2(fd, 1); + close(fd); + } + + if (proc->proc_redirect_stderr) { + fx_iostream_unref(err_r); + fd = fx_iostream_steal_os_handle(err_w); + dup2(fd, 2); + close(fd); + } + + const char **argv = NULL; + const char **envp = NULL; + const char *default_argv[] = { + fx_string_get_cstr(proc->proc_exec_path), + NULL, + }; + + size_t argc = 0; + size_t envc = 0; + + if (proc->proc_args) { + argc = fx_array_get_size(proc->proc_args); + } + + if (proc->proc_environment) { + envc = fx_hashtable_get_count(proc->proc_environment); + } + + if (argc) { + argv = calloc(argc + 1, sizeof(char *)); + + for (size_t i = 0; i < argc; i++) { + const fx_value *arg_value + = fx_array_get_ref(proc->proc_args, i); + fx_value_get_cstr(arg_value, &argv[i]); + } + } else { + argv = default_argv; + } + + if (envc) { + envp = calloc(envc + 1, sizeof(char *)); + size_t i = 0; + fx_stringstream *tmp = fx_stringstream_create(); + const fx_iterator *it + = fx_iterator_begin(proc->proc_environment); + fx_foreach(v, it) + { + fx_hashtable_item *item = NULL; + fx_value_get_object(v, &item); + const fx_value *name_v + = fx_hashtable_item_get_key(item); + const fx_value *value_v + = fx_hashtable_item_get_value(item); + + fx_value_to_string(name_v, tmp, NULL); + fx_stream_write_char(tmp, '='); + fx_value_to_string(value_v, tmp, NULL); + envp[i++] = fx_stringstream_steal(tmp); + } + + fx_iterator_unref(it); + fx_string_unref(tmp); + } + + fprintf(stderr, "argc=%zu, argv=%p\n", argc, argv); + fprintf(stderr, "envc=%zu, envp=%p\n", envc, envp); + execve(fx_string_get_cstr(proc->proc_exec_path), + (char *const *)argv, + (char *const *)envp); + exit(127); + +cleanup: + fx_iostream_unref(in_r); + fx_iostream_unref(in_w); + fx_iostream_unref(out_r); + fx_iostream_unref(out_w); + fx_iostream_unref(err_r); + fx_iostream_unref(err_w); + return status; +} + +static fx_status process_wait(struct fx_process_p *proc, int *out_result) +{ + if (proc->proc_id == 0) { + return FX_ERR_BAD_STATE; + } + + int status = 0; + + do { + int err = waitpid(proc->proc_id, &status, WUNTRACED); + if (err != 0) { + return FX_ERR_BAD_STATE; + } + } while (!WIFEXITED(status)); + + proc->proc_id = 0; + *out_result = WEXITSTATUS(status); + return FX_ERR_NOT_SUPPORTED; +} + +static fx_status process_kill(struct fx_process_p *proc) +{ + if (proc->proc_id == 0) { + return FX_ERR_BAD_STATE; + } + + int err = kill(proc->proc_id, SIGTERM); + if (err != 0) { + return FX_ERR_BAD_STATE; + } + + return FX_SUCCESS; +} + +static const fx_hashtable *process_get_environment( + const struct fx_process_p *proc) +{ + return proc->proc_environment; +} + +static fx_stream *process_get_stdin(struct fx_process_p *proc) +{ + return proc->proc_stdin; +} + +static fx_stream *process_get_stdout(struct fx_process_p *proc) +{ + return proc->proc_stdout; +} + +static fx_stream *process_get_stderr(struct fx_process_p *proc) +{ + return proc->proc_stderr; +} + +static void process_close_stdin(struct fx_process_p *proc) +{ + fx_iostream_unref(proc->proc_stdin); + proc->proc_stdin = NULL; +} + +static void process_close_stdout(struct fx_process_p *proc) +{ + fx_iostream_unref(proc->proc_stdout); + proc->proc_stdout = NULL; +} + +static void process_close_stderr(struct fx_process_p *proc) +{ + fx_iostream_unref(proc->proc_stderr); + proc->proc_stderr = NULL; +} + +/*** PUBLIC FUNCTIONS *********************************************************/ + +fx_process *fx_process_create(const fx_process_start_info *info) +{ + fx_process *out = fx_object_create(FX_DIAGNOSTICS_TYPE_PROCESS); + if (!out) { + return NULL; + } + + struct fx_process_p *proc + = fx_object_get_private(out, FX_DIAGNOSTICS_TYPE_PROCESS); + + proc->proc_exec_path = fx_string_create_from_cstr(info->proc_file_name); + if (!proc->proc_exec_path) { + fx_process_unref(out); + return NULL; + } + + if (info->proc_args_count > 0) { + proc->proc_args = fx_array_create(); + if (!proc->proc_args) { + fx_process_unref(out); + return NULL; + } + } + + for (size_t i = 0; i < info->proc_args_count; i++) { + fx_string *arg = fx_string_create_from_cstr(info->proc_args[i]); + if (!arg) { + fx_process_unref(out); + return NULL; + } + + fx_array_push_back(proc->proc_args, FX_VALUE_OBJECT(arg)); + fx_string_unref(arg); + } + + proc->proc_environment + = fx_hashtable_ref((fx_hashtable *)info->proc_environment); + proc->proc_redirect_stdin = info->proc_redirect_stdin; + proc->proc_redirect_stdout = info->proc_redirect_stdout; + proc->proc_redirect_stderr = info->proc_redirect_stderr; + + return out; +} + +fx_process *fx_process_get_self(void) +{ + if (self) { + return self; + } + + self = fx_object_create(FX_DIAGNOSTICS_TYPE_PROCESS); + if (!self) { + return NULL; + } + + struct fx_process_p *proc + = fx_object_get_private(self, FX_DIAGNOSTICS_TYPE_PROCESS); + + proc->proc_id = getpid(); + fx_hashtable *env = fx_hashtable_create(); + + for (size_t i = 0; environ[i]; i++) { + char *envstr = fx_strdup(environ[i]); + + const char *name = envstr; + char *sep = strchr(name, '='); + const char *value = NULL; + if (sep) { + *sep = '\0'; + value = sep + 1; + } + + fx_string *name_str = fx_string_create_from_cstr(name); + fx_string *value_str = fx_string_create(); + if (value) { + fx_string_append_cstr(value_str, value); + } + + fx_hashtable_put( + env, + &FX_VALUE_OBJECT(name_str), + &FX_VALUE_OBJECT(value_str)); + + fx_string_unref(name_str); + fx_string_unref(value_str); + free(envstr); + } + + proc->proc_environment = env; + + fx_register_global(self); + return self; +} + +fx_status fx_process_start(fx_process *proc) +{ + FX_CLASS_DISPATCH_STATIC_0( + FX_DIAGNOSTICS_TYPE_PROCESS, + process_start, + proc); +} + +fx_status fx_process_wait(fx_process *proc, int *out_result) +{ + FX_CLASS_DISPATCH_STATIC( + FX_DIAGNOSTICS_TYPE_PROCESS, + process_wait, + proc, + out_result); +} + +fx_status fx_process_kill(fx_process *proc) +{ + FX_CLASS_DISPATCH_STATIC_0( + FX_DIAGNOSTICS_TYPE_PROCESS, + process_kill, + proc); +} + +const fx_hashtable *fx_process_get_environment(const fx_process *proc) +{ + FX_CLASS_DISPATCH_STATIC_0( + FX_DIAGNOSTICS_TYPE_PROCESS, + process_get_environment, + proc); +} + +fx_stream *fx_process_get_stdin(fx_process *proc) +{ + FX_CLASS_DISPATCH_STATIC_0( + FX_DIAGNOSTICS_TYPE_PROCESS, + process_get_stdin, + proc); +} + +fx_stream *fx_process_get_stdout(fx_process *proc) +{ + FX_CLASS_DISPATCH_STATIC_0( + FX_DIAGNOSTICS_TYPE_PROCESS, + process_get_stdout, + proc); +} + +fx_stream *fx_process_get_stderr(fx_process *proc) +{ + FX_CLASS_DISPATCH_STATIC_0( + FX_DIAGNOSTICS_TYPE_PROCESS, + process_get_stderr, + proc); +} + +void fx_process_close_stdin(fx_process *proc) +{ + FX_CLASS_DISPATCH_STATIC_V0( + FX_DIAGNOSTICS_TYPE_PROCESS, + process_close_stdin, + proc); +} + +void fx_process_close_stdout(fx_process *proc) +{ + FX_CLASS_DISPATCH_STATIC_V0( + FX_DIAGNOSTICS_TYPE_PROCESS, + process_close_stdout, + proc); +} + +void fx_process_close_stderr(fx_process *proc) +{ + FX_CLASS_DISPATCH_STATIC_V0( + FX_DIAGNOSTICS_TYPE_PROCESS, + process_close_stderr, + proc); +} + +/*** VIRTUAL FUNCTIONS ********************************************************/ + +static void process_init(fx_object *obj, void *priv) +{ +} + +static void process_fini(fx_object *obj, void *priv) +{ + struct fx_process_p *proc = priv; + fx_array_unref(proc->proc_args); + fx_iostream_unref(proc->proc_stdin); + fx_iostream_unref(proc->proc_stdout); + fx_iostream_unref(proc->proc_stderr); + fx_string_unref(proc->proc_exec_path); + fx_hashtable_unref(proc->proc_environment); +} + +/*** ITERATOR DEFINITION + * ******************************************************/ + +static enum fx_status process_iterator_move_next(const fx_iterator *obj) +{ + return FX_ERR_NO_DATA; +} + +static const fx_value *process_iterator_get_value(const fx_iterator *obj) +{ + return NULL; +} + +/*** CLASS DEFINITION + * *********************************************************/ + +FX_TYPE_CLASS_BEGIN(fx_process) + FX_TYPE_VTABLE_INTERFACE_BEGIN(fx_object, FX_TYPE_OBJECT) + FX_INTERFACE_ENTRY(to_string) = NULL; + FX_TYPE_VTABLE_INTERFACE_END(fx_object, FX_TYPE_OBJECT) +FX_TYPE_CLASS_END(fx_process) + +FX_TYPE_DEFINITION_BEGIN(fx_process) + FX_TYPE_ID(0x7334594f, 0xa1c3, 0x4715, 0xab69, 0x68a709e1b64b); + FX_TYPE_NAME("fx.diagnostics.process"); + FX_TYPE_CLASS(fx_process_class); + FX_TYPE_INSTANCE_PRIVATE(struct fx_process_p); + FX_TYPE_INSTANCE_INIT(process_init); + FX_TYPE_INSTANCE_FINI(process_fini); +FX_TYPE_DEFINITION_END(fx_process) + +FX_TYPE_CLASS_BEGIN(fx_process_iterator) + FX_TYPE_VTABLE_INTERFACE_BEGIN(fx_object, FX_TYPE_OBJECT) + FX_INTERFACE_ENTRY(to_string) = NULL; + FX_TYPE_VTABLE_INTERFACE_END(fx_object, FX_TYPE_OBJECT) + + FX_TYPE_VTABLE_INTERFACE_BEGIN(fx_iterator, FX_TYPE_ITERATOR) + FX_INTERFACE_ENTRY(it_move_next) = process_iterator_move_next; + FX_INTERFACE_ENTRY(it_get_value) = process_iterator_get_value; + FX_TYPE_VTABLE_INTERFACE_END(fx_iterator, FX_TYPE_ITERATOR) +FX_TYPE_CLASS_END(fx_process_iterator) + +FX_TYPE_DEFINITION_BEGIN(fx_process_iterator) + FX_TYPE_ID(0x67eb13b6, 0x25d1, 0x424a, 0xb136, 0x871a07829089); + FX_TYPE_NAME("fx.diagnostics.process.iterator"); + FX_TYPE_EXTENDS(FX_TYPE_ITERATOR); + FX_TYPE_CLASS(fx_process_iterator_class); + FX_TYPE_INSTANCE_PRIVATE(struct fx_process_iterator_p); +FX_TYPE_DEFINITION_END(fx_process_iterator) diff --git a/fx.diagnostics/test/redirect.c b/fx.diagnostics/test/redirect.c index 5681f00..2885e73 100644 --- a/fx.diagnostics/test/redirect.c +++ b/fx.diagnostics/test/redirect.c @@ -9,11 +9,21 @@ int main(int argc, const char **argv) const char *exec = argv[1]; const char *msg = argv[2]; + const char *args[] = { + "hello", + "world", + }; + + fx_process *self = fx_process_get_self(); + const fx_hashtable *env = fx_process_get_environment(self); + fx_process_start_info info = { - .proc_args = NULL, - .proc_args_count = 0, + .proc_args = args, + .proc_args_count = sizeof args / sizeof args[0], + .proc_redirect_stdout = true, .proc_redirect_stdin = true, .proc_file_name = exec, + .proc_environment = env, }; fx_process *process = fx_process_create(&info); @@ -30,11 +40,20 @@ int main(int argc, const char **argv) return -1; } -#if 1 + printf("process stdin: %s\n", msg); + fx_stream *in = fx_process_get_stdin(process); - fx_stream_write_cstr(in, "Hello, world!\n", NULL); + fx_stream_write_cstr(in, msg, NULL); + fx_stream_write_char(in, '\n'); fx_process_close_stdin(process); -#endif + + fx_stream *out = fx_process_get_stdout(process); + printf("process stdout:\n"); + printf("---------------------\n"); + + while (FX_OK(fx_stream_read_line_s(out, fx_stdout))) { } + + printf("---------------------\n"); int result; fx_process_wait(process, &result);