nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@2: #ifdef _MSC_VER nuclear@2: #include nuclear@2: #else nuclear@2: #include nuclear@2: #endif nuclear@0: #include "logger.h" nuclear@0: nuclear@0: #if defined(unix) || defined(__unix__) || defined(__APPLE__) nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #endif nuclear@0: nuclear@0: enum targ_type { TARG_STREAM, TARG_FILE, TARG_FUNC }; nuclear@0: nuclear@0: struct log_target { nuclear@0: unsigned int msg_type; nuclear@0: enum targ_type targ_type; nuclear@0: FILE *fp; nuclear@0: void (*func)(const char*, void*); nuclear@0: void *func_cls; nuclear@0: nuclear@0: struct log_target *next; nuclear@0: }; nuclear@0: nuclear@0: static struct log_target *targ_list = (void*)1; /* uninitialized */ nuclear@0: nuclear@0: static void init_once(void) nuclear@0: { nuclear@0: if(targ_list == (void*)1) { nuclear@0: /* perform one-time init */ nuclear@0: targ_list = 0; nuclear@0: log_add_stream(LOG_INFO | LOG_DEBUG, stdout); nuclear@0: log_add_stream(LOG_WARNING | LOG_ERROR, stderr); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: static void log_string(unsigned int type, const char *str); nuclear@0: static int typecolor(int type); nuclear@0: nuclear@0: void log_clear_targets(void) nuclear@0: { nuclear@0: init_once(); nuclear@0: nuclear@0: while(targ_list) { nuclear@0: struct log_target *tmp = targ_list; nuclear@0: targ_list = targ_list->next; nuclear@0: nuclear@0: if(tmp->targ_type == TARG_FILE) { nuclear@0: fclose(tmp->fp); nuclear@0: } nuclear@0: free(tmp); nuclear@0: } nuclear@0: targ_list = 0; nuclear@0: } nuclear@0: nuclear@0: int log_add_stream(unsigned int type_mask, FILE *fp) nuclear@0: { nuclear@0: struct log_target *targ; nuclear@0: nuclear@0: init_once(); nuclear@0: nuclear@0: if(!(targ = malloc(sizeof *targ))) { nuclear@0: perror("failed to allocate memory for log target"); nuclear@0: return -1; nuclear@0: } nuclear@0: targ->msg_type = type_mask; nuclear@0: targ->targ_type = TARG_STREAM; nuclear@0: targ->fp = fp; nuclear@0: targ->next = targ_list; nuclear@0: targ_list = targ; nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: int log_add_file(unsigned int type_mask, const char *fname) nuclear@0: { nuclear@0: FILE *fp; nuclear@0: struct log_target *targ; nuclear@0: nuclear@0: init_once(); nuclear@0: nuclear@0: if(!(fp = fopen(fname, "w"))) { nuclear@0: fprintf(stderr, "failed to open logfile: %s: %s\n", fname, strerror(errno)); nuclear@0: return -1; nuclear@0: } nuclear@0: if(type_mask & (LOG_ERROR | LOG_DEBUG)) { nuclear@0: /* make error and debug logs unbuffered */ nuclear@0: setvbuf(fp, 0, _IONBF, 0); nuclear@0: } nuclear@0: if(!(targ = malloc(sizeof *targ))) { nuclear@0: perror("failed to allocate memory for log target"); nuclear@0: fclose(fp); nuclear@0: return -1; nuclear@0: } nuclear@0: targ->msg_type = type_mask; nuclear@0: targ->targ_type = TARG_FILE; nuclear@0: targ->fp = fp; nuclear@0: targ->next = targ_list; nuclear@0: targ_list = targ; nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: int log_add_func(unsigned int type_mask, void (*func)(const char*, void*), void *cls) nuclear@0: { nuclear@0: struct log_target *targ; nuclear@0: nuclear@0: init_once(); nuclear@0: nuclear@0: if(!(targ = malloc(sizeof *targ))) { nuclear@0: perror("failed to allocate memory for log target"); nuclear@0: return -1; nuclear@0: } nuclear@0: targ->msg_type = type_mask; nuclear@0: targ->targ_type = TARG_FUNC; nuclear@0: targ->func = func; nuclear@0: targ->func_cls = cls; nuclear@0: targ->next = targ_list; nuclear@0: targ_list = targ; nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: void log_va_msg(unsigned int type, const char *fmt, va_list ap) nuclear@0: { nuclear@0: char fixedbuf[256]; nuclear@0: char *buf = fixedbuf; nuclear@0: int len, sz = sizeof fixedbuf; nuclear@0: nuclear@0: init_once(); nuclear@0: nuclear@0: if(!targ_list || !*fmt) return; /* don't waste our time */ nuclear@0: nuclear@0: /* try with the fixed size buffer first which should be sufficient most of the nuclear@0: * time. if this fails, allocate a buffer of the correct size. nuclear@0: */ nuclear@0: while((len = vsnprintf(buf, sz, fmt, ap)) >= sz || len < 0) { nuclear@0: sz = len >= 0 ? len + 1 : sz * 2; nuclear@0: if(buf != fixedbuf) nuclear@0: free(buf); nuclear@0: if(!(buf = malloc(sz))) { nuclear@0: return; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if(buf != fixedbuf) nuclear@0: free(buf); nuclear@0: nuclear@0: log_string(type, buf); nuclear@0: } nuclear@0: nuclear@0: /* helpers */ nuclear@0: #define LOG_VA_MSG(t) \ nuclear@0: do { \ nuclear@0: va_list ap; \ nuclear@0: va_start(ap, fmt); \ nuclear@0: log_va_msg(t, fmt, ap); \ nuclear@0: va_end(ap); \ nuclear@0: } while(0) nuclear@0: nuclear@0: void log_msg(unsigned int type, const char *fmt, ...) nuclear@0: { nuclear@0: LOG_VA_MSG(type); nuclear@0: } nuclear@0: nuclear@0: void log_info(const char *fmt, ...) nuclear@0: { nuclear@0: LOG_VA_MSG(LOG_INFO); nuclear@0: } nuclear@0: nuclear@0: void log_warning(const char *fmt, ...) nuclear@0: { nuclear@0: LOG_VA_MSG(LOG_WARNING); nuclear@0: } nuclear@0: nuclear@0: void log_error(const char *fmt, ...) nuclear@0: { nuclear@0: LOG_VA_MSG(LOG_ERROR); nuclear@0: } nuclear@0: nuclear@0: void log_debug(const char *fmt, ...) nuclear@0: { nuclear@0: LOG_VA_MSG(LOG_DEBUG); nuclear@0: } nuclear@0: nuclear@0: void log_va_info(const char *fmt, va_list ap) nuclear@0: { nuclear@0: log_va_msg(LOG_INFO, fmt, ap); nuclear@0: } nuclear@0: nuclear@0: void log_va_warning(const char *fmt, va_list ap) nuclear@0: { nuclear@0: log_va_msg(LOG_WARNING, fmt, ap); nuclear@0: } nuclear@0: nuclear@0: void log_va_error(const char *fmt, va_list ap) nuclear@0: { nuclear@0: log_va_msg(LOG_ERROR, fmt, ap); nuclear@0: } nuclear@0: nuclear@0: void log_va_debug(const char *fmt, va_list ap) nuclear@0: { nuclear@0: log_va_msg(LOG_DEBUG, fmt, ap); nuclear@0: } nuclear@0: nuclear@0: static void log_string(unsigned int type, const char *str) nuclear@0: { nuclear@0: struct log_target *targ; nuclear@0: nuclear@0: targ = targ_list; nuclear@0: while(targ) { nuclear@0: if(targ->msg_type & type) { nuclear@0: if(targ->targ_type == TARG_STREAM || targ->targ_type == TARG_FILE) { nuclear@0: #if defined(unix) || defined(__unix__) || defined(__APPLE__) nuclear@0: if(isatty(fileno(targ->fp)) && type != LOG_INFO) { nuclear@0: int c = typecolor(type); nuclear@2: nuclear@2: int len = strlen(str); nuclear@2: if(str[len - 1] == '\n') { nuclear@2: char *s = alloca(len); nuclear@2: memcpy(s, str, len); nuclear@2: s[len - 1] = 0; nuclear@2: fprintf(targ->fp, "\033[%dm%s\033[0m\n", c, s); nuclear@2: } else { nuclear@2: fprintf(targ->fp, "\033[%dm%s\033[0m", c, str); nuclear@2: } nuclear@0: } else nuclear@0: #endif nuclear@0: { nuclear@0: fputs(str, targ->fp); nuclear@0: } nuclear@0: nuclear@0: } else if(targ->targ_type == TARG_FUNC) { nuclear@0: targ->func(str, targ->func_cls); nuclear@0: } nuclear@0: } nuclear@0: targ = targ->next; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: enum { nuclear@0: BLACK = 0, nuclear@0: RED, nuclear@0: GREEN, nuclear@0: YELLOW, nuclear@0: BLUE, nuclear@0: MAGENTA, nuclear@0: CYAN, nuclear@0: WHITE nuclear@0: }; nuclear@0: nuclear@0: #define ANSI_FGCOLOR(x) (30 + (x)) nuclear@0: #define ANSI_BGCOLOR(x) (40 + (x)) nuclear@0: nuclear@0: static int typecolor(int type) nuclear@0: { nuclear@0: switch(type) { nuclear@0: case LOG_ERROR: nuclear@0: return ANSI_FGCOLOR(RED); nuclear@0: case LOG_WARNING: nuclear@0: return ANSI_FGCOLOR(YELLOW); nuclear@0: case LOG_DEBUG: nuclear@0: return ANSI_FGCOLOR(MAGENTA); nuclear@0: default: nuclear@0: break; nuclear@0: } nuclear@0: return 37; nuclear@0: } nuclear@0: nuclear@0: #if defined(unix) || defined(__unix__) || defined(__APPLE__) nuclear@0: static int out_pipe[2], err_pipe[2]; nuclear@0: static int thr_running; nuclear@0: nuclear@0: static void *thr_func(void *arg); nuclear@0: nuclear@0: static int std_intercept(void) nuclear@0: { nuclear@0: pthread_t thr; nuclear@0: nuclear@0: if(thr_running) return 0; nuclear@0: nuclear@0: pipe(out_pipe); nuclear@0: pipe(err_pipe); nuclear@0: nuclear@0: fcntl(out_pipe[0], F_SETFL, fcntl(out_pipe[0], F_GETFL) | O_NONBLOCK); nuclear@0: fcntl(err_pipe[0], F_SETFL, fcntl(err_pipe[0], F_GETFL) | O_NONBLOCK); nuclear@0: nuclear@0: if(pthread_create(&thr, 0, thr_func, 0) == -1) { nuclear@0: log_error("failed to start std intercept thread\n"); nuclear@0: return -1; nuclear@0: } nuclear@0: thr_running = 1; nuclear@0: pthread_detach(thr); nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: static void *thr_func(void *arg) nuclear@0: { nuclear@0: static char buf[512]; nuclear@0: static int bufsz = sizeof buf; nuclear@0: int max_fd = out_pipe[0] > err_pipe[0] ? out_pipe[0] : err_pipe[0]; nuclear@0: nuclear@0: for(;;) { nuclear@0: int sz, res; nuclear@0: fd_set rdset; nuclear@0: nuclear@0: FD_ZERO(&rdset); nuclear@0: FD_SET(out_pipe[0], &rdset); nuclear@0: FD_SET(err_pipe[0], &rdset); nuclear@0: nuclear@0: while((res = select(max_fd + 1, &rdset, 0, 0, 0)) == -1 && errno == EINTR); nuclear@0: if(res == -1) { nuclear@0: break; /* assume EPIPE or non-recoverable error */ nuclear@0: } nuclear@0: if(res == 0) continue; nuclear@0: nuclear@0: if(FD_ISSET(out_pipe[0], &rdset)) { nuclear@0: while((sz = read(out_pipe[0], buf, bufsz - 1)) > 0) { nuclear@0: buf[bufsz - 1] = 0; nuclear@0: log_info("%s", buf); nuclear@0: } nuclear@0: if(sz == -1 && errno == EPIPE) break; nuclear@0: } nuclear@0: if(FD_ISSET(err_pipe[0], &rdset)) { nuclear@0: while((sz = read(err_pipe[0], buf, bufsz - 1)) > 0) { nuclear@0: buf[bufsz - 1] = 0; nuclear@0: log_error("%s", buf); nuclear@0: } nuclear@0: if(sz == -1 && errno == EPIPE) break; nuclear@0: } nuclear@0: } nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: void log_grab_stdout(void) nuclear@0: { nuclear@0: if(std_intercept() == -1) { nuclear@0: return; nuclear@0: } nuclear@0: dup2(out_pipe[1], 1); nuclear@0: } nuclear@0: nuclear@0: void log_grab_stderr(void) nuclear@0: { nuclear@0: if(std_intercept() == -1) { nuclear@0: return; nuclear@0: } nuclear@0: dup2(err_pipe[1], 2); nuclear@0: } nuclear@0: #else nuclear@0: void log_grab_stdout(void) nuclear@0: { nuclear@0: log_error("log_grab_stdout only works on UNIX\n"); nuclear@0: } nuclear@0: nuclear@0: void log_grab_stderr(void) nuclear@0: { nuclear@0: log_error("log_grab_stderr only works on UNIX\n"); nuclear@0: } nuclear@0: #endif