diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 14c9213..dcbc635 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,6 @@ set(target flog) -add_executable(flog main.c flog.c flog.h config.c config.h defs.h utils.c utils.h) +add_executable(flog main.c flog.c flog.h config.c config.h common.h common.c common.h) target_link_libraries(${target} PRIVATE ${POPT_LINK_LIBRARIES}) target_include_directories(${target} PRIVATE ${POPT_INCLUDE_DIRS}) diff --git a/src/config.c b/src/config.c index aea54b7..3360458 100644 --- a/src/config.c +++ b/src/config.c @@ -21,10 +21,9 @@ // SOFTWARE. #include "config.h" -#include "defs.h" +#include "common.h" #include #include -#include #include #include #include @@ -70,22 +69,20 @@ struct FlogConfigData { }; FlogConfig * -flog_config_new(int argc, char *argv[]) { +flog_config_new(int argc, char *argv[], FlogError *error) { assert(argc > 0); assert(argv != NULL); + assert(error != NULL); - if (argc == 1) { - errno = ERR_NO_ARGUMENTS_PROVIDED; - return NULL; - } + *error = FLOG_ERROR_NONE; FlogConfig *config = calloc(1, sizeof(struct FlogConfigData)); if (config == NULL) { - errno = ERR_CONFIG_ALLOCATION; + *error = FLOG_ERROR_ALLOC; return NULL; } - flog_config_set_level(config, Default); + flog_config_set_level(config, LVL_DEFAULT); flog_config_set_message_type(config, Public); flog_config_set_version_flag(config, false); flog_config_set_help_flag(config, false); @@ -111,9 +108,16 @@ flog_config_new(int argc, char *argv[]) { break; case 'l': flog_config_set_level(config, flog_config_parse_level(option_argument)); + if (flog_config_get_level(config) == LVL_UNKNOWN) { + flog_config_free(config); + poptFreeContext(context); + *error = FLOG_ERROR_LVL; + return NULL; + } break; case 's': flog_config_set_subsystem(config, option_argument); + break; case 'c': flog_config_set_category(config, option_argument); @@ -134,15 +138,14 @@ flog_config_new(int argc, char *argv[]) { flog_config_free(config); poptFreeContext(context); - errno = ERR_PROGRAM_OPTIONS; + *error = FLOG_ERROR_OPTS; return NULL; } if (strlen(flog_config_get_category(config)) > 0 && strlen(flog_config_get_subsystem(config)) == 0) { - fprintf(stderr, "%s: category option requires subsystem option\n", PROGRAM_NAME); flog_config_free(config); poptFreeContext(context); - errno = ERR_CATEGORY_OPTION_REQUIRES_SUBSYSTEM; + *error = FLOG_ERROR_SUBSYS; return NULL; } @@ -151,7 +154,7 @@ flog_config_new(int argc, char *argv[]) { if (message_args == NULL) { flog_config_free(config); poptFreeContext(context); - errno = ERR_NO_MESSAGE_STRING_PROVIDED; + *error = FLOG_ERROR_MSG; return NULL; } @@ -243,17 +246,17 @@ flog_config_parse_level(const char *str) { FlogConfigLevel level; if (strcmp(str, "default") == 0) { - level = Default; + level = LVL_DEFAULT; } else if (strcmp(str, "info") == 0) { - level = Info; + level = LVL_INFO; } else if (strcmp(str, "debug") == 0) { - level = Debug; + level = LVL_DEBUG; } else if (strcmp(str, "error") == 0) { - level = Error; + level = LVL_ERROR; } else if (strcmp(str, "fault") == 0) { - level = Fault; + level = LVL_FAULT; } else { - level = Unknown; + level = LVL_UNKNOWN; } return level; diff --git a/src/config.h b/src/config.h index 8b45b05..c71e2d7 100644 --- a/src/config.h +++ b/src/config.h @@ -31,15 +31,16 @@ #include #include #include +#include "common.h" /*! \brief An enumerated type representing the log level. */ typedef enum FlogConfigLevelData { - Default, - Info, - Debug, - Error, - Fault, - Unknown + LVL_DEFAULT, + LVL_INFO, + LVL_DEBUG, + LVL_ERROR, + LVL_FAULT, + LVL_UNKNOWN } FlogConfigLevel; /*! \brief An enumerated type representing the log message type. */ @@ -57,18 +58,21 @@ typedef struct FlogConfigData FlogConfig; /*! \brief Create a FlogConfig object representing configuration to be used * with a FlogCli logger object. * - * \param argc An integer representing the number of command-line arguments - * \param argv A pointer to an array of null-terminated strings representing - * command-line arguments + * \param[in] argc An integer representing the number of command-line arguments + * \param[in] argv A pointer to an array of null-terminated strings representing + * command-line arguments + * \param[out] error A pointer to a FlogError object that will be used to represent + * an error condition on failure * * \pre \c argc is greater than \c 0 * \pre \c argv is \e not \c NULL + * \pre \c error is \e not \c NULL * * \return If successful, a pointer to a FlogConfig object; if there is an error - * a \c NULL pointer is returned and \c errno will be set to one of several - * potential error values (see defs.h) + * a \c NULL pointer is returned and \c error will be set to a FlogError + * variant representing an error condition */ -FlogConfig * flog_config_new(int argc, char *argv[]); +FlogConfig * flog_config_new(int argc, char *argv[], FlogError *error); /*! \brief Free a FlogConfig object. * diff --git a/src/defs.h b/src/defs.h deleted file mode 100644 index a65b98b..0000000 --- a/src/defs.h +++ /dev/null @@ -1,36 +0,0 @@ -// MIT License -// -// Copyright (c) 2022 Marc Ransome -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#ifndef FLOG_DEFS_H -#define FLOG_DEFS_H - -#define PROGRAM_NAME "flog" -#define PROGRAM_VERSION "1.4.0" - -#define ERR_CATEGORY_OPTION_REQUIRES_SUBSYSTEM 1 -#define ERR_NO_MESSAGE_STRING_PROVIDED 2 -#define ERR_NO_ARGUMENTS_PROVIDED 3 -#define ERR_PROGRAM_OPTIONS 4 -#define ERR_CONFIG_ALLOCATION 5 -#define ERR_FLOG_ALLOCATION 6 - -#endif //FLOG_DEFS_H diff --git a/src/flog.c b/src/flog.c index 76ce9e2..2d09797 100644 --- a/src/flog.c +++ b/src/flog.c @@ -22,11 +22,10 @@ #include "flog.h" #include -#include #include #include #include -#include "defs.h" +#include "common.h" #include "config.h" #ifdef UNIT_TESTING @@ -50,12 +49,15 @@ struct FlogCliData { }; FlogCli * -flog_cli_new(FlogConfig *config) { +flog_cli_new(FlogConfig *config, FlogError *error) { assert(config != NULL); + assert(error != NULL); + + *error = FLOG_ERROR_NONE; FlogCli *flog = calloc(1, sizeof(struct FlogCliData)); if (flog == NULL) { - errno = ERR_FLOG_ALLOCATION; + *error = FLOG_ERROR_ALLOC; return NULL; } @@ -109,7 +111,7 @@ flog_commit_message(FlogCli *flog) { } } -void +FlogError flog_append_message_output(FlogCli *flog) { assert(flog != NULL); @@ -122,8 +124,8 @@ flog_append_message_output(FlogCli *flog) { FILE *fd = fopen(output_file, "a"); if (fd == NULL) { - fprintf(stderr, "%s: unable to append log message to file (%s)\n", PROGRAM_NAME, strerror(errno)); - exit(errno); + umask(original_umask); + return FLOG_ERROR_APPEND; } umask(original_umask); @@ -131,6 +133,8 @@ flog_append_message_output(FlogCli *flog) { fprintf(fd, "%s", flog_config_get_message(config)); fclose(fd); } + + return FLOG_ERROR_NONE; } void @@ -142,19 +146,19 @@ flog_commit_public_message(FlogCli *flog) { const char *message = flog_config_get_message(config); switch (level) { - case Default: + case LVL_DEFAULT: os_log(flog->log, OS_LOG_FORMAT_PUBLIC, message); break; - case Info: + case LVL_INFO: os_log_info(flog->log, OS_LOG_FORMAT_PUBLIC, message); break; - case Debug: + case LVL_DEBUG: os_log_debug(flog->log, OS_LOG_FORMAT_PUBLIC, message); break; - case Error: + case LVL_ERROR: os_log_error(flog->log, OS_LOG_FORMAT_PUBLIC, message); break; - case Fault: + case LVL_FAULT: os_log_fault(flog->log, OS_LOG_FORMAT_PUBLIC, message); break; default: @@ -172,19 +176,19 @@ flog_commit_private_message(FlogCli *flog) { const char *message = flog_config_get_message(config); switch (level) { - case Default: + case LVL_DEFAULT: os_log(flog->log, OS_LOG_FORMAT_PRIVATE, message); break; - case Info: + case LVL_INFO: os_log_info(flog->log, OS_LOG_FORMAT_PRIVATE, message); break; - case Debug: + case LVL_DEBUG: os_log_debug(flog->log, OS_LOG_FORMAT_PRIVATE, message); break; - case Error: + case LVL_ERROR: os_log_error(flog->log, OS_LOG_FORMAT_PRIVATE, message); break; - case Fault: + case LVL_FAULT: os_log_fault(flog->log, OS_LOG_FORMAT_PRIVATE, message); break; default: diff --git a/src/flog.h b/src/flog.h index b1f27c5..7b7670e 100644 --- a/src/flog.h +++ b/src/flog.h @@ -24,6 +24,7 @@ #define FLOG_H #include "config.h" +#include "common.h" /*! \file flog.h * @@ -38,14 +39,18 @@ typedef struct FlogCliData FlogCli; /*! \brief Create a FlogCli object to be used for logging messages to the unified logging system. * - * \param config A pointer to a FlogConfig object + * \param[in] config A pointer to a FlogConfig object + * \param[out] error A pointer to a FlogError object that will be used to represent + * an error condition on failure * * \pre \c config is \e not \c NULL + * \pre \c error is \e not \c NULL * * \return If successful, a pointer to a FlogCli object; if there is an error - * a \c NULL pointer is returned and \c errno will be set to \c ERR_FLOG_ALLOCATION + * a \c NULL pointer is returned and \c error will be set to a FlogError + * variant representing an error condition */ -FlogCli * flog_cli_new(FlogConfig *config); +FlogCli * flog_cli_new(FlogConfig *config, FlogError *error); /*! \brief Free a FlogCli object. * @@ -88,7 +93,10 @@ void flog_commit_message(FlogCli *flog); * \param flog A pointer to the FlogCli object * * \pre \c flog is \e not \c NULL + * + * \return If successful, the FlogError variant FLOG_ERROR_NONE, otherwise some + * other variant representing an error condition */ -void flog_append_message_output(FlogCli *flog); +FlogError flog_append_message_output(FlogCli *flog); #endif //FLOG_H diff --git a/src/main.c b/src/main.c index c4e23db..3e63ac5 100644 --- a/src/main.c +++ b/src/main.c @@ -22,44 +22,54 @@ #include #include -#include #include "flog.h" -#include "defs.h" -#include "utils.h" +#include "common.h" int main(int argc, char *argv[]) { - FlogConfig *config = flog_config_new(argc, argv); + + if (argc == 1 ) { + flog_usage(); + return 1; + } + + FlogError error = FLOG_ERROR_NONE; + FlogConfig *config = flog_config_new(argc, argv, &error); if (config == NULL) { - if (errno == ERR_CONFIG_ALLOCATION) { - fprintf(stderr, "%s: config allocation failure\n", PROGRAM_NAME); - } else if (errno == ERR_NO_ARGUMENTS_PROVIDED) { - flog_usage(); - } else if (errno == ERR_NO_MESSAGE_STRING_PROVIDED) { - fprintf(stderr, "%s: message string required\n", PROGRAM_NAME); + if (error != FLOG_ERROR_OPTS) { + flog_print_error(error); } - return errno; + return error; } if (flog_config_get_version_flag(config)) { + flog_config_free(config); flog_version(); return EXIT_SUCCESS; } else if (flog_config_get_help_flag(config)) { + flog_config_free(config); flog_usage(); return EXIT_SUCCESS; - } else if (flog_config_get_level(config) == Unknown) { - fprintf(stderr, "%s: unknown log level\n", PROGRAM_NAME); - return EXIT_FAILURE; } - FlogCli *flog = flog_cli_new(config); - if (flog == NULL && errno == ERR_FLOG_ALLOCATION) { - fprintf(stderr, "%s: logger allocation failure\n", PROGRAM_NAME); - return errno; + error = FLOG_ERROR_NONE; + FlogCli *flog = flog_cli_new(config, &error); + if (flog == NULL) { + flog_config_free(config); + flog_print_error(error); + return error; + } + + error = FLOG_ERROR_NONE; + error = flog_append_message_output(flog); + if (error != FLOG_ERROR_NONE) { + flog_cli_free(flog); + flog_config_free(config); + flog_print_error(error); + return error; } flog_commit_message(flog); - flog_append_message_output(flog); flog_cli_free(flog); flog_config_free(config); diff --git a/src/utils.c b/src/utils.c deleted file mode 100644 index 27f40d3..0000000 --- a/src/utils.c +++ /dev/null @@ -1,58 +0,0 @@ -// MIT License -// -// Copyright (c) 2022 Marc Ransome -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#include "utils.h" -#include "defs.h" -#include - -void -flog_usage(void) { - printf( - "%s %s\n" - "\n" - "Usage:\n" - " %s [options] message\n" - "\n" - "Help Options:\n" - " -h, --help Show this help message\n" - " -v, --version Print the version string\n" - "\n" - "Application Options:\n" - " -s, --subsystem Specify a subsystem name\n" - " -c, --category Specify a category name (requires subsystem option)\n" - " -l, --level Specify the log level ('default' if not provided)\n" - " -a, --append Append the log message to a file (creating it if necessary)\n" - " -p, --private Mark the log message as private\n" - "\n" - "Log Levels:\n" - " default, info, debug, error, fault\n" - "\n", - PROGRAM_NAME, - PROGRAM_VERSION, - PROGRAM_NAME - ); -} - -void -flog_version(void) { - printf("%s %s\n", PROGRAM_NAME, PROGRAM_VERSION); -} diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index 54ad3f8..0000000 --- a/src/utils.h +++ /dev/null @@ -1,37 +0,0 @@ -// MIT License -// -// Copyright (c) 2022 Marc Ransome -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#ifndef FLOG_UTILS_H -#define FLOG_UTILS_H - -/*! \file utils.h - * - * General helper functions for common operations. - */ - -/*! \brief Print help information. */ -void flog_usage(void); - -/*! \brief Print the version string. */ -void flog_version(void); - -#endif //FLOG_UTILS_H diff --git a/test/test_config.c b/test/test_config.c index 7c85571..d3ef78a 100644 --- a/test/test_config.c +++ b/test/test_config.c @@ -24,9 +24,8 @@ #include #include #include -#include #include "config.h" -#include "defs.h" +#include "common.h" #define TEST_PROGRAM_NAME "flog" #define TEST_MESSAGE "test message" @@ -38,110 +37,134 @@ #define TEST_OPTION_SUBSYSTEM_SHORT "-s" #define TEST_OPTION_SUBSYSTEM_LONG "--subsystem" +#define TEST_ERROR 255 + void flog_usage() {} void flog_version() {} static void test_new_config_with_zero_arg_count_calls_assert(void **state) { + FlogError error; int mock_argc = 0; // should fail >0 assertion char *mock_argv[] = {}; // should pass non-null assertion - expect_assert_failure(flog_config_new(mock_argc, mock_argv)); + expect_assert_failure(flog_config_new(mock_argc, mock_argv, &error)); } static void test_new_config_with_null_arg_values_calls_assert(void **state) { + FlogError error; int mock_argc = 1; // should pass >0 assertion char **mock_argv = NULL; // should fail non-null assertion - expect_assert_failure(flog_config_new(mock_argc, mock_argv)); + expect_assert_failure(flog_config_new(mock_argc, mock_argv, &error)); +} + +static void test_new_config_with_no_error_ptr_calls_assert(void **state) { + int mock_argc = 1; // should pass >0 assertion + char *mock_argv[] = {}; // should pass non-null assertion + + expect_assert_failure(flog_config_new(mock_argc, mock_argv, NULL)); } static void test_new_config_returns_non_null(void **state) { + FlogError error = TEST_ERROR; int mock_argc = 2; char *mock_argv[] = { TEST_PROGRAM_NAME, TEST_MESSAGE }; - FlogConfig *config = flog_config_new(mock_argc, mock_argv); + FlogConfig *config = flog_config_new(mock_argc, mock_argv, &error); assert_non_null(config); + assert_int_equal(error, FLOG_ERROR_NONE); flog_config_free(config); } static void test_new_config_with_short_version_option(void **state) { + FlogError error = TEST_ERROR; int mock_argc = 2; char *mock_argv[] = { TEST_PROGRAM_NAME, TEST_OPTION_VERSION_SHORT }; - FlogConfig *config = flog_config_new(mock_argc, mock_argv); + + FlogConfig *config = flog_config_new(mock_argc, mock_argv, &error); assert_true(flog_config_get_version_flag(config)); + assert_int_equal(error, FLOG_ERROR_NONE); flog_config_free(config); } static void test_new_config_with_long_version_option(void **state) { + FlogError error = TEST_ERROR; int mock_argc = 2; char *mock_argv[] = { TEST_PROGRAM_NAME, TEST_OPTION_VERSION_LONG }; - FlogConfig *config = flog_config_new(mock_argc, mock_argv); + FlogConfig *config = flog_config_new(mock_argc, mock_argv, &error); assert_true(flog_config_get_version_flag(config)); + assert_int_equal(error, FLOG_ERROR_NONE); flog_config_free(config); } static void test_new_config_with_short_help_option(void **state) { + FlogError error = TEST_ERROR; int mock_argc = 2; char *mock_argv[] = { TEST_PROGRAM_NAME, TEST_OPTION_HELP_SHORT }; - FlogConfig *config = flog_config_new(mock_argc, mock_argv); + FlogConfig *config = flog_config_new(mock_argc, mock_argv, &error); assert_true(flog_config_get_help_flag(config)); + assert_int_equal(error, FLOG_ERROR_NONE); flog_config_free(config); } static void test_new_config_with_long_help_option(void **state) { + FlogError error = TEST_ERROR; int mock_argc = 2; char *mock_argv[] = { TEST_PROGRAM_NAME, TEST_OPTION_HELP_LONG }; - FlogConfig *config = flog_config_new(mock_argc, mock_argv); + FlogConfig *config = flog_config_new(mock_argc, mock_argv, &error); assert_true(flog_config_get_help_flag(config)); + assert_int_equal(error, FLOG_ERROR_NONE); flog_config_free(config); } -static void test_new_config_with_short_subsystem_option_returns_null_and_sets_errno(void **state) { +static void test_new_config_with_short_subsystem_option_returns_null_and_sets_error(void **state) { + FlogError error = TEST_ERROR; int mock_argc = 2; char *mock_argv[] = { TEST_PROGRAM_NAME, TEST_OPTION_SUBSYSTEM_SHORT }; - FlogConfig *config = flog_config_new(mock_argc, mock_argv); + FlogConfig *config = flog_config_new(mock_argc, mock_argv, &error); assert_null(config); - assert_int_equal(errno, ERR_PROGRAM_OPTIONS); + assert_int_equal(error, FLOG_ERROR_OPTS); } -static void test_new_config_with_long_subsystem_option_returns_null_and_sets_errno(void **state) { +static void test_new_config_with_long_subsystem_option_returns_null_and_sets_error(void **state) { + FlogError error = TEST_ERROR; int mock_argc = 2; char *mock_argv[] = { TEST_PROGRAM_NAME, TEST_OPTION_SUBSYSTEM_LONG }; - FlogConfig *config = flog_config_new(mock_argc, mock_argv); + FlogConfig *config = flog_config_new(mock_argc, mock_argv, &error); assert_null(config); - assert_int_equal(errno, ERR_PROGRAM_OPTIONS); + assert_int_equal(error, FLOG_ERROR_OPTS); } int main(void) { @@ -155,8 +178,8 @@ int main(void) { cmocka_unit_test(test_new_config_with_long_version_option), cmocka_unit_test(test_new_config_with_short_help_option), cmocka_unit_test(test_new_config_with_long_help_option), - cmocka_unit_test(test_new_config_with_short_subsystem_option_returns_null_and_sets_errno), - cmocka_unit_test(test_new_config_with_long_subsystem_option_returns_null_and_sets_errno), + cmocka_unit_test(test_new_config_with_short_subsystem_option_returns_null_and_sets_error), + cmocka_unit_test(test_new_config_with_long_subsystem_option_returns_null_and_sets_error), }; return cmocka_run_group_tests(tests, NULL, NULL);