diff options
Diffstat (limited to 'src/usyslog.c')
-rw-r--r-- | src/usyslog.c | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/src/usyslog.c b/src/usyslog.c new file mode 100644 index 0000000..75becac --- /dev/null +++ b/src/usyslog.c @@ -0,0 +1,296 @@ +/* + * License: BSD-style license + * Copyright: Bernd Schubert <bernd.schubert@fastmail.fm> + * + * Details: + * Log files might be located on our own filesystem. If we then want to log + * a message to syslog, we would need to log to ourself, which easily ends up + * in a deadlock. Initializing openlog() using the flags + * LOG_NDELAY | LOG_NOWAIT should prevent that, but real live has shown that + * this does not work reliable and systems 'crashed' just because we + * tried to log a harmless message. + * So this file introduces a syslog thread and a syslog buffer. usyslog() + * calls write without a risk to deadlock into the syslog buffer (chained + * list) and then the seperate syslog_thread call syslog(). That way our + * our filesystem thread(s) cannot stall from syslog() calls. + */ + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <malloc.h> +#include <pthread.h> +#include <stdarg.h> + +#include "usyslog.h" +#include "debug.h" + +static ulogs_t *free_head, *free_bottom; // free chained list log entries +static ulogs_t *used_head = NULL, *used_bottom = NULL; //used chained list pointers + +static pthread_mutex_t list_lock; // locks the entire chained list +static pthread_cond_t cond_message; // used to wake up the syslog thread + +// Only used for debugging, protected by list_lock +static int free_entries; +static int used_entries = 0; + +//#define USYSLOG_DEBUG + +#ifdef USYSLOG_DEBUG +static void verify_lists() +{ + pthread_mutex_lock(&list_lock); + + ulogs_t *entry = free_head; + int free_count = -1; + bool first_free = true; + while (entry) { + if (first_free) { + first_free = false; + free_count = 1; + } else + free_count++; + entry = entry->next; + } + if (free_count != free_entries && free_entries != 0) + DBG("usyslog list error detected: number of free entries inconsistent!" + " %d vs. %d", free_count, free_entries); + + entry = used_head; + int used_count = -1; + bool first_used = true; + while (entry) { + if (first_used) { + first_used = false; + used_count = 1; + } else + used_count++; + entry = entry->next; + } + if (used_count != used_entries && used_entries != 0) + DBG("usyslog list error detected: number of used entries inconsistent!" + " (used: %d vs. %d) (free: %d vs. %d) \n", + used_count, used_entries, free_count, free_entries); + + pthread_mutex_unlock(&list_lock); +} +#else +#define verify_lists() +#endif + + +/** + * Walks the chained used-list and calls syslog() + */ +static void do_syslog(void) +{ + pthread_mutex_lock(&list_lock); // we MUST ensure not to keep that forever + + ulogs_t *log_entry = used_head; + + while (log_entry) { + pthread_mutex_t *entry_lock = &log_entry->lock; + int res = pthread_mutex_trylock(entry_lock); + if (res) { + if (res != EBUSY) + DBG("Entirely unexpected locking error %s\n", + strerror(res)); + // If something goes wrong with the log_entry we do not + // block the critical list_lock forever! + // EBUSY might come up rarely, if we race with usyslog() + pthread_mutex_unlock(&list_lock); + sleep(1); + pthread_mutex_lock(&list_lock); + log_entry = used_head; + continue; + } + pthread_mutex_unlock(&list_lock); + + // This syslog call and so this lock might block, so be + // carefull on using locks! The filesystem IO thread + // *MUST* only try to lock it using pthread_mutex_trylock() + syslog(log_entry->priority, "%s", log_entry->message); + log_entry->used = false; + + // NOTE: The list is only locked now, after syslog() succeeded! + pthread_mutex_lock(&list_lock); + ulogs_t *next_entry = log_entry->next; // just to save the pointer + + used_head = log_entry->next; + if (!used_head) + used_bottom = NULL; // no used entries left + + if (free_bottom) + free_bottom->next = log_entry; + free_bottom = log_entry; + free_bottom->next = NULL; + + if (!free_head) + free_head = log_entry; + + free_entries++; + used_entries--; + + pthread_mutex_unlock(&list_lock); // unlock ist ASAP + + log_entry = next_entry; + pthread_mutex_unlock(entry_lock); + } + + verify_lists(); +} + +/** + * syslog backgroung thread that tries to to empty the syslog buffer + */ +static void * syslog_thread(void *arg) +{ + // FIXME: What is a better way to prevent a compiler warning about + // unused variable 'arg' + int tinfo = *((int *) arg); + if (1 == 0) + printf("Starting thread %d", tinfo); + + pthread_mutex_t sleep_mutex; + + pthread_mutex_init(&sleep_mutex, NULL); + pthread_mutex_lock(&sleep_mutex); + while (1) { + pthread_cond_wait(&cond_message, &sleep_mutex); + do_syslog(); + } + + return NULL; +} + +/** + * usyslog - function to be called if something shall be logged to syslog + */ +void usyslog(int priority, const char *format, ...) +{ + int res; + ulogs_t *log; + + // Lock the entire list first, which means the syslog thread MUST NOT + // lock it if there is any chance it might be locked forever. + pthread_mutex_lock(&list_lock); + + // Some sanity checks. If we fail here, we will leak a log entry, + // but will not lock up. + + if (free_head == NULL) { + DBG("All syslog entries already busy\n"); + pthread_mutex_unlock(&list_lock); + return; + } + + log = free_head; + free_head = log->next; + + res = pthread_mutex_trylock(&log->lock); + if (res == EBUSY) { + // huh, that never should happen! + DBG("Critical log error, log entry is BUSY, but should not\n"); + pthread_mutex_unlock(&list_lock); + return; + } else if (res) { + // huh, that never should happen either! + DBG("Never should happen, can get lock: %s\n", strerror(res)); + pthread_mutex_unlock(&list_lock); + return; + } + + if (log->used) { + // huh, that never should happen either! + DBG("Never should happen, entry is busy, but should not!\n"); + pthread_mutex_unlock(&log->lock); + pthread_mutex_unlock(&list_lock); + return; + } + + if (!used_head) + used_head = used_bottom = log; + else { + used_bottom->next = log; + used_bottom = log; + } + + if (log->next) { + // from free_list to end of used_list, so next is NULL now + log->next = NULL; + } else { + // so the last entry in free_list + free_bottom = NULL; + } + + + free_entries--; + used_entries++; + + // Everything below is log entry related, so we can free the list_lock + pthread_mutex_unlock(&list_lock); + + va_list ap; + va_start(ap, format); + vsnprintf(log->message, MAX_MSG_SIZE, format, ap); + log->priority = priority; + log->used = 1; + + pthread_mutex_unlock(&log->lock); + + pthread_cond_signal(&cond_message); // wake up the syslog thread +} + +/** + * Initialize syslogs + */ +void init_syslog(void) +{ + openlog("unionfs-fuse: ", LOG_CONS | LOG_NDELAY | LOG_NOWAIT | LOG_PID, LOG_DAEMON); + + pthread_mutex_init(&list_lock, NULL); + pthread_cond_init(&cond_message, NULL); + pthread_t thread; + pthread_attr_t attr; + int t_arg = 0; // thread argument, not required for us + + int i; + ulogs_t *log, *last = NULL; + for (i = 0; i < MAX_SYSLOG_MESSAGES; i++) { + log = malloc(sizeof(ulogs_t)); + if (log == NULL) { + fprintf(stderr, "\nLog initialization failed: %s\n", strerror(errno)); + fprintf(stderr, "Aborting!\n"); + // Still initialazation phase, we can abort. + exit (1); + } + + log->used = false; + pthread_mutex_init(&log->lock, NULL); + + if (last) { + last->next = log; + } else { + // so the very first entry + free_head = log; + } + last = log; + } + last->next = NULL; + free_bottom = last; + + free_entries = MAX_SYSLOG_MESSAGES; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + int res = pthread_create(&thread, &attr, syslog_thread, (void *) &t_arg); + if (res != 0) { + fprintf(stderr, "Failed to initialize the syslog threads: %s\n", + strerror(res)); + exit(1); + } +} + |