From cb406988ac587a0f2b75948f3fab860005d6bc76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 3 Dec 2025 15:23:12 +0100 Subject: [PATCH 01/12] cgtop: fix confusion in --help When we say '-n --iterations=N' in --help, this means that the program can be invoked with '-n N' or '--iterations=N' or '--iterations N'. (The short option is specified without the argument.) Here we tried to use '-p --order=path' to mean that the program can be invoked with '-p' or '--order=path', but that is incompatible with the established convention. Also, indicate that the arg to --cpu is optional. --- src/cgtop/cgtop.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index bb1b256902..60181caffc 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -702,13 +702,18 @@ static int help(void) { "Show top control groups by their resource usage.\n\n" " -h --help Show this help\n" " --version Show package version\n" - " -p --order=path Order by path\n" - " -t --order=tasks Order by number of tasks/processes\n" - " -c --order=cpu Order by CPU load (default)\n" - " -m --order=memory Order by memory load\n" - " -i --order=io Order by IO load\n" + + " --order=path|tasks|cpu|memory|io\n" + " Order by specified property\n" + " -p Same as --order=path, order by path\n" + " -t Same as --order=tasks, order by number of\n" + " tasks/processes\n" + " -c Same as --order=cpu, order by CPU load\n" + " -m Same as --order=memory, order by memory load\n" + " -i Same as --order=io, order by IO load\n" " -r --raw Provide raw (not human-readable) numbers\n" - " --cpu=percentage Show CPU usage as percentage (default)\n" + " --cpu[=percentage]\n" + " Show CPU usage as percentage (default)\n" " --cpu=time Show CPU usage as time\n" " -P Count userspace processes instead of tasks (excl. kernel)\n" " -k Count all processes instead of tasks (incl. kernel)\n" From 84f69675589a28ca5ee94c5dc4c1d4b2fe3e2c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 6 Dec 2025 13:36:43 +0100 Subject: [PATCH 02/12] importctl: fix confusion in --help See parent commit for explanation. --- src/import/importctl.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/import/importctl.c b/src/import/importctl.c index d131943413..eddddb86d1 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -1035,12 +1035,15 @@ static int help(int argc, char *argv[], void *userdata) { " --format=xz|gzip|bzip2|zstd\n" " Desired output format for export\n" " --force Install image even if already exists\n" - " -m --class=machine Install as machine image\n" - " -P --class=portable Install as portable service image\n" - " -S --class=sysext Install as system extension image\n" - " -C --class=confext Install as configuration extension image\n" + " --class=TYPE Install as the specified TYPE\n" + " -m Install as --class=machine, machine image\n" + " -P Install as --class=portable,\n" + " portable service image\n" + " -S Install as --class=sysext, system extension image\n" + " -C Install as --class=confext,\n" + " configuration extension image\n" " --keep-download=BOOL Control whether to keep pristine copy of download\n" - " -N Shortcut for --keep-download=no\n" + " -N Same as --keep-download=no\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, From e4c6ee2ce546ce772548bf728b6c235bd997e5c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 9 Dec 2025 13:46:48 +0100 Subject: [PATCH 03/12] mount-tool: fix confusion in --help See grandparent commit for explanation. --- src/mount/mount-tool.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index 7dd45aacea..24b93e1239 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -141,7 +141,8 @@ static int help(void) { " --fsck=no Don't run file system check before mount\n" " --description=TEXT Description for unit\n" " -p --property=NAME=VALUE Set mount unit property\n" - " -A --automount=BOOL Create an auto-mount point\n" + " --automount=BOOL Create an automount point\n" + " -A Same as --automount=yes\n" " --timeout-idle-sec=SEC Specify automount idle timeout\n" " --automount-property=NAME=VALUE\n" " Set automount unit property\n" From ff12bcd4fb9ca31b3366c2eb7343ee462550f0f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 4 Dec 2025 14:00:28 +0100 Subject: [PATCH 04/12] path: simplify run() No functional change, just less indents. --- src/path/path-tool.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/path/path-tool.c b/src/path/path-tool.c index 227468dfe8..62eade3b05 100644 --- a/src/path/path-tool.c +++ b/src/path/path-tool.c @@ -250,13 +250,11 @@ static int run(int argc, char* argv[]) { if (r <= 0) return r; - if (argc > optind) { - r = 0; - for (int i = optind; i < argc; i++) - RET_GATHER(r, print_path(argv[i])); - } else - r = list_paths(); + if (optind >= argc) + return list_paths(); + for (int i = optind; i < argc; i++) + RET_GATHER(r, print_path(argv[i])); return r; } From 4a991af01780923d0816f5bf52aefa9aeb75c0f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 5 Dec 2025 19:14:00 +0100 Subject: [PATCH 05/12] cryptenroll: split out parser for --wipe-slot= --- src/cryptenroll/cryptenroll.c | 104 ++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 86d57e964f..2e63b31edb 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -169,6 +169,58 @@ static int determine_default_node(void) { return 0; } +static int parse_wipe_slot(const char *arg) { + int r; + + assert(arg); + + if (isempty(arg)) { + arg_wipe_slots_mask = 0; + arg_wipe_slots_scope = WIPE_EXPLICIT; + return 0; + } + + for (const char *p = arg;;) { + _cleanup_free_ char *slot = NULL; + + r = extract_first_word(&p, &slot, ",", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r == 0) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to parse slot list: %s", arg); + + if (streq(slot, "all")) + arg_wipe_slots_scope = WIPE_ALL; + else if (streq(slot, "empty")) { + if (arg_wipe_slots_scope != WIPE_ALL) /* if "all" was specified before, that wins */ + arg_wipe_slots_scope = WIPE_EMPTY_PASSPHRASE; + } else if (streq(slot, "password")) + arg_wipe_slots_mask |= 1U << ENROLL_PASSWORD; + else if (streq(slot, "recovery")) + arg_wipe_slots_mask |= 1U << ENROLL_RECOVERY; + else if (streq(slot, "pkcs11")) + arg_wipe_slots_mask |= 1U << ENROLL_PKCS11; + else if (streq(slot, "fido2")) + arg_wipe_slots_mask |= 1U << ENROLL_FIDO2; + else if (streq(slot, "tpm2")) + arg_wipe_slots_mask |= 1U << ENROLL_TPM2; + else { + unsigned n; + + r = safe_atou(slot, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse slot index: %s", slot); + if (n > INT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Slot index out of range: %u", n); + + if (!GREEDY_REALLOC(arg_wipe_slots, arg_n_wipe_slots + 1)) + return log_oom(); + + arg_wipe_slots[arg_n_wipe_slots++] = (int) n; + } + } +} + static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -580,55 +632,11 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_WIPE_SLOT: { - const char *p = optarg; - - if (isempty(optarg)) { - arg_wipe_slots_mask = 0; - arg_wipe_slots_scope = WIPE_EXPLICIT; - break; - } - - for (;;) { - _cleanup_free_ char *slot = NULL; - unsigned n; - - r = extract_first_word(&p, &slot, ",", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r == 0) - break; - if (r < 0) - return log_error_errno(r, "Failed to parse slot list: %s", optarg); - - if (streq(slot, "all")) - arg_wipe_slots_scope = WIPE_ALL; - else if (streq(slot, "empty")) { - if (arg_wipe_slots_scope != WIPE_ALL) /* if "all" was specified before, that wins */ - arg_wipe_slots_scope = WIPE_EMPTY_PASSPHRASE; - } else if (streq(slot, "password")) - arg_wipe_slots_mask |= 1U << ENROLL_PASSWORD; - else if (streq(slot, "recovery")) - arg_wipe_slots_mask |= 1U << ENROLL_RECOVERY; - else if (streq(slot, "pkcs11")) - arg_wipe_slots_mask |= 1U << ENROLL_PKCS11; - else if (streq(slot, "fido2")) - arg_wipe_slots_mask |= 1U << ENROLL_FIDO2; - else if (streq(slot, "tpm2")) - arg_wipe_slots_mask |= 1U << ENROLL_TPM2; - else { - r = safe_atou(slot, &n); - if (r < 0) - return log_error_errno(r, "Failed to parse slot index: %s", slot); - if (n > INT_MAX) - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Slot index out of range: %u", n); - - if (!GREEDY_REALLOC(arg_wipe_slots, arg_n_wipe_slots + 1)) - return log_oom(); - - arg_wipe_slots[arg_n_wipe_slots++] = (int) n; - } - } + case ARG_WIPE_SLOT: + r = parse_wipe_slot(optarg); + if (r < 0) + return r; break; - } case ARG_LIST_DEVICES: r = blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_REQUIRE_LUKS, /* ret_devices= */ NULL, /* ret_n_devices= */ NULL); From 7b6f1ea371df5c504807198f2556f98f11293a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 5 Dec 2025 19:14:43 +0100 Subject: [PATCH 06/12] cryptenroll: simplification --- src/cryptenroll/cryptenroll.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 2e63b31edb..2a914a9b49 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -639,11 +639,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_LIST_DEVICES: - r = blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_REQUIRE_LUKS, /* ret_devices= */ NULL, /* ret_n_devices= */ NULL); - if (r < 0) - return r; - - return 0; + return blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_REQUIRE_LUKS, + /* ret_devices= */ NULL, + /* ret_n_devices= */ NULL); case '?': return -EINVAL; From c1fcc8042180b2b6e3c9acb525e730bd6d0c75d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 8 Dec 2025 10:52:11 +0100 Subject: [PATCH 07/12] journal-upload: drop custom option error handling The line to set opterr=0 was added in the initial commit in 3d090cc6f34e5970765dd1e7ee5e648a056d180d. But afaict, this never worked as intended, because ':' must be the first char in optstring given to getopt_long() for it to return ':' for a missing option value. Since this wasn't set, getopt_long() would return '?', and the missing value would be handled as an unknown option: $ build/systemd-journal-upload --key Unknown option --key. $ build/systemd-journal-upload --asdf Unknown option --asdf. Let's just do the standard thing: $ build/systemd-journal-upload --key build/systemd-journal-upload: option '--key' requires an argument $ build/systemd-journal-upload --asdf build/systemd-journal-upload: unrecognized option '--asdf' --- src/journal-remote/journal-upload.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index decb6df5da..c6123146a5 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -776,8 +776,6 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - opterr = 0; - while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0) switch (c) { case 'h': @@ -882,14 +880,7 @@ static int parse_argv(int argc, char *argv[]) { break; case '?': - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown option %s.", - argv[optind - 1]); - - case ':': - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Missing argument to %s.", - argv[optind - 1]); + return -EINVAL; default: assert_not_reached(); From 26de1563d0ee1c6d1a94b99c662da7c5e103991e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 3 Dec 2025 22:58:46 +0100 Subject: [PATCH 08/12] nspawn: simplify parsing of --hostname/--machine --- src/nspawn/nspawn.c | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index d2adc64931..803eecb86a 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -942,31 +942,23 @@ static int parse_argv(int argc, char *argv[]) { } case 'M': - if (isempty(optarg)) - arg_machine = mfree(arg_machine); - else { - if (!hostname_is_valid(optarg, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid machine name: %s", optarg); + if (!isempty(optarg) && !hostname_is_valid(optarg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid machine name: %s", optarg); - r = free_and_strdup(&arg_machine, optarg); - if (r < 0) - return log_oom(); - } + r = free_and_strdup_warn(&arg_machine, optarg); + if (r < 0) + return r; break; case ARG_HOSTNAME: - if (isempty(optarg)) - arg_hostname = mfree(arg_hostname); - else { - if (!hostname_is_valid(optarg, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid hostname: %s", optarg); + if (!isempty(optarg) && !hostname_is_valid(optarg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid hostname: %s", optarg); - r = free_and_strdup(&arg_hostname, optarg); - if (r < 0) - return log_oom(); - } + r = free_and_strdup_warn(&arg_hostname, optarg); + if (r < 0) + return r; arg_settings_mask |= SETTING_HOSTNAME; break; From b4df0a9ee62d553e21f3b70c28841cfd1b8736f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 3 Dec 2025 23:18:13 +0100 Subject: [PATCH 09/12] nspawn: split out helper to parse --private-users This is just to make parse_argv() slightly more managable. --- src/nspawn/nspawn.c | 118 ++++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 49 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 803eecb86a..1c0ab6e712 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -292,6 +292,71 @@ STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_background, freep); +static int parse_private_users( + const char *s, + UserNamespaceMode *ret_userns_mode, + uid_t *ret_uid_shift, + uid_t *ret_uid_range) { + + int boolean, r; + + assert(ret_userns_mode); + assert(ret_uid_shift); + assert(ret_uid_range); + + if (!s) + boolean = true; + else if (!in_charset(s, DIGITS)) + /* do *not* parse numbers as booleans */ + boolean = parse_boolean(s); + else + boolean = -1; + + if (boolean == 0) { + /* no: User namespacing off */ + *ret_userns_mode = USER_NAMESPACE_NO; + *ret_uid_shift = UID_INVALID; + *ret_uid_range = UINT32_C(0x10000); + + } else if (boolean > 0) { + /* yes: User namespacing on, UID range is read from root dir */ + *ret_userns_mode = USER_NAMESPACE_FIXED; + *ret_uid_shift = UID_INVALID; + *ret_uid_range = UINT32_C(0x10000); + + } else if (streq(s, "pick")) { + /* pick: User namespacing on, UID range is picked randomly */ + *ret_userns_mode = USER_NAMESPACE_PICK; /* Note that arg_userns_ownership is + * implied by USER_NAMESPACE_PICK + * further down. */ + *ret_uid_shift = UID_INVALID; + *ret_uid_range = UINT32_C(0x10000); + + } else if (streq(s, "identity")) { + /* identity: User namespaces on, UID range is map of the 0…0xFFFF range to + * itself, i.e. we don't actually map anything, but do take benefit of + * isolation of capability sets. */ + *ret_userns_mode = USER_NAMESPACE_FIXED; + *ret_uid_shift = 0; + *ret_uid_range = UINT32_C(0x10000); + + } else if (streq(optarg, "managed")) { + /* managed: User namespace on, and acquire it from systemd-nsresourced */ + *ret_userns_mode = USER_NAMESPACE_MANAGED; + *ret_uid_shift = UID_INVALID; + *ret_uid_range = UINT32_C(0x10000); + + } else { + /* anything else: User namespacing on, UID range is explicitly configured */ + r = parse_userns_uid_range(optarg, ret_uid_shift, ret_uid_range); + if (r < 0) + return r; + *ret_userns_mode = USER_NAMESPACE_FIXED; + } + + return 0; +} + static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -1133,58 +1198,13 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_PRIVATE_USERS: { - int boolean; - - if (!optarg) - boolean = true; - else if (!in_charset(optarg, DIGITS)) - /* do *not* parse numbers as booleans */ - boolean = parse_boolean(optarg); - else - boolean = -1; - - if (boolean == 0) { - /* no: User namespacing off */ - arg_userns_mode = USER_NAMESPACE_NO; - arg_uid_shift = UID_INVALID; - arg_uid_range = UINT32_C(0x10000); - } else if (boolean > 0) { - /* yes: User namespacing on, UID range is read from root dir */ - arg_userns_mode = USER_NAMESPACE_FIXED; - arg_uid_shift = UID_INVALID; - arg_uid_range = UINT32_C(0x10000); - } else if (streq(optarg, "pick")) { - /* pick: User namespacing on, UID range is picked randomly */ - arg_userns_mode = USER_NAMESPACE_PICK; /* Note that arg_userns_ownership is - * implied by USER_NAMESPACE_PICK - * further down. */ - arg_uid_shift = UID_INVALID; - arg_uid_range = UINT32_C(0x10000); - - } else if (streq(optarg, "identity")) { - /* identity: User namespaces on, UID range is map of the 0…0xFFFF range to - * itself, i.e. we don't actually map anything, but do take benefit of - * isolation of capability sets. */ - arg_userns_mode = USER_NAMESPACE_FIXED; - arg_uid_shift = 0; - arg_uid_range = UINT32_C(0x10000); - } else if (streq(optarg, "managed")) { - /* managed: User namespace on, and acquire it from systemd-nsresourced */ - arg_userns_mode = USER_NAMESPACE_MANAGED; - arg_uid_shift = UID_INVALID; - arg_uid_range = UINT32_C(0x10000); - } else { - /* anything else: User namespacing on, UID range is explicitly configured */ - r = parse_userns_uid_range(optarg, &arg_uid_shift, &arg_uid_range); - if (r < 0) - return r; - arg_userns_mode = USER_NAMESPACE_FIXED; - } + case ARG_PRIVATE_USERS: + r = parse_private_users(optarg, &arg_userns_mode, &arg_uid_shift, &arg_uid_range); + if (r < 0) + return r; arg_settings_mask |= SETTING_USERNS; break; - } case 'U': if (userns_supported()) { From 097f130731513bd934ed74975ac34070197614af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 9 Dec 2025 11:30:17 +0100 Subject: [PATCH 10/12] resolvectl: split out helper to parse stdin No functional change, just splitting up of a lengthy function. --- src/resolve/resolvconf-compat.c | 160 +++++++++++++++++--------------- 1 file changed, 84 insertions(+), 76 deletions(-) diff --git a/src/resolve/resolvconf-compat.c b/src/resolve/resolvconf-compat.c index 47abdc900f..d8e68d524a 100644 --- a/src/resolve/resolvconf-compat.c +++ b/src/resolve/resolvconf-compat.c @@ -14,6 +14,12 @@ #include "string-util.h" #include "strv.h" +typedef enum LookupType { + LOOKUP_TYPE_REGULAR, + LOOKUP_TYPE_PRIVATE, + LOOKUP_TYPE_EXCLUSIVE, /* -x */ +} LookupType; + static int resolvconf_help(void) { _cleanup_free_ char *link = NULL; int r; @@ -94,6 +100,75 @@ static int parse_search_domain(const char *string) { return 0; } +static int parse_stdin(LookupType lookup_type) { + int r; + + for (unsigned n = 0;;) { + _cleanup_free_ char *line = NULL; + const char *a; + + r = read_stripped_line(stdin, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read from stdin: %m"); + if (r == 0) + break; + n++; + + if (IN_SET(*line, '#', ';', 0)) + continue; + + a = first_word(line, "nameserver"); + if (a) { + (void) parse_nameserver(a); + continue; + } + + a = first_word(line, "domain"); + if (!a) + a = first_word(line, "search"); + if (a) { + (void) parse_search_domain(a); + continue; + } + + log_syntax(NULL, LOG_DEBUG, "stdin", n, 0, "Ignoring resolv.conf line: %s", line); + } + + switch (lookup_type) { + case LOOKUP_TYPE_REGULAR: + break; + + case LOOKUP_TYPE_PRIVATE: + arg_disable_default_route = true; + break; + + case LOOKUP_TYPE_EXCLUSIVE: + /* If -x mode is selected, let's preferably route non-suffixed lookups to this interface. + * This somewhat matches the original -x behaviour */ + + r = strv_extend(&arg_set_domain, "~."); + if (r < 0) + return log_oom(); + break; + + default: + assert_not_reached(); + } + + if (strv_isempty(arg_set_dns)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "No DNS servers specified, refusing operation."); + + if (strv_isempty(arg_set_domain)) { + /* When no domain/search is set, clear the current domains. */ + r = strv_extend(&arg_set_domain, ""); + if (r < 0) + return log_oom(); + } + + return 0; +} + int resolvconf_parse_argv(int argc, char *argv[]) { enum { @@ -114,22 +189,18 @@ int resolvconf_parse_argv(int argc, char *argv[]) { {} }; - enum { - TYPE_REGULAR, - TYPE_PRIVATE, - TYPE_EXCLUSIVE, /* -x */ - } type = TYPE_REGULAR; - int c, r; assert(argc >= 0); assert(argv); /* openresolv checks these environment variables */ + LookupType lookup_type = LOOKUP_TYPE_REGULAR; + if (getenv("IF_EXCLUSIVE")) - type = TYPE_EXCLUSIVE; + lookup_type = LOOKUP_TYPE_EXCLUSIVE; if (getenv("IF_PRIVATE")) - type = TYPE_PRIVATE; + lookup_type = LOOKUP_TYPE_PRIVATE; arg_mode = _MODE_INVALID; @@ -153,11 +224,11 @@ int resolvconf_parse_argv(int argc, char *argv[]) { /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */ case 'x': - type = TYPE_EXCLUSIVE; + lookup_type = LOOKUP_TYPE_EXCLUSIVE; break; case 'p': - type = TYPE_PRIVATE; + lookup_type = LOOKUP_TYPE_PRIVATE; break; case 'f': @@ -215,75 +286,12 @@ int resolvconf_parse_argv(int argc, char *argv[]) { r = ifname_resolvconf_mangle(argv[optind]); if (r <= 0) return r; - optind++; if (arg_mode == MODE_SET_LINK) { - unsigned n = 0; - - for (;;) { - _cleanup_free_ char *line = NULL; - const char *a; - - r = read_stripped_line(stdin, LONG_LINE_MAX, &line); - if (r < 0) - return log_error_errno(r, "Failed to read from stdin: %m"); - if (r == 0) - break; - - n++; - - if (IN_SET(*line, '#', ';', 0)) - continue; - - a = first_word(line, "nameserver"); - if (a) { - (void) parse_nameserver(a); - continue; - } - - a = first_word(line, "domain"); - if (!a) - a = first_word(line, "search"); - if (a) { - (void) parse_search_domain(a); - continue; - } - - log_syntax(NULL, LOG_DEBUG, "stdin", n, 0, "Ignoring resolv.conf line: %s", line); - } - - switch (type) { - case TYPE_REGULAR: - break; - - case TYPE_PRIVATE: - arg_disable_default_route = true; - break; - - case TYPE_EXCLUSIVE: - /* If -x mode is selected, let's preferably route non-suffixed lookups to this interface. This - * somewhat matches the original -x behaviour */ - - r = strv_extend(&arg_set_domain, "~."); - if (r < 0) - return log_oom(); - break; - - default: - assert_not_reached(); - } - - if (strv_isempty(arg_set_dns)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "No DNS servers specified, refusing operation."); - - if (strv_isempty(arg_set_domain)) { - /* When no domain/search is set, clear the current domains. */ - r = strv_extend(&arg_set_domain, ""); - if (r < 0) - return log_oom(); - } + r = parse_stdin(lookup_type); + if (r < 0) + return r; } return 1; /* work to do */ From 952f3a5dbfa4d5c97d0d1488793cbc3ed33e2adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 17 Dec 2025 15:11:07 +0100 Subject: [PATCH 11/12] homectl: drop --and-resize and --and-change-password The two options are were not documented or ever used in the codebase. Additionally, the parser expected an argumentless option, while the option table declared a required argument. So I think this was added for debugging and never excercised properly. Since there was no public documentation for those, it's as if they never existed, so it should be fine to drop them. --- src/home/homectl.c | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index c6a3d4b3eb..2b92ab6481 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -106,8 +106,6 @@ static int arg_fido2_cred_alg = 0; #endif static bool arg_recovery_key = false; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; -static bool arg_and_resize = false; -static bool arg_and_change_password = false; static ExportFormat arg_export_format = EXPORT_FORMAT_FULL; static uint64_t arg_capability_bounding_set = CAP_MASK_UNSET; static uint64_t arg_capability_ambient_set = CAP_MASK_UNSET; @@ -1922,6 +1920,9 @@ static int update_home(int argc, char *argv[], void *userdata) { } else username = NULL; + bool and_change_password = !strv_isempty(arg_pkcs11_token_uri) || !strv_isempty(arg_fido2_device); + bool and_resize = arg_disk_size != UINT64_MAX || arg_disk_size_relative != UINT64_MAX; + r = acquire_bus(&bus); if (r < 0) return r; @@ -1953,7 +1954,7 @@ static int update_home(int argc, char *argv[], void *userdata) { /* If we do multiple operations, let's output things more verbosely, since otherwise the repeated * authentication might be confusing. */ - if (arg_and_resize || arg_and_change_password) + if (and_resize || and_change_password) log_info("Updating home directory."); if (arg_offline) @@ -1988,7 +1989,7 @@ static int update_home(int argc, char *argv[], void *userdata) { r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) { - if (arg_and_change_password && + if (and_change_password && sd_bus_error_has_name(&error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN)) /* In the generic handler we'd ask for a password in this case, but when * changing passwords that's not sufficient, as we need to acquire all keys @@ -2002,13 +2003,13 @@ static int update_home(int argc, char *argv[], void *userdata) { break; } - if (arg_and_resize) + if (and_resize) log_info("Resizing home."); (void) home_record_reset_human_interaction_permission(hr); /* Also sync down disk size to underlying LUKS/fscrypt/quota */ - while (arg_and_resize) { + while (and_resize) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; @@ -2027,7 +2028,7 @@ static int update_home(int argc, char *argv[], void *userdata) { r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) { - if (arg_and_change_password && + if (and_change_password && sd_bus_error_has_name(&error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN)) return log_error_errno(r, "Security token not inserted, refusing."); @@ -2038,13 +2039,13 @@ static int update_home(int argc, char *argv[], void *userdata) { break; } - if (arg_and_change_password) + if (and_change_password) log_info("Synchronizing passwords and encryption keys."); (void) home_record_reset_human_interaction_permission(hr); /* Also sync down passwords to underlying LUKS/fscrypt */ - while (arg_and_change_password) { + while (and_change_password) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; @@ -4156,8 +4157,6 @@ static int parse_argv(int argc, char *argv[]) { ARG_FIDO2_WITH_UP, ARG_FIDO2_WITH_UV, ARG_RECOVERY_KEY, - ARG_AND_RESIZE, - ARG_AND_CHANGE_PASSWORD, ARG_DROP_CACHES, ARG_LUKS_EXTRA_MOUNT_OPTIONS, ARG_AUTO_RESIZE_MODE, @@ -4266,8 +4265,6 @@ static int parse_argv(int argc, char *argv[]) { { "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP }, { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV }, { "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY }, - { "and-resize", required_argument, NULL, ARG_AND_RESIZE }, - { "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD }, { "drop-caches", required_argument, NULL, ARG_DROP_CACHES }, { "luks-extra-mount-options", required_argument, NULL, ARG_LUKS_EXTRA_MOUNT_OPTIONS }, { "auto-resize-mode", required_argument, NULL, ARG_AUTO_RESIZE_MODE }, @@ -4800,14 +4797,6 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_AND_RESIZE: - arg_and_resize = true; - break; - - case ARG_AND_CHANGE_PASSWORD: - arg_and_change_password = true; - break; - case ARG_DROP_CACHES: r = parse_boolean_field(match_identity ?: &arg_identity_extra, "dropCaches", optarg); if (r < 0) @@ -5008,12 +4997,6 @@ static int parse_argv(int argc, char *argv[]) { } } - if (!strv_isempty(arg_pkcs11_token_uri) || !strv_isempty(arg_fido2_device)) - arg_and_change_password = true; - - if (arg_disk_size != UINT64_MAX || arg_disk_size_relative != UINT64_MAX) - arg_and_resize = true; - if (!strv_isempty(arg_languages)) { char **additional; From c46cc2630deebb8baf6be2008af57ebc32ecd652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 17 Dec 2025 15:19:29 +0100 Subject: [PATCH 12/12] man: make it clear that homectl -A/T/N do not take an argument Just like in the --help strings, we need to avoid confusion between short options which take an argument and those that don't. --- man/homectl.xml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/man/homectl.xml b/man/homectl.xml index 82ea2e3943..a59efd7112 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -256,20 +256,21 @@ - + - Takes one of this, other, - any or auto. Some user record settings can be defined to match - only specific machines, or all machines but one, or all machines. With this switch it is possibly to - control to which machines to apply the settings appearing on the command line after it. If - this is specified the setting will only apply to the local system (positive - match), if other it will apply to all but the local system (negative match), if - any it will apply to all systems (unless there's a matching positive or negative - per-machine setting). If auto returns to the default logic: whether a setting - applies by default to the local system or all systems depends on the option in question. + Option takes one of this, + other, any or auto. Some user record + settings can be defined to match only specific machines, or all machines but one, or all machines. + With this switch it is possibly to control to which machines to apply the settings appearing on the + command line after it. If this is specified the setting will only apply to the + local system (positive match), if other it will apply to all but the local system + (negative match), if any it will apply to all systems (unless there's a matching + positive or negative per-machine setting). If auto returns to the default logic: + whether a setting applies by default to the local system or all systems depends on the option in + question. Note that only some user record settings can be conditioned like this. This option has no effect on the others and is ignored there. This option may appear multiple times in a single command