aboutsummaryrefslogtreecommitdiff
path: root/lib/commands/commands.c
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-04-07 18:14:20 -0400
committerBruce Hill <bruce@bruce-hill.com>2025-04-07 18:14:20 -0400
commit3efd7d9cfbd330ebb45f39648ee96a3e429a06f9 (patch)
tree29acb9e2b2370bd155fed24fba79d01b553a24f3 /lib/commands/commands.c
parent15fabfb9be3e3620e4b96983a49017116cea40e2 (diff)
Move core libraries into their own folder
Diffstat (limited to 'lib/commands/commands.c')
-rw-r--r--lib/commands/commands.c285
1 files changed, 285 insertions, 0 deletions
diff --git a/lib/commands/commands.c b/lib/commands/commands.c
new file mode 100644
index 00000000..973fc0c5
--- /dev/null
+++ b/lib/commands/commands.c
@@ -0,0 +1,285 @@
+#pragma once
+
+#include <errno.h>
+#include <gc.h>
+#include <poll.h>
+#include <pthread.h>
+#include <signal.h>
+#include <spawn.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <unistr.h>
+
+#define READ_END 0
+#define WRITE_END 1
+int run_command(Text_t exe, List_t arg_list, Table_t env_table,
+ OptionalList_t input_bytes, List_t *output_bytes, List_t *error_bytes)
+{
+ pthread_testcancel();
+
+ struct sigaction sa = { .sa_handler = SIG_IGN }, oldint, oldquit;
+ sigaction(SIGINT, &sa, &oldint);
+ sigaction(SIGQUIT, &sa, &oldquit);
+ sigaddset(&sa.sa_mask, SIGCHLD);
+ sigset_t old, reset;
+ sigprocmask(SIG_BLOCK, &sa.sa_mask, &old);
+ sigemptyset(&reset);
+ if (oldint.sa_handler != SIG_IGN) sigaddset(&reset, SIGINT);
+ if (oldquit.sa_handler != SIG_IGN) sigaddset(&reset, SIGQUIT);
+ posix_spawnattr_t attr;
+ posix_spawnattr_init(&attr);
+ posix_spawnattr_setsigmask(&attr, &old);
+ posix_spawnattr_setsigdefault(&attr, &reset);
+ posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF|POSIX_SPAWN_SETSIGMASK);
+
+ int child_inpipe[2], child_outpipe[2], child_errpipe[2];
+ if (input_bytes.length >= 0) pipe(child_inpipe);
+ if (output_bytes) pipe(child_outpipe);
+ if (error_bytes) pipe(child_errpipe);
+
+ posix_spawn_file_actions_t actions;
+ posix_spawn_file_actions_init(&actions);
+ if (input_bytes.length >= 0) {
+ posix_spawn_file_actions_adddup2(&actions, child_inpipe[READ_END], STDIN_FILENO);
+ posix_spawn_file_actions_addclose(&actions, child_inpipe[WRITE_END]);
+ }
+ if (output_bytes) {
+ posix_spawn_file_actions_adddup2(&actions, child_outpipe[WRITE_END], STDOUT_FILENO);
+ posix_spawn_file_actions_addclose(&actions, child_outpipe[READ_END]);
+ }
+ if (error_bytes) {
+ posix_spawn_file_actions_adddup2(&actions, child_errpipe[WRITE_END], STDERR_FILENO);
+ posix_spawn_file_actions_addclose(&actions, child_errpipe[READ_END]);
+ }
+
+ const char *exe_str = Text$as_c_string(exe);
+
+ List_t arg_strs = {};
+ List$insert_value(&arg_strs, exe_str, I(0), sizeof(char*));
+ for (int64_t i = 0; i < arg_list.length; i++)
+ List$insert_value(&arg_strs, Text$as_c_string(*(Text_t*)(arg_list.data + i*arg_list.stride)), I(0), sizeof(char*));
+ List$insert_value(&arg_strs, NULL, I(0), sizeof(char*));
+ char **args = arg_strs.data;
+
+ extern char **environ;
+ char **env = environ;
+ if (env_table.entries.length > 0) {
+ List_t env_list = {}; // List of const char*
+ for (char **e = environ; *e; e++)
+ List$insert(&env_list, e, I(0), sizeof(char*));
+
+ for (int64_t i = 0; i < env_table.entries.length; i++) {
+ struct { Text_t key, value; } *entry = env_table.entries.data + env_table.entries.stride*i;
+ const char *env_entry = String(entry->key, "=", entry->value);
+ List$insert(&env_list, &env_entry, I(0), sizeof(char*));
+ }
+ List$insert_value(&env_list, NULL, I(0), sizeof(char*));
+ assert(env_list.stride == sizeof(char*));
+ env = env_list.data;
+ }
+
+ pid_t pid;
+ int ret = exe_str[0] == '/' ?
+ posix_spawn(&pid, exe_str, &actions, &attr, args, env)
+ : posix_spawnp(&pid, exe_str, &actions, &attr, args, env);
+ if (ret != 0)
+ return -1;
+
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&actions);
+
+ if (input_bytes.length >= 0) close(child_inpipe[READ_END]);
+ if (output_bytes) close(child_outpipe[WRITE_END]);
+ if (error_bytes) close(child_errpipe[WRITE_END]);
+
+ struct pollfd pollfds[3] = {};
+ if (input_bytes.length >= 0) pollfds[0] = (struct pollfd){.fd=child_inpipe[WRITE_END], .events=POLLOUT};
+ if (output_bytes) pollfds[1] = (struct pollfd){.fd=child_outpipe[WRITE_END], .events=POLLIN};
+ if (error_bytes) pollfds[2] = (struct pollfd){.fd=child_errpipe[WRITE_END], .events=POLLIN};
+
+ if (input_bytes.length > 0 && input_bytes.stride != 1)
+ List$compact(&input_bytes, sizeof(char));
+ if (output_bytes)
+ *output_bytes = (List_t){.atomic=1, .stride=1, .length=0};
+ if (error_bytes)
+ *error_bytes = (List_t){.atomic=1, .stride=1, .length=0};
+
+ while (input_bytes.length > 0 || output_bytes || error_bytes) {
+ (void)poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), -1); // Wait for data or readiness
+ bool did_something = false;
+ if (input_bytes.length >= 0 && pollfds[0].revents) {
+ if (input_bytes.length > 0) {
+ ssize_t written = write(child_inpipe[WRITE_END], input_bytes.data, (size_t)input_bytes.length);
+ if (written > 0) {
+ input_bytes.data += written;
+ input_bytes.length -= (int64_t)written;
+ did_something = true;
+ } else if (written < 0) {
+ close(child_inpipe[WRITE_END]);
+ pollfds[0].events = 0;
+ }
+ }
+ if (input_bytes.length <= 0) {
+ close(child_inpipe[WRITE_END]);
+ pollfds[0].events = 0;
+ }
+ }
+ char buf[256];
+ if (output_bytes && pollfds[1].revents) {
+ ssize_t n = read(child_outpipe[READ_END], buf, sizeof(buf));
+ did_something = did_something || (n > 0);
+ if (n <= 0) {
+ close(child_outpipe[READ_END]);
+ pollfds[1].events = 0;
+ } else if (n > 0) {
+ if (output_bytes->free < n) {
+ output_bytes->data = GC_REALLOC(output_bytes->data, (size_t)(output_bytes->length + n));
+ output_bytes->free = 0;
+ }
+ memcpy(output_bytes->data + output_bytes->length, buf, (size_t)n);
+ output_bytes->length += n;
+ }
+ }
+ if (error_bytes && pollfds[2].revents) {
+ ssize_t n = read(child_errpipe[READ_END], buf, sizeof(buf));
+ did_something = did_something || (n > 0);
+ if (n <= 0) {
+ close(child_errpipe[READ_END]);
+ pollfds[2].events = 0;
+ } else if (n > 0) {
+ if (error_bytes->free < n) {
+ error_bytes->data = GC_REALLOC(error_bytes->data, (size_t)(error_bytes->length + n));
+ error_bytes->free = 0;
+ }
+ memcpy(error_bytes->data + error_bytes->length, buf, (size_t)n);
+ error_bytes->length += n;
+ }
+ }
+ if (!did_something) break;
+ }
+
+ int status = 0;
+ if (ret == 0) {
+ while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {
+ if (WIFEXITED(status) || WIFSIGNALED(status))
+ break;
+ else if (WIFSTOPPED(status))
+ kill(pid, SIGCONT);
+ }
+ }
+
+ if (input_bytes.length >= 0) close(child_inpipe[WRITE_END]);
+ if (output_bytes) close(child_outpipe[READ_END]);
+ if (error_bytes) close(child_errpipe[READ_END]);
+
+ sigaction(SIGINT, &oldint, NULL);
+ sigaction(SIGQUIT, &oldquit, NULL);
+ sigprocmask(SIG_SETMASK, &old, NULL);
+
+ if (ret) errno = ret;
+ return status;
+}
+
+typedef struct {
+ pid_t pid;
+ FILE *out;
+} child_info_t;
+
+static void _line_reader_cleanup(child_info_t *child)
+{
+ if (child && child->out) {
+ fclose(child->out);
+ child->out = NULL;
+ }
+ if (child->pid) {
+ kill(child->pid, SIGTERM);
+ child->pid = 0;
+ }
+}
+
+static Text_t _next_line(child_info_t *child)
+{
+ if (!child || !child->out) return NONE_TEXT;
+
+ char *line = NULL;
+ size_t size = 0;
+ ssize_t len = getline(&line, &size, child->out);
+ if (len <= 0) {
+ _line_reader_cleanup(child);
+ return NONE_TEXT;
+ }
+
+ while (len > 0 && (line[len-1] == '\r' || line[len-1] == '\n'))
+ --len;
+
+ if (u8_check((uint8_t*)line, (size_t)len) != NULL)
+ fail("Invalid UTF8!");
+
+ Text_t line_text = Text$from_strn(line, len);
+ free(line);
+ return line_text;
+}
+
+OptionalClosure_t command_by_line(Text_t exe, List_t arg_list, Table_t env_table)
+{
+ posix_spawnattr_t attr;
+ posix_spawnattr_init(&attr);
+
+ int child_outpipe[2];
+ pipe(child_outpipe);
+
+ posix_spawn_file_actions_t actions;
+ posix_spawn_file_actions_init(&actions);
+ posix_spawn_file_actions_adddup2(&actions, child_outpipe[WRITE_END], STDOUT_FILENO);
+ posix_spawn_file_actions_addclose(&actions, child_outpipe[READ_END]);
+
+ const char *exe_str = Text$as_c_string(exe);
+
+ List_t arg_strs = {};
+ List$insert_value(&arg_strs, exe_str, I(0), sizeof(char*));
+ for (int64_t i = 0; i < arg_list.length; i++)
+ List$insert_value(&arg_strs, Text$as_c_string(*(Text_t*)(arg_list.data + i*arg_list.stride)), I(0), sizeof(char*));
+ List$insert_value(&arg_strs, NULL, I(0), sizeof(char*));
+ char **args = arg_strs.data;
+
+ extern char **environ;
+ char **env = environ;
+ if (env_table.entries.length > 0) {
+ List_t env_list = {}; // List of const char*
+ for (char **e = environ; *e; e++)
+ List$insert(&env_list, e, I(0), sizeof(char*));
+
+ for (int64_t i = 0; i < env_table.entries.length; i++) {
+ struct { Text_t key, value; } *entry = env_table.entries.data + env_table.entries.stride*i;
+ const char *env_entry = String(entry->key, "=", entry->value);
+ List$insert(&env_list, &env_entry, I(0), sizeof(char*));
+ }
+ List$insert_value(&env_list, NULL, I(0), sizeof(char*));
+ assert(env_list.stride == sizeof(char*));
+ env = env_list.data;
+ }
+
+ pid_t pid;
+ int ret = exe_str[0] == '/' ?
+ posix_spawn(&pid, exe_str, &actions, &attr, args, env)
+ : posix_spawnp(&pid, exe_str, &actions, &attr, args, env);
+ if (ret != 0)
+ return NONE_CLOSURE;
+
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&actions);
+
+ close(child_outpipe[WRITE_END]);
+
+ child_info_t *child_info = GC_MALLOC(sizeof(child_info_t));
+ child_info->out = fdopen(child_outpipe[READ_END], "r");
+ child_info->pid = pid;
+ GC_register_finalizer(child_info, (void*)_line_reader_cleanup, NULL, NULL, NULL);
+ return (Closure_t){.fn=(void*)_next_line, .userdata=child_info};
+}
+
+#undef READ_END
+#undef WRITE_END