code / tomo-commands

Lines334 C249 Tomo74 Markdown11
(295 lines)
1 // Logic for running system commands
3 #pragma once
5 #include <errno.h>
6 #include <gc.h>
7 #include <poll.h>
8 #include <pthread.h>
9 #include <signal.h>
10 #include <spawn.h>
11 #include <stdbool.h>
12 #include <stdint.h>
13 #include <string.h>
14 #include <sys/param.h>
15 #include <sys/wait.h>
16 #include <unistd.h>
18 // This is a workaround fix for an issue on some systems that don't have `__GLIBC__` defined
19 // and run into problems with <unistr.h>
21 #ifndef __GLIBC__
22 #define __GLIBC__ 2
23 #include <unistr.h> // IWYU pragma: export
24 #undef __GLIBC__
25 #else
26 #include <unistr.h> // IWYU pragma: export
27 #endif
29 #define READ_END 0
30 #define WRITE_END 1
32 static void xpipe(int fd[2]) {
33 if (pipe(fd) != 0) fail("Failed to create pipe: ", strerror(errno));
36 int run_command(Text_t exe, List_t arg_list, Table_t env_table, OptionalList_t input_bytes, List_t *output_bytes,
37 List_t *error_bytes) {
38 pthread_testcancel();
40 struct sigaction sa = {.sa_handler = SIG_IGN}, oldint, oldquit;
41 sigaction(SIGINT, &sa, &oldint);
42 sigaction(SIGQUIT, &sa, &oldquit);
43 sigaddset(&sa.sa_mask, SIGCHLD);
44 sigset_t old, reset;
45 sigprocmask(SIG_BLOCK, &sa.sa_mask, &old);
46 sigemptyset(&reset);
47 if (oldint.sa_handler != SIG_IGN) sigaddset(&reset, SIGINT);
48 if (oldquit.sa_handler != SIG_IGN) sigaddset(&reset, SIGQUIT);
49 posix_spawnattr_t attr;
50 posix_spawnattr_init(&attr);
51 posix_spawnattr_setsigmask(&attr, &old);
52 posix_spawnattr_setsigdefault(&attr, &reset);
53 posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK);
55 int child_inpipe[2], child_outpipe[2], child_errpipe[2];
56 if (input_bytes.length >= 0) xpipe(child_inpipe);
57 if (output_bytes) xpipe(child_outpipe);
58 if (error_bytes) xpipe(child_errpipe);
60 posix_spawn_file_actions_t actions;
61 posix_spawn_file_actions_init(&actions);
62 if (input_bytes.length >= 0) {
63 posix_spawn_file_actions_adddup2(&actions, child_inpipe[READ_END], STDIN_FILENO);
64 posix_spawn_file_actions_addclose(&actions, child_inpipe[WRITE_END]);
66 if (output_bytes) {
67 posix_spawn_file_actions_adddup2(&actions, child_outpipe[WRITE_END], STDOUT_FILENO);
68 posix_spawn_file_actions_addclose(&actions, child_outpipe[READ_END]);
70 if (error_bytes) {
71 posix_spawn_file_actions_adddup2(&actions, child_errpipe[WRITE_END], STDERR_FILENO);
72 posix_spawn_file_actions_addclose(&actions, child_errpipe[READ_END]);
75 const char *exe_str = Text$as_c_string(exe);
77 List_t arg_strs = {};
78 List$insert_value(&arg_strs, exe_str, I(0), sizeof(char *));
79 for (int64_t i = 0; i < arg_list.length; i++)
80 List$insert_value(&arg_strs, Text$as_c_string(*(Text_t *)(arg_list.data + i * arg_list.stride)), I(0),
81 sizeof(char *));
82 List$insert_value(&arg_strs, NULL, I(0), sizeof(char *));
83 char **args = arg_strs.data;
85 extern char **environ;
86 char **env = environ;
87 if (env_table.entries.length > 0) {
88 List_t env_list = {}; // List of const char*
89 for (char **e = environ; *e; e++)
90 List$insert(&env_list, e, I(0), sizeof(char *));
92 for (int64_t i = 0; i < env_table.entries.length; i++) {
93 struct {
94 Text_t key, value;
95 } *entry = env_table.entries.data + env_table.entries.stride * i;
96 const char *env_entry = String(entry->key, "=", entry->value);
97 List$insert(&env_list, &env_entry, I(0), sizeof(char *));
99 List$insert_value(&env_list, NULL, I(0), sizeof(char *));
100 assert(env_list.stride == sizeof(char *));
101 env = env_list.data;
104 pid_t pid;
105 int ret = exe_str[0] == '/' ? posix_spawn(&pid, exe_str, &actions, &attr, args, env)
106 : posix_spawnp(&pid, exe_str, &actions, &attr, args, env);
107 if (ret != 0) return -1;
109 posix_spawnattr_destroy(&attr);
110 posix_spawn_file_actions_destroy(&actions);
112 if (input_bytes.length >= 0) close(child_inpipe[READ_END]);
113 if (output_bytes) close(child_outpipe[WRITE_END]);
114 if (error_bytes) close(child_errpipe[WRITE_END]);
116 struct pollfd pollfds[3] = {};
117 if (input_bytes.length >= 0) pollfds[0] = (struct pollfd){.fd = child_inpipe[WRITE_END], .events = POLLOUT};
118 if (output_bytes) pollfds[1] = (struct pollfd){.fd = child_outpipe[WRITE_END], .events = POLLIN};
119 if (error_bytes) pollfds[2] = (struct pollfd){.fd = child_errpipe[WRITE_END], .events = POLLIN};
121 if (input_bytes.length > 0 && input_bytes.stride != 1) List$compact(&input_bytes, sizeof(char));
122 if (output_bytes) *output_bytes = (List_t){.atomic = 1, .stride = 1, .length = 0};
123 if (error_bytes) *error_bytes = (List_t){.atomic = 1, .stride = 1, .length = 0};
125 while (input_bytes.length > 0 || output_bytes || error_bytes) {
126 (void)poll(pollfds, sizeof(pollfds) / sizeof(pollfds[0]), -1); // Wait for data or readiness
127 bool did_something = false;
128 if (input_bytes.length >= 0 && pollfds[0].revents) {
129 if (input_bytes.length > 0) {
130 ssize_t written = write(child_inpipe[WRITE_END], input_bytes.data, (size_t)input_bytes.length);
131 if (written > 0) {
132 input_bytes.data += written;
133 input_bytes.length -= (int64_t)written;
134 did_something = true;
135 } else if (written < 0) {
136 close(child_inpipe[WRITE_END]);
137 pollfds[0].events = 0;
140 if (input_bytes.length <= 0) {
141 close(child_inpipe[WRITE_END]);
142 pollfds[0].events = 0;
145 char buf[256];
146 if (output_bytes && pollfds[1].revents) {
147 ssize_t n = read(child_outpipe[READ_END], buf, sizeof(buf));
148 did_something = did_something || (n > 0);
149 if (n <= 0) {
150 close(child_outpipe[READ_END]);
151 pollfds[1].events = 0;
152 } else if (n > 0) {
153 if (output_bytes->free < n) {
154 output_bytes->data = GC_REALLOC(output_bytes->data, (size_t)(output_bytes->length + n));
155 output_bytes->free = 0;
157 memcpy(output_bytes->data + output_bytes->length, buf, (size_t)n);
158 output_bytes->length += n;
161 if (error_bytes && pollfds[2].revents) {
162 ssize_t n = read(child_errpipe[READ_END], buf, sizeof(buf));
163 did_something = did_something || (n > 0);
164 if (n <= 0) {
165 close(child_errpipe[READ_END]);
166 pollfds[2].events = 0;
167 } else if (n > 0) {
168 if (error_bytes->free < n) {
169 error_bytes->data = GC_REALLOC(error_bytes->data, (size_t)(error_bytes->length + n));
170 error_bytes->free = 0;
172 memcpy(error_bytes->data + error_bytes->length, buf, (size_t)n);
173 error_bytes->length += n;
176 if (!did_something) break;
179 int status = 0;
180 if (ret == 0) {
181 while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {
182 if (WIFEXITED(status) || WIFSIGNALED(status)) break;
183 else if (WIFSTOPPED(status)) kill(pid, SIGCONT);
187 if (input_bytes.length >= 0) close(child_inpipe[WRITE_END]);
188 if (output_bytes) close(child_outpipe[READ_END]);
189 if (error_bytes) close(child_errpipe[READ_END]);
191 sigaction(SIGINT, &oldint, NULL);
192 sigaction(SIGQUIT, &oldquit, NULL);
193 sigprocmask(SIG_SETMASK, &old, NULL);
195 if (ret) errno = ret;
196 return status;
199 typedef struct {
200 pid_t pid;
201 FILE *out;
202 } child_info_t;
204 static void _line_reader_cleanup(child_info_t *child) {
205 if (child && child->out) {
206 fclose(child->out);
207 child->out = NULL;
209 if (child->pid) {
210 kill(child->pid, SIGTERM);
211 child->pid = 0;
215 static Text_t _next_line(child_info_t *child) {
216 if (!child || !child->out) return NONE_TEXT;
218 char *line = NULL;
219 size_t size = 0;
220 ssize_t len = getline(&line, &size, child->out);
221 if (len <= 0) {
222 _line_reader_cleanup(child);
223 return NONE_TEXT;
226 while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n'))
227 --len;
229 if (u8_check((uint8_t *)line, (size_t)len) != NULL) fail("Invalid UTF8!");
231 Text_t line_text = Text$from_strn(line, len);
232 free(line);
233 return line_text;
236 OptionalClosure_t command_by_line(Text_t exe, List_t arg_list, Table_t env_table) {
237 posix_spawnattr_t attr;
238 posix_spawnattr_init(&attr);
240 int child_outpipe[2];
241 xpipe(child_outpipe);
243 posix_spawn_file_actions_t actions;
244 posix_spawn_file_actions_init(&actions);
245 posix_spawn_file_actions_adddup2(&actions, child_outpipe[WRITE_END], STDOUT_FILENO);
246 posix_spawn_file_actions_addclose(&actions, child_outpipe[READ_END]);
248 const char *exe_str = Text$as_c_string(exe);
250 List_t arg_strs = {};
251 List$insert_value(&arg_strs, exe_str, I(0), sizeof(char *));
252 for (int64_t i = 0; i < arg_list.length; i++)
253 List$insert_value(&arg_strs, Text$as_c_string(*(Text_t *)(arg_list.data + i * arg_list.stride)), I(0),
254 sizeof(char *));
255 List$insert_value(&arg_strs, NULL, I(0), sizeof(char *));
256 char **args = arg_strs.data;
258 extern char **environ;
259 char **env = environ;
260 if (env_table.entries.length > 0) {
261 List_t env_list = {}; // List of const char*
262 for (char **e = environ; *e; e++)
263 List$insert(&env_list, e, I(0), sizeof(char *));
265 for (int64_t i = 0; i < env_table.entries.length; i++) {
266 struct {
267 Text_t key, value;
268 } *entry = env_table.entries.data + env_table.entries.stride * i;
269 const char *env_entry = String(entry->key, "=", entry->value);
270 List$insert(&env_list, &env_entry, I(0), sizeof(char *));
272 List$insert_value(&env_list, NULL, I(0), sizeof(char *));
273 assert(env_list.stride == sizeof(char *));
274 env = env_list.data;
277 pid_t pid;
278 int ret = exe_str[0] == '/' ? posix_spawn(&pid, exe_str, &actions, &attr, args, env)
279 : posix_spawnp(&pid, exe_str, &actions, &attr, args, env);
280 if (ret != 0) return NONE_CLOSURE;
282 posix_spawnattr_destroy(&attr);
283 posix_spawn_file_actions_destroy(&actions);
285 close(child_outpipe[WRITE_END]);
287 child_info_t *child_info = GC_MALLOC(sizeof(child_info_t));
288 child_info->out = fdopen(child_outpipe[READ_END], "r");
289 child_info->pid = pid;
290 GC_register_finalizer(child_info, (void *)_line_reader_cleanup, NULL, NULL, NULL);
291 return (Closure_t){.fn = (void *)_next_line, .userdata = child_info};
294 #undef READ_END
295 #undef WRITE_END