From 80a67b7d20393a29aa5d2cb92197f3381be7fd96 Mon Sep 17 00:00:00 2001 From: turret Date: Sat, 30 Mar 2024 16:04:45 -0500 Subject: *: directory changes since this project is a skeleton and not meant to clutter up the code that will actually consume the bot, i've opted to consolidate the majority of files under a single directory and minimise extra files *: move code to util/ *: move include files to include/dbs/ net: consolidate net functions into single file config: remove config --- include/api.h | 35 ----- include/config.h | 4 - include/dbs/api.h | 30 ++++ include/dbs/init.h | 23 ++++ include/dbs/log.h | 67 +++++++++ include/dbs/subsys.h | 12 ++ include/dbs/util.h | 3 + include/init.h | 23 ---- include/log.h | 67 --------- include/subsys.h | 7 - include/util.h | 3 - init/init.c | 210 ---------------------------- init/log.c | 209 ---------------------------- init/subsys.c | 180 ------------------------ net/api.c | 87 ------------ net/net.c | 203 --------------------------- net/ws.c | 67 --------- util/init.c | 200 +++++++++++++++++++++++++++ util/log.c | 210 ++++++++++++++++++++++++++++ util/net.c | 383 +++++++++++++++++++++++++++++++++++++++++++++++++++ util/subsys.c | 179 ++++++++++++++++++++++++ 21 files changed, 1107 insertions(+), 1095 deletions(-) delete mode 100644 include/api.h delete mode 100644 include/config.h create mode 100644 include/dbs/api.h create mode 100644 include/dbs/init.h create mode 100644 include/dbs/log.h create mode 100644 include/dbs/subsys.h create mode 100644 include/dbs/util.h delete mode 100644 include/init.h delete mode 100644 include/log.h delete mode 100644 include/subsys.h delete mode 100644 include/util.h delete mode 100644 init/init.c delete mode 100644 init/log.c delete mode 100644 init/subsys.c delete mode 100644 net/api.c delete mode 100644 net/net.c delete mode 100644 net/ws.c create mode 100644 util/init.c create mode 100644 util/log.c create mode 100644 util/net.c create mode 100644 util/subsys.c diff --git a/include/api.h b/include/api.h deleted file mode 100644 index a70d447..0000000 --- a/include/api.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef __API_H -#define __API_H -#define _Nullable - -typedef enum { - HTTP_GET, - HTTP_POST, - HTTP_PUT, - HTTP_DELETE, - HTTP_PATCH -} HTTPMethod; - -#ifndef __API_INTERNAL - -int http_request(HTTPMethod method, char *url, - struct curl_slist *_Nullable headers, char *writebuf, size_t bufsiz); - -int api_request(HTTPMethod method, char * url, - struct curl_slist *_Nullable headers, char *writebuf, size_t bufsiz); - -#endif - -#define http_get(...) http_request(HTTP_GET, __VA_ARGS__) -#define http_post(...) http_request(HTTP_POST, __VA_ARGS__) -#define http_put(...) http_request(HTTP_PUT, __VA_ARGS__) -#define http_delete(...) http_request(HTTP_DELETE, __VA_ARGS__) -#define http_patch(...) http_request(HTTP_PATCH, __VA_ARGS__) - -#define api_get(...) api_request(HTTP_GET, __VA_ARGS__) -#define api_post(...) api_request(HTTP_POST, __VA_ARGS__) -#define api_put(...) api_request(HTTP_PUT, __VA_ARGS__) -#define api_delete(...) api_request(HTTP_DELETE, __VA_ARGS__) -#define api_patch(...) api_request(HTTP_PATCH, __VA_ARGS__) - -#endif diff --git a/include/config.h b/include/config.h deleted file mode 100644 index 4254edc..0000000 --- a/include/config.h +++ /dev/null @@ -1,4 +0,0 @@ -#define VERSION "0.0.1" -#define NAME_SHORTHAND "TCDBF" -#define NAME "turret.'s C Discord Bot Framework" - diff --git a/include/dbs/api.h b/include/dbs/api.h new file mode 100644 index 0000000..d421f08 --- /dev/null +++ b/include/dbs/api.h @@ -0,0 +1,30 @@ +#ifndef __API_H +#define __API_H + +typedef enum { + HTTP_GET, + HTTP_POST, + HTTP_PUT, + HTTP_DELETE, + HTTP_PATCH +} HTTPMethod; + +int http_request(HTTPMethod method, char *url, + struct curl_slist *headers, char *writebuf, size_t bufsiz); + +int api_request(HTTPMethod method, char * url, + struct curl_slist *headers, char *writebuf, size_t bufsiz); + +#define http_get(...) http_request(HTTP_GET, __VA_ARGS__) +#define http_post(...) http_request(HTTP_POST, __VA_ARGS__) +#define http_put(...) http_request(HTTP_PUT, __VA_ARGS__) +#define http_delete(...) http_request(HTTP_DELETE, __VA_ARGS__) +#define http_patch(...) http_request(HTTP_PATCH, __VA_ARGS__) + +#define api_get(...) api_request(HTTP_GET, __VA_ARGS__) +#define api_post(...) api_request(HTTP_POST, __VA_ARGS__) +#define api_put(...) api_request(HTTP_PUT, __VA_ARGS__) +#define api_delete(...) api_request(HTTP_DELETE, __VA_ARGS__) +#define api_patch(...) api_request(HTTP_PATCH, __VA_ARGS__) + +#endif diff --git a/include/dbs/init.h b/include/dbs/init.h new file mode 100644 index 0000000..79e60f1 --- /dev/null +++ b/include/dbs/init.h @@ -0,0 +1,23 @@ +#ifndef __INIT_H +#define __INIT_H + +typedef void (*initcall_t)(void); +typedef initcall_t initcall_entry_t; + +#define __define_initcall(fn, id) \ + static initcall_t __initcall_##fn##id \ + __attribute__((used)) \ + __attribute__((section(".initcall" #id ".init"))) = fn + +#define l1_initcall(fn) __define_initcall(fn, 1) +#define l2_initcall(fn) __define_initcall(fn, 2) +#define l3_initcall(fn) __define_initcall(fn, 3) +#define l4_initcall(fn) __define_initcall(fn, 4) +#define l5_initcall(fn) __define_initcall(fn, 5) + +static inline initcall_t initcall_from_entry(initcall_entry_t *entry) +{ + return *entry; +} + +#endif diff --git a/include/dbs/log.h b/include/dbs/log.h new file mode 100644 index 0000000..30fec81 --- /dev/null +++ b/include/dbs/log.h @@ -0,0 +1,67 @@ +#define LOG_SOH "\001" +#define LOG_SOH_ASCII '\001' + +#define EMERG_LOGLEVEL 0 +#define ALERT_LOGLEVEL 1 +#define CRIT_LOGLEVEL 2 +#define ERR_LOGLEVEL 3 +#define WARNING_LOGLEVEL 4 +#define NOTICE_LOGLEVEL 5 +#define INFO_LOGLEVEL 6 +#define DEBUG_LOGLEVEL 7 + +#define DEFAULT_LOGLEVEL NOTICE_LOGLEVEL +#define CONSOLE_LOGLEVEL DEBUG_LOGLEVEL + +#define LOG_EMERG LOG_SOH "\1" "0" +#define LOG_ALERT LOG_SOH "\1" "1" +#define LOG_CRIT LOG_SOH "\1" "2" +#define LOG_ERR LOG_SOH "\1" "3" +#define LOG_WARNING LOG_SOH "\1" "4" +#define LOG_NOTICE LOG_SOH "\1" "5" +#define LOG_INFO LOG_SOH "\1" "6" +#define LOG_DEBUG LOG_SOH "\1" "7" + +#define LOG_DEFAULT "" + +int print(const char *fmt, ...); + +#define PANICMODE_DEBUGONLY 'o' +#define PANICMODE_RESPAWN 'r' +#define PANICMODE_DIE 'd' + +#define PANIC_OOPS LOG_SOH "o" +#define PANIC_RESPAWN LOG_SOH "r" +#define PANIC_PANIC LOG_SOH "d" + +#define PANIC_DEFAULT PANIC_PANIC + +void _panic(const char *fileorigin, const int lineorigin, const char *fmt, ...); +#define panic(...) _panic(__FILE__, __LINE__, __VA_ARGS__) +#define oops(...) _panic(__FILE__, __LINE__, PANIC_OOPS __VA_ARGS__) + +#define ANSI_CSI "\x1b[" + +#define ANSI_BOLD ANSI_CSI "1m" +#define ANSI_ITALIC ANSI_CSI "3m" +#define ANSI_BLINK ANSI_CSI "5m" +#define ANSI_REVERSE ANSI_CSI "7m" +#define ANSI_RESET ANSI_CSI "0m" + +#define ANSI_BLACK ANSI_CSI "30m" +#define ANSI_RED ANSI_CSI "31m" +#define ANSI_GREEN ANSI_CSI "32m" +#define ANSI_YELLOW ANSI_CSI "33m" +#define ANSI_BLUE ANSI_CSI "34m" +#define ANSI_MAGENTA ANSI_CSI "35m" +#define ANSI_CYAN ANSI_CSI "36m" +#define ANSI_WHITE ANSI_CSI "37m" + +#define ANSI_BRIGHT_BLACK ANSI_CSI "90m" +#define ANSI_BRIGHT_RED ANSI_CSI "91m" +#define ANSI_BRIGHT_GREEN ANSI_CSI "92m" +#define ANSI_BRIGHT_YELLOW ANSI_CSI "93m" +#define ANSI_BRIGHT_BLUE ANSI_CSI "94m" +#define ANSI_BRIGHT_MAGENTA ANSI_CSI "95m" +#define ANSI_BRIGHT_CYAN ANSI_CSI "96m" +#define ANSI_BRIGHT_WHITE ANSI_CSI "97m" diff --git a/include/dbs/subsys.h b/include/dbs/subsys.h new file mode 100644 index 0000000..1885a80 --- /dev/null +++ b/include/dbs/subsys.h @@ -0,0 +1,12 @@ +#ifndef __SUBSYS_H +#define __SUBSYS_H + +int __impl_start_subsystem(char *name, int (*fn)(void)); +#define start_subsystem(fn) __impl_start_subsystem(#fn, fn) +#define declare_subsystem(fn) \ + void subsys_start_##fn(void) { \ + start_subsystem(fn); \ + } \ + l5_initcall(subsys_start_##fn) + +#endif diff --git a/include/dbs/util.h b/include/dbs/util.h new file mode 100644 index 0000000..bad4a33 --- /dev/null +++ b/include/dbs/util.h @@ -0,0 +1,3 @@ +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#define writeputs(str) write(STDOUT_FILENO, str, strlen(str)); diff --git a/include/init.h b/include/init.h deleted file mode 100644 index 79e60f1..0000000 --- a/include/init.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef __INIT_H -#define __INIT_H - -typedef void (*initcall_t)(void); -typedef initcall_t initcall_entry_t; - -#define __define_initcall(fn, id) \ - static initcall_t __initcall_##fn##id \ - __attribute__((used)) \ - __attribute__((section(".initcall" #id ".init"))) = fn - -#define l1_initcall(fn) __define_initcall(fn, 1) -#define l2_initcall(fn) __define_initcall(fn, 2) -#define l3_initcall(fn) __define_initcall(fn, 3) -#define l4_initcall(fn) __define_initcall(fn, 4) -#define l5_initcall(fn) __define_initcall(fn, 5) - -static inline initcall_t initcall_from_entry(initcall_entry_t *entry) -{ - return *entry; -} - -#endif diff --git a/include/log.h b/include/log.h deleted file mode 100644 index 30fec81..0000000 --- a/include/log.h +++ /dev/null @@ -1,67 +0,0 @@ -#define LOG_SOH "\001" -#define LOG_SOH_ASCII '\001' - -#define EMERG_LOGLEVEL 0 -#define ALERT_LOGLEVEL 1 -#define CRIT_LOGLEVEL 2 -#define ERR_LOGLEVEL 3 -#define WARNING_LOGLEVEL 4 -#define NOTICE_LOGLEVEL 5 -#define INFO_LOGLEVEL 6 -#define DEBUG_LOGLEVEL 7 - -#define DEFAULT_LOGLEVEL NOTICE_LOGLEVEL -#define CONSOLE_LOGLEVEL DEBUG_LOGLEVEL - -#define LOG_EMERG LOG_SOH "\1" "0" -#define LOG_ALERT LOG_SOH "\1" "1" -#define LOG_CRIT LOG_SOH "\1" "2" -#define LOG_ERR LOG_SOH "\1" "3" -#define LOG_WARNING LOG_SOH "\1" "4" -#define LOG_NOTICE LOG_SOH "\1" "5" -#define LOG_INFO LOG_SOH "\1" "6" -#define LOG_DEBUG LOG_SOH "\1" "7" - -#define LOG_DEFAULT "" - -int print(const char *fmt, ...); - -#define PANICMODE_DEBUGONLY 'o' -#define PANICMODE_RESPAWN 'r' -#define PANICMODE_DIE 'd' - -#define PANIC_OOPS LOG_SOH "o" -#define PANIC_RESPAWN LOG_SOH "r" -#define PANIC_PANIC LOG_SOH "d" - -#define PANIC_DEFAULT PANIC_PANIC - -void _panic(const char *fileorigin, const int lineorigin, const char *fmt, ...); -#define panic(...) _panic(__FILE__, __LINE__, __VA_ARGS__) -#define oops(...) _panic(__FILE__, __LINE__, PANIC_OOPS __VA_ARGS__) - -#define ANSI_CSI "\x1b[" - -#define ANSI_BOLD ANSI_CSI "1m" -#define ANSI_ITALIC ANSI_CSI "3m" -#define ANSI_BLINK ANSI_CSI "5m" -#define ANSI_REVERSE ANSI_CSI "7m" -#define ANSI_RESET ANSI_CSI "0m" - -#define ANSI_BLACK ANSI_CSI "30m" -#define ANSI_RED ANSI_CSI "31m" -#define ANSI_GREEN ANSI_CSI "32m" -#define ANSI_YELLOW ANSI_CSI "33m" -#define ANSI_BLUE ANSI_CSI "34m" -#define ANSI_MAGENTA ANSI_CSI "35m" -#define ANSI_CYAN ANSI_CSI "36m" -#define ANSI_WHITE ANSI_CSI "37m" - -#define ANSI_BRIGHT_BLACK ANSI_CSI "90m" -#define ANSI_BRIGHT_RED ANSI_CSI "91m" -#define ANSI_BRIGHT_GREEN ANSI_CSI "92m" -#define ANSI_BRIGHT_YELLOW ANSI_CSI "93m" -#define ANSI_BRIGHT_BLUE ANSI_CSI "94m" -#define ANSI_BRIGHT_MAGENTA ANSI_CSI "95m" -#define ANSI_BRIGHT_CYAN ANSI_CSI "96m" -#define ANSI_BRIGHT_WHITE ANSI_CSI "97m" diff --git a/include/subsys.h b/include/subsys.h deleted file mode 100644 index 1515293..0000000 --- a/include/subsys.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef __SUBSYS_H -#define __SUBSYS_H - -int __impl_start_subsystem(char *name, int (*fn)(void)); -#define start_subsystem(fn) __impl_start_subsystem(#fn, fn) - -#endif diff --git a/include/util.h b/include/util.h deleted file mode 100644 index bad4a33..0000000 --- a/include/util.h +++ /dev/null @@ -1,3 +0,0 @@ -#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) - -#define writeputs(str) write(STDOUT_FILENO, str, strlen(str)); diff --git a/init/init.c b/init/init.c deleted file mode 100644 index 126442e..0000000 --- a/init/init.c +++ /dev/null @@ -1,210 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -extern int subsystem_handle_term(int pid); -extern int subsystem_count; -int mainpid = 0; -long stack_size = 8192 * 512; -char *token; - -/* For some reason, I get SIGSEGV'd when running because a random-ass - byte was inserted where it isnt supposed to be. Added a safety byte - because I cannot be asked to try to figure out how to do this cleanly. */ -static unsigned long __1bsafebuf - __attribute__((used)) __attribute__((section(".1bsafebuf.init"))) = 0; - -/* We start initcall levels at [1] instead of [0], so we must adjust - in code for this minor design choice. Math is done on the level passed - through i.e. do_initcall_level so that you can call it with (1) and have - the expected initcall (l1_initcall) run. */ -extern initcall_entry_t __initcall1_start[]; -extern initcall_entry_t __initcall2_start[]; -extern initcall_entry_t __initcall3_start[]; -extern initcall_entry_t __initcall4_start[]; -extern initcall_entry_t __initcall5_start[]; -extern initcall_entry_t __initcall_end[]; - -static initcall_entry_t *initcall_levels[] = { - __initcall1_start, - __initcall2_start, - __initcall3_start, - __initcall4_start, - __initcall5_start, - __initcall_end, -}; - -static void do_initcall_level(int level) -{ - initcall_entry_t *fn; - - for (fn = initcall_levels[level - 1]; - fn < initcall_levels[level]; - fn++) - initcall_from_entry(fn)(); -} - -static void do_initcalls(void) -{ - unsigned long level; - for (level = 1; level < ARRAY_SIZE(initcall_levels); level++) { - do_initcall_level(level); - } -} - -static void doenv(char *path) -{ - int fd = open(path, O_RDONLY); - if(fd < 0) - return; - - struct stat statbuf; - if(fstat(fd, &statbuf) < 0) - return; - - char *file_mmap = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if(file_mmap == NULL) - return; - - char *file = malloc(statbuf.st_size + 1); - file[statbuf.st_size + 1] = 0; - memcpy(file, file_mmap, statbuf.st_size); - munmap(file_mmap, statbuf.st_size); - - int offset = 0; - while(1) { - char *line = &(file[offset]); - if(*line == '\0') - break; - - char *eol = strchrnul(line, '\n'); - *eol = '\0'; - if(*line == '#') - goto nextline; - - char *divider = strchr(line, '='); - if(divider == NULL) - goto nextline; - - *divider = '\0'; - setenv(line, divider + 1, 0); - -nextline: - offset += (eol - line) + 1; - continue; - } - - free(file); -} - -int main(void) -{ - /* Hello, World! */ - - /* set mainpid for the subsystem service so it is fully accessible - during l1 */ - mainpid = getpid(); - - /* set stack_size for subsystem service */ - struct rlimit *stack_rlimit = malloc(sizeof(struct rlimit)); - getrlimit(RLIMIT_STACK, stack_rlimit); - if(stack_rlimit->rlim_cur != RLIM_INFINITY) { - stack_size = MIN(stack_rlimit->rlim_cur, stack_size); - } - free(stack_rlimit); - - /* configure signal handlers early to prevent race condition where subsystems - can terminate main process on accident, and disable Terminated output during - early-mode panic */ - static sigset_t set; - sigaddset(&set, SIGCHLD); - sigaddset(&set, SIGINT); - sigaddset(&set, SIGTERM); - sigprocmask(SIG_BLOCK, &set, NULL); - - /* use .env files if present */ - doenv(".env"); - - /* find directory of self and use env from there if it exists */ - char *buf = calloc(PATH_MAX, sizeof(char)); - ssize_t self_size = readlink("/proc/self/exe", buf, PATH_MAX); - if(self_size + strlen(".env") + 1 > PATH_MAX) - goto skip_self; - - char *lastslash = strrchr(buf, '/'); - *lastslash = '\0'; - - char *cwd = get_current_dir_name(); - int cwd_is_exec_dir = strcmp(buf, cwd) == 0; - free(cwd); - if(cwd_is_exec_dir) - goto skip_self; - - strcat(buf, "/.env"); - doenv(buf); -skip_self: - free(buf); - - /* fetch token */ - char *token_base = getenv("TOKEN"); - if(!token_base) - panic("init: cannot find TOKEN in env"); - - token = calloc(strlen(token_base) + strlen("Authorization: Bot ") + 1, - sizeof(char)); - strcpy(token, "Authorization: Bot "); - strcat(token, token_base); - - /* init curl */ - if(curl_global_init(CURL_GLOBAL_DEFAULT)) - panic("init: curl init failed"); - - /* init random seed */ - srand(time(NULL)); - - /* Rest of the program.. */ - do_initcalls(); - - /* Reaper. Much like init. */ - - siginfo_t siginfo; - while(subsystem_count > 0) { - sigwaitinfo(&set, &siginfo); - int sig = siginfo.si_signo; - switch(sig) { - case SIGCHLD: ; - int process = 0; - while((process = waitpid(-1, NULL, WNOHANG)) > 0) - if(subsystem_handle_term(process) > 0) - print(LOG_WARNING "init: failed to reap process %d", - process); - if(siginfo.si_status != 0) { - panic("init: process %d exited with non-zero status (%d)", siginfo.si_pid, siginfo.si_status); - } - break; - case SIGINT: - panic("init: keyboard interrupt"); - break; - case SIGTERM: - exit(0); - break; - default: - break; - } - } - - panic("init: no more subsystems"); -} diff --git a/init/log.c b/init/log.c deleted file mode 100644 index f39467c..0000000 --- a/init/log.c +++ /dev/null @@ -1,209 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -extern int subsystem_change_mode(int pid, char mode); -extern char *subsystem_get_name(int pid); -extern int mainpid; - -static const char *colors[] = { - [EMERG_LOGLEVEL] = ANSI_BLINK ANSI_REVERSE ANSI_BOLD ANSI_RED, - [ALERT_LOGLEVEL] = ANSI_REVERSE ANSI_BOLD ANSI_RED, - [CRIT_LOGLEVEL] = ANSI_BOLD ANSI_RED, - [ERR_LOGLEVEL] = ANSI_RED, - [WARNING_LOGLEVEL] = ANSI_BOLD, - [NOTICE_LOGLEVEL] = ANSI_BRIGHT_WHITE, - [INFO_LOGLEVEL] = ANSI_RESET, - [DEBUG_LOGLEVEL] = ANSI_ITALIC ANSI_BRIGHT_BLUE, -}; - -static const char *mode_to_string[] = { - [PANICMODE_DEBUGONLY] = "subsystem OOPS", - [PANICMODE_RESPAWN] = "subsystem failure", - [PANICMODE_DIE] = "catastrophic failure", -}; - -static int console_lock = 0; - -#define MAX_TRY_COUNT 1 << 17 -static void obtain_console_lock(void) -{ - int try_count = 0; - register int rax asm("rax"); -retry: - while(console_lock && try_count <= MAX_TRY_COUNT) - try_count += 1; - - asm("mov %0, 1 \n" - "xchg %0, %1" : "=r" (rax), "=m" (console_lock)); - - if(rax > 0 && try_count <= MAX_TRY_COUNT) - goto retry; - - if(try_count > MAX_TRY_COUNT) { - print(LOG_SOH "\3" "4" "log: broken console lock"); - } - - return; -} - -static int vaprint(const char *fmt, va_list ap) -{ - int loglevel = DEFAULT_LOGLEVEL; - int dolocks = 1; - int parsecolon = 1; - if(fmt[0] == LOG_SOH_ASCII) { - loglevel = (fmt[2] - 0x30) % 10; - char flags = fmt[1]; - if(flags & 1 << 1) - dolocks = 0; - if(flags & 1 << 2) - parsecolon = 0; - fmt += 3; - } - - /* not going to be printed? dont bother! */ - if(loglevel > CONSOLE_LOGLEVEL) - return 0; - - /* we essentially print the user's raw input to its own buffer, - later we will parse it and print out ANSI colors and what not */ - char buf[512]; - - vsnprintf(buf, 512, fmt, ap); - buf[512 - 1] = '\0'; - - size_t colon = 0; - if(parsecolon) { - for(; colon < strlen(buf); ++colon) { - if(buf[colon] == ':') - break; - } - } - - char tsbuf[64] = "\0"; - struct timeval time; - gettimeofday(&time, NULL); - snprintf(tsbuf, sizeof(tsbuf), "[%5ld.%06ld] ", - (long)time.tv_sec % 100000, (long)time.tv_usec); - - /* spin lock, at the cost of architecture portability - concurrency is something that we need to adjust for, and the - console will be scrambled and unreadable if we allow writing all - at the same time. I considered simply writing all at once, but - ended up just not caring enough to the point where spinlocks - prevail. */ - if(dolocks) - obtain_console_lock(); - - - /* we want to support stuff without colons, but frankly I havent - tested this at time of writing. will find out later */ - writeputs(ANSI_RESET ANSI_GREEN); - writeputs(tsbuf); - writeputs(ANSI_RESET); - if(parsecolon && buf[colon] == ':') { - writeputs(colors[loglevel]); - writeputs(ANSI_YELLOW); - write(STDOUT_FILENO, buf, colon); - writeputs(ANSI_RESET); - } - writeputs(colors[loglevel]); - if(colon && *(buf + colon)) { - writeputs(buf + colon); - } else { - writeputs(buf); - } - writeputs(ANSI_RESET); - write(STDOUT_FILENO, "\n", 1); - if(dolocks) - console_lock = 0; - return 0; -} - -void _panic(const char *fileorigin, - const int lineorigin, - const char *fmt, ...) -{ - char mode = PANICMODE_DIE; - int pid = getpid(); - if(fmt[0] == LOG_SOH_ASCII) { - mode = fmt[1]; - /* cannot respawn main thread */ - if(pid == mainpid && mode == PANICMODE_RESPAWN) - mode = PANICMODE_DIE; - fmt += 2; - } - -#define NOLOCK(loglevel) LOG_SOH "\3" loglevel - va_list ap; - va_start(ap, fmt); - char *_fmt = malloc(strlen(fmt) + 4 * sizeof(char)); - sprintf(_fmt, NOLOCK("1") "%s", fmt); - - void **backtrace_addresses = malloc(sizeof(void*) * 32); - int backtrace_count = backtrace(backtrace_addresses, 32); - char **backtrace_symbolnames = - backtrace_symbols(backtrace_addresses, backtrace_count); - - obtain_console_lock(); - - print(NOLOCK("5") "------------[ cut here ]------------"); - print(LOG_SOH "\7""0" "%s at %s:%d", mode_to_string[(int)mode], - fileorigin, lineorigin); - vaprint(_fmt, ap); - print(LOG_SOH "\7""7" "Call Trace:"); - for(int i = 0; i < backtrace_count; ++i) { - print(NOLOCK("7") " [0x%016x] %s", backtrace_addresses[i], - backtrace_symbolnames[i]); - } - if(mainpid == pid){ - print(NOLOCK("7") " "); - } else { - print(NOLOCK("7") " ", - subsystem_get_name(pid), pid); - } - - /* if we are going to die, we dont really need to clean up */ - if(mode == PANICMODE_DIE) { - kill(0, SIGTERM); - raise(SIGTERM); - exit(0); - } - - print(NOLOCK("5") "------------[ cut here ]------------"); - - console_lock = 0; - free(_fmt); - free(backtrace_symbolnames); - free(backtrace_addresses); - va_end(ap); - - if(mode == PANICMODE_DEBUGONLY) - return; - - if(pid != mainpid && mode == PANICMODE_RESPAWN) { - /* we want to let the main process handle the rest */ - subsystem_change_mode(pid, mode); - syscall(SYS_exit_group, 0); - } -} - -int print(const char *fmt, ...) -{ - int ret = 0; - va_list ap; - va_start(ap, fmt); - ret = vaprint(fmt, ap); - va_end(ap); - return ret; -} diff --git a/init/subsys.c b/init/subsys.c deleted file mode 100644 index 704678f..0000000 --- a/init/subsys.c +++ /dev/null @@ -1,180 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#define MAX_SUBSYSTEMS 32 -#define MAX_RESPAWN 3 - -struct subsystem_info { - char *fn_name; - int (*fn)(void); - int pid; - void *stack; - char mode; - int respawn_count; -}; - -extern long stack_size; -extern int mainpid; -static struct subsystem_info *subsystems[MAX_SUBSYSTEMS + 1]; -int subsystem_count = 0; - -static int __subsystem_entry(struct subsystem_info *info) -{ - /* entry point from clone(). we setup the process name so we know - what we are looking at from a glance in a ps view or htop or - whatever. */ - char *name = malloc(16 * sizeof(char)); - snprintf(name, 16, NAME_SHORTHAND ": %s", info->fn_name); - name[15] = '\0'; - prctl(PR_SET_NAME, name); - free(name); - - /* clear signal handlers so SIGTERM is no longer caught */ - static sigset_t set; - sigprocmask(SIG_SETMASK, &set, NULL); - - int ret = info->fn(); - - return ret; -} - -char *subsystem_get_name(int pid) -{ - for(int i = 0; i < MAX_SUBSYSTEMS; ++i) { - struct subsystem_info *subsystem = subsystems[i]; - if(!subsystem || subsystem->pid != pid) - continue; - - return subsystem->fn_name; - } - return 0; -} - -int subsystem_change_mode(int pid, char mode) -{ - for(int i = 0; i < MAX_SUBSYSTEMS; ++i) { - struct subsystem_info *subsystem = subsystems[i]; - if(!subsystem || subsystem->pid != pid) - continue; - - subsystem->mode = mode; - return 0; - } - - return 1; -} - -int subsystem_handle_term(int pid) -{ - for(int i = 0; i < MAX_SUBSYSTEMS; ++i) { - struct subsystem_info *subsystem = subsystems[i]; - if(!subsystem || subsystem->pid != pid) - continue; - - if(subsystem->mode == PANICMODE_RESPAWN - && subsystem->respawn_count < MAX_RESPAWN) { - ++(subsystem->respawn_count); - - int pid = clone((int (*)(void *))__subsystem_entry, - (void *)((long)(subsystem->stack) + stack_size), - CLONE_FILES | CLONE_VM | SIGCHLD, subsystem); - subsystem->pid = pid; - if(pid < 0) { - print(LOG_CRIT "subsys: cannot re-start subsystem %s: " - "clone failed (errno %d)", subsystem->fn_name, errno); - if(munmap(subsystem->stack, stack_size) < 0) - print(LOG_CRIT "subsys: failed to deallocate " - "stack for subsystem %s (%d) (errno %d)", - subsystem->fn_name, pid, errno); - free(subsystem); - return 0; - } - - subsystem->mode = 'o'; - return 0; - } else if(subsystem->mode == PANICMODE_RESPAWN) { - panic("subsys: exceeded maximum respawn count for subsystem " - "%s (%d)", subsystem->fn_name, subsystem->pid); - } - - if(munmap(subsystem->stack, stack_size) < 0) - print(LOG_CRIT "subsys: failed to deallocate stack " - "for subsystem %s (%d) (errno %d)", - subsystem->fn_name, pid, errno); - subsystems[i] = 0; - --subsystem_count; - free(subsystem); - - return 0; - } - - return 1; -} - -int __impl_start_subsystem(char *fn_name, int (*fn)(void)) -{ - if(getpid() != mainpid) { - print(LOG_CRIT "subsys: cannot perform subsystem inception " - "(attempted from %d)", getpid()); - return 1; - } - if(subsystem_count >= MAX_SUBSYSTEMS) { - print(LOG_CRIT "subsys: cannot start subsystem %s: " - "reached maximum number of subsystems", fn_name); - return 1; - } - - /* because CLONE_VM is being set, our stack is not duplicated and - therefore we need to map a stack */ - void *stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_STACK | MAP_PRIVATE, -1, 0); - if((long)stack <= 0) { - print(LOG_CRIT "subsys: cannot start subsystem %s: " - "failed to allocate stack (errno %d)", fn_name, errno); - return 1; - } - - /* the libc gods have graced us with the ability to pass one (1) arg - to the function. struct required. the absence of a free is not a - memory leak because we free it above. */ - struct subsystem_info *info = malloc(sizeof(struct subsystem_info)); - info->fn_name = fn_name; - info->fn = fn; - info->stack = stack; - info->mode = 'o'; - info->respawn_count = 0; - - int pid = clone((int (*)(void *))__subsystem_entry, - (void *)((long)stack + stack_size), - CLONE_FILES | CLONE_VM | SIGCHLD, info); - info->pid = pid; - if(pid < 0) { - print(LOG_CRIT "subsys: cannot start subsystem %s: " - "clone failed (errno %d)", fn_name, errno); - munmap(stack, stack_size); - free(info); - return 1; - } - - for(int i = 0; i < MAX_SUBSYSTEMS; ++i) { - if(!subsystems[i]) { - subsystems[i] = info; - ++subsystem_count; - break; - } - } - - return 0; -} diff --git a/net/api.c b/net/api.c deleted file mode 100644 index be36866..0000000 --- a/net/api.c +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include -#include -#include - -#include - -#define __API_INTERNAL -#include -#include - -extern char *token; - -int http_request(HTTPMethod method, char *url, - struct curl_slist *_Nullable headers, char *writebuf, size_t bufsiz) -{ - int inputpipe[2]; - int outputpipe[2]; - - if(pipe(inputpipe) < 0) - return -(errno << 8); - if(pipe(outputpipe) < 0) - return -(errno << 8); - - if(writebuf && bufsiz > 0) - write(inputpipe[1], writebuf, bufsiz); - close(inputpipe[1]); - - FILE *input_read = fdopen(inputpipe[0], "r"); - FILE *output_write = fdopen(outputpipe[1], "w"); - - int ret = outputpipe[0]; - - CURL *job = curl_easy_init(); - if(job == NULL) - panic("api: curl_easy_init failed"); - - curl_easy_setopt(job, CURLOPT_URL, url); - curl_easy_setopt(job, CURLOPT_READDATA, input_read); - curl_easy_setopt(job, CURLOPT_WRITEDATA, output_write); - char *requestmethod = "GET"; - switch(method) { - case HTTP_PATCH: - requestmethod = "PATCH"; - break; - case HTTP_DELETE: - requestmethod = "DELETE"; - break; - case HTTP_PUT: - requestmethod = "PUT"; - break; - case HTTP_POST: - requestmethod = "POST"; - break; - case HTTP_GET: /* fallthrough */ - default: - break; - } - curl_easy_setopt(job, CURLOPT_CUSTOMREQUEST, requestmethod); - if(headers) - curl_easy_setopt(job, CURLOPT_HTTPHEADER, headers); - CURLcode res = curl_easy_perform(job); - - if(res > 0) { - close(outputpipe[0]); - ret = -res; - } - - curl_easy_cleanup(job); - fclose(input_read); - fclose(output_write); - return ret; -} - -int api_request(HTTPMethod method, char *url, - struct curl_slist *_Nullable headers, char *writebuf, size_t bufsiz) -{ - char *new_url = calloc((strlen("https://discord.com/api") + strlen(url) + 1), - sizeof(char)); - strcpy(new_url, "https://discord.com/api"); - strcat(new_url, url); - struct curl_slist *headers_auth = curl_slist_append(headers, token); - int ret = http_request(method, new_url, headers_auth, writebuf, bufsiz); - free(new_url); - curl_slist_free_all(headers_auth); - return ret; -} diff --git a/net/net.c b/net/net.c deleted file mode 100644 index ee3b043..0000000 --- a/net/net.c +++ /dev/null @@ -1,203 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -extern void ws_handle_event(cJSON *event); -extern void ws_send_heartbeat(); -CURL *ws_handle; -char *gateway_url; - -int net_subsystem(void) -{ - if(!gateway_url) - panic("net: gateway url invalid"); - - /* Initialise CURL */ - ws_handle = curl_easy_init(); - - curl_easy_setopt(ws_handle, CURLOPT_URL, gateway_url); - curl_easy_setopt(ws_handle, CURLOPT_CONNECT_ONLY, 2L); - - CURLcode ret = curl_easy_perform(ws_handle); - - if(ret > 0) { - panic("net: cannot open websocket: %s", curl_easy_strerror(ret)); - } - - int ws_sockfd; - if((ret = curl_easy_getinfo(ws_handle, - CURLINFO_ACTIVESOCKET, &ws_sockfd)) != CURLE_OK) - panic("net: curl cannot get active socket: " - "%s", curl_easy_strerror(ret)); - - - /* Block ALRM */ - sigset_t *set = malloc(sizeof(sigset_t)); - sigemptyset(set); - sigaddset(set, SIGALRM); - sigprocmask(SIG_BLOCK, set, NULL); - int alrmfd = signalfd(-1, set, 0); - free(set); - - /* Prepare poll */ - struct pollfd pollarray[2] = { - { - .fd = ws_sockfd, - .events = POLLIN, - .revents = POLLIN - }, - { - .fd = alrmfd, - .events = POLLIN, - .revents = 0 - } - }; - - struct pollfd *sockpoll = &(pollarray[0]); - struct pollfd *alrmpoll = &(pollarray[1]); - - /* Misc. variables */ - char *inbuf = malloc(1<<16 * sizeof(char)); - size_t rlen; - const struct curl_ws_frame *meta; - - errno = 0; - do { - if((sockpoll->revents & POLLIN) == POLLIN) { - ret = curl_ws_recv(ws_handle, inbuf, 1<<16, &rlen, &meta); - /* sometimes only SSL information gets sent through, so no actual - data is received. curl uses NONBLOCK internally so it lets us - know if there is no more data remaining */ - if(ret == CURLE_AGAIN) - continue; - if(ret != CURLE_OK) { - print(LOG_ERR "net: encountered error while reading socket: " - "%s", curl_easy_strerror(ret)); - break; - } - - /* TODO: partial frames */ - if((meta->offset | meta->bytesleft) > 0) { - print(LOG_ERR "net: dropped partial frame"); - continue; - } - - cJSON *event = cJSON_ParseWithLength(inbuf, rlen); - if(!event) { - print(LOG_ERR "net: dropped malformed frame"); - continue; - } - ws_handle_event(event); - cJSON_Delete(event); - } else if((sockpoll->revents & - (POLLRDHUP | POLLERR | POLLHUP | POLLNVAL)) > 0) { - break; - } - - if((alrmpoll->revents & POLLIN) == POLLIN) { - struct signalfd_siginfo siginfo; - read(alrmfd, &siginfo, sizeof(struct signalfd_siginfo)); - ws_send_heartbeat(); - } - } while(poll(pollarray, 2, -1) >= 0); - - if(errno > 0) { - print(LOG_ERR "net: poll: %s", strerror(errno)); - } - - free(inbuf); - - curl_easy_cleanup(ws_handle); - - panic("net: websocket closed unexpectedly"); - - return 0; -} - -void net_get_gateway_url() -{ - /* determine if websockets are supported */ - curl_version_info_data *curl_version = - curl_version_info(CURLVERSION_NOW); - const char * const* curl_protocols = curl_version->protocols; - int wss_supported = 0; - for(int i = 0; curl_protocols[i]; ++i) { - if(strcmp(curl_protocols[i], "wss") == 0) { - wss_supported = 1; - break; - } - } - - if(!wss_supported) - panic("net: wss not supported by libcurl"); - - /* fetch preferred url from discord */ - int fd = api_get("/gateway/bot", NULL, NULL, 0); - if(fd < 0) { - print(LOG_ERR "net: cannot get gateway url: %s", curl_easy_strerror(-fd)); - goto assume; - } - - char buf[512]; - int buf_length = read(fd, buf, 512); - close(fd); - - cJSON *gateway_info = cJSON_ParseWithLength(buf, buf_length); - cJSON *gateway_url_json = - cJSON_GetObjectItemCaseSensitive(gateway_info, "url"); - if(!cJSON_IsString(gateway_url_json) || - gateway_url_json->valuestring == NULL) { - - cJSON *gateway_message = - cJSON_GetObjectItemCaseSensitive(gateway_info, "message"); - - if(cJSON_IsString(gateway_message)) { - print(LOG_ERR "net: cannot get gateway url from api: " - "%s: assuming url", cJSON_GetStringValue(gateway_message)); - } else { - print(LOG_ERR "net: cannot get gateway url from api " - "(unknown error): assuming url"); - } - cJSON_Delete(gateway_info); - goto assume; - } - - /* curl requires websocket secure URLs to begin with WSS instead - of wss, so we fix up the received url for curl */ - gateway_url = calloc(strlen(gateway_url_json->valuestring) + 1, - sizeof(char)); - strcpy(gateway_url, gateway_url_json->valuestring); - gateway_url[0] = 'W'; - gateway_url[1] = 'S'; - gateway_url[2] = 'S'; - - cJSON_Delete(gateway_info); - return; - -assume: - gateway_url = calloc(strlen("WSS://gateway.discord.gg") + 1, - sizeof(char)); - strcpy(gateway_url, "WSS://gateway.discord.gg"); - return; -} -l1_initcall(net_get_gateway_url); - -void net_initcall() -{ - start_subsystem(net_subsystem); -} -l2_initcall(net_initcall); diff --git a/net/ws.c b/net/ws.c deleted file mode 100644 index 3a8d512..0000000 --- a/net/ws.c +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include - -#include - -extern CURL *ws_handle; -long last_sequence = -1; -struct timeval heartbeat_time; - -void ws_send_heartbeat() -{ - char buf[128] = "{\"op\":1,\"d\":null}"; - if(last_sequence > 0) - snprintf(buf, 128, "{\"op\":1,\"d\":%ld}", last_sequence); - size_t sent; - curl_ws_send(ws_handle, buf, strnlen(buf, 128), &sent, 0, CURLWS_TEXT); - - /* if we receive a heartbeat request from discord, we need to fix - the itimer so we don't send another one before the desired - heartbeat interval. if our itimer is off more than 2 seconds - then we fix it up and reset it */ - struct itimerval itimer; - getitimer(ITIMER_REAL, &itimer); - if(itimer.it_value.tv_sec < heartbeat_time.tv_sec - 2) { - itimer.it_value = heartbeat_time; - setitimer(ITIMER_REAL, &itimer, NULL); - } -} - -void ws_handle_event(cJSON *event) -{ - int op = cJSON_GetObjectItem(event, "op")->valueint; - cJSON *data = cJSON_GetObjectItem(event, "d"); - switch(op) { - case 1: /* Heartbeat request */ - ws_send_heartbeat(); - break; - case 10: ; /* Hello */ - int heartbeat_wait = cJSON_GetObjectItem(data, - "heartbeat_interval")->valueint; - float jitter = (float)rand() / (RAND_MAX * 1.0f); - - heartbeat_time.tv_sec = heartbeat_wait / 1000; - heartbeat_time.tv_usec = (heartbeat_wait % 1000) * 1000; - struct timeval jitter_time = { - .tv_sec = heartbeat_time.tv_sec * jitter, - .tv_usec = heartbeat_time.tv_usec * jitter, - }; - struct itimerval new_itimer = { - .it_interval = heartbeat_time, - .it_value = jitter_time - }; - setitimer(ITIMER_REAL, &new_itimer, NULL); - break; - case 11: /* Heartbeat ACK */ - break; - default: - print(LOG_ERR "ws: received unknown WS opcode %d", op); - break; - } -} diff --git a/util/init.c b/util/init.c new file mode 100644 index 0000000..77cd4eb --- /dev/null +++ b/util/init.c @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +extern int subsystem_handle_term(int pid); +extern int subsystem_count; +int mainpid = 0; +long stack_size = 8192 * 512; + +/* For some reason, I get SIGSEGV'd when running because a random-ass + byte was inserted where it isnt supposed to be. Added a safety byte + because I cannot be asked to try to figure out how to do this cleanly. */ +static unsigned long __1bsafebuf + __attribute__((used)) __attribute__((section(".1bsafebuf.init"))) = 0; + +/* We start initcall levels at [1] instead of [0], so we must adjust + in code for this minor design choice. Math is done on the level passed + through i.e. do_initcall_level so that you can call it with (1) and have + the expected initcall (l1_initcall) run. */ +extern initcall_entry_t __initcall1_start[]; +extern initcall_entry_t __initcall2_start[]; +extern initcall_entry_t __initcall3_start[]; +extern initcall_entry_t __initcall4_start[]; +extern initcall_entry_t __initcall5_start[]; +extern initcall_entry_t __initcall_end[]; + +static initcall_entry_t *initcall_levels[] = { + __initcall1_start, + __initcall2_start, + __initcall3_start, + __initcall4_start, + __initcall5_start, + __initcall_end, +}; + +static void do_initcall_level(int level) +{ + initcall_entry_t *fn; + + for (fn = initcall_levels[level - 1]; + fn < initcall_levels[level]; + fn++) + initcall_from_entry(fn)(); +} + +static void do_initcalls(void) +{ + unsigned long level; + for (level = 1; level < ARRAY_SIZE(initcall_levels); level++) { + do_initcall_level(level); + } +} + +static void doenv(char *path) +{ + int fd = open(path, O_RDONLY); + if(fd < 0) + return; + + struct stat statbuf; + if(fstat(fd, &statbuf) < 0) + return; + + char *file_mmap = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if(file_mmap == NULL) + return; + + char *file = malloc(statbuf.st_size + 1); + file[statbuf.st_size + 1] = 0; + memcpy(file, file_mmap, statbuf.st_size); + munmap(file_mmap, statbuf.st_size); + + int offset = 0; + while(1) { + char *line = &(file[offset]); + if(*line == '\0') + break; + + char *eol = strchrnul(line, '\n'); + *eol = '\0'; + if(*line == '#') + goto nextline; + + char *divider = strchr(line, '='); + if(divider == NULL) + goto nextline; + + *divider = '\0'; + setenv(line, divider + 1, 0); + +nextline: + offset += (eol - line) + 1; + continue; + } + + free(file); +} + +int main(void) +{ + /* Hello, World! */ + + /* set mainpid for the subsystem service so it is fully accessible + during l1 */ + mainpid = getpid(); + + /* set stack_size for subsystem service */ + struct rlimit *stack_rlimit = malloc(sizeof(struct rlimit)); + getrlimit(RLIMIT_STACK, stack_rlimit); + if(stack_rlimit->rlim_cur != RLIM_INFINITY) { + stack_size = MIN(stack_rlimit->rlim_cur, stack_size); + } + free(stack_rlimit); + + /* configure signal handlers early to prevent race condition where subsystems + can terminate main process on accident, and disable Terminated output during + early-mode panic */ + static sigset_t set; + sigaddset(&set, SIGCHLD); + sigaddset(&set, SIGINT); + sigaddset(&set, SIGTERM); + sigprocmask(SIG_BLOCK, &set, NULL); + + /* use .env files if present */ + doenv(".env"); + + /* find directory of self and use env from there if it exists */ + { + char *buf = calloc(PATH_MAX, sizeof(char)); + ssize_t self_size = readlink("/proc/self/exe", buf, PATH_MAX); + if(self_size + strlen(".env") + 1 > PATH_MAX) + goto skip_self; + + char *lastslash = strrchr(buf, '/'); + *lastslash = '\0'; + + char *cwd = get_current_dir_name(); + int cwd_is_exec_dir = strcmp(buf, cwd) == 0; + free(cwd); + if(cwd_is_exec_dir) + goto skip_self; + + strcat(buf, "/.env"); + doenv(buf); +skip_self: + free(buf); + } + + /* init curl */ + if(curl_global_init(CURL_GLOBAL_DEFAULT)) + panic("init: curl init failed"); + + /* init random seed */ + srand(time(NULL)); + + /* Perform initcalls */ + do_initcalls(); + + /* Reaper. Much like init. */ + + siginfo_t siginfo; + while(subsystem_count > 0) { + sigwaitinfo(&set, &siginfo); + int sig = siginfo.si_signo; + switch(sig) { + case SIGCHLD: ; + int process = 0; + while((process = waitpid(-1, NULL, WNOHANG)) > 0) + if(subsystem_handle_term(process) > 0) + print(LOG_WARNING "init: failed to reap process %d", + process); + if(siginfo.si_status != 0) { + panic("init: process %d exited with non-zero status (%d)", siginfo.si_pid, siginfo.si_status); + } + break; + case SIGINT: + panic("init: keyboard interrupt"); + break; + case SIGTERM: + exit(0); + break; + default: + break; + } + } + + panic("init: no more subsystems"); +} diff --git a/util/log.c b/util/log.c new file mode 100644 index 0000000..afbc2e3 --- /dev/null +++ b/util/log.c @@ -0,0 +1,210 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +extern int subsystem_change_mode(int pid, char mode); +extern char *subsystem_get_name(int pid); +extern int mainpid; + +static const char *colors[] = { + [EMERG_LOGLEVEL] = ANSI_BLINK ANSI_REVERSE ANSI_BOLD ANSI_RED, + [ALERT_LOGLEVEL] = ANSI_REVERSE ANSI_BOLD ANSI_RED, + [CRIT_LOGLEVEL] = ANSI_BOLD ANSI_RED, + [ERR_LOGLEVEL] = ANSI_RED, + [WARNING_LOGLEVEL] = ANSI_BOLD, + [NOTICE_LOGLEVEL] = ANSI_BRIGHT_WHITE, + [INFO_LOGLEVEL] = ANSI_RESET, + [DEBUG_LOGLEVEL] = ANSI_ITALIC ANSI_BRIGHT_BLUE, +}; + +static const char *mode_to_string[] = { + [PANICMODE_DEBUGONLY] = "subsystem OOPS", + [PANICMODE_RESPAWN] = "subsystem failure", + [PANICMODE_DIE] = "catastrophic failure", +}; + +static int console_lock = 0; + +#define MAX_TRY_COUNT 1 << 17 +static void obtain_console_lock(void) +{ + int try_count = 0; + register int rax asm("rax"); +retry: + while(console_lock && try_count <= MAX_TRY_COUNT) + try_count += 1; + + asm("mov %0, 1 \n" + "xchg %0, %1" : "=r" (rax), "=m" (console_lock)); + + if(rax > 0 && try_count <= MAX_TRY_COUNT) + goto retry; + + if(try_count > MAX_TRY_COUNT) { + print(LOG_SOH "\3" "4" "log: broken console lock"); + } + + return; +} + +static int vaprint(const char *fmt, va_list ap) +{ + int loglevel = DEFAULT_LOGLEVEL; + int dolocks = 1; + int parsecolon = 1; + if(fmt[0] == LOG_SOH_ASCII) { + loglevel = (fmt[2] - 0x30) % 10; + char flags = fmt[1]; + if(flags & 1 << 1) + dolocks = 0; + if(flags & 1 << 2) + parsecolon = 0; + fmt += 3; + } + + /* not going to be printed? dont bother! */ + if(loglevel > CONSOLE_LOGLEVEL) + return 0; + + /* we essentially print the user's raw input to its own buffer, + later we will parse it and print out ANSI colors and what not */ + char buf[512]; + + vsnprintf(buf, 512, fmt, ap); + buf[512 - 1] = '\0'; + + size_t colon = 0; + if(parsecolon) { + for(; colon < strlen(buf); ++colon) { + if(buf[colon] == ':') + break; + } + } + + char tsbuf[64] = "\0"; + struct timeval time; + gettimeofday(&time, NULL); + snprintf(tsbuf, sizeof(tsbuf), "[%5ld.%06ld] ", + (long)time.tv_sec % 100000, (long)time.tv_usec); + + /* spin lock, at the cost of architecture portability + concurrency is something that we need to adjust for, and the + console will be scrambled and unreadable if we allow writing all + at the same time. I considered simply writing all at once, but + ended up just not caring enough to the point where spinlocks + prevail. */ + if(dolocks) + obtain_console_lock(); + + + /* we want to support stuff without colons, but frankly I havent + tested this at time of writing. will find out later */ + writeputs(ANSI_RESET ANSI_GREEN); + writeputs(tsbuf); + writeputs(ANSI_RESET); + if(parsecolon && buf[colon] == ':') { + writeputs(colors[loglevel]); + writeputs(ANSI_YELLOW); + write(STDOUT_FILENO, buf, colon); + writeputs(ANSI_RESET); + } + writeputs(colors[loglevel]); + if(colon && *(buf + colon)) { + writeputs(buf + colon); + } else { + writeputs(buf); + } + writeputs(ANSI_RESET); + write(STDOUT_FILENO, "\n", 1); + if(dolocks) + console_lock = 0; + return 0; +} + +void _panic(const char *fileorigin, + const int lineorigin, + const char *fmt, ...) +{ + char mode = PANICMODE_DIE; + int pid = getpid(); + if(fmt[0] == LOG_SOH_ASCII) { + mode = fmt[1]; + /* cannot respawn main thread */ + if(pid == mainpid && mode == PANICMODE_RESPAWN) + mode = PANICMODE_DIE; + fmt += 2; + } + +#define NOLOCK(loglevel) LOG_SOH "\3" loglevel + va_list ap; + va_start(ap, fmt); + char *_fmt = malloc(strlen(fmt) + 4 * sizeof(char)); + sprintf(_fmt, NOLOCK("1") "%s", fmt); + + void **backtrace_addresses = malloc(sizeof(void*) * 32); + int backtrace_count = backtrace(backtrace_addresses, 32); + char **backtrace_symbolnames = + backtrace_symbols(backtrace_addresses, backtrace_count); + + obtain_console_lock(); + + print(NOLOCK("5") "------------[ cut here ]------------"); + print(LOG_SOH "\7""0" "%s at %s:%d", mode_to_string[(int)mode], + fileorigin, lineorigin); + vaprint(_fmt, ap); + print(LOG_SOH "\7""7" "Call Trace:"); + for(int i = 0; i < backtrace_count; ++i) { + print(NOLOCK("7") " [0x%016x] %s", backtrace_addresses[i], + backtrace_symbolnames[i]); + } + if(mainpid == pid){ + print(NOLOCK("7") " "); + } else { + print(NOLOCK("7") " ", + subsystem_get_name(pid), pid); + } + + /* if we are going to die, we dont really need to clean up */ + if(mode == PANICMODE_DIE) { + kill(0, SIGTERM); + raise(SIGTERM); + exit(0); + } + + print(NOLOCK("5") "------------[ cut here ]------------"); + + console_lock = 0; + free(_fmt); + free(backtrace_symbolnames); + free(backtrace_addresses); + va_end(ap); + + if(mode == PANICMODE_DEBUGONLY) + return; + + if(pid != mainpid && mode == PANICMODE_RESPAWN) { + /* we want to let the main process handle the rest */ + subsystem_change_mode(pid, mode); + syscall(SYS_exit_group, 0); + } +} + +int print(const char *fmt, ...) +{ + int ret = 0; + va_list ap; + va_start(ap, fmt); + ret = vaprint(fmt, ap); + va_end(ap); + return ret; +} diff --git a/util/net.c b/util/net.c new file mode 100644 index 0000000..98f371a --- /dev/null +++ b/util/net.c @@ -0,0 +1,383 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +/* functions */ +int http_request(HTTPMethod method, char *url, + struct curl_slist *headers, char *writebuf, size_t bufsiz); +int api_request(HTTPMethod method, char *url, + struct curl_slist *headers, char *writebuf, size_t bufsiz); +static void setup_token_header(); + +static void ws_send_heartbeat(); +static void ws_handle_event(cJSON *event); + +int net_subsystem(); +void net_get_gateway_url(); + +/* variables */ +static CURL *ws_handle; +static char *gateway_url; +static char *token_header; + +static long last_sequence = -1; +static struct timeval heartbeat_time; + +int http_request(HTTPMethod method, char *url, + struct curl_slist *headers, char *writebuf, size_t bufsiz) +{ + int inputpipe[2]; + int outputpipe[2]; + + if(pipe(inputpipe) < 0) + return -(errno << 8); + if(pipe(outputpipe) < 0) + return -(errno << 8); + + if(writebuf && bufsiz > 0) + write(inputpipe[1], writebuf, bufsiz); + close(inputpipe[1]); + + FILE *input_read = fdopen(inputpipe[0], "r"); + FILE *output_write = fdopen(outputpipe[1], "w"); + + int ret = outputpipe[0]; + + CURL *job = curl_easy_init(); + if(job == NULL) + panic("api: curl_easy_init failed"); + + curl_easy_setopt(job, CURLOPT_URL, url); + curl_easy_setopt(job, CURLOPT_READDATA, input_read); + curl_easy_setopt(job, CURLOPT_WRITEDATA, output_write); + char *requestmethod = "GET"; + switch(method) { + case HTTP_PATCH: + requestmethod = "PATCH"; + break; + case HTTP_DELETE: + requestmethod = "DELETE"; + break; + case HTTP_PUT: + requestmethod = "PUT"; + break; + case HTTP_POST: + requestmethod = "POST"; + break; + case HTTP_GET: /* fallthrough */ + default: + break; + } + curl_easy_setopt(job, CURLOPT_CUSTOMREQUEST, requestmethod); + if(headers) + curl_easy_setopt(job, CURLOPT_HTTPHEADER, headers); + CURLcode res = curl_easy_perform(job); + + if(res > 0) { + close(outputpipe[0]); + ret = -res; + } + + curl_easy_cleanup(job); + fclose(input_read); + fclose(output_write); + return ret; +} + +static void setup_token_header() +{ + if(token_header != NULL) + return; + char *token = getenv("TOKEN"); + if(!token) + panic("api: cannot find TOKEN in env"); + token_header = calloc(strlen(token) + strlen("Authorization: Bot ") + 1, sizeof(char)); + strcpy(token_header, "Authorization: Bot "); + strcat(token_header, token); +} +l1_initcall(setup_token_header); + +int api_request(HTTPMethod method, char *url, + struct curl_slist *headers, char *writebuf, size_t bufsiz) +{ + char *new_url = calloc((strlen("https://discord.com/api") + strlen(url) + 1), + sizeof(char)); + strcpy(new_url, "https://discord.com/api"); + strcat(new_url, url); + if(token_header == NULL) + setup_token_header(); + struct curl_slist *headers_auth = curl_slist_append(headers, token_header); + int ret = http_request(method, new_url, headers_auth, writebuf, bufsiz); + free(new_url); + curl_slist_free_all(headers_auth); + return ret; +} + +static void ws_send_heartbeat() +{ + char buf[128] = "{\"op\":1,\"d\":null}"; + if(last_sequence > 0) + snprintf(buf, 128, "{\"op\":1,\"d\":%ld}", last_sequence); + size_t sent; + curl_ws_send(ws_handle, buf, strnlen(buf, 128), &sent, 0, CURLWS_TEXT); + + /* if we receive a heartbeat request from discord, we need to fix + the itimer so we don't send another one before the desired + heartbeat interval. if our itimer is off more than 2 seconds + then we fix it up and reset it */ + struct itimerval itimer; + getitimer(ITIMER_REAL, &itimer); + if(itimer.it_value.tv_sec < heartbeat_time.tv_sec - 2) { + itimer.it_value = heartbeat_time; + setitimer(ITIMER_REAL, &itimer, NULL); + } +} + +static void ws_handle_event(cJSON *event) +{ + int op = cJSON_GetObjectItem(event, "op")->valueint; + cJSON *data = cJSON_GetObjectItem(event, "d"); + switch(op) { + case 0: /* Event dispatch */ + break; + case 1: /* Heartbeat request */ + ws_send_heartbeat(); + break; + case 9: /* Invalid Session */ + if(!cJSON_IsTrue(data)) { + /* discord sets data to true if we can reconnect, + but in this statement it is false, so we just die */ + /* note: discord closes the websocket after sending this, + so we let our ws code accept and handle the error */ + break; + } + /* FALLTHROUGH */ + case 7: /* Reconnect */ + /* TODO */ + panic("ws: cannot reconnect to ws after failure"); + break; + case 10: ; /* Hello */ + int heartbeat_wait = cJSON_GetObjectItem(data, + "heartbeat_interval")->valueint; + float jitter = (float)rand() / (RAND_MAX * 1.0f); + + heartbeat_time.tv_sec = heartbeat_wait / 1000; + heartbeat_time.tv_usec = (heartbeat_wait % 1000) * 1000; + struct timeval jitter_time = { + .tv_sec = heartbeat_time.tv_sec * jitter, + .tv_usec = heartbeat_time.tv_usec * jitter, + }; + struct itimerval new_itimer = { + .it_interval = heartbeat_time, + .it_value = jitter_time + }; + setitimer(ITIMER_REAL, &new_itimer, NULL); + break; + case 11: /* Heartbeat ACK */ + print(LOG_DEBUG "ws: heartbeat ACK"); + break; + default: + print(LOG_ERR "ws: received unknown WS opcode %d", op); + break; + } +} + +int net_subsystem(void) +{ + if(!gateway_url) + panic("net: gateway url invalid"); + + /* Initialise CURL */ + ws_handle = curl_easy_init(); + + curl_easy_setopt(ws_handle, CURLOPT_URL, gateway_url); + curl_easy_setopt(ws_handle, CURLOPT_CONNECT_ONLY, 2L); + + CURLcode ret = curl_easy_perform(ws_handle); + + if(ret > 0) { + panic("net: cannot open websocket: %s", curl_easy_strerror(ret)); + } + + int ws_sockfd; + if((ret = curl_easy_getinfo(ws_handle, + CURLINFO_ACTIVESOCKET, &ws_sockfd)) != CURLE_OK) + panic("net: curl cannot get active socket: " + "%s", curl_easy_strerror(ret)); + + + /* Block ALRM */ + sigset_t *set = malloc(sizeof(sigset_t)); + sigemptyset(set); + sigaddset(set, SIGALRM); + sigprocmask(SIG_BLOCK, set, NULL); + int alrmfd = signalfd(-1, set, 0); + free(set); + + /* Prepare poll */ + struct pollfd pollarray[2] = { + { + .fd = ws_sockfd, + .events = POLLIN, + .revents = POLLIN + }, + { + .fd = alrmfd, + .events = POLLIN, + .revents = 0 + } + }; + + struct pollfd *sockpoll = &(pollarray[0]); + struct pollfd *alrmpoll = &(pollarray[1]); + + /* Misc. variables */ + char *inbuf = malloc(1<<16 * sizeof(char)); + size_t rlen; + const struct curl_ws_frame *meta; + + errno = 0; + do { + if((sockpoll->revents & POLLIN) == POLLIN) { + ret = curl_ws_recv(ws_handle, inbuf, 1<<16, &rlen, &meta); + /* sometimes only SSL information gets sent through, so no actual + data is received. curl uses NONBLOCK internally so it lets us + know if there is no more data remaining */ + if(ret == CURLE_AGAIN) + goto sockpoll_continue; + if(ret != CURLE_OK) { + print(LOG_ERR "net: encountered error while reading socket: " + "%s", curl_easy_strerror(ret)); + break; + } + + /* TODO: partial frames */ + if((meta->offset | meta->bytesleft) > 0) { + print(LOG_ERR "net: dropped partial frame"); + goto sockpoll_continue; + } + + switch(meta->flags) { + case(CURLWS_PING): + curl_ws_send(ws_handle, NULL, 0, NULL, 0, CURLWS_PONG); + goto sockpoll_continue; + case(CURLWS_CLOSE): + default: + break; + } + + cJSON *event = cJSON_ParseWithLength(inbuf, rlen); + if(!event) { + print(LOG_ERR "net: dropped malformed frame"); + goto sockpoll_continue; + } + ws_handle_event(event); + cJSON_Delete(event); + } else if((sockpoll->revents & + (POLLRDHUP | POLLERR | POLLHUP | POLLNVAL)) > 0) { + break; + } +sockpoll_continue: + + if((alrmpoll->revents & POLLIN) == POLLIN) { + struct signalfd_siginfo siginfo; + read(alrmfd, &siginfo, sizeof(struct signalfd_siginfo)); + ws_send_heartbeat(); + } + } while(poll(pollarray, 2, -1) >= 0); + + if(errno > 0) { + print(LOG_ERR "net: poll: %s", strerror(errno)); + } + + free(inbuf); + + curl_easy_cleanup(ws_handle); + + panic("net: websocket closed unexpectedly"); + + return 0; +} /* net_subsystem */ +declare_subsystem(net_subsystem); + +void net_get_gateway_url() +{ + /* determine if websockets are supported */ + curl_version_info_data *curl_version = + curl_version_info(CURLVERSION_NOW); + const char * const* curl_protocols = curl_version->protocols; + int wss_supported = 0; + for(int i = 0; curl_protocols[i]; ++i) { + if(strcmp(curl_protocols[i], "wss") == 0) { + wss_supported = 1; + break; + } + } + + if(!wss_supported) + panic("net: wss not supported by libcurl"); + + /* fetch preferred url from discord */ + int fd = api_get("/gateway/bot", NULL, NULL, 0); + if(fd < 0) { + print(LOG_ERR "net: cannot get gateway url: %s", curl_easy_strerror(-fd)); + goto assume; + } + + char buf[512]; + int buf_length = read(fd, buf, 512); + close(fd); + + cJSON *gateway_info = cJSON_ParseWithLength(buf, buf_length); + cJSON *gateway_url_json = + cJSON_GetObjectItemCaseSensitive(gateway_info, "url"); + if(!cJSON_IsString(gateway_url_json) || + gateway_url_json->valuestring == NULL) { + + cJSON *gateway_message = + cJSON_GetObjectItemCaseSensitive(gateway_info, "message"); + + if(cJSON_IsString(gateway_message)) { + print(LOG_ERR "net: cannot get gateway url from api: " + "%s: assuming url", cJSON_GetStringValue(gateway_message)); + } else { + print(LOG_ERR "net: cannot get gateway url from api " + "(unknown error): assuming url"); + } + cJSON_Delete(gateway_info); + goto assume; + } + + /* curl requires websocket secure URLs to begin with WSS instead + of wss, so we fix up the received url for curl */ + gateway_url = calloc(strlen(gateway_url_json->valuestring) + 1, + sizeof(char)); + strcpy(gateway_url, gateway_url_json->valuestring); + gateway_url[0] = 'W'; + gateway_url[1] = 'S'; + gateway_url[2] = 'S'; + + cJSON_Delete(gateway_info); + return; + +assume: + gateway_url = calloc(strlen("WSS://gateway.discord.gg") + 1, + sizeof(char)); + strcpy(gateway_url, "WSS://gateway.discord.gg"); + return; +} +l1_initcall(net_get_gateway_url); diff --git a/util/subsys.c b/util/subsys.c new file mode 100644 index 0000000..a2dc057 --- /dev/null +++ b/util/subsys.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MAX_SUBSYSTEMS 32 +#define MAX_RESPAWN 3 + +struct subsystem_info { + char *fn_name; + int (*fn)(void); + int pid; + void *stack; + char mode; + int respawn_count; +}; + +extern long stack_size; +extern int mainpid; +static struct subsystem_info *subsystems[MAX_SUBSYSTEMS + 1]; +int subsystem_count = 0; + +static int __subsystem_entry(struct subsystem_info *info) +{ + /* entry point from clone(). we setup the process name so we know + what we are looking at from a glance in a ps view or htop or + whatever. */ + char *name = malloc(16 * sizeof(char)); + snprintf(name, 16, "DBS: %s", info->fn_name); + name[15] = '\0'; + prctl(PR_SET_NAME, name); + free(name); + + /* clear signal handlers so SIGTERM is no longer caught */ + static sigset_t set; + sigprocmask(SIG_SETMASK, &set, NULL); + + int ret = info->fn(); + + return ret; +} + +char *subsystem_get_name(int pid) +{ + for(int i = 0; i < MAX_SUBSYSTEMS; ++i) { + struct subsystem_info *subsystem = subsystems[i]; + if(!subsystem || subsystem->pid != pid) + continue; + + return subsystem->fn_name; + } + return 0; +} + +int subsystem_change_mode(int pid, char mode) +{ + for(int i = 0; i < MAX_SUBSYSTEMS; ++i) { + struct subsystem_info *subsystem = subsystems[i]; + if(!subsystem || subsystem->pid != pid) + continue; + + subsystem->mode = mode; + return 0; + } + + return 1; +} + +int subsystem_handle_term(int pid) +{ + for(int i = 0; i < MAX_SUBSYSTEMS; ++i) { + struct subsystem_info *subsystem = subsystems[i]; + if(!subsystem || subsystem->pid != pid) + continue; + + if(subsystem->mode == PANICMODE_RESPAWN + && subsystem->respawn_count < MAX_RESPAWN) { + ++(subsystem->respawn_count); + + int pid = clone((int (*)(void *))__subsystem_entry, + (void *)((long)(subsystem->stack) + stack_size), + CLONE_FILES | CLONE_VM | SIGCHLD, subsystem); + subsystem->pid = pid; + if(pid < 0) { + print(LOG_CRIT "subsys: cannot re-start subsystem %s: " + "clone failed (errno %d)", subsystem->fn_name, errno); + if(munmap(subsystem->stack, stack_size) < 0) + print(LOG_CRIT "subsys: failed to deallocate " + "stack for subsystem %s (%d) (errno %d)", + subsystem->fn_name, pid, errno); + free(subsystem); + return 0; + } + + subsystem->mode = 'o'; + return 0; + } else if(subsystem->mode == PANICMODE_RESPAWN) { + panic("subsys: exceeded maximum respawn count for subsystem " + "%s (%d)", subsystem->fn_name, subsystem->pid); + } + + if(munmap(subsystem->stack, stack_size) < 0) + print(LOG_CRIT "subsys: failed to deallocate stack " + "for subsystem %s (%d) (errno %d)", + subsystem->fn_name, pid, errno); + subsystems[i] = 0; + --subsystem_count; + free(subsystem); + + return 0; + } + + return 1; +} + +int __impl_start_subsystem(char *fn_name, int (*fn)(void)) +{ + if(getpid() != mainpid) { + print(LOG_CRIT "subsys: cannot perform subsystem inception " + "(attempted from %d)", getpid()); + return 1; + } + if(subsystem_count >= MAX_SUBSYSTEMS) { + print(LOG_CRIT "subsys: cannot start subsystem %s: " + "reached maximum number of subsystems", fn_name); + return 1; + } + + /* because CLONE_VM is being set, our stack is not duplicated and + therefore we need to map a stack */ + void *stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_STACK | MAP_PRIVATE, -1, 0); + if((long)stack <= 0) { + print(LOG_CRIT "subsys: cannot start subsystem %s: " + "failed to allocate stack (errno %d)", fn_name, errno); + return 1; + } + + /* the libc gods have graced us with the ability to pass one (1) arg + to the function. struct required. the absence of a free is not a + memory leak because we free it above. */ + struct subsystem_info *info = malloc(sizeof(struct subsystem_info)); + info->fn_name = fn_name; + info->fn = fn; + info->stack = stack; + info->mode = 'o'; + info->respawn_count = 0; + + int pid = clone((int (*)(void *))__subsystem_entry, + (void *)((long)stack + stack_size), + CLONE_FILES | CLONE_VM | SIGCHLD, info); + info->pid = pid; + if(pid < 0) { + print(LOG_CRIT "subsys: cannot start subsystem %s: " + "clone failed (errno %d)", fn_name, errno); + munmap(stack, stack_size); + free(info); + return 1; + } + + for(int i = 0; i < MAX_SUBSYSTEMS; ++i) { + if(!subsystems[i]) { + subsystems[i] = info; + ++subsystem_count; + break; + } + } + + return 0; +} -- cgit v1.2.3