Make util's option parsing more modular 33/244633/5
authorMateusz Majewski <m.majewski2@samsung.com>
Fri, 18 Sep 2020 09:05:26 +0000 (11:05 +0200)
committerMateusz Majewski <m.majewski2@samsung.com>
Fri, 25 Sep 2020 06:54:49 +0000 (08:54 +0200)
Will be helpful in making the logger daemon use this implementation too
instead of having its own implementation (DRY!).

Change-Id: I899923f4299d6a4604526d3bafb8c5ee60029d39

src/logutil/logutil.c

index eac4923..079642e 100644 (file)
@@ -51,25 +51,71 @@ static void show_version(const char *name)
        printf("%s version: %s-%s\n", name, __DLOG_VERSION, __DLOG_RELEASE);
 }
 
-static int enomem_err()
+struct parse_result {
+       enum {
+               PARSE_OK,
+               PARSE_OOM,
+               PARSE_BAD_COLOR,
+               PARSE_BAD_SORT_BY,
+               PARSE_BAD_BUFFER,
+               PARSE_BAD_FORMAT,
+               PARSE_OPTION_REPEATED,
+               PARSE_OPTION_NUMERICAL_ARGUMENT,
+               PARSE_VERSION_REQUESTED,
+               PARSE_HELP_REQUESTED,
+               PARSE_NO_PARSE,
+       } status;
+       union {
+               struct {
+                       bool colors_auto;
+                       bool colors_force;
+                       log_print_format format;
+                       size_t rotate_size_kbytes;
+                       size_t max_rotated;
+                       const char *file_path;
+                       size_t write_buffer_size;
+                       int enabled_buffers;
+                       action_e action;
+                       list_head filterspecs;
+                       list_head pid_filters;
+                       list_head tid_filters;
+                       int sorting_settings;
+                       dlogutil_mode_e mode;
+                       unsigned dump_size;
+                       dlogutil_sorting_order_e sort_by;
+               };
+               const char *which_option;
+               const char *bad_contents;
+       };
+};
+
+void parse_result_cleanup(struct parse_result *pr)
 {
-       ERR("Error: out of memory\n");
-       return -ENOMEM;
+       if (pr && pr->status == PARSE_OK) {
+               list_clear(&pr->filterspecs);
+               list_clear_free_contents(&pr->pid_filters);
+               list_clear_free_contents(&pr->tid_filters);
+       }
 }
 
