diff --git a/src/common.c b/src/common.c index 233a1f9..808c87c 100644 --- a/src/common.c +++ b/src/common.c @@ -53,7 +53,7 @@ flog_print_error(FlogError error) { void flog_usage(void) { printf( - "%s %s\n" + "%s v%d.%d.%d\n" "\n" "Usage:\n" " %s [options] message\n" @@ -73,12 +73,19 @@ flog_usage(void) { " default, info, debug, error, fault\n" "\n", PROGRAM_NAME, - PROGRAM_VERSION, + PROGRAM_VERSION_MAJOR, + PROGRAM_VERSION_MINOR, + PROGRAM_VERSION_PATCH, PROGRAM_NAME ); } void flog_version(void) { - printf("%s %s\n", PROGRAM_NAME, PROGRAM_VERSION); + printf("%s v%d.%d.%d\n", + PROGRAM_NAME, + PROGRAM_VERSION_MAJOR, + PROGRAM_VERSION_MINOR, + PROGRAM_VERSION_PATCH + ); } diff --git a/src/common.h b/src/common.h index d7981bd..2aac0bb 100644 --- a/src/common.h +++ b/src/common.h @@ -28,20 +28,28 @@ * Constants, helper functions, and error handling types for common operations. */ +/* Program information */ #define PROGRAM_NAME "flog" -#define PROGRAM_VERSION "v1.7.3" +#define PROGRAM_VERSION_MAJOR 1 +#define PROGRAM_VERSION_MINOR 7 +#define PROGRAM_VERSION_PATCH 3 /*! \brief An enumerated type representing error conditions. */ typedef enum FlogErrorData { + /* General errors */ FLOG_ERROR_NONE, FLOG_ERROR_ALLOC, + + /* I/O errors */ FLOG_ERROR_APPEND, + FLOG_ERROR_FILE, + FLOG_ERROR_STAT, + + /* Configuration errors */ FLOG_ERROR_LVL, FLOG_ERROR_MSG, FLOG_ERROR_OPTS, - FLOG_ERROR_SUBSYS, - FLOG_ERROR_FILE, - FLOG_ERROR_STAT, + FLOG_ERROR_SUBSYS } FlogError; /*! \brief Print usage information to stdout stream. */ diff --git a/src/config.c b/src/config.c index 0b422a4..c985263 100644 --- a/src/config.c +++ b/src/config.c @@ -38,9 +38,9 @@ bool is_regular_file_or_pipe(int fd, FlogError *error); -const int subsystem_len = 257; -const int category_len = 257; -const int message_len = 8193; +const size_t subsystem_len = 128; +const size_t category_len = 128; +const size_t message_len = 1024; FlogConfigLevel flog_config_parse_level(const char *str); @@ -205,7 +205,7 @@ flog_config_set_subsystem(FlogConfig *config, const char *subsystem) { assert(subsystem != NULL); if (strlcpy(config->subsystem, subsystem, subsystem_len) >= subsystem_len) { - fprintf(stderr, "%s: subsystem name truncated to %d bytes\n", PROGRAM_NAME, subsystem_len - 1); + fprintf(stderr, "%s: subsystem name truncated to %zu bytes\n", PROGRAM_NAME, subsystem_len - 1); } } @@ -222,7 +222,7 @@ flog_config_set_category(FlogConfig *config, const char *category) { assert(category != NULL); if (strlcpy(config->category, category, category_len) >= category_len) { - fprintf(stderr, "%s: category name truncated to %d bytes\n", PROGRAM_NAME, category_len - 1); + fprintf(stderr, "%s: category name truncated to %zu bytes\n", PROGRAM_NAME, category_len - 1); } } @@ -293,7 +293,7 @@ flog_config_set_message(FlogConfig *config, const char *message) { assert(message != NULL); if (strlcpy(config->message, message, message_len) >= message_len) { - fprintf(stderr, "%s: message string was truncated to %d bytes\n", PROGRAM_NAME, message_len - 1); + fprintf(stderr, "%s: message truncated to %zu bytes\n", PROGRAM_NAME, message_len - 1); } } @@ -320,7 +320,7 @@ flog_config_set_message_from_args(FlogConfig *config, const char **args) { } if (message_truncated) { - fprintf(stderr, "%s: message was truncated to %d bytes\n", PROGRAM_NAME, message_len - 1); + fprintf(stderr, "%s: message truncated to %zu bytes\n", PROGRAM_NAME, message_len - 1); } } @@ -329,10 +329,26 @@ flog_config_set_message_from_stream(FlogConfig *config, FILE *restrict stream) { assert(config != NULL); assert(stream != NULL); - if (fread(config->message, sizeof(char), message_len, stream) >= message_len) { - fprintf(stderr, "%s: message was truncated to %d bytes\n", PROGRAM_NAME, message_len - 1); - config->message[message_len - 1] = '\0'; + config->message[0] = '\0'; + + // Read message from stream + size_t bytes_read = fread(config->message, sizeof(char), message_len - 1, stream); + if (ferror(stream)) { + fprintf(stderr, "%s: error reading message from stream\n", PROGRAM_NAME); + clearerr(stream); + return; + } + + // Ensure safe bounds and null termination + bytes_read = (bytes_read >= message_len - 1) ? message_len - 1 : bytes_read; + config->message[bytes_read] = '\0'; + + // Check for message truncation + char c; + if (fread(&c, sizeof(char), 1, stream) > 0) { + fprintf(stderr, "%s: message truncated to %zu bytes\n", PROGRAM_NAME, message_len - 1); } + clearerr(stream); } FlogConfigMessageType diff --git a/src/config.h b/src/config.h index 0015c28..3422ca9 100644 --- a/src/config.h +++ b/src/config.h @@ -33,9 +33,9 @@ #include #include "common.h" -extern const int subsystem_len; -extern const int category_len; -extern const int message_len; +extern const size_t subsystem_len; +extern const size_t category_len; +extern const size_t message_len; /*! \brief An enumerated type representing the log level. */ typedef enum FlogConfigLevelData { diff --git a/test/test_common.c b/test/test_common.c index 2253194..04a40e8 100644 --- a/test/test_common.c +++ b/test/test_common.c @@ -109,7 +109,7 @@ flog_usage_succeeds(void **state) { char expected_string[1024] = {0}; sprintf(expected_string, - "%s %s\n" + "%s v%d.%d.%d\n" "\n" "Usage:\n" " %s [options] message\n" @@ -129,7 +129,9 @@ flog_usage_succeeds(void **state) { " default, info, debug, error, fault\n" "\n", PROGRAM_NAME, - PROGRAM_VERSION, + PROGRAM_VERSION_MAJOR, + PROGRAM_VERSION_MINOR, + PROGRAM_VERSION_PATCH, PROGRAM_NAME ); @@ -143,7 +145,12 @@ flog_version_succeeds(void **state) { UNUSED(state); char expected_string[ERROR_STRING_LEN] = {0}; - sprintf(expected_string, "%s %s\n", PROGRAM_NAME, PROGRAM_VERSION); + sprintf(expected_string, "%s v%d.%d.%d\n", + PROGRAM_NAME, + PROGRAM_VERSION_MAJOR, + PROGRAM_VERSION_MINOR, + PROGRAM_VERSION_PATCH + ); flog_version(); diff --git a/test/test_config.c b/test/test_config.c index 1536898..c18abdd 100644 --- a/test/test_config.c +++ b/test/test_config.c @@ -745,33 +745,42 @@ flog_config_new_with_message_from_pipe_stream_succeeds(void **state) { int pipe_fd[2]; const char *message = "0123456789ABCDEF"; - // Create a pipe and allocate a file descriptor pair for reading the message string + // Create a pipe and allocate a file descriptor pair for the message string if (pipe(pipe_fd) == -1) { perror("pipe"); fail(); }; - // Write a test message to the write end of the pipe and close its file descriptor - write(pipe_fd[1], message, strlen(message)); - close(pipe_fd[1]); - // Associate a stream with the read end of the pipe - FILE *pipe_file = fdopen(pipe_fd[0], "r"); - if (pipe_file == NULL) { - perror("fdopen"); + FILE *pipe_read_stream = fdopen(pipe_fd[0], "r"); + if (pipe_read_stream == NULL) { + perror("fdopen(pipe_fd[0])"); + close(pipe_fd[0]); + close(pipe_fd[1]); + fail(); + } + + // Associate a stream with the write end of the pipe + FILE *pipe_write_stream = fdopen(pipe_fd[1], "w"); + if (pipe_write_stream == NULL) { + perror("fdopen(pipe_fd[1])"); close(pipe_fd[0]); + close(pipe_fd[1]); fail(); } + // Write a test message to the pipe stream and close the stream and file descriptor + fprintf(pipe_write_stream, "%s", message); + fclose(pipe_write_stream); + // Save stdin and reassign temporarily to the pipe stream FILE *saved_stdin = stdin; - stdin = pipe_file; + stdin = pipe_read_stream; FlogConfig *config = flog_config_new(mock_argc, mock_argv, &error); // Ensure stdin stream is restored before making assertions in order to avoid impacting // other tests if an assertion fails and leaves stdin pointing to the pipe stream - fclose(pipe_file); stdin = saved_stdin; assert_non_null(config); @@ -779,6 +788,7 @@ flog_config_new_with_message_from_pipe_stream_succeeds(void **state) { assert_string_equal(flog_config_get_message(config), message); flog_config_free(config); + fclose(pipe_read_stream); } static void @@ -790,39 +800,39 @@ flog_config_new_with_message_from_regular_file_stream_succeeds(void **state) { TEST_PROGRAM_NAME ) - int fd; + int temp_fd; char template[] = "/tmp/flog.XXXXXXXX"; - // Create a temporary file for reading the message string - if ((fd = mkstemp(template)) == -1) { + // Create a temporary file for the message string + if ((temp_fd = mkstemp(template)) == -1) { perror("mkstemp"); fail(); } - // Write a test message to the temporary file - const char *message = "0123456789ABCDEF"; - write(fd, message, strlen(message)); - // Associate a stream with the temporary file - FILE *temp_file = fdopen(fd, "r"); - if (temp_file == NULL) { + FILE *temp_stream = fdopen(temp_fd, "r+"); + if (temp_stream == NULL) { perror("fdopen"); - close(fd); + close(temp_fd); + unlink(template); fail(); } + // Write a test message to the temporary file stream + const char *message = "0123456789ABCDEF"; + fprintf(temp_stream, "%s", message); + // Set the file position indicator for the stream to the beginning of the file - rewind(temp_file); + rewind(temp_stream); // Save stdin and reassign temporarily to the temporary file stream FILE *saved_stdin = stdin; - stdin = temp_file; + stdin = temp_stream; FlogConfig *config = flog_config_new(mock_argc, mock_argv, &error); // Ensure stdin stream is restored before making assertions in order to avoid impacting // other tests if an assertion fails and leaves stdin pointing to the temporary file stream - fclose(temp_file); stdin = saved_stdin; assert_non_null(config); @@ -831,6 +841,7 @@ flog_config_new_with_message_from_regular_file_stream_succeeds(void **state) { flog_config_free(config); unlink(template); + fclose(temp_stream); } static void @@ -1312,6 +1323,49 @@ flog_config_set_message_from_stream_with_null_stream_arg_fails(void **state) { flog_config_free(config); } +static void +flog_config_set_message_from_stream_with_broken_stream_fails(void **state) { + UNUSED(state); + + FlogError error = TEST_ERROR; + MOCK_ARGS( + TEST_PROGRAM_NAME, + TEST_MESSAGE + ) + + int temp_fd; + char template[] = "/tmp/flog.XXXXXXXX"; + + // Create a temporary file + if ((temp_fd = mkstemp(template)) == -1) { + perror("mkstemp"); + fail(); + } + + // Associate a stream with the temporary file + FILE *temp_stream = fdopen(temp_fd, "r+"); + if (temp_stream == NULL) { + perror("fdopen"); + close(temp_fd); + unlink(template); + fail(); + } + + // Simulate a broken stream + fclose(temp_stream); + + // Initialise a FlogConfig object with message string from arguments + FlogConfig *config = flog_config_new(mock_argc, mock_argv, &error); + + // Test message parsing when read from a broken stream + flog_config_set_message_from_stream(config, temp_stream); + + assert_non_null(config); + assert_string_equal(flog_config_get_message(config), ""); + + flog_config_free(config); +} + static void flog_config_set_and_get_message_from_stream_succeeds(void **state) { UNUSED(state); @@ -1345,19 +1399,46 @@ flog_config_set_message_from_stream_with_long_message_truncates(void **state) { TEST_MESSAGE ) + int temp_fd; + char template[] = "/tmp/flog.XXXXXXXX"; + + // Create a temporary file for the message string + if ((temp_fd = mkstemp(template)) == -1) { + perror("mkstemp"); + fail(); + } + + // Associate a stream with the temporary file + FILE *temp_stream = fdopen(temp_fd, "r+"); + if (temp_stream == NULL) { + perror("fdopen"); + close(temp_fd); + unlink(template); + fail(); + } + + // Write a test message that exceeds message_len to the temporary file char *message = malloc(message_len + 1); memset(message, TEST_CHAR, message_len); message[message_len] = '\0'; + fprintf(temp_stream, "%s", message); - FILE *mock_stream = fmemopen(message, strlen(message), "r"); + // Set the file position indicator for the stream to the beginning of the file + rewind(temp_stream); + + // Initialise a FlogConfig object with message string from arguments FlogConfig *config = flog_config_new(mock_argc, mock_argv, &error); - flog_config_set_message_from_stream(config, mock_stream); + // Test message truncation when read from a stream + flog_config_set_message_from_stream(config, temp_stream); assert_int_equal(strlen(flog_config_get_message(config)), message_len - 1); + assert_string_not_equal(flog_config_get_message(config), message); + message[message_len - 1] = '\0'; + assert_string_equal(flog_config_get_message(config), message); - fclose(mock_stream); flog_config_free(config); free(message); + fclose(temp_stream); } static void @@ -1580,6 +1661,9 @@ int main(void) { cmocka_unit_test(flog_config_set_message_from_stream_with_null_config_arg_fails), cmocka_unit_test(flog_config_set_message_from_stream_with_null_stream_arg_fails), + // flog_config_set_message_from_stream() failure tests + cmocka_unit_test(flog_config_set_message_from_stream_with_broken_stream_fails), + // flog_config_set_message_from_stream() success tests cmocka_unit_test(flog_config_set_and_get_message_from_stream_succeeds),