]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
shared/options: implement the equivalent of 'opterr'
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Wed, 13 May 2026 21:29:46 +0000 (23:29 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 14 May 2026 07:15:48 +0000 (09:15 +0200)
All log messages during option parsing are emitted using log_full,
and the level is set as LOG_ERR + state->log_level_shift. The default
shift is 0, but if set to e.g. 4, we log at LOG_DEBUG, and if set
to 5 or higher, logging is effectively suppressed. (Unless compiled
with LOG_TRACE, when it'd be suppressed if the shift if set to 6
or higher.)  So this gives something like 'opterr', except that
without global state and potentially more flexible.

src/shared/options.c
src/shared/options.h

index 34d02d2850e9c1e130e0b30ee336ec2a0276e1fb..ac36fe07653ec16d046b6f6e3282b2a5ae684467 100644 (file)
@@ -39,6 +39,7 @@ static void shift_arg(char* argv[], int target, int source) {
 }
 
 static int partial_match_error(
+                const OptionParser *state,
                 const Option options[],
                 const Option options_end[],
                 const char *optname,
@@ -57,15 +58,17 @@ static int partial_match_error(
 
                         r = strv_extendf(&s, "--%s", option->long_code);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to format message: %m");
+                                return log_full_errno(LOG_ERR + state->log_level_shift, r,
+                                                      "Failed to format message: %m");
                 }
 
         assert(strv_length(s) == n_partial_matches);
 
         _cleanup_free_ char *p = strv_join_full(s, ", ", /* prefix= */ NULL, /* escape_separator= */ false);
-        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                               "%s: option '%s' is ambiguous; possibilities: %s",
-                               program_invocation_short_name, optname, strnull(p));
+        return log_full_errno(LOG_ERR + state->log_level_shift,
+                              SYNTHETIC_ERRNO(EINVAL),
+                              "%s: option '%s' is ambiguous; possibilities: %s",
+                              program_invocation_short_name, optname, strnull(p));
 }
 
 int option_parse(
@@ -84,9 +87,14 @@ int option_parse(
 
         case OPTION_PARSER_INIT: {
                 assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX);
+                /* Make sure the shifted log level is between LOG_EMERG/0 and LOG_DEBUG/7. */
+                assert(state->log_level_shift >= LOG_EMERG - LOG_ERR &&
+                       state->log_level_shift <= LOG_DEBUG - LOG_ERR);
 
                 if (state->argc < 1) {
-                        r = log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty");
+                        r = log_full_errno(LOG_ERR + state->log_level_shift,
+                                           SYNTHETIC_ERRNO(EUCLEAN),
+                                           "argv cannot be empty");
                         goto fail;
                 }
 
@@ -134,7 +142,9 @@ int option_parse(
                 goto done;
 
         case OPTION_PARSER_FAILED:
-                return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Option parser failed before, refusing.");
+                return log_full_errno(LOG_ERR + state->log_level_shift,
+                                      SYNTHETIC_ERRNO(ESTALE),
+                                      "Option parser failed before, refusing.");
 
         default:
                 assert_not_reached();
@@ -203,7 +213,7 @@ int option_parse(
                         if (eq) {
                                 optname = _optname = strndup(state->argv[state->optind], eq - state->argv[state->optind]);
                                 if (!_optname) {
-                                        r = log_oom();
+                                        r = log_oom_full(LOG_ERR + state->log_level_shift);
                                         goto fail;
                                 }
 
@@ -219,13 +229,15 @@ int option_parse(
                         for (option = state->namespace_start;; option++) {
                                 if (option >= state->namespace_end) {
                                         if (n_partial_matches == 0) {
-                                                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                                    "%s: unrecognized option '%s'",
-                                                                    program_invocation_short_name, optname);
+                                                r = log_full_errno(LOG_ERR + state->log_level_shift,
+                                                                   SYNTHETIC_ERRNO(EINVAL),
+                                                                   "%s: unrecognized option '%s'",
+                                                                   program_invocation_short_name, optname);
                                                 goto fail;
                                         }
                                         if (n_partial_matches > 1) {
                                                 r = partial_match_error(
+                                                                state,
                                                                 state->namespace_start,
                                                                 state->namespace_end,
                                                                 optname,
@@ -261,16 +273,17 @@ int option_parse(
                 char optchar = state->argv[state->optind][state->short_option_offset];
 
                 if (asprintf(&_optname, "-%c", optchar) < 0) {
-                        r = log_oom();
+                        r = log_oom_full(LOG_ERR + state->log_level_shift);
                         goto fail;
                 }
                 optname = _optname;
 
                 for (option = state->namespace_start;; option++) {
                         if (option >= state->namespace_end) {
-                                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                    "%s: unrecognized option '%s'",
-                                                    program_invocation_short_name, optname);
+                                r = log_full_errno(LOG_ERR + state->log_level_shift,
+                                                   SYNTHETIC_ERRNO(EINVAL),
+                                                   "%s: unrecognized option '%s'",
+                                                   program_invocation_short_name, optname);
                                 goto fail;
                         }
 
@@ -295,16 +308,18 @@ int option_parse(
         assert(option);
 
         if (!handling_positional_arg && optval && !option_takes_arg(option)) {
-                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                    "%s: option '%s' doesn't allow an argument",
-                                    program_invocation_short_name, optname);
+                r = log_full_errno(LOG_ERR + state->log_level_shift,
+                                   SYNTHETIC_ERRNO(EINVAL),
+                                   "%s: option '%s' doesn't allow an argument",
+                                   program_invocation_short_name, optname);
                 goto fail;
         }
         if (!handling_positional_arg && !optval && option_arg_required(option)) {
                 if (!state->argv[state->optind + 1]) {
-                        r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                            "%s: option '%s' requires an argument",
-                                            program_invocation_short_name, optname);
+                        r = log_full_errno(LOG_ERR + state->log_level_shift,
+                                           SYNTHETIC_ERRNO(EINVAL),
+                                           "%s: option '%s' requires an argument",
+                                           program_invocation_short_name, optname);
                         goto fail;
                 }
                 optval = state->argv[state->optind + 1];
index f88275821a6e8f2443cce0190461136ea46a5140..d60c9afe81bfe57363ed71f1872cc8ef8db88a8c 100644 (file)
@@ -200,6 +200,9 @@ typedef struct OptionParser {
         char **argv;                  /* The argv array, possibly reordered. */
         OptionParserMode mode;
         const char *namespace;        /* The namespace, may be NULL. */
+        int log_level_shift;          /* The log level difference from the default of LOG_ERR.
+                                       * Allowed values are -3..4.
+                                       * Use 4 == LOG_DEBUG - LOG_ERR to log at debug level. */
 
         const Option *namespace_start, *namespace_end; /* The range of options that are part of our namespace. */