From 2bc3f755354befae982b4068b0137f15ac9f16c0 Mon Sep 17 00:00:00 2001 From: hkc Date: Wed, 15 Nov 2023 10:24:27 +0300 Subject: [PATCH] Process management and logging --- .gitignore | 1 + cbt.c | 8 +- cbt.h | 415 ++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 372 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index a52df5f..f58ee7e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ cbt +state diff --git a/cbt.c b/cbt.c index 2a84385..fdbba78 100644 --- a/cbt.c +++ b/cbt.c @@ -1,9 +1,15 @@ -// x-run: tcc -Wextra -Wall -run % +// x-run: ~/scripts/runc.sh % -Wall -Wextra #define CBT_IMPLEMENTATION #include "cbt.h" int main(int argc, char **argv) { + cbt_verbosity = CBT_LOG_ALL; CBT_INIT(argc, argv); + struct cbt_proc proc = cbt_proc_new(CBT_PROC_NORMAL, "sh", "-c", "while sleep 0.1; do echo 'nya'; done"); + + cbt_proc_wait(proc); + + cbt_cleanup(); return 0; } diff --git a/cbt.h b/cbt.h index 28127c0..bdcde2e 100644 --- a/cbt.h +++ b/cbt.h @@ -6,12 +6,6 @@ #include #include -extern int cbt_verbosity; -extern char *cbt_cc; - -void cbt__init(int argc, char **argv); -#define CBT_INIT(ARGC, ARGV) cbt__init(ARGC, ARGV); - enum cbt_loglevel { CBT_LOG_FATAL, CBT_LOG_ERROR, @@ -21,12 +15,35 @@ enum cbt_loglevel { CBT_LOG_ALL }; -const extern char *cbt_log__colors[CBT_LOG_ALL + 1]; -const extern char *cbt_log__typecolors[8]; +extern long cbt_start_time; +extern enum cbt_loglevel cbt_verbosity; +extern char *cbt_cc; + +void cbt__init(int argc, char **argv, const char *source_file); +void cbt_cleanup(void); +#define CBT_INIT(ARGC, ARGV) cbt__init(ARGC, ARGV, __FILE__); + +extern const char *cbt_log__colors[CBT_LOG_ALL + 1]; +extern const char *cbt_log__typecolors[8]; void cbt_log(enum cbt_loglevel lvl, const char *fmt, ...); +long cbt_get_time(void); unsigned long cbt_get_modtime(const char *filename); bool cbt_needs_recompilation(const char *input, const char *output); +const char *cbt_escape_shell(char *arg); +const char *cbt_escape_args(char **args); + + +#ifndef CBT_FMT_NBUFS +#define CBT_FMT_NBUFS 64 +#endif + +#ifndef CBT_FMT_BUFSIZE +#define CBT_FMT_BUFSIZE 1024 +#endif + + +const char *cbt_fmt(const char *fmt, ...); struct cbt_proc_args { @@ -36,11 +53,7 @@ struct cbt_proc_args { struct cbt_proc { FILE *fp_stdin, *fp_stdout, *fp_stderr; -#ifdef _WIN32 - HANDLE *handle; -#else int pid; -#endif }; struct cbt_procgroup { @@ -48,6 +61,18 @@ struct cbt_procgroup { size_t size, cap; }; +enum cbt_proc_mode { + CBT_PROC_NORMAL = 0, + CBT_PROC_R = 1, + CBT_PROC_W = 2, + CBT_PROC_RW = 3, +}; + +#define cbt_proc_new(...) _cbt_proc_new(__VA_ARGS__, NULL) +struct cbt_proc _cbt_proc_new(enum cbt_proc_mode mode, ...); +struct cbt_proc cbt_proc_newv(enum cbt_proc_mode mode, va_list args); +int cbt_proc_wait(struct cbt_proc proc); + #define CBT_ARRLEN(ARR) (sizeof(ARR)/sizeof(ARR[0])) #ifndef CBT_REALLOC @@ -56,24 +81,63 @@ struct cbt_procgroup { #define CBT_DA_INIT_SIZE 256 -#define cbt_da_add(ARR, ITEM) { \ - if (ARR.size >= ARR.cap) { \ - size_t size = ARR.size == 0 ? CBT_DA_INIT_SIZE : ARR.size * 2; \ - ARR.items = CBT_REALLOC(NULL, size); \ - } \ - ARR.items[ARR.size] = (ITEM); \ - ARR.size++; \ +#define cbt_da_add(ARR, ITEM) { \ + if (ARR.size >= ARR.cap) { \ + size_t size = ARR.size == 0 ? CBT_DA_INIT_SIZE : ARR.size * 2; \ + ARR.items = CBT_REALLOC(ARR.items, size * sizeof(ARR.items[0])); \ + } \ + ARR.items[ARR.size] = (ITEM); \ + ARR.size++; \ +} + +#define cbt_da_remove(ARR, I) if (I >= 0 && I < ARR.size) { \ + memmove(&ARR.items[I], \ + &ARR.items[I+1], \ + (ARR.size - 1) * sizeof((ARR).items[0])); \ + ARR.size--; \ } -#endif -// Implementation #ifdef CBT_IMPLEMENTATION - +#define __USE_GNU +#include #include #include -int cbt_verbosity = 0; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CBT_FAIL(COND) if (COND) { cbt_log(CBT_LOG_FATAL, "condition %s failed: %s", #COND, strerror(errno)); abort(); }; + +enum cbt_loglevel cbt_verbosity = 0; char *cbt_cc = NULL; +long cbt_start_time = 0; + +struct _cbt__autoproc_fdset { + struct pollfd *items; + size_t size, cap; +} cbt__autoproc_fdset = { 0 }; + +struct _cbt__autoproc_set { + struct _cbt__autoproc_set__item { + FILE *fp; + bool is_err; + } *items; + size_t size, cap; +} cbt__autoproc_set = { 0 }; + +pthread_t _cbt__autoproc_thread; +pthread_mutex_t _cbt__autoproc_mut = PTHREAD_MUTEX_INITIALIZER; +bool cbt_running = false; + const char *cbt_log__colors[CBT_LOG_ALL + 1] = { "\x1b[1;31m", "\x1b[31m", @@ -82,45 +146,294 @@ const char *cbt_log__colors[CBT_LOG_ALL + 1] = { "\x1b[34m", "\x1b[35m" }; -const char *cbt_log__typecolors[8] = { - /* int */ "\x1b[31m", - /* float */ "\x1b[33m", - /* char* */ "\x1b[34m", - /* void* */ "\x1b[35m", - /* bool */ NULL, - /* */ "", - /* */ "", - /* */ "", + +const char *cbt_log__text[CBT_LOG_ALL + 1] = { + "FATAL", "ERROR", "WARN ", "INFO ", "DEBUG", "TRACE" }; +void *cbt__line_processor(void *); -void cbt__init(int argc, char **argv) { - cbt_cc = getenv("CC"); +void cbt__init(int argc, char **argv, const char *source_file) { + cbt_start_time = cbt_get_time(); + cbt_running = true; + (void)argc; + + cbt_log(CBT_LOG_INFO, "Running CBT build %s %s from %s", __DATE__, __TIME__, __FILE__); + + if (!cbt_cc) cbt_cc = getenv("CC"); if (!cbt_cc) cbt_cc = "cc"; - cbt_log(CBT_LOG_INFO, "omg hii hiiii hello %s", "world"); + + cbt_log(CBT_LOG_INFO, "Found C Compiler: %s", cbt_cc); + cbt_log(CBT_LOG_DEBUG, "Args: %s", cbt_escape_args(argv)); + cbt_log(CBT_LOG_DEBUG, "%s", cbt_fmt("format test: %s", "ok")); + + if (cbt_needs_recompilation(source_file, argv[0])) { + cbt_log(CBT_LOG_WARNING, "Needs recompilation, but not implemented yet"); + } + + cbt_log(CBT_LOG_DEBUG, "Starting line processor thread"); + // TODO: error checking + pthread_create(&_cbt__autoproc_thread, NULL, cbt__line_processor, NULL); } -struct cbt_proc cbt_proc__open(void); +void cbt_cleanup(void) { + usleep(50000); + pthread_mutex_lock(&_cbt__autoproc_mut); + cbt_running = false; + cbt_log(CBT_LOG_INFO, "cbt shutting down"); + pthread_join(_cbt__autoproc_thread, NULL); + pthread_mutex_unlock(&_cbt__autoproc_mut); +} + +const char *cbt_fmt(const char *fmt, ...) { + static char cbt_fmt_buffers[CBT_FMT_NBUFS][CBT_FMT_BUFSIZE]; + static int cbt_fmt_buffer = 0; + char *buffer = cbt_fmt_buffers[cbt_fmt_buffer++ % CBT_FMT_NBUFS]; + va_list args; + va_start(args, fmt); + vsnprintf(buffer, CBT_FMT_BUFSIZE - 1, fmt, args); + va_end(args); + + return buffer; +} + +long cbt_get_time(void) { + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + return (long)(now.tv_sec * 1000000L) + (long)(now.tv_nsec / 1000L); +} -/* - * Simplified version of printf, but with pretty colors depending on the type - * - * Supported format specifiers: - * %{len}d %{len}u - signed and unsigned integer - * %(len}(.{len})f - float/double - * %{len}s - string - * %p - raw pointer - */ void cbt_log(enum cbt_loglevel lvl, const char *fmt, ...) { - const char *fmt_ptr = fmt; - for (const char *fmt_ptr = fmt; *fmt_ptr != 0; fmt_ptr++) { - if (*fmt_ptr != '%') { - putchar(*fmt_ptr); + if (lvl > cbt_verbosity) return; + + double time_delta = (double)(cbt_get_time() - cbt_start_time) / 1000.0L; + printf("[%10.3lf] [%s%s\x1b[0m] ", time_delta, cbt_log__colors[lvl], cbt_log__text[lvl]); + + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + fputc('\n', stdout); +} + +// TODO: find a better option +const char *cbt_escape_shell(char *arg) { + int n = strlen(arg); + + char *esc = (char *)cbt_fmt(""); // just take a buffer + + esc[0] = '\''; + int j = 1, i; + + for (i = 0; i < n; i++) { + if (arg[i] == '\'') { + if (j + 4 >= CBT_FMT_BUFSIZE) { return NULL; } + esc[j] = '\''; + esc[j+1] = '\\'; + esc[j+2] = '\''; + esc[j+3] = '\''; + j += 4; } else { - // todo + if (j >= CBT_FMT_BUFSIZE) { return NULL; } + esc[j++] = arg[i]; } } - putchar('\n'); + + if (j >= CBT_FMT_BUFSIZE) { return NULL; } + esc[j] = '\''; + + return esc; } +const char *cbt_escape_args(char **args) { + if (args == NULL || args[0] == NULL) return cbt_fmt(""); + char *out = (char *)cbt_escape_shell(args[0]); + for (int i = 1; args[i] != NULL; i++) { + out = (char *)cbt_fmt("%s %s", out, cbt_escape_shell(args[i])); + } + return out; +} + +unsigned long cbt_get_modtime(const char *filename) { + struct stat st; + int ret = lstat(filename, &st); + if (ret == -1) return -1; + + cbt_log(CBT_LOG_DEBUG, "stat(%s) -> %ld", filename, st.st_mtim.tv_sec); + return st.st_mtim.tv_sec; +} + +bool cbt_needs_recompilation(const char *input, const char *output) { + unsigned long mod_in = cbt_get_modtime(input), + mod_out = cbt_get_modtime(output); + if (mod_in == (unsigned long)-1) { + cbt_log(CBT_LOG_WARNING, "cbt_needs_recompilation(): input file %s does not exist", input); + return false; + } + + return mod_out == (unsigned long)-1 || mod_in > mod_out; +} + +void cbt__add_fd(int fd, bool is_err) { + cbt_log(CBT_LOG_DEBUG, "cbt__add_fd() adding %d", fd); + pthread_kill(_cbt__autoproc_thread, SIGUSR1); + pthread_mutex_lock(&_cbt__autoproc_mut); + cbt_log(CBT_LOG_DEBUG, "cbt__add_fd() locked mutex for fd=%d", fd); + + struct pollfd pfd; + pfd.fd = fd; + pfd.events = POLLIN | POLLOUT; + + struct _cbt__autoproc_set__item info = { + .is_err = is_err + }; + + cbt_da_add(cbt__autoproc_fdset, pfd); + cbt_da_add(cbt__autoproc_set, info); + pthread_mutex_unlock(&_cbt__autoproc_mut); +} + +struct cbt_proc _cbt_proc_new(enum cbt_proc_mode mode, ...) { + va_list args; + va_start(args, mode); + struct cbt_proc process = cbt_proc_newv(mode, args); + va_end(args); + return process; +} + +struct cbt_proc cbt_proc_newv(enum cbt_proc_mode mode, va_list args) { + struct cbt_proc procinfo = { 0 }; + + struct cbt_proc_args args_da = { 0 }; + while (true) { + const char *arg = va_arg(args, const char *); + if (arg == NULL) break; + cbt_da_add(args_da, strdup(arg)); + } + + cbt_log(CBT_LOG_DEBUG, "Spawning process with mode %d and args %s", mode, cbt_escape_args(args_da.items)); + + int pipe_fds_sout[2] = { 0 }, + pipe_fds_serr[2] = { 0 }, + pipe_fds_sin[2] = { 0 }; + if (mode & CBT_PROC_W) CBT_FAIL(pipe(pipe_fds_sin) == -1); + CBT_FAIL(pipe(pipe_fds_sout) == -1); + CBT_FAIL(pipe(pipe_fds_serr) == -1); + + procinfo.pid = fork(); + if (procinfo.pid < 0) { + cbt_log(CBT_LOG_ERROR, "fork() failed: %s", strerror(errno)); + goto cbt_proc_new_cleanup; + } + + + if (procinfo.pid == 0) { // child + + if (mode & CBT_PROC_W) CBT_FAIL(dup2(pipe_fds_sin[1], STDIN_FILENO) == -1); + CBT_FAIL(dup2(pipe_fds_sout[1], STDOUT_FILENO) == -1); + CBT_FAIL(dup2(pipe_fds_serr[1], STDERR_FILENO) == -1); + + execvp(args_da.items[0], (char* const*)args_da.items); + __builtin_unreachable(); + } else { + if (mode & CBT_PROC_W) procinfo.fp_stdin = fdopen(pipe_fds_sin[0], "w"); + if (mode & CBT_PROC_R) procinfo.fp_stdout = fdopen(pipe_fds_sout[0], "r"); + if (mode & CBT_PROC_R) procinfo.fp_stderr = fdopen(pipe_fds_serr[0], "r"); + + cbt_log(CBT_LOG_DEBUG, "stdin: %p (fd=%d)", procinfo.fp_stdin, pipe_fds_sin[0]); + cbt_log(CBT_LOG_DEBUG, "stdout: %p (fd=%d)", procinfo.fp_stdout, pipe_fds_sout[0]); + cbt_log(CBT_LOG_DEBUG, "stderr: %p (fd=%d)", procinfo.fp_stderr, pipe_fds_serr[0]); + + if (!(mode & CBT_PROC_R)) { + cbt__add_fd(pipe_fds_sout[0], false); + cbt__add_fd(pipe_fds_serr[0], true); + } + +cbt_proc_new_cleanup: + cbt_log(CBT_LOG_DEBUG, "freeing up args"); + for (size_t i = 0; i < args_da.size; i++) { + free(args_da.items[i]); + } + free(args_da.items); + + return procinfo; + } +} + +int cbt_proc_wait(struct cbt_proc proc) { + int wstatus; + + do { + pid_t w = waitpid(proc.pid, &wstatus, WUNTRACED | WCONTINUED); + if (w == -1) { + cbt_log(CBT_LOG_WARNING, "waitpid(%d) -> %d (%s)", proc.pid, w, strerror(errno)); + return -2000; + } + + if (WIFEXITED(wstatus)) { + return WEXITSTATUS(wstatus); + } else if (WIFSIGNALED(wstatus)) { + return 1000 + WTERMSIG(wstatus); + } + } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus)); + return -1000; +} + +static void cbt__line_processor_sig_handler(int signal) { + (void)signal; +} + +void *cbt__line_processor(void *arg) { + (void)arg; + struct timespec timeout = { .tv_sec = 0, .tv_nsec = 100 }; + + sigset_t sigmask; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGUSR1); + + struct sigaction sa; + sa.sa_handler = cbt__line_processor_sig_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + CBT_FAIL(sigaction(SIGUSR1, &sa, NULL) == -1); + + while (cbt_running) { + pthread_mutex_lock(&_cbt__autoproc_mut); + +#if 1 + int res = ppoll(cbt__autoproc_fdset.items, cbt__autoproc_fdset.size, &timeout, &sigmask); + if (res < 0) { + cbt_log(CBT_LOG_ERROR, "ppoll() -> %d (%s)", res, strerror(errno)); + break; + } +#else + for (int i = 0; i < cbt__autoproc_set.size; i++) { + cbt_log(CBT_LOG_DEBUG, "fdset[%d] = { %d, %d }", i, cbt__autoproc_set.items[i].fd, cbt__autoproc_set.items[i].events); + } + cbt_log(CBT_LOG_DEBUG, "poll(_, %d, _) ...", cbt__autoproc_set.size); + int res = poll(cbt__autoproc_set.items, cbt__autoproc_set.size, 1000); +#endif + + char buf[1024]; + for (size_t i = 0; i < cbt__autoproc_fdset.size; i++) { + struct pollfd pfd = cbt__autoproc_fdset.items[i]; + struct _cbt__autoproc_set__item item = cbt__autoproc_set.items[i]; + if (pfd.revents != 0) { + if (pfd.revents & POLLIN) { + ssize_t s = read(pfd.fd, buf, sizeof(buf)); + cbt_log(item.is_err ? CBT_LOG_ERROR : CBT_LOG_INFO, "fd(%d): %.*s", pfd.fd, (int)s - 1, buf); + } + } + } + + pthread_mutex_unlock(&_cbt__autoproc_mut); + usleep(1000); + } + return NULL; +} + +#undef CBT_FAIL +#endif + #endif