From b66e88d87e300e620a3c4d42ce5b6ff4b851ef05 Mon Sep 17 00:00:00 2001 From: Philipp Kaeser Date: Fri, 9 Oct 2026 09:53:50 +0200 Subject: [PATCH 1/3] log: Adds options for always logging to stderr. --- include/libbase/log.h | 9 +++++++ src/log.c | 59 +++++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/include/libbase/log.h b/include/libbase/log.h index 08173dd..b582f9c 100644 --- a/include/libbase/log.h +++ b/include/libbase/log.h @@ -50,6 +50,15 @@ typedef enum { /** Current severity for logging. Only equal-or-higher severity is logged. */ extern bs_log_severity_t bs_log_severity; +/** Defines the options available for the logger. */ +struct bs_log_options { + /** Override: Log to stderr, even after bs_log_init_file(). */ + bool log_to_stderr; +}; + +/** Options for the logger. */ +extern struct bs_log_options bs_log_options; + /** Actually write a log message. Helper to bs_log. */ void bs_log_write(bs_log_severity_t severity, const char *file_name_ptr, diff --git a/src/log.c b/src/log.c index 30efbe9..7655922 100644 --- a/src/log.c +++ b/src/log.c @@ -40,7 +40,11 @@ /* == Data ================================================================= */ -bs_log_severity_t bs_log_severity = BS_WARNING; +bs_log_severity_t bs_log_severity = BS_WARNING; + +struct bs_log_options bs_log_options = { + .log_to_stderr = false +}; static const char *_severity_names[5] = { "DEBUG", "INFO", "WARNING", "ERROR", "FATAL"}; @@ -62,32 +66,37 @@ bool bs_log_init_file( static bool initialized = false; if (initialized) return false; - if (NULL == dirname_ptr) dirname_ptr = "./"; - - // Attempt to create the directory, unless it exists. - if (!bs_file_realpath_is(dirname_ptr, S_IFDIR) && - !bs_file_mkdir_p(dirname_ptr, 0700)) return false; - - // Construct the log file's name. - char path[PATH_MAX + 1]; - int len = snprintf(path, PATH_MAX + 1, "%s/%s%s", - dirname_ptr, filebase_ptr, _bs_log_extension_ptr); - if (PATH_MAX <= len) { - bs_log(BS_ERROR, "Failed snprintf(%p, %d, \"%s/%s%s\"): %d", - path, PATH_MAX + 1, dirname_ptr, filebase_ptr, - _bs_log_extension_ptr, len); - return false; - } + if (bs_log_options.log_to_stderr) { + _log_fd = 2; + } else { + if (NULL == dirname_ptr) dirname_ptr = "./"; + + // Attempt to create the directory, unless it exists. + if (!bs_file_realpath_is(dirname_ptr, S_IFDIR) && + !bs_file_mkdir_p(dirname_ptr, 0700)) return false; + + // Construct the log file's name. + char path[PATH_MAX + 1]; + int len = snprintf(path, PATH_MAX + 1, "%s/%s%s", + dirname_ptr, filebase_ptr, _bs_log_extension_ptr); + if (PATH_MAX <= len) { + bs_log(BS_ERROR, "Failed snprintf(%p, %d, \"%s/%s%s\"): %d", + path, PATH_MAX + 1, dirname_ptr, filebase_ptr, + _bs_log_extension_ptr, len); + return false; + } - // Now, actually create it. - int fd = open(path, O_CREAT|O_WRONLY|O_TRUNC, S_IWUSR|S_IRUSR); - if (0 > fd) { - bs_log(BS_ERROR | BS_ERRNO, - "Failed open(%s, O_CREATE|O_WRONLY|O_TRUNC, S_IWUSR|S_IRUSR)", - path); - return false; + // Now, actually create it. + int fd = open(path, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR); + if (0 > fd) { + bs_log(BS_ERROR | BS_ERRNO, + "Failed open(%s, O_CREATE | O_WRONLY, S_IWUSR | S_IRUSR)", + path); + return false; + } + _log_fd = fd; } - _log_fd = fd; + bs_log_severity = severity; initialized = true; return true; From 7e0df8705a14a2b435cfda05c696df8e9d9d5b5c Mon Sep 17 00:00:00 2001 From: Philipp Kaeser Date: Sun, 11 Oct 2026 21:40:57 +0200 Subject: [PATCH 2/3] log: Adds an arg switch for --log_to_stderr and unifies bs_log_options. --- include/libbase/arg.h | 12 ---- include/libbase/log.h | 53 ++++++++++---- include/libbase/log_wrappers.h | 6 +- src/arg.c | 30 -------- src/log.c | 128 ++++++++++++++++++++------------- 5 files changed, 121 insertions(+), 108 deletions(-) diff --git a/include/libbase/arg.h b/include/libbase/arg.h index ade49c2..b5f1e4e 100644 --- a/include/libbase/arg.h +++ b/include/libbase/arg.h @@ -22,7 +22,6 @@ #ifndef __LIBBASE_ARG_H__ #define __LIBBASE_ARG_H__ -#include "log.h" #include "test.h" #include @@ -206,17 +205,6 @@ void bs_arg_cleanup(const bs_arg_t *arg_ptr); */ int bs_arg_print_usage(FILE *stream_ptr, const bs_arg_t *arg_ptr); -/** Severity level names, for logging. Used for bs_arg_log_level. */ -extern const bs_arg_enum_table_t bs_arg_log_level_names[]; - -/** Common commandline arg for `--log_level`. Sets @ref bs_log_severity. */ -static const bs_arg_t bs_arg_log_level = BS_ARG_ENUM( - "log_level", - "Log level to apply. One of DEBUG, INFO, WARNING, ERROR.", - "INFO", - &bs_arg_log_level_names[0], - (int*)&bs_log_severity); - /** Unit test set. */ extern const bs_test_set_t bs_arg_test_set; diff --git a/include/libbase/log.h b/include/libbase/log.h index b582f9c..d1f0ae5 100644 --- a/include/libbase/log.h +++ b/include/libbase/log.h @@ -21,12 +21,13 @@ #ifndef __LIBBASE_LOG_H__ #define __LIBBASE_LOG_H__ -#include "def.h" -#include "test.h" - #include #include +#include "arg.h" +#include "def.h" +#include "test.h" + #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -47,17 +48,17 @@ typedef enum { BS_ERRNO = 0x80 } bs_log_severity_t; -/** Current severity for logging. Only equal-or-higher severity is logged. */ -extern bs_log_severity_t bs_log_severity; - -/** Defines the options available for the logger. */ -struct bs_log_options { +/** Defines the settings used for the logger. */ +struct bs_log_settings { + /** Configured log severity. */ + bs_log_severity_t severity; /** Override: Log to stderr, even after bs_log_init_file(). */ bool log_to_stderr; + /** File descriptor to use */ }; -/** Options for the logger. */ -extern struct bs_log_options bs_log_options; +/** The logger's settings. */ +extern struct bs_log_settings bs_log_settings; /** Actually write a log message. Helper to bs_log. */ void bs_log_write(bs_log_severity_t severity, @@ -78,19 +79,17 @@ void bs_log_vwrite(bs_log_severity_t severity, * * @param dirname_ptr Directory to log into. Uses "./" if NULL. * @param filebase_ptr - * @param severity * * @return true on success. This function will fail if called more than once. */ -bool bs_log_init_file( +bool bs_log_init( const char *dirname_ptr, - const char *filebase_ptr, - bs_log_severity_t severity); + const char *filebase_ptr); /** Returns whether log outut will happen for `severity`. */ static inline bool bs_will_log(bs_log_severity_t severity) { - return ((severity & 0x7f) >= bs_log_severity || + return ((severity & 0x7f) >= bs_log_settings.severity || (severity & 0x7f) == BS_FATAL); } @@ -103,6 +102,30 @@ static inline bool bs_will_log(bs_log_severity_t severity) } \ } +/** Severity level names, for logging. Used for bs_arg_log_level. */ +extern const bs_arg_enum_table_t bs_log_level_names[]; + +/** + * Common definition for commandline argument `--log_level`. Sets + * @ref bs_log_settings.severity. + */ +static const bs_arg_t bs_arg_log_level = BS_ARG_ENUM( + "log_level", + "Log level to apply. One of DEBUG, INFO, WARNING, ERROR.", + "INFO", + &bs_log_level_names[0], + (int*)&bs_log_settings.severity); + +/** + * Common definition for commandline argument `--log_to_stderr`. Sets + * @ref bs_log_settings.log_to_stderr. + */ +static const bs_arg_t bs_arg_log_to_stderr = BS_ARG_BOOL( + "log_to_stderr", + "Overrules log output to be written to STDERR.", + false, + &bs_log_settings.log_to_stderr); + /** Unit testset. */ extern const bs_test_set_t bs_log_test_set; diff --git a/include/libbase/log_wrappers.h b/include/libbase/log_wrappers.h index c604a9c..90f42a5 100644 --- a/include/libbase/log_wrappers.h +++ b/include/libbase/log_wrappers.h @@ -34,7 +34,7 @@ static inline void *_logged_calloc( const char *filename_ptr, int line_no, size_t nmemb, size_t size) { void *ptr = calloc(nmemb, size); - if ((NULL == ptr) && (BS_ERROR >= bs_log_severity)) { + if ((NULL == ptr) && bs_will_log(BS_ERROR)) { bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr, line_no, "Failed calloc(%zu, %zu)", nmemb, size); } @@ -50,7 +50,7 @@ static inline void *_logged_malloc( const char *filename_ptr, int line_no, size_t size) { void *ptr = malloc(size); - if ((NULL == ptr) && (BS_ERROR >= bs_log_severity)) { + if ((NULL == ptr) && bs_will_log(BS_ERROR)) { bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr, line_no, "Failed malloc(%zu)", size); } @@ -69,7 +69,7 @@ static inline char *_logged_strdup( char *new_str = malloc(len); if (NULL != new_str) { memcpy(new_str, str, len); - } else if (BS_ERROR >= bs_log_severity) { + } else if (bs_will_log(BS_ERROR)) { bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr, line_no, "Failed strdup(%s)", str); } diff --git a/src/arg.c b/src/arg.c index fa4c575..b1bd0d3 100644 --- a/src/arg.c +++ b/src/arg.c @@ -90,14 +90,6 @@ static bool lookup_enum(const bs_arg_enum_table_t *lookup_table, static const char *bs_arg_bool_value_true = "true"; static const char *bs_arg_bool_value_false = "false"; -const bs_arg_enum_table_t bs_arg_log_level_names[] = { - { .name_ptr = "DEBUG", BS_DEBUG }, - { .name_ptr = "INFO", BS_INFO }, - { .name_ptr = "WARNING", BS_WARNING }, - { .name_ptr = "ERROR", BS_ERROR }, - { .name_ptr = NULL }, -}; - /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ @@ -681,7 +673,6 @@ static void bs_arg_test_parse_arg_for_enum(bs_test_t *test_ptr); static void bs_arg_test_set_defaults(bs_test_t *test_ptr); static void bs_arg_test_parse(bs_test_t *test_ptr); static void bs_arg_test_check_arg(bs_test_t *test_ptr); -static void bs_arg_test_log_level(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_arg_test_cases[] = { @@ -696,7 +687,6 @@ static const bs_test_case_t bs_arg_test_cases[] = { { true, "set_defaults", bs_arg_test_set_defaults }, { true, "parse", bs_arg_test_parse }, { true, "check_arg", bs_arg_test_check_arg }, - { true, "log_level", bs_arg_test_log_level }, { false, NULL, NULL } }; @@ -1040,24 +1030,4 @@ static void bs_arg_test_check_arg(bs_test_t *test_ptr) BS_TEST_VERIFY_TRUE(test_ptr, check_arg(valid_args)); } -/* ------------------------------------------------------------------------- */ -void bs_arg_test_log_level(bs_test_t *test_ptr) -{ - static const bs_arg_t args[] = { - bs_arg_log_level, - BS_ARG_SENTINEL() - }; - - const char *argv[] = { "program", "--log_level", "DEBUG" }; - int argc = sizeof(argv) / sizeof(const char*); - - bs_log_severity_t log_level = bs_log_severity; - bs_log_severity = BS_INFO; - BS_TEST_VERIFY_TRUE( - test_ptr, - bs_arg_parse(args, BS_ARG_MODE_NO_EXTRA, &argc, argv)); - BS_TEST_VERIFY_EQ(test_ptr, BS_DEBUG, bs_log_severity); - bs_log_severity = log_level; -} - /* == End of arg.c ========================================================= */ diff --git a/src/log.c b/src/log.c index 7655922..faae902 100644 --- a/src/log.c +++ b/src/log.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -38,66 +39,67 @@ #include #include -/* == Data ================================================================= */ -bs_log_severity_t bs_log_severity = BS_WARNING; +/* == Data ================================================================= */ -struct bs_log_options bs_log_options = { +struct bs_log_settings bs_log_settings = { + .severity = BS_WARNING, .log_to_stderr = false }; -static const char *_severity_names[5] = { - "DEBUG", "INFO", "WARNING", "ERROR", "FATAL"}; +const bs_arg_enum_table_t bs_log_level_names[] = { + { .name_ptr = "DEBUG", BS_DEBUG }, + { .name_ptr = "INFO", BS_INFO }, + { .name_ptr = "WARNING", BS_WARNING }, + { .name_ptr = "ERROR", BS_ERROR }, + { .name_ptr = NULL }, +}; -static int _log_fd = 2; +static int _log_fd = 2; -static const char *_bs_log_extension_ptr = ".log"; +static const char *_bs_log_extension_ptr = ".log"; static const char *_strip_prefix(const char *path_ptr); /* == Functions ============================================================ */ /* ------------------------------------------------------------------------- */ -bool bs_log_init_file( +bool bs_log_init( const char *dirname_ptr, - const char *filebase_ptr, - bs_log_severity_t severity) + const char *filebase_ptr) { static bool initialized = false; if (initialized) return false; - if (bs_log_options.log_to_stderr) { - _log_fd = 2; - } else { - if (NULL == dirname_ptr) dirname_ptr = "./"; - - // Attempt to create the directory, unless it exists. - if (!bs_file_realpath_is(dirname_ptr, S_IFDIR) && - !bs_file_mkdir_p(dirname_ptr, 0700)) return false; - - // Construct the log file's name. - char path[PATH_MAX + 1]; - int len = snprintf(path, PATH_MAX + 1, "%s/%s%s", - dirname_ptr, filebase_ptr, _bs_log_extension_ptr); - if (PATH_MAX <= len) { - bs_log(BS_ERROR, "Failed snprintf(%p, %d, \"%s/%s%s\"): %d", - path, PATH_MAX + 1, dirname_ptr, filebase_ptr, - _bs_log_extension_ptr, len); - return false; - } + _log_fd = 2; + if (bs_log_settings.log_to_stderr) return true; + + if (NULL == dirname_ptr) dirname_ptr = "./"; + + // Attempt to create the directory, unless it exists. + if (!bs_file_realpath_is(dirname_ptr, S_IFDIR) && + !bs_file_mkdir_p(dirname_ptr, 0700)) return false; + + // Construct the log file's name. + char path[PATH_MAX + 1]; + int len = snprintf(path, PATH_MAX + 1, "%s/%s%s", + dirname_ptr, filebase_ptr, _bs_log_extension_ptr); + if (PATH_MAX <= len) { + bs_log(BS_ERROR, "Failed snprintf(%p, %d, \"%s/%s%s\"): %d", + path, PATH_MAX + 1, dirname_ptr, filebase_ptr, + _bs_log_extension_ptr, len); + return false; + } - // Now, actually create it. - int fd = open(path, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR); - if (0 > fd) { - bs_log(BS_ERROR | BS_ERRNO, + // Now, actually create it. + int fd = open(path, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR); + if (0 > fd) { + bs_log(BS_ERROR | BS_ERRNO, "Failed open(%s, O_CREATE | O_WRONLY, S_IWUSR | S_IRUSR)", - path); - return false; - } - _log_fd = fd; + path); + return false; } - - bs_log_severity = severity; + _log_fd = fd; initialized = true; return true; } @@ -148,7 +150,7 @@ void bs_log_vwrite(bs_log_severity_t severity, tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec, (int)(tv.tv_usec / 1000), color_attr_ptr, - _severity_names[severity & 0x7f], + bs_log_level_names[severity & 0x7f].name_ptr, reset_ptr, _strip_prefix(file_name_ptr), line_num); @@ -170,8 +172,9 @@ void bs_log_vwrite(bs_log_severity_t severity, size_t written_bytes = 0; while (written_bytes < pos) { - ssize_t more_bytes = write(_log_fd, &buf[written_bytes], - pos - written_bytes); + int fd = bs_log_settings.log_to_stderr ? 2 : _log_fd; + ssize_t more_bytes = write( + fd, &buf[written_bytes], pos - written_bytes); if (0 > more_bytes) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { continue; @@ -200,12 +203,14 @@ const char *_strip_prefix(const char *path_ptr) static void test_file(bs_test_t *test_ptr); static void test_strip_prefix(bs_test_t *test_ptr); static void test_log(bs_test_t *test_ptr); +static void _bs_log_test_parse_args(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_log_test_cases[] = { { true, "file", test_file }, { true, "basename", test_strip_prefix }, { true, "log", test_log }, + { true, "parse_args", _bs_log_test_parse_args }, BS_TEST_CASE_SENTINEL(), }; @@ -280,7 +285,7 @@ void test_file(bs_test_t *test_ptr) { BS_TEST_VERIFY_TRUE( test_ptr, - bs_log_init_file(bs_test_temp_path(test_ptr, "sub"), "base", BS_INFO)); + bs_log_init(bs_test_temp_path(test_ptr, "sub"), "base")); const char *log_name_ptr = bs_test_temp_path(test_ptr, "sub/base.log"); BS_TEST_VERIFY_TRUE(test_ptr, bs_file_realpath_is(log_name_ptr, S_IFREG)); @@ -292,7 +297,7 @@ void test_file(bs_test_t *test_ptr) // Must fail on further initialization attempt(s). BS_TEST_VERIFY_FALSE( test_ptr, - bs_log_init_file(bs_test_temp_path(test_ptr, "sub"), "other", BS_INFO)); + bs_log_init(bs_test_temp_path(test_ptr, "sub"), "other")); } /* ------------------------------------------------------------------------- */ @@ -313,8 +318,8 @@ void test_log(bs_test_t *test_ptr) char expected_output[BS_LOG_MAX_BUF_SIZE + 1]; bs_log_severity_t backup_severity; - backup_severity = bs_log_severity; - bs_log_severity = BS_WARNING; + backup_severity = bs_log_settings.severity; + bs_log_settings.severity = BS_WARNING; int fds[2]; if (0 != pipe(fds)) { @@ -351,7 +356,7 @@ void test_log(bs_test_t *test_ptr) verify_log_output_equals_at( test_ptr, __FILE__, __LINE__, fds[0], NULL); - bs_log_severity = BS_INFO; + bs_log_settings.severity = BS_INFO; snprintf(expected_output, sizeof(expected_output), "(\e[37mINFO\e[0m) \e[90mlog.c:%d\e[0m \e[37mtest 45\e[0m\n", __LINE__ + 1); @@ -363,7 +368,34 @@ void test_log(bs_test_t *test_ptr) close(fds[0]); close(fds[1]); - bs_log_severity = backup_severity; + bs_log_settings.severity = backup_severity; +} + +/* ------------------------------------------------------------------------- */ +/** Verifies that the common commandline args are parsed correctly. */ +void _bs_log_test_parse_args(bs_test_t *test_ptr) +{ + static const bs_arg_t args[] = { + bs_arg_log_level, + bs_arg_log_to_stderr, + BS_ARG_SENTINEL(), + }; + + const char *a1[] = { "", "--log_level", "ERROR", "--log_to_stderr", 0 }; + int l = sizeof(a1) / sizeof(const char*) - 1; + BS_TEST_VERIFY_TRUE( + test_ptr, + bs_arg_parse(args, BS_ARG_MODE_NO_EXTRA, &l, a1)); + BS_TEST_VERIFY_EQ(test_ptr, BS_ERROR, bs_log_settings.severity); + BS_TEST_VERIFY_TRUE(test_ptr, bs_log_settings.log_to_stderr); + + const char *a2[] = { "", "--log_level", "DEBUG", "--nolog_to_stderr", 0 }; + l = sizeof(a1) / sizeof(const char*) - 1; + BS_TEST_VERIFY_TRUE( + test_ptr, + bs_arg_parse(args, BS_ARG_MODE_NO_EXTRA, &l, a2)); + BS_TEST_VERIFY_EQ(test_ptr, BS_DEBUG, bs_log_settings.severity); + BS_TEST_VERIFY_FALSE(test_ptr, bs_log_settings.log_to_stderr); } /* == End of log.c ========================================================= */ From 5279d7501188e555d1f16d5e79be64a113459ef4 Mon Sep 17 00:00:00 2001 From: Philipp Kaeser Date: Sat, 10 Oct 2026 11:09:56 +0200 Subject: [PATCH 3/3] log: Sets a write lock for the log file, and fall back to a PID-indexed name. --- BACKLOG.md | 2 +- include/libbase/log.h | 1 - src/log.c | 71 +++++++++++++++++++++++++++++++------------ 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/BACKLOG.md b/BACKLOG.md index e52b60b..9332394 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -7,7 +7,7 @@ ## bs_arg -* Support "extra" arguments of no type, eg. for `--help` or `--version`. +* Support "extra" arguments of no type, eg. for `--help` or `--version` ("switch"). ## bs_file diff --git a/include/libbase/log.h b/include/libbase/log.h index d1f0ae5..0395350 100644 --- a/include/libbase/log.h +++ b/include/libbase/log.h @@ -54,7 +54,6 @@ struct bs_log_settings { bs_log_severity_t severity; /** Override: Log to stderr, even after bs_log_init_file(). */ bool log_to_stderr; - /** File descriptor to use */ }; /** The logger's settings. */ diff --git a/src/log.c b/src/log.c index faae902..32c6f48 100644 --- a/src/log.c +++ b/src/log.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -39,7 +40,6 @@ #include #include - /* == Data ================================================================= */ struct bs_log_settings bs_log_settings = { @@ -59,6 +59,7 @@ static int _log_fd = 2; static const char *_bs_log_extension_ptr = ".log"; +static int _bs_log_prepare_file(const char *fmt_ptr, ...); static const char *_strip_prefix(const char *path_ptr); /* == Functions ============================================================ */ @@ -80,25 +81,17 @@ bool bs_log_init( if (!bs_file_realpath_is(dirname_ptr, S_IFDIR) && !bs_file_mkdir_p(dirname_ptr, 0700)) return false; - // Construct the log file's name. - char path[PATH_MAX + 1]; - int len = snprintf(path, PATH_MAX + 1, "%s/%s%s", - dirname_ptr, filebase_ptr, _bs_log_extension_ptr); - if (PATH_MAX <= len) { - bs_log(BS_ERROR, "Failed snprintf(%p, %d, \"%s/%s%s\"): %d", - path, PATH_MAX + 1, dirname_ptr, filebase_ptr, - _bs_log_extension_ptr, len); - return false; - } - - // Now, actually create it. - int fd = open(path, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR); + // Prepare the log file. Include the PID, if the file with the primary + // name cannot be prepared. + int fd = _bs_log_prepare_file( + "%s/%s%s", dirname_ptr, filebase_ptr, _bs_log_extension_ptr); if (0 > fd) { - bs_log(BS_ERROR | BS_ERRNO, - "Failed open(%s, O_CREATE | O_WRONLY, S_IWUSR | S_IRUSR)", - path); - return false; + fd = _bs_log_prepare_file( + "%s/%s.%"PRIdMAX"%s-FIXME", dirname_ptr, filebase_ptr, + (intmax_t)getpid(), _bs_log_extension_ptr); + if (0 > fd) return false; } + _log_fd = fd; initialized = true; return true; @@ -187,9 +180,49 @@ void bs_log_vwrite(bs_log_severity_t severity, if (severity == BS_FATAL) BS_ABORT(); } - /* == Local (static) methods =============================================== */ +/* ------------------------------------------------------------------------- */ +/** Prepares a log file: Opens it, obtains a write lock, and truncates it. */ +int _bs_log_prepare_file(const char *fmt_ptr, ...) +{ + // Construct the file name. + char path[PATH_MAX + 1]; + va_list ap; + va_start(ap, fmt_ptr); + int len = vsnprintf(path, PATH_MAX + 1, fmt_ptr, ap); + va_end(ap); + if (PATH_MAX <= len) { + bs_log(BS_ERROR, "Failed vsnprintf(%p, %d, \"%s\", ...): %d", + path, PATH_MAX + 1, fmt_ptr, len); + return -1; + } + + // Now, actually create it. + int fd = open(path, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR); + if (0 > fd) { + bs_log(BS_ERROR | BS_ERRNO, + "Failed open(%s, O_CREATE | O_WRONLY, S_IWUSR | S_IRUSR)", + path); + return -1; + } + + // File may already exist and be in use. Attempt to lock entire file. + struct flock file_lock = { .l_type = F_WRLCK, .l_whence = SEEK_SET }; + if (0 > fcntl(fd, F_SETLK, &file_lock)) { + bs_log(BS_INFO | BS_ERRNO, "fcntl(%d, F_SETLK, %p) for \"%s\" failed", + fd, &file_lock, path); + close(fd); + return -1; + } + + if (0 > ftruncate(fd, 0)) { + bs_log(BS_WARNING | BS_ERRNO, + "Failed truncate(%d, 0) for \"%s\"", fd, path); + } + return fd; +} + /** Strips leading relative (or absolute) path prefix. */ const char *_strip_prefix(const char *path_ptr) {