diff options
Diffstat (limited to 'lib/commands')
| -rw-r--r-- | lib/commands/CHANGES.md | 5 | ||||
| -rw-r--r-- | lib/commands/README.md | 5 | ||||
| -rw-r--r-- | lib/commands/commands.c | 295 | ||||
| -rw-r--r-- | lib/commands/commands.tm | 90 |
4 files changed, 0 insertions, 395 deletions
diff --git a/lib/commands/CHANGES.md b/lib/commands/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/lib/commands/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/lib/commands/README.md b/lib/commands/README.md deleted file mode 100644 index 040f4bd5..00000000 --- a/lib/commands/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Commands - -This module provides a way to run executable programs and get their output. You -can also feed in text to the programs' `stdin`. Think of it as `popen()` on -steroids. diff --git a/lib/commands/commands.c b/lib/commands/commands.c deleted file mode 100644 index 110ae696..00000000 --- a/lib/commands/commands.c +++ /dev/null @@ -1,295 +0,0 @@ -// Logic for running system commands - -#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 <string.h> -#include <sys/param.h> -#include <sys/wait.h> -#include <unistd.h> - -// This is a workaround fix for an issue on some systems that don't have `__GLIBC__` defined -// and run into problems with <unistr.h> - -#ifndef __GLIBC__ -#define __GLIBC__ 2 -#include <unistr.h> // IWYU pragma: export -#undef __GLIBC__ -#else -#include <unistr.h> // IWYU pragma: export -#endif - -#define READ_END 0 -#define WRITE_END 1 - -static void xpipe(int fd[2]) { - if (pipe(fd) != 0) fail("Failed to create pipe: ", strerror(errno)); -} - -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) xpipe(child_inpipe); - if (output_bytes) xpipe(child_outpipe); - if (error_bytes) xpipe(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]; - xpipe(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 diff --git a/lib/commands/commands.tm b/lib/commands/commands.tm deleted file mode 100644 index 8a131042..00000000 --- a/lib/commands/commands.tm +++ /dev/null @@ -1,90 +0,0 @@ -# Functions for running system commands - -use ./commands.c - -extern run_command : func(exe:Text, args:[Text], env:{Text=Text}, input:[Byte]?, output:&[Byte]?, error:&[Byte]? -> Int32) -extern command_by_line : func(exe:Text, args:[Text], env:{Text=Text} -> func(->Text?)?) - -enum ExitType(Exited(status:Int32), Signaled(signal:Int32), Failed) - func succeeded(e:ExitType -> Bool) - when e is Exited(status) return (status == 0) - else return no - - func or_fail(e:ExitType, message:Text?=none) - if not e.succeeded() - fail(message or "Program failed: $e") - -struct ProgramResult(output:[Byte], errors:[Byte], exit_type:ExitType) - func or_fail(r:ProgramResult, message:Text?=none -> ProgramResult) - when r.exit_type is Exited(status) - if status != 0 - fail(message or "Program failed: $r") - else fail(message or "Program failed: $r") - return r - - func output_text(r:ProgramResult, trim_newline=yes -> Text?) - when r.exit_type is Exited(status) - if status == 0 - if text := Text.from_bytes(r.output) - if trim_newline - text = text.without_suffix("\n") - return text - else return none - return none - - func error_text(r:ProgramResult -> Text?) - when r.exit_type is Exited(status) - if status == 0 - return Text.from_bytes(r.errors) - else return none - return none - - func succeeded(r:ProgramResult -> Bool) - when r.exit_type is Exited(status) - return (status == 0) - else - return no - -struct Command(command:Text, args:[Text]=[], env:{Text=Text}={}) - func from_path(path:Path, args:[Text]=[], env:{Text=Text}={} -> Command) - return Command(Text(path), args, env) - - func result(command:Command, input="", input_bytes:[Byte]=[] -> ProgramResult) - if input.length > 0 - (&input_bytes).insert_all(input.bytes()) - - output : [Byte] - errors : [Byte] - status := run_command(command.command, command.args, command.env, input_bytes, &output, &errors) - - if C_code:Bool(WIFEXITED(@status)) - return ProgramResult(output, errors, ExitType.Exited(C_code:Int32(WEXITSTATUS(@status)))) - - if C_code:Bool(WIFSIGNALED(@status)) - return ProgramResult(output, errors, ExitType.Signaled(C_code:Int32(WTERMSIG(@status)))) - - return ProgramResult(output, errors, ExitType.Failed) - - func run(command:Command, -> ExitType) - status := run_command(command.command, command.args, command.env, none, none, none) - - if C_code:Bool(WIFEXITED(@status)) - return ExitType.Exited(C_code:Int32(WEXITSTATUS(@status))) - - if C_code:Bool(WIFSIGNALED(@status)) - return ExitType.Signaled(C_code:Int32(WTERMSIG(@status))) - - return ExitType.Failed - - func get_output(command:Command, input="", trim_newline=yes -> Text?) - return command.result(input=input).output_text(trim_newline=trim_newline) - - func get_output_bytes(command:Command, input="", input_bytes:[Byte]=[] -> [Byte]?) - result := command.result(input=input, input_bytes=input_bytes) - when result.exit_type is Exited(status) - if status == 0 return result.output - return none - else return none - - func by_line(command:Command -> func(->Text?)?) - return command_by_line(command.command, command.args, command.env) |