-static int parse_options(int argc, char **argv, struct log_file *l_file, int *enabled_buffers, action_e *action,
-       dlogutil_config_s *config, dlogutil_mode_e *mode, unsigned int *dump_size, dlogutil_sorting_order_e *sort_by)
+static struct parse_result parse_options(int argc, char **argv)
 {
-       assert(argv);
-       assert(l_file);
-       assert(enabled_buffers);
-       assert(action);
-       assert(config);
-       assert(mode);
-       assert(sort_by);
-
-       *mode = DLOGUTIL_MODE_CONTINUOUS;
-       *dump_size = DLOGUTIL_MAX_DUMP_SIZE;
+       bool colors_auto = true;
+       bool colors_force = false;
+       log_print_format format = FORMAT_BRIEF;
+       size_t rotate_size_kbytes = DEFAULT_ROTATE_SIZE_KB;
+       size_t max_rotated = DEFAULT_ROTATE_NUM_FILES;
+       const char *file_path = NULL;
+       size_t write_buffer_size = 0;
+       int enabled_buffers = 0;
+       action_e action = ACTION_PRINT;
+       __attribute__((cleanup(list_clear))) list_head filterspecs = NULL;
+       __attribute__((cleanup(list_clear_free_contents))) list_head pid_filters = NULL;
+       __attribute__((cleanup(list_clear_free_contents))) list_head tid_filters = NULL;
+       int sorting_settings = DEFAULT_SORT_BUFFER_SIZE;
+       dlogutil_mode_e mode = DLOGUTIL_MODE_CONTINUOUS;
+       unsigned dump_size = DLOGUTIL_MAX_DUMP_SIZE;
+       dlogutil_sorting_order_e sort_by = DLOGUTIL_SORT_DEFAULT;
 
        bool buffer_made = false;
 
@@ -83,7 +129,6 @@ static int parse_options(int argc, char **argv, struct log_file *l_file, int *en
                        {"help"   ,       no_argument, NULL, 'h'},
                        {0}
                };
-               int err_arg_nondigit = 0;
                int option = getopt_long(argc, argv, "cdmt:gsf:r:n:v:b:u:e:h", long_options, NULL);
 
                if (option < 0)
@@ -93,164 +138,165 @@ static int parse_options(int argc, char **argv, struct log_file *l_file, int *en
                case 0: { /* tid filter */
                        int tid;
                        if (sscanf(optarg, "%d", &tid) != 1)
-                               err_arg_nondigit = 1;
-                       else if (dlogutil_config_filter_tid(config, tid))
-                               return enomem_err();
+                               return (struct parse_result) { .status = PARSE_OPTION_NUMERICAL_ARGUMENT, .which_option = "--tid", };
+                       else {
+                               pid_t *insert = calloc(1, sizeof(pid_t));
+                               if (insert == NULL)
+                                       return (struct parse_result) { .status = PARSE_OOM, };
+                               *insert = tid;
+                               if (!list_add(&tid_filters, insert)) {
+                                       free(insert);
+                                       return (struct parse_result) { .status = PARSE_OOM, };
+                               }
+                       }
                        break;
                }
                case 1: { /* pid filter */
                        int pid;
                        if (sscanf(optarg, "%d", &pid) != 1)
-                               err_arg_nondigit = 1;
-                       else if (dlogutil_config_filter_pid(config, pid))
-                               return enomem_err();
+                               return (struct parse_result) { .status = PARSE_OPTION_NUMERICAL_ARGUMENT, .which_option = "--pid", };
+                       else {
+                               pid_t *insert = calloc(1, sizeof(pid_t));
+                               if (insert == NULL)
+                                       return (struct parse_result) { .status = PARSE_OOM, };
+                               *insert = pid;
+                               if (!list_add(&tid_filters, insert)) {
+                                       free(insert);
+                                       return (struct parse_result) { .status = PARSE_OOM, };
+                               }
+                       }
                        break;
                }
                case 2: { /* version */
-                       show_version(argv[0]);
-                       return 1;
+                       return (struct parse_result) { .status = PARSE_VERSION_REQUESTED, };
                }
                case 3: /* colored headers */
-                       l_file->colors_auto = false;
+                       colors_auto = false;
                        if (!strcmp(optarg, "always"))
-                               l_file->format.color = true;
+                               colors_force = true;
                        else if (!strcmp(optarg, "auto"))
-                               l_file->colors_auto = true;
+                               colors_auto = true;
                        else if (!strcmp(optarg, "never"))
-                               l_file->format.color = false;
-                       else {
-                               ERR("Error: invalid color setting\n");
-                               return -EINVAL;
-                       }
+                               colors_force = false;
+                       else
+                               return (struct parse_result) { .status = PARSE_BAD_COLOR, .bad_contents = optarg, };
                        break;
                case 4: /* timestamp */
                        if (!strcmp(optarg, "default"))
-                               *sort_by = DLOGUTIL_SORT_DEFAULT;
+                               sort_by = DLOGUTIL_SORT_DEFAULT;
                        else {
-                               *sort_by = get_order_from_string(optarg);
-                               if (*sort_by == DLOGUTIL_SORT_DEFAULT) {
-                                       ERR("Error: invalid sort by\n");
-                                       return -EINVAL;
-                               }
+                               sort_by = get_order_from_string(optarg);
+                               if (sort_by == DLOGUTIL_SORT_DEFAULT)
+                                       return (struct parse_result) { .status = PARSE_BAD_SORT_BY, .bad_contents = optarg, };
                        }
                        break;
                case 'd':
-                       *mode = DLOGUTIL_MODE_DUMP;
+                       mode = DLOGUTIL_MODE_DUMP;
                        break;
                case 'm':
-                       *mode = DLOGUTIL_MODE_MONITOR;
+                       mode = DLOGUTIL_MODE_MONITOR;
                        break;
                case 't':
-                       *mode = DLOGUTIL_MODE_DUMP;
-                       if (sscanf(optarg, "%u", dump_size) != 1)
-                               err_arg_nondigit = 1;
+                       mode = DLOGUTIL_MODE_DUMP;
+                       if (sscanf(optarg, "%u", &dump_size) != 1)
+                               return (struct parse_result) { .status = PARSE_OPTION_NUMERICAL_ARGUMENT, .which_option = "-t", };
                        break;
                case 'c':
-                       *action = ACTION_CLEAR;
+                       action = ACTION_CLEAR;
                        break;
                case 'g':
-                       *action = ACTION_GET_CAPACITY;
+                       action = ACTION_GET_CAPACITY;
                        break;
                case 'b': {
                        log_id_t id = log_id_by_name(optarg);
-                       if (id == LOG_ID_INVALID) {
-                               ERR("Error: there is no buffer \"%s\"\n", optarg);
-                               return -ENOENT;
-                       }
-                       bit_set(enabled_buffers, id);
+                       if (id == LOG_ID_INVALID)
+                               return (struct parse_result) { .status = PARSE_BAD_BUFFER, .bad_contents = optarg, };
+                       bit_set(&enabled_buffers, id);
                        break;
                }
                case 'u': {
                        unsigned size;
                        if (sscanf(optarg, "%u", &size) != 1)
-                               err_arg_nondigit = 1;
+                               return (struct parse_result) { .status = PARSE_OPTION_NUMERICAL_ARGUMENT, .which_option = "-u", };
                        if (size == 0)
-                               dlogutil_config_sorting_disable(config);
+                               sorting_settings = 0;
                        else
-                               dlogutil_config_sorting_enable_with_size(config, size);
+                               sorting_settings = size;
                        break;
                }
                case 'f':
-                       if (logfile_set_path(l_file, optarg) < 0)
-                               return enomem_err();
+                       file_path = optarg;
                        break;
                case 'v': {
-                       l_file->format.format = log_format_from_string(optarg);
-                       if (l_file->format.format == FORMAT_OFF) {
-                               ERR("Error: invalid format\n");
-                               return -EINVAL;
-                       }
+                       format = log_format_from_string(optarg);
+                       if (format == FORMAT_OFF)
+                               return (struct parse_result) { .status = PARSE_BAD_FORMAT, .bad_contents = optarg, };
                        break;
                }
                case 's':
-                       if (dlogutil_config_filter_filterspec(config, "*:S"))
-                               return enomem_err();
+                       if (!list_add(&filterspecs, "*:S"))
+                               return (struct parse_result) { .status = PARSE_OOM, };
                        break;
                case 'r':
-                       if (sscanf(optarg, "%zu", &l_file->rotate_size_kbytes) != 1)
-                               err_arg_nondigit = 1;
+                       if (sscanf(optarg, "%zu", &rotate_size_kbytes) != 1)
+                               return (struct parse_result) { .status = PARSE_OPTION_NUMERICAL_ARGUMENT, .which_option = "-r", };
                        break;
                case 'n':
-                       if (sscanf(optarg, "%zu", &l_file->max_rotated) != 1)
-                               err_arg_nondigit = 1;
+                       if (sscanf(optarg, "%zu", &max_rotated) != 1)
+                               return (struct parse_result) { .status = PARSE_OPTION_NUMERICAL_ARGUMENT, .which_option = "-n", };
                        break;
                case 'e': {
-                       size_t buf_size;
-                       if (buffer_made) {
-                               ERR("Multiple -e\n");
-                               return -EINVAL;
-                       }
-                       if (sscanf(optarg, "%zu", &buf_size) != 1)
-                               err_arg_nondigit = 1;
-                       if (buf_size > 0)
-                               if (!logfile_init_buffer(l_file, buf_size))
-                                       ERR("Warning: failed to create the write buffer\n");
-                                       // Not an error, since we can continue
+                       if (buffer_made)
+                               return (struct parse_result) { .status = PARSE_OPTION_REPEATED, .which_option = "-e", };
+                       if (sscanf(optarg, "%zu", &write_buffer_size) != 1)
+                               return (struct parse_result) { .status = PARSE_OPTION_NUMERICAL_ARGUMENT, .which_option = "-e", };
                        buffer_made = true;
                        break;
                }
                case 'h':
-                       show_help(argv[0], true);
-                       return 1;
+                       return (struct parse_result) { .status = PARSE_HELP_REQUESTED, };
                default: // invalid option or missing mandatory parameter
-                       show_help(argv[0], false);
-                       return -EINVAL;
-               }
-
-               if (err_arg_nondigit) {
-                       ERR("Error: -%c requires a numerical parameter\n", option);
-                       return -EINVAL;
+                       return (struct parse_result) { .status = PARSE_NO_PARSE, };
                }
        }
 
        while (optind < argc) {
-               int r = dlogutil_config_filter_filterspec(config, argv[optind++]);
-               switch (r) {
-               case TIZEN_ERROR_INVALID_PARAMETER:
-                       /* We assert filter is not NULL above, and the system should
-                        * guarantee that argv[i] are also not NULL. Therefore this
-                        * error always means invalid string contents passed by the
-                        * user, for example `dlogutil :D`. */
-                       show_help(argv[0], false);
-                       return -EINVAL;
-               case TIZEN_ERROR_OUT_OF_MEMORY:
-                       return enomem_err();
-               case TIZEN_ERROR_NONE:
-                       continue;
-               default:
-                       assert(false);
-               }
+               /* TODO: We don't want to call log_filter_set_filterspec/dlogutil_config_filter_filterspec here,
+                * as we want the option parser to be general. Unfortunately, this means that the user has to parse
+                * the filterspecs and not us. Any idea on how to make this nicer? */
+               if (!list_add(&filterspecs, argv[optind++]))
+                       return (struct parse_result) { .status = PARSE_OOM, };
        }
 
