From acfbdf2359f533f88e284b5f5219b5c671992fc7 Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Tue, 16 Feb 2021 01:14:10 -0500 Subject: processes --- arr.c | 11 ++++-- main.c | 56 ++++++++++++++++++++++++++ process-posix.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ process.h | 26 +++++++++++++ ted.h | 2 +- 5 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 process-posix.c create mode 100644 process.h diff --git a/arr.c b/arr.c index 0e545f7..4e37892 100644 --- a/arr.c +++ b/arr.c @@ -157,11 +157,16 @@ static void arr_set_len_(void **arr, size_t member_size, size_t n) { } } -static void arr_remove_(void *arr, size_t member_size, size_t index) { +static void *arr_remove_(void *arr, size_t member_size, size_t index) { ArrHeader *hdr = arr_hdr_(arr); assert(index < hdr->len); memmove((char *)arr + index * member_size, (char *)arr + (index+1) * member_size, (hdr->len - (index+1)) * member_size); - --hdr->len; + if (--hdr->len == 0) { + free(hdr); + return NULL; + } else { + return arr; + } } #ifdef __cplusplus @@ -189,7 +194,7 @@ static void arr_remove_(void *arr, size_t member_size, size_t index) { // the newly-added elements are zero-initialized. #define arr_qsort(a, cmp) qsort((a), arr_len(a), sizeof *(a), (cmp)) #define arr_remove_last(a) do { assert(a); if (--arr_hdr_(a)->len == 0) arr_free(a); } while (0) -#define arr_remove(a, i) arr_remove_((a), sizeof *(a), (i)) +#define arr_remove(a, i) (void)((a) = arr_remove_((a), sizeof *(a), (i))) #define arr_pop_last(a) ((a)[--arr_hdr_(a)->len]) #define arr_size_in_bytes(a) (arr_len(a) * sizeof *(a)) #define arr_lastp(a) ((a) ? &(a)[arr_len(a)-1] : NULL) diff --git a/main.c b/main.c index 81a9d90..8c6308b 100644 --- a/main.c +++ b/main.c @@ -42,6 +42,13 @@ no_warn_end #include "arr.c" #include "math.c" +#if _WIN32 +#include "process-win.c" +#elif __unix__ +#include "process-posix.c" +#else +#error "Unrecognized operating system." +#endif #include "text.h" #include "command.h" @@ -110,6 +117,11 @@ static void ted_update_window_dimensions(Ted *ted) { gl_window_height = ted->window_height = (float)h; } +void chld_handler(int x, siginfo_t *y, void *z) { + (void)x; (void)y; (void)z; + printf("got singal!\n"); +} + #if _WIN32 INT WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, INT nCmdShow) { @@ -134,6 +146,50 @@ int main(int argc, char **argv) { #endif setlocale(LC_ALL, ""); // allow unicode + process_init(); + + + char *program = "/usr/bin/make"; + char *args[] = {program, NULL}; + Process *process = process_exec(program, args); + if (!process) { + printf("Error: %s\n", process_geterr()); + return EXIT_FAILURE; + } + #if 1 + { + i64 bytes = 0; + char buf[256]; + while (1) { + bytes = process_read(process, buf, sizeof buf); + if (bytes == -2) { + printf("Error: %s\n", process_geterr()); + } else if (bytes == -1) { + usleep(1000); + } else if (bytes == 0) { + break; + } else { + fwrite(buf, 1, (size_t)bytes, stdout); + } + } + } + { + char message[256]; + while (1) { + int status = process_check_status(process, message, sizeof message); + if (status == -1) { + printf("%s!!!\n",message); + break; + } else if (status == +1) { + printf("%s\n", message); + break; + } else { + usleep(1000); + } + } + } + #endif + return 0; // read command-line arguments char const *starting_filename = NULL; diff --git a/process-posix.c b/process-posix.c new file mode 100644 index 0000000..53363b6 --- /dev/null +++ b/process-posix.c @@ -0,0 +1,119 @@ +#include "process.h" + +#include +#include + +struct Process { + pid_t pid; + int pipe; + bool stopped; + int exit_status; // INT_MAX if process was terminated +}; + +static char process_error[64]; +static Process *processes; + +static void process_sigaction_sigchld(int signal, siginfo_t *info, void *context) { + (void)context; + if (signal == SIGCHLD) { + pid_t pid = info->si_pid; + arr_foreach_ptr(processes, Process, process) { + if (process->pid == pid) { + process->stopped = true; + if (info->si_code == CLD_EXITED) + process->exit_status = info->si_status; + else + process->exit_status = INT_MAX; + } + } + } +} + +void process_init(void) { + struct sigaction act = {0}; + act.sa_sigaction = process_sigaction_sigchld; + act.sa_flags = SA_SIGINFO | SA_NOCLDSTOP; + sigaction(SIGCHLD, &act, NULL); +} + + + +Process *process_exec(char const *program, char **argv) { + Process *proc = NULL; + + int pipefd[2]; + if (pipe(pipefd) == 0) { + pid_t pid = fork(); + if (pid == 0) { + // child process + // send stdout and stderr to pipe + dup2(pipefd[1], STDOUT_FILENO); + dup2(pipefd[1], STDERR_FILENO); + close(pipefd[0]); + close(pipefd[1]); + if (execv(program, argv) == -1) { + dprintf(STDERR_FILENO, "%s: %s\n", program, strerror(errno)); + exit(127); + } + } else if (pid > 0) { + proc = arr_addp(processes); + // parent process + close(pipefd[1]); + fcntl(pipefd[0], F_SETFL, fcntl(pipefd[0], F_GETFL) | O_NONBLOCK); // set pipe to non-blocking + proc->pid = pid; + proc->pipe = pipefd[0]; + } + } else { + strbuf_printf(process_error, "%s", strerror(errno)); + } + return proc; +} + +char const *process_geterr(void) { + return *process_error ? process_error : NULL; +} + +long long process_read(Process *proc, char *data, size_t size) { + assert(proc->pipe); + ssize_t bytes_read = read(proc->pipe, data, size); + if (bytes_read >= 0) { + return (long long)bytes_read; + } else if (errno == EAGAIN) { + return -1; + } else { + strbuf_printf(process_error, "%s", strerror(errno)); + return -2; + } + +} + +void process_stop(Process *proc) { + kill(proc->pid, SIGKILL); + // get rid of zombie process + waitpid(proc->pid, NULL, 0); + close(proc->pipe); + for (u32 i = 0; i < arr_len(processes); ++i) + if (processes[i].pid == proc->pid) + arr_remove(processes, i); +} +#include +int process_check_status(Process *proc, char *message, size_t message_size) { + if (proc->stopped) { + int status = proc->exit_status; + process_stop(proc); + + switch (status) { + case 0: + if (message) str_printf(message, message_size, "exited successfully"); + return +1; + case INT_MAX: + if (message) str_printf(message, message_size, "terminated by a signal"); + return -1; + default: + if (message) str_printf(message, message_size, "exited with code %d", status); + return -1; + } + } else { + return 0; + } +} diff --git a/process.h b/process.h new file mode 100644 index 0000000..2b34120 --- /dev/null +++ b/process.h @@ -0,0 +1,26 @@ +// like popen, but allowing for non-blocking reads +#ifndef PROCESS_H_ +#define PROCESS_H_ + +typedef struct Process Process; + +// returns NULL on failure +Process *process_exec(char const *program, char **argv); +// returns the error last error produced, or NULL if there was no error. +char const *process_geterr(void); +// returns: +// -2 on error +// -1 if no data is available right now +// 0 on end of file +// or a positive number indicating the number of bytes read to data (at most size) +long long process_read(Process *process, char *data, size_t size); +// Checks if the process has exited. Returns: +// -1 if the process returned a non-zero exit code, or got a signal. +// 1 if the process exited successfully +// 0 if the process hasn't exited. +// If message is not NULL, it will be set to a description of what happened (e.g. "exited successfully") +int process_check_status(Process *process, char *message, size_t message_size); +// kills process if still running, and frees data associated with it. +void process_stop(Process *process); + +#endif diff --git a/ted.h b/ted.h index a1150f3..0341ec1 100644 --- a/ted.h +++ b/ted.h @@ -112,7 +112,7 @@ typedef struct { } Line; // this refers to replacing prev_len characters (found in prev_text) at pos with new_len characters -typedef struct BufferEdit { +typedef struct { bool chain; // should this + the next edit be treated as one? BufferPos pos; u32 new_len; -- cgit v1.2.3