1 | #include "api.h" |
2 | |
3 | #include <string.h> |
4 | #include <stdbool.h> |
5 | #include <stdint.h> |
6 | #include <stdlib.h> |
7 | #include <SDL.h> |
8 | #include <SDL_thread.h> |
9 | #include <assert.h> |
10 | |
11 | #if _WIN32 |
12 | // https://stackoverflow.com/questions/60645/overlapped-i-o-on-anonymous-pipe |
13 | // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output |
14 | #include <windows.h> |
15 | #else |
16 | #include <errno.h> |
17 | #include <unistd.h> |
18 | #include <signal.h> |
19 | #include <fcntl.h> |
20 | #include <sys/types.h> |
21 | #include <sys/wait.h> |
22 | #endif |
23 | |
24 | #define READ_BUF_SIZE 2048 |
25 | #define PROCESS_TERM_TRIES 3 |
26 | #define PROCESS_TERM_DELAY 50 |
27 | #define PROCESS_KILL_LIST_NAME "__process_kill_list__" |
28 | |
29 | #if _WIN32 |
30 | |
31 | typedef DWORD process_error_t; |
32 | typedef HANDLE process_stream_t; |
33 | typedef HANDLE process_handle_t; |
34 | |
35 | #define HANDLE_INVALID (INVALID_HANDLE_VALUE) |
36 | #define PROCESS_GET_HANDLE(P) ((P)->process_information.hProcess) |
37 | |
38 | static volatile long PipeSerialNumber; |
39 | |
40 | #else |
41 | |
42 | typedef int process_error_t; |
43 | typedef int process_stream_t; |
44 | typedef pid_t process_handle_t; |
45 | |
46 | #define HANDLE_INVALID (0) |
47 | #define PROCESS_GET_HANDLE(P) ((P)->pid) |
48 | |
49 | #endif |
50 | |
51 | typedef struct { |
52 | bool running, detached; |
53 | int returncode, deadline; |
54 | long pid; |
55 | #if _WIN32 |
56 | PROCESS_INFORMATION process_information; |
57 | OVERLAPPED overlapped[2]; |
58 | bool reading[2]; |
59 | char buffer[2][READ_BUF_SIZE]; |
60 | #endif |
61 | process_stream_t child_pipes[3][2]; |
62 | } process_t; |
63 | |
64 | typedef struct process_kill_s { |
65 | int tries; |
66 | uint32_t start_time; |
67 | process_handle_t handle; |
68 | struct process_kill_s *next; |
69 | } process_kill_t; |
70 | |
71 | typedef struct { |
72 | bool stop; |
73 | SDL_mutex *mutex; |
74 | SDL_cond *has_work, *work_done; |
75 | SDL_Thread *worker_thread; |
76 | process_kill_t *head; |
77 | process_kill_t *tail; |
78 | } process_kill_list_t; |
79 | |
80 | typedef enum { |
81 | SIGNAL_KILL, |
82 | SIGNAL_TERM, |
83 | SIGNAL_INTERRUPT |
84 | } signal_e; |
85 | |
86 | typedef enum { |
87 | WAIT_NONE = 0, |
88 | WAIT_DEADLINE = -1, |
89 | WAIT_INFINITE = -2 |
90 | } wait_e; |
91 | |
92 | typedef enum { |
93 | STDIN_FD, |
94 | STDOUT_FD, |
95 | STDERR_FD, |
96 | // Special values for redirection. |
97 | REDIRECT_DEFAULT = -1, |
98 | REDIRECT_DISCARD = -2, |
99 | REDIRECT_PARENT = -3, |
100 | } filed_e; |
101 | |
102 | static void close_fd(process_stream_t *handle) { |
103 | if (*handle && *handle != HANDLE_INVALID) { |
104 | #ifdef _WIN32 |
105 | CloseHandle(*handle); |
106 | #else |
107 | close(*handle); |
108 | #endif |
109 | *handle = HANDLE_INVALID; |
110 | } |
111 | } |
112 | |
113 | |
114 | static int kill_list_worker(void *ud); |
115 | |
116 | |
117 | static void kill_list_free(process_kill_list_t *list) { |
118 | process_kill_t *node, *temp; |
119 | SDL_WaitThread(list->worker_thread, NULL); |
120 | SDL_DestroyMutex(list->mutex); |
121 | SDL_DestroyCond(list->has_work); |
122 | SDL_DestroyCond(list->work_done); |
123 | node = list->head; |
124 | while (node) { |
125 | temp = node; |
126 | node = node->next; |
127 | free(temp); |
128 | } |
129 | memset(list, 0, sizeof(process_kill_list_t)); |
130 | } |
131 | |
132 | |
133 | static bool kill_list_init(process_kill_list_t *list) { |
134 | memset(list, 0, sizeof(process_kill_list_t)); |
135 | list->mutex = SDL_CreateMutex(); |
136 | list->has_work = SDL_CreateCond(); |
137 | list->work_done = SDL_CreateCond(); |
138 | list->head = list->tail = NULL; |
139 | list->stop = false; |
140 | if (!list->mutex || !list->has_work || !list->work_done) { |
141 | kill_list_free(list); |
142 | return false; |
143 | } |
144 | list->worker_thread = SDL_CreateThread(kill_list_worker, "process_kill" , list); |
145 | if (!list->worker_thread) { |
146 | kill_list_free(list); |
147 | return false; |
148 | } |
149 | return true; |
150 | } |
151 | |
152 | |
153 | static void kill_list_push(process_kill_list_t *list, process_kill_t *task) { |
154 | if (!list) return; |
155 | task->next = NULL; |
156 | if (list->tail) { |
157 | list->tail->next = task; |
158 | list->tail = task; |
159 | } else { |
160 | list->head = list->tail = task; |
161 | } |
162 | } |
163 | |
164 | |
165 | static void kill_list_pop(process_kill_list_t *list) { |
166 | if (!list || !list->head) return; |
167 | process_kill_t *head = list->head; |
168 | list->head = list->head->next; |
169 | if (!list->head) list->tail = NULL; |
170 | head->next = NULL; |
171 | } |
172 | |
173 | |
174 | static void kill_list_wait_all(process_kill_list_t *list) { |
175 | SDL_LockMutex(list->mutex); |
176 | // wait until list is empty |
177 | while (list->head) |
178 | SDL_CondWait(list->work_done, list->mutex); |
179 | // tell the worker to stop |
180 | list->stop = true; |
181 | SDL_CondSignal(list->has_work); |
182 | SDL_UnlockMutex(list->mutex); |
183 | } |
184 | |
185 | |
186 | static void process_handle_close(process_handle_t *handle) { |
187 | #ifdef _WIN32 |
188 | if (*handle) { |
189 | CloseHandle(*handle); |
190 | *handle = NULL; |
191 | } |
192 | #endif |
193 | (void) 0; |
194 | } |
195 | |
196 | |
197 | static bool process_handle_is_running(process_handle_t handle, int *status) { |
198 | #ifdef _WIN32 |
199 | DWORD s; |
200 | if (GetExitCodeProcess(handle, &s) && s != STILL_ACTIVE) { |
201 | if (status != NULL) |
202 | *status = s; |
203 | return false; |
204 | } |
205 | #else |
206 | int s; |
207 | if (waitpid(handle, &s, WNOHANG) != 0) { |
208 | if (status != NULL) |
209 | *status = WEXITSTATUS(s); |
210 | return false; |
211 | } |
212 | #endif |
213 | return true; |
214 | } |
215 | |
216 | |
217 | static bool process_handle_signal(process_handle_t handle, signal_e sig) { |
218 | #if _WIN32 |
219 | switch(sig) { |
220 | case SIGNAL_TERM: return GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, GetProcessId(handle)); |
221 | case SIGNAL_KILL: return TerminateProcess(handle, -1); |
222 | case SIGNAL_INTERRUPT: return DebugBreakProcess(handle); |
223 | } |
224 | #else |
225 | switch (sig) { |
226 | case SIGNAL_TERM: return kill(-handle, SIGTERM) == 0; break; |
227 | case SIGNAL_KILL: return kill(-handle, SIGKILL) == 0; break; |
228 | case SIGNAL_INTERRUPT: return kill(-handle, SIGINT) == 0; break; |
229 | } |
230 | #endif |
231 | return false; |
232 | } |
233 | |
234 | |
235 | static int kill_list_worker(void *ud) { |
236 | process_kill_list_t *list = (process_kill_list_t *) ud; |
237 | process_kill_t *current_task; |
238 | uint32_t delay; |
239 | |
240 | while (true) { |
241 | SDL_LockMutex(list->mutex); |
242 | |
243 | // wait until we have work to do |
244 | while (!list->head && !list->stop) |
245 | SDL_CondWait(list->has_work, list->mutex); // LOCK MUTEX |
246 | |
247 | if (list->stop) break; |
248 | |
249 | while ((current_task = list->head)) { |
250 | if ((SDL_GetTicks() - current_task->start_time) < PROCESS_TERM_DELAY) |
251 | break; |
252 | kill_list_pop(list); |
253 | if (process_handle_is_running(current_task->handle, NULL)) { |
254 | if (current_task->tries < PROCESS_TERM_TRIES) |
255 | process_handle_signal(current_task->handle, SIGNAL_TERM); |
256 | else if (current_task->tries == PROCESS_TERM_TRIES) |
257 | process_handle_signal(current_task->handle, SIGNAL_KILL); |
258 | else |
259 | goto free_task; |
260 | |
261 | // add the task back into the queue |
262 | current_task->tries++; |
263 | current_task->start_time = SDL_GetTicks(); |
264 | kill_list_push(list, current_task); |
265 | } else { |
266 | free_task: |
267 | SDL_CondSignal(list->work_done); |
268 | process_handle_close(¤t_task->handle); |
269 | free(current_task); |
270 | } |
271 | } |
272 | delay = list->head ? (list->head->start_time + PROCESS_TERM_DELAY) - SDL_GetTicks() : 0; |
273 | SDL_UnlockMutex(list->mutex); |
274 | SDL_Delay(delay); |
275 | } |
276 | SDL_UnlockMutex(list->mutex); |
277 | return 0; |
278 | } |
279 | |
280 | |
281 | static int push_error_string(lua_State *L, process_error_t err) { |
282 | #ifdef _WIN32 |
283 | char *msg = NULL; |
284 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |
285 | | FORMAT_MESSAGE_ALLOCATE_BUFFER |
286 | | FORMAT_MESSAGE_IGNORE_INSERTS, |
287 | NULL, |
288 | err, |
289 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
290 | (char *) &msg, |
291 | 0, |
292 | NULL); |
293 | if (!msg) |
294 | return 0; |
295 | |
296 | lua_pushstring(L, msg); |
297 | LocalFree(msg); |
298 | #else |
299 | lua_pushstring(L, strerror(err)); |
300 | #endif |
301 | return 1; |
302 | } |
303 | |
304 | static void push_error(lua_State *L, const char *, process_error_t err) { |
305 | const char *msg = "unknown error" ; |
306 | extra = extra != NULL ? extra : "error" ; |
307 | if (push_error_string(L, err)) |
308 | msg = lua_tostring(L, -1); |
309 | lua_pushfstring(L, "%s: %s (%d)" , extra, msg, err); |
310 | } |
311 | |
312 | static bool poll_process(process_t* proc, int timeout) { |
313 | uint32_t ticks; |
314 | |
315 | if (!proc->running) |
316 | return false; |
317 | |
318 | if (timeout == WAIT_DEADLINE) |
319 | timeout = proc->deadline; |
320 | |
321 | ticks = SDL_GetTicks(); |
322 | do { |
323 | int status; |
324 | if (!process_handle_is_running(PROCESS_GET_HANDLE(proc), &status)) { |
325 | proc->running = false; |
326 | proc->returncode = status; |
327 | break; |
328 | } |
329 | if (timeout) |
330 | SDL_Delay(timeout >= 5 ? 5 : 0); |
331 | } while (timeout == WAIT_INFINITE || (int)SDL_GetTicks() - ticks < timeout); |
332 | |
333 | return proc->running; |
334 | } |
335 | |
336 | static bool signal_process(process_t* proc, signal_e sig) { |
337 | if (process_handle_signal(PROCESS_GET_HANDLE(proc), sig)) |
338 | poll_process(proc, WAIT_NONE); |
339 | return true; |
340 | } |
341 | |
342 | static int process_start(lua_State* L) { |
343 | int retval = 1; |
344 | size_t env_len = 0, key_len, val_len; |
345 | const char *cmd[256] = { NULL }, *env_names[256] = { NULL }, *env_values[256] = { NULL }, *cwd = NULL; |
346 | bool detach = false, literal = false; |
347 | int deadline = 10, new_fds[3] = { STDIN_FD, STDOUT_FD, STDERR_FD }; |
348 | size_t arg_len = lua_gettop(L), cmd_len; |
349 | if (lua_type(L, 1) == LUA_TTABLE) { |
350 | #if LUA_VERSION_NUM > 501 |
351 | lua_len(L, 1); |
352 | #else |
353 | lua_pushinteger(L, (int)lua_objlen(L, 1)); |
354 | #endif |
355 | cmd_len = luaL_checknumber(L, -1); lua_pop(L, 1); |
356 | if (!cmd_len) |
357 | // we have not allocated anything here yet, so we can skip cleanup code |
358 | // don't do this anywhere else! |
359 | return luaL_argerror(L, 1,"table cannot be empty" ); |
360 | for (size_t i = 1; i <= cmd_len; ++i) { |
361 | lua_pushinteger(L, i); |
362 | lua_rawget(L, 1); |
363 | cmd[i-1] = luaL_checkstring(L, -1); |
364 | } |
365 | } else { |
366 | literal = true; |
367 | cmd[0] = luaL_checkstring(L, 1); |
368 | cmd_len = 1; |
369 | } |
370 | |
371 | if (arg_len > 1) { |
372 | lua_getfield(L, 2, "env" ); |
373 | if (!lua_isnil(L, -1)) { |
374 | lua_pushnil(L); |
375 | while (lua_next(L, -2) != 0) { |
376 | const char* key = luaL_checklstring(L, -2, &key_len); |
377 | const char* val = luaL_checklstring(L, -1, &val_len); |
378 | env_names[env_len] = malloc(key_len+1); |
379 | strcpy((char*)env_names[env_len], key); |
380 | env_values[env_len] = malloc(val_len+1); |
381 | strcpy((char*)env_values[env_len], val); |
382 | lua_pop(L, 1); |
383 | ++env_len; |
384 | } |
385 | } else |
386 | lua_pop(L, 1); |
387 | lua_getfield(L, 2, "detach" ); detach = lua_toboolean(L, -1); |
388 | lua_getfield(L, 2, "timeout" ); deadline = luaL_optnumber(L, -1, deadline); |
389 | lua_getfield(L, 2, "cwd" ); cwd = luaL_optstring(L, -1, NULL); |
390 | lua_getfield(L, 2, "stdin" ); new_fds[STDIN_FD] = luaL_optnumber(L, -1, STDIN_FD); |
391 | lua_getfield(L, 2, "stdout" ); new_fds[STDOUT_FD] = luaL_optnumber(L, -1, STDOUT_FD); |
392 | lua_getfield(L, 2, "stderr" ); new_fds[STDERR_FD] = luaL_optnumber(L, -1, STDERR_FD); |
393 | for (int stream = STDIN_FD; stream <= STDERR_FD; ++stream) { |
394 | if (new_fds[stream] > STDERR_FD || new_fds[stream] < REDIRECT_PARENT) { |
395 | lua_pushfstring(L, "error: redirect to handles, FILE* and paths are not supported" ); |
396 | retval = -1; |
397 | goto cleanup; |
398 | } |
399 | } |
400 | } |
401 | |
402 | process_t* self = lua_newuserdata(L, sizeof(process_t)); |
403 | memset(self, 0, sizeof(process_t)); |
404 | luaL_setmetatable(L, API_TYPE_PROCESS); |
405 | self->deadline = deadline; |
406 | self->detached = detach; |
407 | #if _WIN32 |
408 | for (int i = 0; i < 3; ++i) { |
409 | switch (new_fds[i]) { |
410 | case REDIRECT_PARENT: |
411 | switch (i) { |
412 | case STDIN_FD: self->child_pipes[i][0] = GetStdHandle(STD_INPUT_HANDLE); break; |
413 | case STDOUT_FD: self->child_pipes[i][1] = GetStdHandle(STD_OUTPUT_HANDLE); break; |
414 | case STDERR_FD: self->child_pipes[i][1] = GetStdHandle(STD_ERROR_HANDLE); break; |
415 | } |
416 | self->child_pipes[i][i == STDIN_FD ? 1 : 0] = INVALID_HANDLE_VALUE; |
417 | break; |
418 | case REDIRECT_DISCARD: |
419 | self->child_pipes[i][0] = INVALID_HANDLE_VALUE; |
420 | self->child_pipes[i][1] = INVALID_HANDLE_VALUE; |
421 | break; |
422 | default: { |
423 | if (new_fds[i] == i) { |
424 | char pipeNameBuffer[MAX_PATH]; |
425 | sprintf(pipeNameBuffer, "\\\\.\\Pipe\\RemoteExeAnon.%08lx.%08lx" , GetCurrentProcessId(), InterlockedIncrement(&PipeSerialNumber)); |
426 | self->child_pipes[i][0] = CreateNamedPipeA(pipeNameBuffer, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, |
427 | PIPE_TYPE_BYTE | PIPE_WAIT, 1, READ_BUF_SIZE, READ_BUF_SIZE, 0, NULL); |
428 | if (self->child_pipes[i][0] == INVALID_HANDLE_VALUE) { |
429 | push_error(L, "cannot create pipe" , GetLastError()); |
430 | retval = -1; |
431 | goto cleanup; |
432 | } |
433 | self->child_pipes[i][1] = CreateFileA(pipeNameBuffer, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
434 | if (self->child_pipes[i][1] == INVALID_HANDLE_VALUE) { |
435 | // prevent CloseHandle from messing up error codes |
436 | DWORD err = GetLastError(); |
437 | CloseHandle(self->child_pipes[i][0]); |
438 | push_error(L, "cannot open pipe" , err); |
439 | retval = -1; |
440 | goto cleanup; |
441 | } |
442 | if (!SetHandleInformation(self->child_pipes[i][i == STDIN_FD ? 1 : 0], HANDLE_FLAG_INHERIT, 0) || |
443 | !SetHandleInformation(self->child_pipes[i][i == STDIN_FD ? 0 : 1], HANDLE_FLAG_INHERIT, 1)) { |
444 | push_error(L, "cannot set pipe permission" , GetLastError()); |
445 | retval = -1; |
446 | goto cleanup; |
447 | } |
448 | } |
449 | } break; |
450 | } |
451 | } |
452 | for (int i = 0; i < 3; ++i) { |
453 | if (new_fds[i] != i) { |
454 | self->child_pipes[i][0] = self->child_pipes[new_fds[i]][0]; |
455 | self->child_pipes[i][1] = self->child_pipes[new_fds[i]][1]; |
456 | } |
457 | } |
458 | STARTUPINFO siStartInfo; |
459 | memset(&self->process_information, 0, sizeof(self->process_information)); |
460 | memset(&siStartInfo, 0, sizeof(siStartInfo)); |
461 | siStartInfo.cb = sizeof(siStartInfo); |
462 | siStartInfo.dwFlags |= STARTF_USESTDHANDLES; |
463 | siStartInfo.hStdInput = self->child_pipes[STDIN_FD][0]; |
464 | siStartInfo.hStdOutput = self->child_pipes[STDOUT_FD][1]; |
465 | siStartInfo.hStdError = self->child_pipes[STDERR_FD][1]; |
466 | char commandLine[32767] = { 0 }, environmentBlock[32767], wideEnvironmentBlock[32767*2]; |
467 | int offset = 0; |
468 | if (!literal) { |
469 | for (size_t i = 0; i < cmd_len; ++i) { |
470 | size_t len = strlen(cmd[i]); |
471 | if (offset + len + 2 >= sizeof(commandLine)) break; |
472 | if (i > 0) |
473 | commandLine[offset++] = ' '; |
474 | commandLine[offset++] = '"'; |
475 | int backslashCount = 0; // Yes, this is necessary. |
476 | for (size_t j = 0; j < len && offset + 2 + backslashCount < sizeof(commandLine); ++j) { |
477 | if (cmd[i][j] == '\\') |
478 | ++backslashCount; |
479 | else if (cmd[i][j] == '"') { |
480 | for (size_t k = 0; k < backslashCount; ++k) |
481 | commandLine[offset++] = '\\'; |
482 | commandLine[offset++] = '\\'; |
483 | backslashCount = 0; |
484 | } else |
485 | backslashCount = 0; |
486 | commandLine[offset++] = cmd[i][j]; |
487 | } |
488 | if (offset + 1 + backslashCount >= sizeof(commandLine)) break; |
489 | for (size_t k = 0; k < backslashCount; ++k) |
490 | commandLine[offset++] = '\\'; |
491 | commandLine[offset++] = '"'; |
492 | } |
493 | commandLine[offset] = 0; |
494 | } else { |
495 | strncpy(commandLine, cmd[0], sizeof(commandLine)); |
496 | } |
497 | offset = 0; |
498 | for (size_t i = 0; i < env_len; ++i) { |
499 | if (offset + strlen(env_values[i]) + strlen(env_names[i]) + 1 >= sizeof(environmentBlock)) |
500 | break; |
501 | offset += snprintf(&environmentBlock[offset], sizeof(environmentBlock) - offset, "%s=%s" , env_names[i], env_values[i]); |
502 | environmentBlock[offset++] = 0; |
503 | } |
504 | environmentBlock[offset++] = 0; |
505 | if (env_len > 0) |
506 | MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, environmentBlock, offset, (LPWSTR)wideEnvironmentBlock, sizeof(wideEnvironmentBlock)); |
507 | if (!CreateProcess(NULL, commandLine, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_len > 0 ? wideEnvironmentBlock : NULL, cwd, &siStartInfo, &self->process_information)) { |
508 | push_error(L, NULL, GetLastError()); |
509 | retval = -1; |
510 | goto cleanup; |
511 | } |
512 | self->pid = (long)self->process_information.dwProcessId; |
513 | if (detach) |
514 | CloseHandle(self->process_information.hProcess); |
515 | CloseHandle(self->process_information.hThread); |
516 | #else |
517 | int control_pipe[2] = { 0 }; |
518 | for (int i = 0; i < 3; ++i) { // Make only the parents fd's non-blocking. Children should block. |
519 | if (pipe(self->child_pipes[i]) || fcntl(self->child_pipes[i][i == STDIN_FD ? 1 : 0], F_SETFL, O_NONBLOCK) == -1) { |
520 | push_error(L, "cannot create pipe" , errno); |
521 | retval = -1; |
522 | goto cleanup; |
523 | } |
524 | } |
525 | // create a pipe to get the exit code of exec() |
526 | if (pipe(control_pipe) == -1) { |
527 | lua_pushfstring(L, "Error creating control pipe: %s" , strerror(errno)); |
528 | retval = -1; |
529 | goto cleanup; |
530 | } |
531 | if (fcntl(control_pipe[1], F_SETFD, FD_CLOEXEC) == -1) { |
532 | lua_pushfstring(L, "Error setting FD_CLOEXEC: %s" , strerror(errno)); |
533 | retval = -1; |
534 | goto cleanup; |
535 | } |
536 | |
537 | self->pid = (long)fork(); |
538 | if (self->pid < 0) { |
539 | push_error(L, "cannot create child process" , errno); |
540 | retval = -1; |
541 | goto cleanup; |
542 | } else if (!self->pid) { |
543 | // child process |
544 | if (!detach) |
545 | setpgid(0,0); |
546 | for (int stream = 0; stream < 3; ++stream) { |
547 | if (new_fds[stream] == REDIRECT_DISCARD) { // Close the stream if we don't want it. |
548 | close(self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]); |
549 | close(stream); |
550 | } else if (new_fds[stream] != REDIRECT_PARENT) // Use the parent handles if we redirect to parent. |
551 | dup2(self->child_pipes[new_fds[stream]][new_fds[stream] == STDIN_FD ? 0 : 1], stream); |
552 | close(self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]); |
553 | } |
554 | size_t set; |
555 | for (set = 0; set < env_len && setenv(env_names[set], env_values[set], 1) == 0; ++set); |
556 | if (set == env_len && (!detach || setsid() != -1) && (!cwd || chdir(cwd) != -1)) |
557 | execvp(cmd[0], (char** const)cmd); |
558 | write(control_pipe[1], &errno, sizeof(errno)); |
559 | _exit(-1); |
560 | } |
561 | // close our write side so we can read from child |
562 | close(control_pipe[1]); |
563 | control_pipe[1] = 0; |
564 | |
565 | // wait for child process to respond |
566 | int sz, process_rc; |
567 | while ((sz = read(control_pipe[0], &process_rc, sizeof(int))) == -1) { |
568 | if (errno == EPIPE) break; |
569 | if (errno != EINTR) { |
570 | lua_pushfstring(L, "Error getting child process status: %s" , strerror(errno)); |
571 | retval = -1; |
572 | goto cleanup; |
573 | } |
574 | } |
575 | |
576 | if (sz) { |
577 | // read something from pipe; exec failed |
578 | int status; |
579 | waitpid(self->pid, &status, 0); |
580 | lua_pushfstring(L, "Error creating child process: %s" , strerror(process_rc)); |
581 | retval = -1; |
582 | goto cleanup; |
583 | } |
584 | |
585 | #endif |
586 | cleanup: |
587 | #ifndef _WIN32 |
588 | if (control_pipe[0]) close(control_pipe[0]); |
589 | if (control_pipe[1]) close(control_pipe[1]); |
590 | #endif |
591 | for (size_t i = 0; i < env_len; ++i) { |
592 | free((char*)env_names[i]); |
593 | free((char*)env_values[i]); |
594 | } |
595 | for (int stream = 0; stream < 3; ++stream) { |
596 | process_stream_t* pipe = &self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]; |
597 | if (*pipe) { |
598 | close_fd(pipe); |
599 | } |
600 | } |
601 | if (retval == -1) |
602 | return lua_error(L); |
603 | |
604 | self->running = true; |
605 | return retval; |
606 | } |
607 | |
608 | static int g_read(lua_State* L, int stream, unsigned long read_size) { |
609 | process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); |
610 | long length = 0; |
611 | if (stream != STDOUT_FD && stream != STDERR_FD) |
612 | return luaL_error(L, "error: redirect to handles, FILE* and paths are not supported" ); |
613 | #if _WIN32 |
614 | int writable_stream_idx = stream - 1; |
615 | if (self->reading[writable_stream_idx] || !ReadFile(self->child_pipes[stream][0], self->buffer[writable_stream_idx], READ_BUF_SIZE, NULL, &self->overlapped[writable_stream_idx])) { |
616 | if (self->reading[writable_stream_idx] || GetLastError() == ERROR_IO_PENDING) { |
617 | self->reading[writable_stream_idx] = true; |
618 | DWORD bytesTransferred = 0; |
619 | if (GetOverlappedResult(self->child_pipes[stream][0], &self->overlapped[writable_stream_idx], &bytesTransferred, false)) { |
620 | self->reading[writable_stream_idx] = false; |
621 | length = bytesTransferred; |
622 | memset(&self->overlapped[writable_stream_idx], 0, sizeof(self->overlapped[writable_stream_idx])); |
623 | } |
624 | } else { |
625 | signal_process(self, SIGNAL_TERM); |
626 | return 0; |
627 | } |
628 | } else { |
629 | length = self->overlapped[writable_stream_idx].InternalHigh; |
630 | memset(&self->overlapped[writable_stream_idx], 0, sizeof(self->overlapped[writable_stream_idx])); |
631 | } |
632 | lua_pushlstring(L, self->buffer[writable_stream_idx], length); |
633 | #else |
634 | luaL_Buffer b; |
635 | luaL_buffinit(L, &b); |
636 | uint8_t* buffer = (uint8_t*)luaL_prepbuffsize(&b, READ_BUF_SIZE); |
637 | length = read(self->child_pipes[stream][0], buffer, read_size > READ_BUF_SIZE ? READ_BUF_SIZE : read_size); |
638 | if (length == 0 && !poll_process(self, WAIT_NONE)) |
639 | return 0; |
640 | else if (length < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) |
641 | length = 0; |
642 | if (length < 0) { |
643 | signal_process(self, SIGNAL_TERM); |
644 | return 0; |
645 | } |
646 | luaL_addsize(&b, length); |
647 | luaL_pushresult(&b); |
648 | #endif |
649 | return 1; |
650 | } |
651 | |
652 | static int f_write(lua_State* L) { |
653 | process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); |
654 | size_t data_size = 0; |
655 | const char* data = luaL_checklstring(L, 2, &data_size); |
656 | long length; |
657 | #if _WIN32 |
658 | DWORD dwWritten; |
659 | if (!WriteFile(self->child_pipes[STDIN_FD][1], data, data_size, &dwWritten, NULL)) { |
660 | push_error(L, NULL, GetLastError()); |
661 | signal_process(self, SIGNAL_TERM); |
662 | return lua_error(L); |
663 | } |
664 | length = dwWritten; |
665 | #else |
666 | length = write(self->child_pipes[STDIN_FD][1], data, data_size); |
667 | if (length < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) |
668 | length = 0; |
669 | else if (length < 0) { |
670 | push_error(L, "cannot write to child process" , errno); |
671 | signal_process(self, SIGNAL_TERM); |
672 | return lua_error(L); |
673 | } |
674 | #endif |
675 | lua_pushinteger(L, length); |
676 | return 1; |
677 | } |
678 | |
679 | static int f_close_stream(lua_State* L) { |
680 | process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); |
681 | int stream = luaL_checknumber(L, 2); |
682 | close_fd(&self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]); |
683 | lua_pushboolean(L, 1); |
684 | return 1; |
685 | } |
686 | |
687 | // Generic stuff below here. |
688 | static int process_strerror(lua_State* L) { |
689 | return push_error_string(L, luaL_checknumber(L, 1)); |
690 | } |
691 | |
692 | static int f_tostring(lua_State* L) { |
693 | lua_pushliteral(L, API_TYPE_PROCESS); |
694 | return 1; |
695 | } |
696 | |
697 | static int f_pid(lua_State* L) { |
698 | process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); |
699 | lua_pushinteger(L, self->pid); |
700 | return 1; |
701 | } |
702 | |
703 | static int f_returncode(lua_State *L) { |
704 | process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); |
705 | if (self->running) |
706 | return 0; |
707 | lua_pushinteger(L, self->returncode); |
708 | return 1; |
709 | } |
710 | |
711 | static int f_read_stdout(lua_State* L) { |
712 | return g_read(L, STDOUT_FD, luaL_optinteger(L, 2, READ_BUF_SIZE)); |
713 | } |
714 | |
715 | static int f_read_stderr(lua_State* L) { |
716 | return g_read(L, STDERR_FD, luaL_optinteger(L, 2, READ_BUF_SIZE)); |
717 | } |
718 | |
719 | static int f_read(lua_State* L) { |
720 | return g_read(L, luaL_checknumber(L, 2), luaL_optinteger(L, 3, READ_BUF_SIZE)); |
721 | } |
722 | |
723 | static int f_wait(lua_State* L) { |
724 | process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); |
725 | int timeout = luaL_optnumber(L, 2, 0); |
726 | if (poll_process(self, timeout)) |
727 | return 0; |
728 | lua_pushinteger(L, self->returncode); |
729 | return 1; |
730 | } |
731 | |
732 | static int self_signal(lua_State* L, signal_e sig) { |
733 | process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); |
734 | signal_process(self, sig); |
735 | lua_pushboolean(L, 1); |
736 | return 1; |
737 | } |
738 | static int f_terminate(lua_State* L) { return self_signal(L, SIGNAL_TERM); } |
739 | static int f_kill(lua_State* L) { return self_signal(L, SIGNAL_KILL); } |
740 | static int f_interrupt(lua_State* L) { return self_signal(L, SIGNAL_INTERRUPT); } |
741 | |
742 | static int f_gc(lua_State* L) { |
743 | process_kill_list_t *list = NULL; |
744 | process_kill_t *p = NULL; |
745 | process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); |
746 | |
747 | // get the kill_list for the lua_State |
748 | if (lua_getfield(L, LUA_REGISTRYINDEX, PROCESS_KILL_LIST_NAME) == LUA_TUSERDATA) |
749 | list = (process_kill_list_t *) lua_touserdata(L, -1); |
750 | |
751 | if (poll_process(self, 0) && !self->detached) { |
752 | // attempt to kill the process if still running and not detached |
753 | signal_process(self, SIGNAL_TERM); |
754 | if (!list || !list->worker_thread || !(p = malloc(sizeof(process_kill_t)))) { |
755 | // use synchronous waiting |
756 | if (poll_process(self, PROCESS_TERM_DELAY)) { |
757 | signal_process(self, SIGNAL_KILL); |
758 | poll_process(self, PROCESS_TERM_DELAY); |
759 | } |
760 | } else { |
761 | // put the handle into a queue for asynchronous waiting |
762 | p->handle = PROCESS_GET_HANDLE(self); |
763 | p->start_time = SDL_GetTicks(); |
764 | p->tries = 1; |
765 | SDL_LockMutex(list->mutex); |
766 | kill_list_push(list, p); |
767 | SDL_CondSignal(list->has_work); |
768 | SDL_UnlockMutex(list->mutex); |
769 | } |
770 | } |
771 | close_fd(&self->child_pipes[STDIN_FD ][1]); |
772 | close_fd(&self->child_pipes[STDOUT_FD][0]); |
773 | close_fd(&self->child_pipes[STDERR_FD][0]); |
774 | return 0; |
775 | } |
776 | |
777 | static int f_running(lua_State* L) { |
778 | process_t* self = (process_t*)luaL_checkudata(L, 1, API_TYPE_PROCESS); |
779 | lua_pushboolean(L, poll_process(self, WAIT_NONE)); |
780 | return 1; |
781 | } |
782 | |
783 | static int process_gc(lua_State *L) { |
784 | process_kill_list_t *list = NULL; |
785 | // get the kill_list for the lua_State |
786 | if (lua_getfield(L, LUA_REGISTRYINDEX, PROCESS_KILL_LIST_NAME) == LUA_TUSERDATA) { |
787 | list = (process_kill_list_t *) lua_touserdata(L, -1); |
788 | kill_list_wait_all(list); |
789 | kill_list_free(list); |
790 | } |
791 | return 0; |
792 | } |
793 | |
794 | static const struct luaL_Reg process_metatable[] = { |
795 | {"__gc" , f_gc}, |
796 | {"__tostring" , f_tostring}, |
797 | {"pid" , f_pid}, |
798 | {"returncode" , f_returncode}, |
799 | {"read" , f_read}, |
800 | {"read_stdout" , f_read_stdout}, |
801 | {"read_stderr" , f_read_stderr}, |
802 | {"write" , f_write}, |
803 | {"close_stream" , f_close_stream}, |
804 | {"wait" , f_wait}, |
805 | {"terminate" , f_terminate}, |
806 | {"kill" , f_kill}, |
807 | {"interrupt" , f_interrupt}, |
808 | {"running" , f_running}, |
809 | {NULL, NULL} |
810 | }; |
811 | |
812 | static const struct luaL_Reg lib[] = { |
813 | { "start" , process_start }, |
814 | { "strerror" , process_strerror }, |
815 | { NULL, NULL } |
816 | }; |
817 | |
818 | int luaopen_process(lua_State *L) { |
819 | process_kill_list_t *list = lua_newuserdata(L, sizeof(process_kill_list_t)); |
820 | if (kill_list_init(list)) |
821 | lua_setfield(L, LUA_REGISTRYINDEX, PROCESS_KILL_LIST_NAME); |
822 | else |
823 | lua_pop(L, 1); // discard the list |
824 | |
825 | // create the process metatable |
826 | luaL_newmetatable(L, API_TYPE_PROCESS); |
827 | luaL_setfuncs(L, process_metatable, 0); |
828 | lua_pushvalue(L, -1); |
829 | lua_setfield(L, -2, "__index" ); |
830 | |
831 | // create the process library |
832 | luaL_newlib(L, lib); |
833 | lua_newtable(L); |
834 | lua_pushcfunction(L, process_gc); |
835 | lua_setfield(L, -2, "__gc" ); |
836 | lua_setmetatable(L, -2); |
837 | |
838 | API_CONSTANT_DEFINE(L, -1, "WAIT_INFINITE" , WAIT_INFINITE); |
839 | API_CONSTANT_DEFINE(L, -1, "WAIT_DEADLINE" , WAIT_DEADLINE); |
840 | |
841 | API_CONSTANT_DEFINE(L, -1, "STREAM_STDIN" , STDIN_FD); |
842 | API_CONSTANT_DEFINE(L, -1, "STREAM_STDOUT" , STDOUT_FD); |
843 | API_CONSTANT_DEFINE(L, -1, "STREAM_STDERR" , STDERR_FD); |
844 | |
845 | API_CONSTANT_DEFINE(L, -1, "REDIRECT_DEFAULT" , REDIRECT_DEFAULT); |
846 | API_CONSTANT_DEFINE(L, -1, "REDIRECT_STDOUT" , STDOUT_FD); |
847 | API_CONSTANT_DEFINE(L, -1, "REDIRECT_STDERR" , STDERR_FD); |
848 | API_CONSTANT_DEFINE(L, -1, "REDIRECT_PARENT" , REDIRECT_PARENT); // Redirects to parent's STDOUT/STDERR |
849 | API_CONSTANT_DEFINE(L, -1, "REDIRECT_DISCARD" , REDIRECT_DISCARD); // Closes the filehandle, discarding it. |
850 | |
851 | return 1; |
852 | } |
853 | |