/* ALOG: A Logger * Copyright (C) 2022 Aidan Hahn * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see https://www.gnu.org/licenses/. */ #include "alog.h" #include // ^^^ TODO: build a memory management library #include #include #include #include #include #define TEST_ALOG_STATE() if (!ALOG_LOGGING_STATE) { _init_alog(); } #define MAX_TIMESTAMP 30 struct _global_logging_state *ALOG_LOGGING_STATE; int _alog_num_out_fds; int *_alog_out_fds; int _alog_num_err_fds; int *_alog_err_fds; void _init_alog() { if (!ALOG_LOGGING_STATE) { ALOG_LOGGING_STATE = malloc(sizeof(struct _global_logging_state)); ALOG_LOGGING_STATE->broadcast_to_all_fds = 0; ALOG_LOGGING_STATE->log_debug_messages = 0; } _alog_num_out_fds = 1; _alog_out_fds = malloc(sizeof(int) * 1); // stdout _alog_out_fds[0] = 1; _alog_num_err_fds = 1; _alog_err_fds = malloc(sizeof(int) * 1); // stderr _alog_err_fds[0] = 2; } void init_alog() { TEST_ALOG_STATE(); } // TODO: should I dont close stdin and stdout? void deinit_alog() { TEST_ALOG_STATE(); free(ALOG_LOGGING_STATE); for (int i = 0; i < _alog_num_out_fds; i++) { close(_alog_out_fds[i]); } for (int i = 0; i < _alog_num_err_fds; i++) { close(_alog_err_fds[i]); } free(_alog_out_fds); free(_alog_err_fds); } void alog_add_target(int fd, char is_err) { TEST_ALOG_STATE(); int **fd_list = is_err ? &_alog_err_fds : &_alog_out_fds; int *size = is_err ? &_alog_num_err_fds : &_alog_num_out_fds; int *fds = malloc(sizeof(int) * (*size + 1)); for (int i = 0; i < *size; i++) { fds[i] = (*fd_list)[i]; } fds[*size] = fd; free(*fd_list); (*fd_list) = fds; (*size)++; } // inefficient time wise, but avoids extra allocs // TODO: revisit this and see how to do it less tediously void alog_remove_target(int fd) { TEST_ALOG_STATE(); int out_fds = 0; for (int i = 0; i < _alog_num_out_fds; i++) { if (_alog_out_fds[i] != fd) { out_fds++; } } if (!out_fds) { _alog_num_out_fds = 0; free(_alog_out_fds); _alog_out_fds = NULL; } else { int *tmp_outs = malloc(sizeof(int) * out_fds); int tmp_iter_idx = 0; for (int i = 0; i < _alog_num_out_fds; i++) { if (_alog_out_fds[i] != fd) { tmp_outs[tmp_iter_idx] = _alog_out_fds[i]; tmp_iter_idx++; } } free(_alog_out_fds); _alog_out_fds = tmp_outs; _alog_num_out_fds = out_fds; } int err_fds = 0; for (int i = 0; i < _alog_num_err_fds; i++) { if (_alog_err_fds[i] != fd) { err_fds++; } } if (!err_fds) { _alog_num_err_fds = 0; free(_alog_err_fds); _alog_err_fds = NULL; } else { int *tmp_errs = malloc(sizeof(int) * err_fds); int tmp_iter_idx = 0; for (int i = 0; i < _alog_num_err_fds; i++) { if (_alog_err_fds[i] != fd) { tmp_errs[tmp_iter_idx] = _alog_err_fds[i]; tmp_iter_idx++; } } free(_alog_err_fds); _alog_err_fds = tmp_errs; _alog_num_err_fds = err_fds; } } // TODO: use preprocessor directives to gate off the posix timestamp stuff // unless the right variable is passed in at compile time void alog ( alog_sev severity, const char * message, ... ) { TEST_ALOG_STATE(); if (severity == DEBUG && !ALOG_LOGGING_STATE->log_debug_messages) { // nop :) return; } va_list fmt_list; va_start(fmt_list, message); char *buffer; int size; if (severity != PRINT) { // GET TIMESTAMP // TODO: try to get this all into 1 call to sprintf time_t t = time(NULL); struct tm * p = localtime(&t); int timestamp_len = strftime(NULL, MAX_TIMESTAMP, "[%c]", p) + 1; char *timestamp = calloc(timestamp_len , sizeof(char)); strftime(timestamp, timestamp_len+1, "[%c]", p); size = (timestamp_len + strlen(message) + 1); char *msg_and_timestamp = calloc(size, sizeof(char)); sprintf(msg_and_timestamp, "%s %s\n", timestamp, message); free(timestamp); // TODO: Why even use msg_and_timestamp if I am going to write it wholesale into buffer? size = vsnprintf(NULL, 0, msg_and_timestamp, fmt_list); va_start(fmt_list, message); va_end(fmt_list); buffer = malloc(size + 1); vsprintf(buffer, msg_and_timestamp, fmt_list); va_end(fmt_list); free(msg_and_timestamp); // if severity is PRINT we avoid timestamp } else { size = vsnprintf(NULL, 0, message, fmt_list); va_start(fmt_list, message); buffer = malloc(size + 1); vsprintf(buffer, message, fmt_list); va_end(fmt_list); } for (int i = 0; i < _alog_num_out_fds; i++) { if (write(_alog_out_fds[i], buffer, size) < size) { // TODO: how to handle? probably cant log another message lmao // perhaps it should be yanked from the list? maybe not? ;; } fsync(_alog_out_fds[i]); } if (severity == ERROR) { for (int i = 0; i < _alog_num_err_fds; i++) { if (write(_alog_err_fds[i], buffer, size) < size) { // TODO: see above ;; } fsync(_alog_err_fds[i]); } } free(buffer); } #ifdef ALOG_HIJACK_PRINTF int printf(const char *format, ...) { TEST_ALOG_STATE(); va_list fmt_list; va_start(fmt_list, format); /* TODO: this is a duplicate call given the implementation of alog. * Lets figure out a better way to handle this */ int size = snprintf(NULL, 0, format, fmt_list); alog(PRINT, NULL, format, fmt_list); va_end(fmt_list); return size; /* potentially accept a mem leak at end of program for alog state. * That is, if the program using this is not already using alog correctly * or if ALOG has been LD_PRELOAD'ed into a program to override its printf */ } #endif