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
31typedef DWORD process_error_t;
32typedef HANDLE process_stream_t;
33typedef HANDLE process_handle_t;
34
35#define HANDLE_INVALID (INVALID_HANDLE_VALUE)
36#define PROCESS_GET_HANDLE(P) ((P)->process_information.hProcess)
37
38static volatile long PipeSerialNumber;
39
40#else
41
42typedef int process_error_t;
43typedef int process_stream_t;
44typedef pid_t process_handle_t;
45
46#define HANDLE_INVALID (0)
47#define PROCESS_GET_HANDLE(P) ((P)->pid)
48
49#endif
50
51typedef 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
64typedef 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
71typedef 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
80typedef enum {
81 SIGNAL_KILL,
82 SIGNAL_TERM,
83 SIGNAL_INTERRUPT
84} signal_e;
85
86typedef enum {
87 WAIT_NONE = 0,
88 WAIT_DEADLINE = -1,
89 WAIT_INFINITE = -2
90} wait_e;
91
92typedef 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
102static 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
114static int kill_list_worker(void *ud);
115
116
117static 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
133static 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
153static 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
165static 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
174static 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
186static 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
197static 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
217static 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
235static 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(&current_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
281static 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
304static void push_error(lua_State *L, const char *extra, 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
312static 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
336static 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
342static 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
608static 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
652static 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
679static 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.
688static int process_strerror(lua_State* L) {
689 return push_error_string(L, luaL_checknumber(L, 1));
690}
691
692static int f_tostring(lua_State* L) {
693 lua_pushliteral(L, API_TYPE_PROCESS);
694 return 1;
695}
696
697static 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
703static 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
711static int f_read_stdout(lua_State* L) {
712 return g_read(L, STDOUT_FD, luaL_optinteger(L, 2, READ_BUF_SIZE));
713}
714
715static int f_read_stderr(lua_State* L) {
716 return g_read(L, STDERR_FD, luaL_optinteger(L, 2, READ_BUF_SIZE));
717}
718
719static int f_read(lua_State* L) {
720 return g_read(L, luaL_checknumber(L, 2), luaL_optinteger(L, 3, READ_BUF_SIZE));
721}
722
723static 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
732static 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}
738static int f_terminate(lua_State* L) { return self_signal(L, SIGNAL_TERM); }
739static int f_kill(lua_State* L) { return self_signal(L, SIGNAL_KILL); }
740static int f_interrupt(lua_State* L) { return self_signal(L, SIGNAL_INTERRUPT); }
741
742static 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
777static 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
783static 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
794static 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
812static const struct luaL_Reg lib[] = {
813 { "start", process_start },
814 { "strerror", process_strerror },
815 { NULL, NULL }
816};
817
818int 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