shared/options: implement the equivalent of 'opterr'
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.
This commit is contained in:
+35
-20
@@ -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];
|
||||
|
||||
@@ -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. */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user