-       if (*enabled_buffers == 0)
-               *enabled_buffers = default_buffers;
-
-       if (!buffer_made && *mode == DLOGUTIL_MODE_DUMP)
-               if (!logfile_init_buffer(l_file, DEFAULT_WRITE_BUFFER_SIZE))
-                       ERR("Warning: failed to create write buffer\n");
-                       // Again, not an error, since we can continue
-
-       return 0;
+       if (enabled_buffers == 0)
+               enabled_buffers = default_buffers;
+
+       if (!buffer_made && mode == DLOGUTIL_MODE_DUMP)
+               write_buffer_size = DEFAULT_WRITE_BUFFER_SIZE;
+
+       struct parse_result ret = (struct parse_result) {
+               .status = PARSE_OK,
+               .colors_auto = colors_auto,
+               .colors_force = colors_force,
+               .format = format,
+               .rotate_size_kbytes = rotate_size_kbytes,
+               .max_rotated = max_rotated,
+               .file_path = file_path,
+               .write_buffer_size = write_buffer_size,
+               .enabled_buffers = enabled_buffers,
+               .action = action,
+               .filterspecs = filterspecs,
+               .pid_filters = pid_filters,
+               .tid_filters = tid_filters,
+               .sorting_settings = sorting_settings,
+               .mode = mode,
+               .dump_size = dump_size,
+               .sort_by = sort_by,
+       };
+       filterspecs = NULL;
+       pid_filters = NULL;
+       tid_filters = NULL;
+       return ret;
 }
 
 static void config_cleanup(dlogutil_config_s *const *config) {
@@ -468,37 +514,112 @@ static int do_print(dlogutil_mode_e mode, unsigned int dump_size, int enabled_bu
 #ifndef UNIT_TEST
 int main(int argc, char **argv)
 {
-       int enabled_buffers = 0; // bitset
-       action_e action = ACTION_PRINT;
        __attribute__ ((cleanup(logfile_free))) struct log_file l_file;
        logfile_init(&l_file);
        logfile_set_fd(&l_file, fileno(stdout), 0);
 
+       __attribute__((cleanup(parse_result_cleanup))) struct parse_result pr = parse_options(argc, argv);
+       switch (pr.status) {
+               case PARSE_OK:
+                       break;
+               case PARSE_OOM:
+                       ERR("Error: out of memory while cmdline parsing\n");
+                       return EXIT_FAILURE;
+               case PARSE_BAD_COLOR:
+                       ERR("Error: invalid color setting '%s'\n", pr.bad_contents);
+                       return EXIT_FAILURE;
+               case PARSE_BAD_SORT_BY:
+                       ERR("Error: invalid sort by '%s'\n", pr.bad_contents);
+                       return EXIT_FAILURE;
+               case PARSE_BAD_BUFFER:
+                       ERR("Error: there is no buffer '%s'\n", pr.bad_contents);
+                       return EXIT_FAILURE;
+               case PARSE_BAD_FORMAT:
+                       ERR("Error: invalid format '%s'\n", pr.bad_contents);
+                       return EXIT_FAILURE;
+               case PARSE_OPTION_REPEATED:
+                       ERR("Error: multiple %s\n", pr.which_option);
+                       return EXIT_FAILURE;
+               case PARSE_OPTION_NUMERICAL_ARGUMENT:
+                       ERR("Error: %s requires a numerical parameter\n", pr.which_option);
+                       return EXIT_FAILURE;
+               case PARSE_VERSION_REQUESTED:
+                       show_version(argv[0]);
+                       return EXIT_SUCCESS;
+               case PARSE_HELP_REQUESTED:
+                       show_help(argv[0], false);
+                       return EXIT_SUCCESS;
+               case PARSE_NO_PARSE:
+                       show_help(argv[0], true);
+                       return EXIT_FAILURE;
+       }
+
+       l_file.colors_auto = pr.colors_auto;
+       l_file.format.color = pr.colors_force;
+       l_file.format.format = pr.format;
+       l_file.rotate_size_kbytes = pr.rotate_size_kbytes;
+       l_file.max_rotated = pr.max_rotated;
+
+       if (pr.file_path) {
+               if (logfile_set_path(&l_file, pr.file_path) < 0) {
+                       ERR("Error: out of memory while logfile initialization\n");
+                       return EXIT_FAILURE;
+               }
+       }
+       if (pr.write_buffer_size > 0) {
+               if (!logfile_init_buffer(&l_file, pr.write_buffer_size))
+                       ERR("Warning: failed to create the write buffer\n");
+       }
+
        __attribute__((cleanup(config_cleanup))) dlogutil_config_s *config = dlogutil_config_create();
        if (!config) {
                errno = ENOMEM;
                ERR("Error while initialising: %m\n");
                return EXIT_FAILURE;
        }
-       dlogutil_mode_e mode;
-       unsigned int dump_size;
-       dlogutil_sorting_order_e sort_by = DLOGUTIL_SORT_DEFAULT;
-
-       int r = parse_options(argc, argv, &l_file, &enabled_buffers, &action, config, &mode, &dump_size, &sort_by);
-       if (r)
-               return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+       if (pr.sorting_settings == 0)
+               dlogutil_config_sorting_disable(config);
+       else
+               dlogutil_config_sorting_enable_with_size(config, pr.sorting_settings);
+       for (list_head it = pr.filterspecs; it != NULL; list_next(&it)) {
+               int r = dlogutil_config_filter_filterspec(config, list_at(it));
+               switch (r) {
+               case TIZEN_ERROR_INVALID_PARAMETER:
+                       /* The parser makes sure the value isn't NULL. Therefore this
+                        * error always means invalid string contents passed by the
+                        * user, for example `dlogutil :D`. */
+                       show_help(argv[0], true);
+                       return EXIT_FAILURE;
+               case TIZEN_ERROR_OUT_OF_MEMORY:
+                       ERR("Error: out of memory while applying filterspec filter\n");
+                       return EXIT_FAILURE;
+               case TIZEN_ERROR_NONE:
+                       continue;
+               default:
+                       assert(false);
+               }
+       }
+       for (list_head it = pr.pid_filters; it != NULL; list_next(&it)) {
+               if (dlogutil_config_filter_pid(config, *(pid_t *)list_at(it)))
+                       ERR("Error: out of memory while applying PID filter\n");
+       }
+       for (list_head it = pr.tid_filters; it != NULL; list_next(&it)) {
+               if (dlogutil_config_filter_tid(config, *(pid_t *)list_at(it)))
+                       ERR("Error: out of memory while applying TID filter\n");
+       }
 
-       switch (action) {
+       int r;
+       switch (pr.action) {
        case ACTION_PRINT: {
-               r = do_print(mode, dump_size, enabled_buffers, sort_by, config, &l_file);
+               r = do_print(pr.mode, pr.dump_size, pr.enabled_buffers, pr.sort_by, config, &l_file);
                break;
        }
        case ACTION_GET_CAPACITY: {
-               r = for_each_buffer(enabled_buffers, print_buffer_capacity);
+               r = for_each_buffer(pr.enabled_buffers, print_buffer_capacity);
                break;
        }
        case ACTION_CLEAR:
-               r = for_each_buffer(enabled_buffers, clear_buffer);
+               r = for_each_buffer(pr.enabled_buffers, clear_buffer);
                break;
        }
 
@@ -509,7 +630,7 @@ int main(int argc, char **argv)
                        [ACTION_CLEAR] = "clearing buffer",
                };
                errno = -r;
-               ERR("Error while %s: %m\n", action_names[action]);
+               ERR("Error while %s: %m\n", action_names[pr.action]);
                return EXIT_FAILURE;
        }