diff --git a/examples/commands/commands.c b/examples/commands/commands.c index f07b5c3..d7bdeee 100644 --- a/examples/commands/commands.c +++ b/examples/commands/commands.c @@ -11,10 +11,11 @@ #include #include #include +#include #define READ_END 0 #define WRITE_END 1 -public int run_command(Text_t exe, Array_t arg_array, Table_t env_table, +int run_command(Text_t exe, Array_t arg_array, Table_t env_table, Array_t input_bytes, Array_t *output_bytes, Array_t *error_bytes) { pthread_testcancel(); @@ -173,5 +174,103 @@ public int run_command(Text_t exe, Array_t arg_array, Table_t env_table, 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, Array_t arg_array, 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); + + Array_t arg_strs = {}; + Array$insert_value(&arg_strs, exe_str, I(0), sizeof(char*)); + for (int64_t i = 0; i < arg_array.length; i++) + Array$insert_value(&arg_strs, Text$as_c_string(*(Text_t*)(arg_array.data + i*arg_array.stride)), I(0), sizeof(char*)); + Array$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) { + Array_t env_array = {}; // Array of const char* + for (char **e = environ; *e; e++) + Array$insert(&env_array, 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 = heap_strf("%k=%k", &entry->key, &entry->value); + Array$insert(&env_array, &env_entry, I(0), sizeof(char*)); + } + Array$insert_value(&env_array, NULL, I(0), sizeof(char*)); + assert(env_array.stride == sizeof(char*)); + env = env_array.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/examples/commands/commands.tm b/examples/commands/commands.tm index a0e7390..c70d859 100644 --- a/examples/commands/commands.tm +++ b/examples/commands/commands.tm @@ -1,8 +1,10 @@ # Functions for running system commands use ./commands.c +use libunistring.so 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) @@ -61,6 +63,9 @@ struct Command(command:Text, args=[:Text], env={:Text,Text}): return none else: return none + func by_line(command:Command -> func(->Text?)?): + return command_by_line(command.command, command.args, command.env) + func main(command:Text, args:[Text], input=""): cmd := Command(command, args) say(cmd:get_output(input=input)!) diff --git a/examples/shell/shell.tm b/examples/shell/shell.tm index cf5fcd2..f1e2308 100644 --- a/examples/shell/shell.tm +++ b/examples/shell/shell.tm @@ -33,3 +33,9 @@ lang Shell: func get_output_bytes(shell:Shell, input="", input_bytes=[:Byte] -> [Byte]?): return shell:command():get_output_bytes(input=input, input_bytes=input_bytes) + func by_line(shell:Shell -> func(->Text?)?): + return shell:command():by_line() + +func main(command:Shell): + for line in command:by_line()!: + >> line