Process management and logging

This commit is contained in:
Casey 2023-11-15 10:24:27 +03:00
parent 8b6ed7f4ae
commit 2bc3f75535
Signed by: hkc
GPG Key ID: F0F6CFE11CDB0960
3 changed files with 372 additions and 52 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
cbt
state

8
cbt.c
View File

@ -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;
}

415
cbt.h
View File

@ -6,12 +6,6 @@
#include <stddef.h>
#include <stdio.h>
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 <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
int cbt_verbosity = 0;
#include <ctype.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
#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