#define _POSIX_C_SOURCE 200809L #include #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 bool exec_name_is_path(const char *s) { fx_wchar c; while (*s) { c = fx_wchar_utf8_codepoint_decode(s); if (c == FX_WCHAR_INVALID) { break; } if (c == '/') { return true; } s += fx_wchar_utf8_codepoint_stride(s); } return false; } const fx_string *get_path(void) { const char *path_env = NULL; fx_process *self = fx_process_get_self(); if (!self) { goto fallback; } const fx_hashtable *env = fx_process_get_environment(self); if (!env) { goto fallback; } const fx_value *path_v = fx_hashtable_get(env, &FX_CSTR("PATH")); if (!path_v) { goto fallback; } fx_string *result = NULL; fx_value_get_object(path_v, &result); return result; fallback: path_env = getenv("PATH"); if (!path_env) { return NULL; } return fx_string_create_from_cstr(path_env); } static fx_status find_executable(fx_string *exec_path) { const char *exec_path_cstr = fx_string_get_cstr(exec_path); const fx_string *path_env = get_path(); if (!path_env) { return FX_ERR_NO_ENTRY; } const char *delim[] = {":"}; size_t nr_delim = sizeof delim / sizeof delim[0]; fx_path *path = NULL; fx_iterator *it = fx_string_tokenise( path_env, delim, nr_delim, FX_STRING_TOK_F_NORMAL); fx_status status = FX_ERR_NO_ENTRY; fx_foreach(tok, it) { path = fx_path_join_list(2, tok, &FX_VALUE_OBJECT(exec_path)); if (fx_path_is_file(path)) { fx_string_clear(exec_path); fx_string_append_cstr( exec_path, fx_path_get_cstr(path)); status = FX_SUCCESS; break; } } fx_iterator_unref(it); fx_path_unref(path); return status; } 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; bool name_is_path = exec_name_is_path(fx_string_get_cstr(proc->proc_exec_path)); if (!name_is_path) { status = find_executable(proc->proc_exec_path); } if (!FX_OK(status)) { return status; } 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); } 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; } proc->proc_args = fx_array_ref((fx_array *)info->proc_args); 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)