From 689391f8c8f7b367c8b447018f9a0d1b91243fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 16 Dec 2025 14:45:40 +0100 Subject: [PATCH 01/25] TEST-46-HOMED: fix 2 minute timeout in cleanup The test passes but for two minutes the cleanup function just logs: Failed to inspect home: No home for user homedsshtest known Failed to inspect home: No home for user homedsshtest known Failed to inspect home: No home for user homedsshtest known ... Do not wait for the user to become inactive or remove the user if 'homectl inspect' doesn't find the user. This brings a successfull run from 176 s to 58 s here. --- test/units/TEST-46-HOMED.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh index 6de1578966..46abca9bac 100755 --- a/test/units/TEST-46-HOMED.sh +++ b/test/units/TEST-46-HOMED.sh @@ -578,8 +578,10 @@ cleanup_ssh() ( systemctl is-active -q mysshserver.socket && systemctl stop mysshserver.socket rm -f /tmp/homed.id_ecdsa /run/systemd/system/mysshserver{@.service,.socket} systemctl daemon-reload - wait_for_state homedsshtest inactive - homectl remove homedsshtest + if homectl inspect homedsshtest &>/dev/null; then + wait_for_state homedsshtest inactive + homectl remove homedsshtest + fi for dir in /etc /usr/lib; do if [[ -f "$dir/pam.d/sshd.bak" ]]; then mv "$dir/pam.d/sshd.bak" "$dir/pam.d/sshd" From fbd7ca0ef093c027536f894601f95afe6e98cd03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 8 Dec 2025 16:40:35 +0100 Subject: [PATCH 02/25] homectl: split out parser parse_ssh_authorized_keys The idea is to split out helpers to make parse_argv() itself more manageable. If possible, the helpers will be reused in many places. But even if not, as in this case, it seems good to split out the code for a specific option. Always, the sd_json_variant** pointer or pointers that are operated on are selected in the caller. This way it is easier to see which of the identity variables is used and if two different ones are used. --- src/home/homectl.c | 153 +++++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 75 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 8d8f2c0f12..08a0766572 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3048,6 +3048,79 @@ static int drop_from_identity(const char *field) { return 0; } +static int parse_ssh_authorized_keys(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_strv_free_ char **l = NULL, **add = NULL; + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + if (arg[0] == '@') { + /* If prefixed with '@', read from a file */ + + _cleanup_fclose_ FILE *f = fopen(arg + 1, "re"); + if (!f) + return log_error_errno(errno, "Failed to open '%s': %m", arg + 1); + + for (;;) { + _cleanup_free_ char *line = NULL; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read from '%s': %m", arg + 1); + if (r == 0) + break; + + if (isempty(line) || line[0] == '#') + continue; + + r = strv_consume(&add, TAKE_PTR(line)); + if (r < 0) + return log_oom(); + } + } else { + /* Otherwise, assume it's a literal key. Let's do some superficial checks + * before accepting it though. */ + + if (string_has_cc(arg, NULL)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Authorized key contains control characters, refusing."); + if (arg[0] == '#') + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified key is a comment?"); + + add = strv_new(arg); + if (!add) + return log_oom(); + } + + v = sd_json_variant_ref(sd_json_variant_by_key(*identity, field)); + if (v) { + r = sd_json_variant_strv(v, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse %s list: %m", field); + } + + r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ true); + if (r < 0) + return log_oom(); + + v = sd_json_variant_unref(v); + + r = sd_json_variant_new_array_strv(&v, l); + if (r < 0) + return log_oom(); + + r = sd_json_variant_set_field(identity, field, v); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + + return 0; +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -3484,7 +3557,8 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - /* Eventually we should probably turn this into a proper --dry-run option, but as long as it is not hooked up everywhere let's make it an environment variable only. */ + /* Eventually we should probably turn this into a proper --dry-run option, but as long as it is not + * hooked up everywhere let's make it an environment variable only. */ r = getenv_bool("SYSTEMD_HOME_DRY_RUN"); if (r >= 0) arg_dry_run = r; @@ -4212,83 +4286,12 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_SSH_AUTHORIZED_KEYS: { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - _cleanup_strv_free_ char **l = NULL, **add = NULL; - - if (isempty(optarg)) { - r = drop_from_identity("sshAuthorizedKeys"); - if (r < 0) - return r; - - break; - } - - if (optarg[0] == '@') { - _cleanup_fclose_ FILE *f = NULL; - - /* If prefixed with '@' read from a file */ - - f = fopen(optarg+1, "re"); - if (!f) - return log_error_errno(errno, "Failed to open '%s': %m", optarg+1); - - for (;;) { - _cleanup_free_ char *line = NULL; - - r = read_line(f, LONG_LINE_MAX, &line); - if (r < 0) - return log_error_errno(r, "Failed to read from '%s': %m", optarg+1); - if (r == 0) - break; - - if (isempty(line)) - continue; - - if (line[0] == '#') - continue; - - r = strv_consume(&add, TAKE_PTR(line)); - if (r < 0) - return log_oom(); - } - } else { - /* Otherwise, assume it's a literal key. Let's do some superficial checks - * before accept it though. */ - - if (string_has_cc(optarg, NULL)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Authorized key contains control characters, refusing."); - if (optarg[0] == '#') - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified key is a comment?"); - - add = strv_new(optarg); - if (!add) - return log_oom(); - } - - v = sd_json_variant_ref(sd_json_variant_by_key(arg_identity_extra_privileged, "sshAuthorizedKeys")); - if (v) { - r = sd_json_variant_strv(v, &l); - if (r < 0) - return log_error_errno(r, "Failed to parse SSH authorized keys list: %m"); - } - - r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ true); + case ARG_SSH_AUTHORIZED_KEYS: + r = parse_ssh_authorized_keys(&arg_identity_extra_privileged, "sshAuthorizedKeys", optarg); if (r < 0) - return log_oom(); - - v = sd_json_variant_unref(v); - - r = sd_json_variant_new_array_strv(&v, l); - if (r < 0) - return log_oom(); - - r = sd_json_variant_set_field(&arg_identity_extra_privileged, "sshAuthorizedKeys", v); - if (r < 0) - return log_error_errno(r, "Failed to set authorized keys: %m"); + return r; break; - } case ARG_NOT_BEFORE: case ARG_NOT_AFTER: From d7a4dfb8b207d9f90946fe82eecbe3ed04049dae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 14:41:29 +0100 Subject: [PATCH 03/25] homectl: split out helper parse_string_field --- src/home/homectl.c | 142 ++++++++++++++++----------------------------- 1 file changed, 50 insertions(+), 92 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 08a0766572..51084c8656 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3121,6 +3121,21 @@ static int parse_ssh_authorized_keys(sd_json_variant **identity, const char *fie return 0; } +static int parse_string_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + r = sd_json_variant_set_field_string(identity, field, arg); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -3612,21 +3627,13 @@ static int parse_argv(int argc, char *argv[]) { break; case 'c': - if (isempty(optarg)) { - r = drop_from_identity("realName"); - if (r < 0) - return r; + if (!isempty(optarg) && !valid_gecos(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid GECOS field '%s'.", optarg); - break; - } - - if (!valid_gecos(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Real name '%s' not a valid GECOS field.", optarg); - - r = sd_json_variant_set_field_string(match_identity ?: &arg_identity_extra, "realName", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "realName", optarg); if (r < 0) - return log_error_errno(r, "Failed to set realName field: %m"); - + return r; break; case ARG_ALIAS: { @@ -3730,7 +3737,6 @@ static int parse_argv(int argc, char *argv[]) { case ARG_LUKS_EXTRA_MOUNT_OPTIONS: case ARG_SESSION_LAUNCHER: case ARG_SESSION_TYPE: { - const char *field = c == ARG_EMAIL_ADDRESS ? "emailAddress" : c == ARG_LOCATION ? "location" : @@ -3742,55 +3748,30 @@ static int parse_argv(int argc, char *argv[]) { c == ARG_SESSION_LAUNCHER ? "preferredSessionLauncher" : c == ARG_SESSION_TYPE ? "preferredSessionType" : NULL; - assert(field); - if (isempty(optarg)) { - r = drop_from_identity(field); - if (r < 0) - return r; - - break; - } - - r = sd_json_variant_set_field_string(match_identity ?: &arg_identity_extra, field, optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - + return r; break; } case ARG_CIFS_SERVICE: - if (isempty(optarg)) { - r = drop_from_identity("cifsService"); + if (!isempty(optarg)) { + r = parse_cifs_service(optarg, /* ret_host= */ NULL, /* ret_service= */ NULL, /* ret_path= */ NULL); if (r < 0) - return r; - - break; + return log_error_errno(r, "Failed to validate CIFS service name: %s", optarg); } - r = parse_cifs_service(optarg, NULL, NULL, NULL); + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsService", optarg); if (r < 0) - return log_error_errno(r, "Failed to validate CIFS service name: %s", optarg); - - r = sd_json_variant_set_field_string(match_identity ?: &arg_identity_extra, "cifsService", optarg); - if (r < 0) - return log_error_errno(r, "Failed to set cifsService field: %m"); - + return r; break; case ARG_PASSWORD_HINT: - if (isempty(optarg)) { - r = drop_from_identity("passwordHint"); - if (r < 0) - return r; - - break; - } - - r = sd_json_variant_set_field_string(&arg_identity_extra_privileged, "passwordHint", optarg); + r = parse_string_field(&arg_identity_extra_privileged, "passwordHint", optarg); if (r < 0) - return log_error_errno(r, "Failed to set passwordHint field: %m"); + return r; string_erase(optarg); break; @@ -3943,21 +3924,13 @@ static int parse_argv(int argc, char *argv[]) { } case 's': - if (isempty(optarg)) { - r = drop_from_identity("shell"); - if (r < 0) - return r; + if (!isempty(optarg) && !valid_shell(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Shell '%s' not valid.", optarg); - break; - } - - if (!valid_shell(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Shell '%s' not valid.", optarg); - - r = sd_json_variant_set_field_string(match_identity ?: &arg_identity_extra, "shell", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "shell", optarg); if (r < 0) - return log_error_errno(r, "Failed to set shell field: %m"); - + return r; break; case ARG_SETENV: { @@ -3998,21 +3971,13 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_TIMEZONE: - if (isempty(optarg)) { - r = drop_from_identity("timeZone"); - if (r < 0) - return r; + if (!isempty(optarg) && !timezone_is_valid(optarg, LOG_DEBUG)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Timezone '%s' is not valid.", optarg); - break; - } - - if (!timezone_is_valid(optarg, LOG_DEBUG)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Timezone '%s' is not valid.", optarg); - - r = sd_json_variant_set_field_string(match_identity ?: &arg_identity_extra, "timeZone", optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "timeZone", optarg); if (r < 0) - return log_error_errno(r, "Failed to set timezone field: %m"); - + return r; break; case ARG_LANGUAGE: { @@ -4365,35 +4330,28 @@ static int parse_argv(int argc, char *argv[]) { case ARG_LUKS_CIPHER_MODE: case ARG_LUKS_PBKDF_TYPE: case ARG_LUKS_PBKDF_HASH_ALGORITHM: { - const char *field = c == ARG_STORAGE ? "storage" : c == ARG_FS_TYPE ? "fileSystemType" : c == ARG_LUKS_CIPHER ? "luksCipher" : c == ARG_LUKS_CIPHER_MODE ? "luksCipherMode" : c == ARG_LUKS_PBKDF_TYPE ? "luksPbkdfType" : - c == ARG_LUKS_PBKDF_HASH_ALGORITHM ? "luksPbkdfHashAlgorithm" : NULL; - + c == ARG_LUKS_PBKDF_HASH_ALGORITHM ? "luksPbkdfHashAlgorithm" : + NULL; assert(field); - if (isempty(optarg)) { - r = drop_from_identity(field); - if (r < 0) - return r; + sd_json_variant **identity = + match_identity ?: + IN_SET(c, ARG_STORAGE, ARG_FS_TYPE) ? + &arg_identity_extra_this_machine : &arg_identity_extra; - break; - } + if (!isempty(optarg) && !string_is_safe(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", field, optarg); - if (!string_is_safe(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parameter for %s field not valid: %s", field, optarg); - - r = sd_json_variant_set_field_string( - match_identity ?: (IN_SET(c, ARG_STORAGE, ARG_FS_TYPE) ? - &arg_identity_extra_this_machine : - &arg_identity_extra), field, optarg); + r = parse_string_field(identity, field, optarg); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - + return r; break; } From b1982aada6e3d63a9d32e9185803fa193207a622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 14:53:06 +0100 Subject: [PATCH 04/25] homectl: split out helpers parse_{unsigned,u64,size}_field --- src/home/homectl.c | 123 ++++++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 52 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 51084c8656..d663b51bef 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3136,6 +3136,66 @@ static int parse_string_field(sd_json_variant **identity, const char *field, con return 0; } +static int parse_unsigned_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + unsigned n; + r = safe_atou(arg, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + + r = sd_json_variant_set_field_unsigned(identity, field, n); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + +static int parse_u64_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + uint64_t n; + r = safe_atou64(arg, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + + r = sd_json_variant_set_field_unsigned(identity, field, n); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + +static int parse_size_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + uint64_t n; + r = parse_size(arg, 1024, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + + r = sd_json_variant_set_field_unsigned(identity, field, n); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -4185,25 +4245,13 @@ static int parse_argv(int argc, char *argv[]) { c == ARG_LUKS_VOLUME_KEY_SIZE ? "luksVolumeKeySize" : c == ARG_LUKS_PBKDF_FORCE_ITERATIONS ? "luksPbkdfForceIterations" : c == ARG_LUKS_PBKDF_PARALLEL_THREADS ? "luksPbkdfParallelThreads" : - c == ARG_RATE_LIMIT_BURST ? "rateLimitBurst" : NULL; - unsigned n; - + c == ARG_RATE_LIMIT_BURST ? "rateLimitBurst" : + NULL; assert(field); - if (isempty(optarg)) { - r = drop_from_identity(field); - if (r < 0) - return r; - } - - r = safe_atou(optarg, &n); + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, optarg); - - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra, field, n); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - + return r; break; } @@ -4436,26 +4484,12 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_TASKS_MAX: { - uint64_t u; - if (isempty(optarg)) { - r = drop_from_identity("tasksMax"); - if (r < 0) - return r; - break; - } - - r = safe_atou64(optarg, &u); + case ARG_TASKS_MAX: + r = parse_u64_field(match_identity ?: &arg_identity_extra, "tasksMax", optarg); if (r < 0) - return log_error_errno(r, "Failed to parse --tasks-max= parameter: %s", optarg); - - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra, "tasksMax", u); - if (r < 0) - return log_error_errno(r, "Failed to set tasksMax field: %m"); - + return r; break; - } case ARG_MEMORY_MAX: case ARG_MEMORY_HIGH: @@ -4463,27 +4497,12 @@ static int parse_argv(int argc, char *argv[]) { const char *field = c == ARG_MEMORY_MAX ? "memoryMax" : c == ARG_MEMORY_HIGH ? "memoryHigh" : - c == ARG_LUKS_PBKDF_MEMORY_COST ? "luksPbkdfMemoryCost" : NULL; + c == ARG_LUKS_PBKDF_MEMORY_COST ? "luksPbkdfMemoryCost" : + NULL; - uint64_t u; - - assert(field); - - if (isempty(optarg)) { - r = drop_from_identity(field); - if (r < 0) - return r; - break; - } - - r = parse_size(optarg, 1024, &u); + r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, field, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, optarg); - - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra_this_machine, field, u); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - + return r; break; } From 2bcf520f31b4206b4b4f875c86ea07aeb1f70ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 14:59:58 +0100 Subject: [PATCH 05/25] homectl: split out helper parse_boolean_field --- src/home/homectl.c | 95 ++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 66 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index d663b51bef..2ddcf24142 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3196,6 +3196,25 @@ static int parse_size_field(sd_json_variant **identity, const char *field, const return 0; } +static int parse_boolean_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + r = parse_boolean(arg); + if (r < 0) + return log_error_errno(r, "Failed to parse boolean parameter %s: %s", field, arg); + + r = sd_json_variant_set_field_boolean(identity, field, r > 0); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -4099,33 +4118,18 @@ static int parse_argv(int argc, char *argv[]) { c == ARG_AUTO_LOGIN ? "autoLogin" : c == ARG_PASSWORD_CHANGE_NOW ? "passwordChangeNow" : NULL; - assert(field); - if (isempty(optarg)) { - r = drop_from_identity(field); - if (r < 0) - return r; - - break; - } - - r = parse_boolean(optarg); + r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse %s boolean: %m", field); - - r = sd_json_variant_set_field_boolean(match_identity ?: &arg_identity_extra, field, r > 0); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - + return r; break; } case 'P': r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); if (r < 0) - return log_error_errno(r, "Failed to set enforcePasswordPolicy field: %m"); - + return log_error_errno(r, "Failed to set %s field: %m", "enforcePasswordPolicy"); break; case ARG_DISK_SIZE: @@ -4200,42 +4204,14 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_LUKS_DISCARD: - if (isempty(optarg)) { - r = drop_from_identity("luksDiscard"); - if (r < 0) - return r; + case ARG_LUKS_OFFLINE_DISCARD: { + const char *field = c == ARG_LUKS_DISCARD ? "luksDiscard" : "luksOfflineDiscard"; - break; - } - - r = parse_boolean(optarg); + r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse --luks-discard= parameter: %s", optarg); - - r = sd_json_variant_set_field_boolean(match_identity ?: &arg_identity_extra, "luksDiscard", r); - if (r < 0) - return log_error_errno(r, "Failed to set discard field: %m"); - - break; - - case ARG_LUKS_OFFLINE_DISCARD: - if (isempty(optarg)) { - r = drop_from_identity("luksOfflineDiscard"); - if (r < 0) - return r; - - break; - } - - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --luks-offline-discard= parameter: %s", optarg); - - r = sd_json_variant_set_field_boolean(match_identity ?: &arg_identity_extra, "luksOfflineDiscard", r); - if (r < 0) - return log_error_errno(r, "Failed to set offline discard field: %m"); - + return r; break; + } case ARG_LUKS_VOLUME_KEY_SIZE: case ARG_LUKS_PBKDF_FORCE_ITERATIONS: @@ -4742,24 +4718,11 @@ static int parse_argv(int argc, char *argv[]) { arg_and_change_password = true; break; - case ARG_DROP_CACHES: { - if (isempty(optarg)) { - r = drop_from_identity("dropCaches"); - if (r < 0) - return r; - break; - } - - r = parse_boolean_argument("--drop-caches=", optarg, NULL); + case ARG_DROP_CACHES: + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "dropCaches", optarg); if (r < 0) return r; - - r = sd_json_variant_set_field_boolean(match_identity ?: &arg_identity_extra, "dropCaches", r); - if (r < 0) - return log_error_errno(r, "Failed to set drop caches field: %m"); - break; - } case ARG_CAPABILITY_AMBIENT_SET: case ARG_CAPABILITY_BOUNDING_SET: { From 9f4c9a6cd9365211afa7661985703ad605a9f6cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:04:44 +0100 Subject: [PATCH 06/25] homectl: split out helper parse_mode_field --- src/home/homectl.c | 64 +++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 2ddcf24142..cffafa5f1b 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3215,6 +3215,26 @@ static int parse_boolean_field(sd_json_variant **identity, const char *field, co return 0; } +static int parse_mode_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + mode_t mode; + r = parse_mode(arg, &mode); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Access mode '%s' not valid.", arg); + + r = sd_json_variant_set_field_unsigned(identity, field, mode); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -4181,27 +4201,11 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_ACCESS_MODE: { - mode_t mode; - - if (isempty(optarg)) { - r = drop_from_identity("accessMode"); - if (r < 0) - return r; - - break; - } - - r = parse_mode(optarg, &mode); + case ARG_ACCESS_MODE: + r = parse_mode_field(&arg_identity_extra, "accessMode", optarg); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Access mode '%s' not valid.", optarg); - - r = sd_json_variant_set_field_unsigned(&arg_identity_extra, "accessMode", mode); - if (r < 0) - return log_error_errno(r, "Failed to set access mode field: %m"); - + return r; break; - } case ARG_LUKS_DISCARD: case ARG_LUKS_OFFLINE_DISCARD: { @@ -4253,27 +4257,11 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_UMASK: { - mode_t m; - - if (isempty(optarg)) { - r = drop_from_identity("umask"); - if (r < 0) - return r; - - break; - } - - r = parse_mode(optarg, &m); + case ARG_UMASK: + r = parse_mode_field(match_identity ?: &arg_identity_extra, "umask", optarg); if (r < 0) - return log_error_errno(r, "Failed to parse umask: %m"); - - r = sd_json_variant_set_field_integer(match_identity ?: &arg_identity_extra, "umask", m); - if (r < 0) - return log_error_errno(r, "Failed to set umask field: %m"); - + return r; break; - } case ARG_SSH_AUTHORIZED_KEYS: r = parse_ssh_authorized_keys(&arg_identity_extra_privileged, "sshAuthorizedKeys", optarg); From 5db63f9de83e4e3fa2da956247459f8888c03e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:13:15 +0100 Subject: [PATCH 07/25] homectl: split out helpers that extend parse_string_field --- src/home/homectl.c | 136 +++++++++++++++++++++++---------------------- 1 file changed, 70 insertions(+), 66 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index cffafa5f1b..31ec35c149 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3136,6 +3136,69 @@ static int parse_string_field(sd_json_variant **identity, const char *field, con return 0; } +static int parse_home_directory_field(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_free_ char *hd = NULL; + int r; + + assert(identity); + assert(field); + + if (!isempty(arg)) { + r = parse_path_argument(arg, false, &hd); + if (r < 0) + return r; + + if (!valid_home(hd)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home directory '%s' not valid.", hd); + } + + return parse_string_field(identity, field, hd); +} + +static int parse_realm_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (!isempty(arg)) { + r = dns_name_is_valid(arg); + if (r < 0) + return log_error_errno(r, "Failed to determine whether realm '%s' is a valid DNS domain: %m", arg); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Realm '%s' is not a valid DNS domain.", arg); + } + + return parse_string_field(identity, field, arg); +} + +static int parse_path_field(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_free_ char *v = NULL; + int r; + + assert(identity); + assert(field); + + if (!isempty(arg)) { + r = parse_path_argument(arg, false, &v); + if (r < 0) + return r; + } + + return parse_string_field(identity, field, v); +} + +static int parse_filename_field(sd_json_variant **identity, const char *field, const char *arg) { + assert(identity); + assert(field); + + if (!isempty(arg) && !filename_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for %s field not a valid filename: %s", field, arg); + + return parse_string_field(identity, field, arg); +} + static int parse_unsigned_field(sd_json_variant **identity, const char *field, const char *arg) { int r; @@ -3782,49 +3845,16 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 'd': { - _cleanup_free_ char *hd = NULL; - - if (isempty(optarg)) { - r = drop_from_identity("homeDirectory"); - if (r < 0) - return r; - - break; - } - - r = parse_path_argument(optarg, false, &hd); + case 'd': + r = parse_home_directory_field(&arg_identity_extra, "homeDirectory", optarg); if (r < 0) return r; - - if (!valid_home(hd)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home directory '%s' not valid.", hd); - - r = sd_json_variant_set_field_string(&arg_identity_extra, "homeDirectory", hd); - if (r < 0) - return log_error_errno(r, "Failed to set homeDirectory field: %m"); - break; - } case ARG_REALM: - if (isempty(optarg)) { - r = drop_from_identity("realm"); - if (r < 0) - return r; - - break; - } - - r = dns_name_is_valid(optarg); + r = parse_realm_field(&arg_identity_extra, "realm", optarg); if (r < 0) - return log_error_errno(r, "Failed to determine whether realm '%s' is a valid DNS domain: %m", optarg); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Realm '%s' is not a valid DNS domain.", optarg); - - r = sd_json_variant_set_field_string(&arg_identity_extra, "realm", optarg); - if (r < 0) - return log_error_errno(r, "Failed to set realm field: %m"); + return r; break; case ARG_EMAIL_ADDRESS: @@ -4001,24 +4031,10 @@ static int parse_argv(int argc, char *argv[]) { case 'k': case ARG_IMAGE_PATH: { const char *field = c == 'k' ? "skeletonDirectory" : "imagePath"; - _cleanup_free_ char *v = NULL; - if (isempty(optarg)) { - r = drop_from_identity(field); - if (r < 0) - return r; - - break; - } - - r = parse_path_argument(optarg, false, &v); + r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, field, optarg); if (r < 0) return r; - - r = sd_json_variant_set_field_string(match_identity ?: &arg_identity_extra_this_machine, field, v); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", v); - break; } @@ -4878,21 +4894,9 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_DEFAULT_AREA: - if (isempty(optarg)) { - r = drop_from_identity("defaultArea"); - if (r < 0) - return r; - - break; - } - - if (!filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parameter for default area field not valid: %s", optarg); - - r = sd_json_variant_set_field_string(match_identity ?: &arg_identity_extra, "defaultArea", optarg); + r = parse_filename_field(match_identity ?: &arg_identity_extra, "defaultArea", optarg); if (r < 0) - return log_error_errno(r, "Failed to set default area field: %m"); - + return r; break; case ARG_KEY_NAME: From 56c05601f40376db9fa93593bf0f4334dacf981a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:16:00 +0100 Subject: [PATCH 08/25] homectl: split out helper parse_nice_field --- src/home/homectl.c | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 31ec35c149..5435e93370 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3298,6 +3298,25 @@ static int parse_mode_field(sd_json_variant **identity, const char *field, const return 0; } +static int parse_nice_field(sd_json_variant **identity, const char *field, const char *arg) { + int nc, r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + r = parse_nice(arg, &nc); + if (r < 0) + return log_error_errno(r, "Failed to parse nice level '%s': %m", arg); + + r = sd_json_variant_set_field_integer(identity, field, nc); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -3905,26 +3924,11 @@ static int parse_argv(int argc, char *argv[]) { string_erase(optarg); break; - case ARG_NICE: { - int nc; - - if (isempty(optarg)) { - r = drop_from_identity("niceLevel"); - if (r < 0) - return r; - break; - } - - r = parse_nice(optarg, &nc); + case ARG_NICE: + r = parse_nice_field(match_identity ?: &arg_identity_extra, "niceLevel", optarg); if (r < 0) - return log_error_errno(r, "Failed to parse nice level: %s", optarg); - - r = sd_json_variant_set_field_integer(match_identity ?: &arg_identity_extra, "niceLevel", nc); - if (r < 0) - return log_error_errno(r, "Failed to set niceLevel field: %m"); - + return r; break; - } case ARG_RLIMIT: { _cleanup_(sd_json_variant_unrefp) sd_json_variant *jcur = NULL, *jmax = NULL; From 2ef165ba1c169f54128d6571db5c731539914c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 11 Dec 2025 10:47:46 +0100 Subject: [PATCH 09/25] homectl: fix confusion between "FOO" and "RLIMIT_FOO" as json keys We parse RLIMIT_FOO, FOO, and LimitFOO all the same. But the keys in the JSON dict are supposed to be "RLIMIT_FOO". We were creating such keys, but then filtering them using "FOO", i.e. the filtering was not effective. Using strjoina since both strings are under our control. --- src/home/homectl.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 5435e93370..e693bd1825 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3932,7 +3932,7 @@ static int parse_argv(int argc, char *argv[]) { case ARG_RLIMIT: { _cleanup_(sd_json_variant_unrefp) sd_json_variant *jcur = NULL, *jmax = NULL; - _cleanup_free_ char *field = NULL, *t = NULL; + _cleanup_free_ char *key = NULL, *t = NULL; const char *eq; struct rlimit rl; int l; @@ -3953,22 +3953,24 @@ static int parse_argv(int argc, char *argv[]) { if (!eq) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse resource limit assignment: %s", optarg); - field = strndup(optarg, eq - optarg); - if (!field) + key = strndup(optarg, eq - optarg); + if (!key) return log_oom(); - l = rlimit_from_string_harder(field); + l = rlimit_from_string_harder(key); if (l < 0) - return log_error_errno(l, "Unknown resource limit type: %s", field); + return log_error_errno(l, "Unknown resource limit type: %s", key); + + const char *rlimit_field = strjoina("RLIMIT_", rlimit_to_string(l)); if (isempty(eq + 1)) { /* Remove only the specific rlimit */ - r = strv_extend(&arg_identity_filter_rlimits, rlimit_to_string(l)); + r = strv_extend(&arg_identity_filter_rlimits, rlimit_field); if (r < 0) return r; - r = sd_json_variant_filter(&arg_identity_extra_rlimits, STRV_MAKE(field)); + r = sd_json_variant_filter(&arg_identity_extra_rlimits, STRV_MAKE(rlimit_field)); if (r < 0) return log_error_errno(r, "Failed to filter JSON identity data: %m"); @@ -3987,16 +3989,12 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to allocate maximum integer: %m"); - t = strjoin("RLIMIT_", rlimit_to_string(l)); - if (!t) - return log_oom(); - r = sd_json_variant_set_fieldbo( - &arg_identity_extra_rlimits, t, + &arg_identity_extra_rlimits, rlimit_field, SD_JSON_BUILD_PAIR_VARIANT("cur", jcur), SD_JSON_BUILD_PAIR_VARIANT("max", jmax)); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", rlimit_to_string(l)); + return log_error_errno(r, "Failed to set %s field: %m", rlimit_field); break; } From 47749fa7623c07f4f7d9d3d544c27a9912c85ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:17:17 +0100 Subject: [PATCH 10/25] homectl: split out helper parse_rlimit_field --- src/home/homectl.c | 137 +++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 66 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index e693bd1825..d407ab0e6c 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3317,6 +3317,74 @@ static int parse_nice_field(sd_json_variant **identity, const char *field, const return 0; } +static int parse_rlimit_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) { + /* Remove all resource limits */ + + r = drop_from_identity(field); + if (r < 0) + return r; + + arg_identity_filter_rlimits = strv_free(arg_identity_filter_rlimits); + *identity = sd_json_variant_unref(*identity); + return 0; + } + + const char *eq = strchr(arg, '='); + if (!eq) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse resource limit assignment: %s", arg); + + _cleanup_free_ char *s = strndup(arg, eq - arg); + if (!s) + return log_oom(); + + int limit = rlimit_from_string_harder(s); + if (limit < 0) + return log_error_errno(limit, "Unknown resource limit type: %s", s); + + const char *rlimit_field = strjoina("RLIMIT_", rlimit_to_string(limit)); + + if (isempty(eq + 1)) { + /* Remove only the specific rlimit */ + + r = strv_extend(&arg_identity_filter_rlimits, rlimit_field); + if (r < 0) + return r; + + r = sd_json_variant_filter(identity, STRV_MAKE(rlimit_field)); + if (r < 0) + return log_error_errno(r, "Failed to filter JSON identity data: %m"); + return 0; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *jcur = NULL, *jmax = NULL; + struct rlimit rl; + + r = rlimit_parse(limit, eq + 1, &rl); + if (r < 0) + return log_error_errno(r, "Failed to parse resource limit value: %s", eq + 1); + + r = rl.rlim_cur == RLIM_INFINITY ? sd_json_variant_new_null(&jcur) : sd_json_variant_new_unsigned(&jcur, rl.rlim_cur); + if (r < 0) + return log_error_errno(r, "Failed to allocate json variant: %m"); + + r = rl.rlim_max == RLIM_INFINITY ? sd_json_variant_new_null(&jmax) : sd_json_variant_new_unsigned(&jmax, rl.rlim_max); + if (r < 0) + return log_error_errno(r, "Failed to allocate json variant: %m"); + + r = sd_json_variant_set_fieldbo(identity, rlimit_field, + SD_JSON_BUILD_PAIR_VARIANT("cur", jcur), + SD_JSON_BUILD_PAIR_VARIANT("max", jmax)); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", rlimit_field); + return 0; +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -3930,74 +3998,11 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_RLIMIT: { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *jcur = NULL, *jmax = NULL; - _cleanup_free_ char *key = NULL, *t = NULL; - const char *eq; - struct rlimit rl; - int l; - - if (isempty(optarg)) { - /* Remove all resource limits */ - - r = drop_from_identity("resourceLimits"); - if (r < 0) - return r; - - arg_identity_filter_rlimits = strv_free(arg_identity_filter_rlimits); - arg_identity_extra_rlimits = sd_json_variant_unref(arg_identity_extra_rlimits); - break; - } - - eq = strchr(optarg, '='); - if (!eq) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse resource limit assignment: %s", optarg); - - key = strndup(optarg, eq - optarg); - if (!key) - return log_oom(); - - l = rlimit_from_string_harder(key); - if (l < 0) - return log_error_errno(l, "Unknown resource limit type: %s", key); - - const char *rlimit_field = strjoina("RLIMIT_", rlimit_to_string(l)); - - if (isempty(eq + 1)) { - /* Remove only the specific rlimit */ - - r = strv_extend(&arg_identity_filter_rlimits, rlimit_field); - if (r < 0) - return r; - - r = sd_json_variant_filter(&arg_identity_extra_rlimits, STRV_MAKE(rlimit_field)); - if (r < 0) - return log_error_errno(r, "Failed to filter JSON identity data: %m"); - - break; - } - - r = rlimit_parse(l, eq + 1, &rl); + case ARG_RLIMIT: + r = parse_rlimit_field(&arg_identity_extra_rlimits, "resourceLimits", optarg); if (r < 0) - return log_error_errno(r, "Failed to parse resource limit value: %s", eq + 1); - - r = rl.rlim_cur == RLIM_INFINITY ? sd_json_variant_new_null(&jcur) : sd_json_variant_new_unsigned(&jcur, rl.rlim_cur); - if (r < 0) - return log_error_errno(r, "Failed to allocate current integer: %m"); - - r = rl.rlim_max == RLIM_INFINITY ? sd_json_variant_new_null(&jmax) : sd_json_variant_new_unsigned(&jmax, rl.rlim_max); - if (r < 0) - return log_error_errno(r, "Failed to allocate maximum integer: %m"); - - r = sd_json_variant_set_fieldbo( - &arg_identity_extra_rlimits, rlimit_field, - SD_JSON_BUILD_PAIR_VARIANT("cur", jcur), - SD_JSON_BUILD_PAIR_VARIANT("max", jmax)); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", rlimit_field); - + return r; break; - } case 'u': { uid_t uid; From 0126bebccbd2bd2852b851fe35a023fabb840364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:18:38 +0100 Subject: [PATCH 11/25] homectl: split out helper parse_environment_field --- src/home/homectl.c | 71 ++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index d407ab0e6c..67f492d11c 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3385,6 +3385,40 @@ static int parse_rlimit_field(sd_json_variant **identity, const char *field, con return 0; } +static int parse_environment_field(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_strv_free_ char **l = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *ne = NULL; + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + sd_json_variant *e = sd_json_variant_by_key(*identity, field); + if (e) { + r = sd_json_variant_strv(e, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse JSON environment field: %m"); + } + + r = strv_env_replace_strdup_passthrough(&l, arg); + if (r < 0) + return log_error_errno(r, "Cannot assign environment variable %s: %m", arg); + + strv_sort(l); + + r = sd_json_variant_new_array_strv(&ne, l); + if (r < 0) + return log_error_errno(r, "Failed to allocate json list: %m"); + + r = sd_json_variant_set_field(identity, field, ne); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -4055,42 +4089,11 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_SETENV: { - _cleanup_strv_free_ char **l = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *ne = NULL; - sd_json_variant *e; - - if (isempty(optarg)) { - r = drop_from_identity("environment"); - if (r < 0) - return r; - - break; - } - - e = sd_json_variant_by_key(match_identity ? *match_identity: arg_identity_extra, "environment"); - if (e) { - r = sd_json_variant_strv(e, &l); - if (r < 0) - return log_error_errno(r, "Failed to parse JSON environment field: %m"); - } - - r = strv_env_replace_strdup_passthrough(&l, optarg); + case ARG_SETENV: + r = parse_environment_field(match_identity ?: &arg_identity_extra, "environment", optarg); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); - - strv_sort(l); - - r = sd_json_variant_new_array_strv(&ne, l); - if (r < 0) - return log_error_errno(r, "Failed to allocate environment list JSON: %m"); - - r = sd_json_variant_set_field(match_identity ?: &arg_identity_extra, "environment", ne); - if (r < 0) - return log_error_errno(r, "Failed to set environment list: %m"); - + return r; break; - } case ARG_TIMEZONE: if (!isempty(optarg) && !timezone_is_valid(optarg, LOG_DEBUG)) From 873d38eac92e794d791552831d8d3fff3effafc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:20:47 +0100 Subject: [PATCH 12/25] homectl: split out helper parse_language_field --- src/home/homectl.c | 80 ++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 67f492d11c..fc5d92feb0 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3419,6 +3419,43 @@ static int parse_environment_field(sd_json_variant **identity, const char *field return 0; } +static int parse_language_field(char ***languages, const char *arg) { + int r; + + if (isempty(arg)) { + FOREACH_STRING(prop, "preferredLanguage", "additionalLanguages") { + r = drop_from_identity(prop); + if (r < 0) + return r; + } + + strv_freep(languages); + return 0; + } + + for (const char *p = arg;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, ",:", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse locale list: %m"); + if (r == 0) + return 0; + + if (!locale_is_valid(word)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", word); + + if (locale_is_installed(word) <= 0) + log_warning("Locale '%s' is not installed, accepting anyway.", word); + + r = strv_consume(languages, TAKE_PTR(word)); + if (r < 0) + return log_oom(); + + strv_uniq(*languages); + } +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -4105,46 +4142,11 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_LANGUAGE: { - const char *p = optarg; - - if (isempty(p)) { - r = drop_from_identity("preferredLanguage"); - if (r < 0) - return r; - - r = drop_from_identity("additionalLanguages"); - if (r < 0) - return r; - - arg_languages = strv_free(arg_languages); - break; - } - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, ",:", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse locale list: %m"); - if (r == 0) - break; - - if (!locale_is_valid(word)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", word); - - if (locale_is_installed(word) <= 0) - log_warning("Locale '%s' is not installed, accepting anyway.", word); - - r = strv_consume(&arg_languages, TAKE_PTR(word)); - if (r < 0) - return log_oom(); - - strv_uniq(arg_languages); - } - + case ARG_LANGUAGE: + r = parse_language_field(&arg_languages, optarg); + if (r < 0) + return r; break; - } case ARG_NOSUID: case ARG_NODEV: From 12771c0cc68456ac848681e8f9826aca42f28e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:23:00 +0100 Subject: [PATCH 13/25] homectl: split out helpers parse_{disk,sector}_size_field --- src/home/homectl.c | 140 +++++++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 63 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index fc5d92feb0..b8b4463764 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3385,6 +3385,79 @@ static int parse_rlimit_field(sd_json_variant **identity, const char *field, con return 0; } +static int parse_disk_size_field(sd_json_variant **identity, const char *arg) { + int r; + + assert(identity); + + if (isempty(arg)) { + FOREACH_STRING(prop, "diskSize", "diskSizeRelative", "rebalanceWeight") { + r = drop_from_identity(prop); + if (r < 0) + return r; + } + + arg_disk_size = arg_disk_size_relative = UINT64_MAX; + return 0; + } + + r = parse_permyriad(arg); + if (r < 0) { + r = parse_disk_size(arg, &arg_disk_size); + if (r < 0) + return r; + + r = drop_from_identity("diskSizeRelative"); + if (r < 0) + return r; + + r = sd_json_variant_set_field_unsigned(identity, "diskSize", arg_disk_size); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", "diskSize"); + + arg_disk_size_relative = UINT64_MAX; + } else { + /* Normalize to UINT32_MAX == 100% */ + arg_disk_size_relative = UINT32_SCALE_FROM_PERMYRIAD(r); + + r = drop_from_identity("diskSize"); + if (r < 0) + return r; + + r = sd_json_variant_set_field_unsigned(identity, "diskSizeRelative", arg_disk_size_relative); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", "diskSizeRelative"); + + arg_disk_size = UINT64_MAX; + } + + /* Automatically turn off the rebalance logic if user configured a size explicitly */ + r = sd_json_variant_set_field_unsigned(identity, "rebalanceWeight", REBALANCE_WEIGHT_OFF); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", "rebalanceWeight"); + return 0; +} + +static int parse_sector_size_field(sd_json_variant **identity, const char *field, const char *arg) { + uint64_t ss; + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + r = parse_sector_size(arg, &ss); + if (r < 0) + return r; + + r = sd_json_variant_set_field_unsigned(identity, field, ss); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int parse_environment_field(sd_json_variant **identity, const char *field, const char *arg) { _cleanup_strv_free_ char **l = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *ne = NULL; @@ -4181,52 +4254,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_DISK_SIZE: - if (isempty(optarg)) { - FOREACH_STRING(prop, "diskSize", "diskSizeRelative", "rebalanceWeight") { - r = drop_from_identity(prop); - if (r < 0) - return r; - } - - arg_disk_size = arg_disk_size_relative = UINT64_MAX; - break; - } - - r = parse_permyriad(optarg); - if (r < 0) { - r = parse_disk_size(optarg, &arg_disk_size); - if (r < 0) - return r; - - r = drop_from_identity("diskSizeRelative"); - if (r < 0) - return r; - - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra_this_machine, "diskSize", arg_disk_size); - if (r < 0) - return log_error_errno(r, "Failed to set diskSize field: %m"); - - arg_disk_size_relative = UINT64_MAX; - } else { - /* Normalize to UINT32_MAX == 100% */ - arg_disk_size_relative = UINT32_SCALE_FROM_PERMYRIAD(r); - - r = drop_from_identity("diskSize"); - if (r < 0) - return r; - - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra_this_machine, "diskSizeRelative", arg_disk_size_relative); - if (r < 0) - return log_error_errno(r, "Failed to set diskSizeRelative field: %m"); - - arg_disk_size = UINT64_MAX; - } - - /* Automatically turn off the rebalance logic if user configured a size explicitly */ - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra_this_machine, "rebalanceWeight", REBALANCE_WEIGHT_OFF); + r = parse_disk_size_field(match_identity ?: &arg_identity_extra_this_machine, optarg); if (r < 0) - return log_error_errno(r, "Failed to set rebalanceWeight field: %m"); - + return r; break; case ARG_ACCESS_MODE: @@ -4263,27 +4293,11 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_LUKS_SECTOR_SIZE: { - uint64_t ss; - - if (isempty(optarg)) { - r = drop_from_identity("luksSectorSize"); - if (r < 0) - return r; - - break; - } - - r = parse_sector_size(optarg, &ss); + case ARG_LUKS_SECTOR_SIZE: + r = parse_sector_size_field(match_identity ?: &arg_identity_extra, "luksSectorSize", optarg); if (r < 0) return r; - - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra, "luksSectorSize", ss); - if (r < 0) - return log_error_errno(r, "Failed to set sector size field: %m"); - break; - } case ARG_UMASK: r = parse_mode_field(match_identity ?: &arg_identity_extra, "umask", optarg); From 65cf26c72db0479c349bcf48dc7dc1fee7bedadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:24:47 +0100 Subject: [PATCH 14/25] homectl: split out helper parse_weight_field --- src/home/homectl.c | 49 ++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index b8b4463764..8d0dc647b0 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3458,6 +3458,30 @@ static int parse_sector_size_field(sd_json_variant **identity, const char *field return 0; } +static int parse_weight_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + uint64_t u; + r = safe_atou64(arg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + + if (!CGROUP_WEIGHT_IS_OK(u)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Weight %" PRIu64 " is out of valid range for field %s.", u, field); + + r = sd_json_variant_set_field_unsigned(identity, field, u); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int parse_environment_field(sd_json_variant **identity, const char *field, const char *arg) { _cleanup_strv_free_ char **l = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *ne = NULL; @@ -4515,29 +4539,12 @@ static int parse_argv(int argc, char *argv[]) { case ARG_CPU_WEIGHT: case ARG_IO_WEIGHT: { const char *field = c == ARG_CPU_WEIGHT ? "cpuWeight" : - c == ARG_IO_WEIGHT ? "ioWeight" : NULL; - uint64_t u; + c == ARG_IO_WEIGHT ? "ioWeight" : + NULL; - assert(field); - - if (isempty(optarg)) { - r = drop_from_identity(field); - if (r < 0) - return r; - break; - } - - r = safe_atou64(optarg, &u); + r = parse_weight_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse --cpu-weight=/--io-weight= parameter: %s", optarg); - - if (!CGROUP_WEIGHT_IS_OK(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Weight %" PRIu64 " is out of valid weight range.", u); - - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra, field, u); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - + return r; break; } From 316e9887f2a48bd1c4efa3e31b4bfbaeb22de3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:26:08 +0100 Subject: [PATCH 15/25] homectl: split out helper parse_group_field --- src/home/homectl.c | 154 ++++++++++++++++++--------------------------- 1 file changed, 60 insertions(+), 94 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 8d0dc647b0..df360a1e91 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3553,6 +3553,56 @@ static int parse_language_field(char ***languages, const char *arg) { } } +static int parse_group_field( + sd_json_variant *source_identity, + sd_json_variant **identity, + const char *field, + const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + for (const char *p = arg;;) { + _cleanup_free_ char *word = NULL; + _cleanup_strv_free_ char **list = NULL; + + r = extract_first_word(&p, &word, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse group list: %m"); + if (r == 0) + return 0; + + if (!valid_user_group_name(word, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *mo = + sd_json_variant_ref(sd_json_variant_by_key(source_identity, field)); + + r = sd_json_variant_strv(mo, &list); + if (r < 0) + return log_error_errno(r, "Failed to parse group list: %m"); + + r = strv_extend(&list, word); + if (r < 0) + return log_oom(); + + strv_sort_uniq(list); + + mo = sd_json_variant_unref(mo); + r = sd_json_variant_new_array_strv(&mo, list); + if (r < 0) + return log_error_errno(r, "Failed to allocate json list: %m"); + + r = sd_json_variant_set_field(identity, field, mo); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + } +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -4053,52 +4103,11 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_ALIAS: { - if (isempty(optarg)) { - r = drop_from_identity("aliases"); - if (r < 0) - return r; - break; - } - - for (const char *p = optarg;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse alias list: %m"); - if (r == 0) - break; - - if (!valid_user_group_name(word, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid alias user name %s.", word); - - _cleanup_(sd_json_variant_unrefp) sd_json_variant *av = - sd_json_variant_ref(sd_json_variant_by_key(arg_identity_extra, "aliases")); - - _cleanup_strv_free_ char **list = NULL; - r = sd_json_variant_strv(av, &list); - if (r < 0) - return log_error_errno(r, "Failed to parse group list: %m"); - - r = strv_extend(&list, word); - if (r < 0) - return log_oom(); - - strv_sort_uniq(list); - - av = sd_json_variant_unref(av); - r = sd_json_variant_new_array_strv(&av, list); - if (r < 0) - return log_error_errno(r, "Failed to create alias list JSON: %m"); - - r = sd_json_variant_set_field(&arg_identity_extra, "aliases", av); - if (r < 0) - return log_error_errno(r, "Failed to update alias list: %m"); - } - + case ARG_ALIAS: + r = parse_group_field(arg_identity_extra, &arg_identity_extra, "aliases", optarg); + if (r < 0) + return r; break; - } case 'd': r = parse_home_directory_field(&arg_identity_extra, "homeDirectory", optarg); @@ -4464,56 +4473,13 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 'G': { - const char *p = optarg; - - if (isempty(p)) { - r = drop_from_identity("memberOf"); - if (r < 0) - return r; - - break; - } - - for (;;) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *mo = NULL; - _cleanup_strv_free_ char **list = NULL; - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse group list: %m"); - if (r == 0) - break; - - if (!valid_user_group_name(word, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word); - - mo = sd_json_variant_ref(sd_json_variant_by_key(arg_identity_extra, "memberOf")); - - r = sd_json_variant_strv(mo, &list); - if (r < 0) - return log_error_errno(r, "Failed to parse group list: %m"); - - r = strv_extend(&list, word); - if (r < 0) - return log_oom(); - - strv_sort_uniq(list); - - mo = sd_json_variant_unref(mo); - r = sd_json_variant_new_array_strv(&mo, list); - if (r < 0) - return log_error_errno(r, "Failed to create group list JSON: %m"); - - r = sd_json_variant_set_field(match_identity ?: &arg_identity_extra, "memberOf", mo); - if (r < 0) - return log_error_errno(r, "Failed to update group list: %m"); - } - + case 'G': + r = parse_group_field(arg_identity_extra, + match_identity ?: &arg_identity_extra, + "memberOf", optarg); + if (r < 0) + return r; break; - } - case ARG_TASKS_MAX: r = parse_u64_field(match_identity ?: &arg_identity_extra, "tasksMax", optarg); From ed93b1ed14730b122d82e5670cdd27768492b998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:29:35 +0100 Subject: [PATCH 16/25] homectl: split out helpers parse_{time,timestamp}_field --- src/home/homectl.c | 110 ++++++++++++++++++++------------------------- 1 file changed, 49 insertions(+), 61 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index df360a1e91..66da4de34e 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3298,6 +3298,46 @@ static int parse_mode_field(sd_json_variant **identity, const char *field, const return 0; } +static int parse_timestamp_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + usec_t n; + r = parse_timestamp(arg, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + + r = sd_json_variant_set_field_unsigned(identity, field, n); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + +static int parse_time_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + usec_t n; + r = parse_sec(arg, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + + r = sd_json_variant_set_field_unsigned(identity, field, n); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int parse_nice_field(sd_json_variant **identity, const char *field, const char *arg) { int nc, r; @@ -4348,33 +4388,11 @@ static int parse_argv(int argc, char *argv[]) { case ARG_NOT_BEFORE: case ARG_NOT_AFTER: case 'e': { - const char *field; - usec_t n; + const char *field = c == ARG_NOT_BEFORE ? "notBeforeUSec" : "notAfterUSec"; - field = c == ARG_NOT_BEFORE ? "notBeforeUSec" : - IN_SET(c, ARG_NOT_AFTER, 'e') ? "notAfterUSec" : NULL; - - assert(field); - - if (isempty(optarg)) { - r = drop_from_identity(field); - if (r < 0) - return r; - - break; - } - - /* Note the minor discrepancy regarding -e parsing here: we support that for compat - * reasons, and in the original useradd(8) implementation it accepts dates in the - * format YYYY-MM-DD. Coincidentally, we accept dates formatted like that too, but - * with greater precision. */ - r = parse_timestamp(optarg, &n); + r = parse_timestamp_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %m", field); - - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra, field, n); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return r; break; } @@ -4382,32 +4400,17 @@ static int parse_argv(int argc, char *argv[]) { case ARG_PASSWORD_CHANGE_MAX: case ARG_PASSWORD_CHANGE_WARN: case ARG_PASSWORD_CHANGE_INACTIVE: { - const char *field; - usec_t n; - - field = c == ARG_PASSWORD_CHANGE_MIN ? "passwordChangeMinUSec" : + const char *field = + c == ARG_PASSWORD_CHANGE_MIN ? "passwordChangeMinUSec" : c == ARG_PASSWORD_CHANGE_MAX ? "passwordChangeMaxUSec" : c == ARG_PASSWORD_CHANGE_WARN ? "passwordChangeWarnUSec" : c == ARG_PASSWORD_CHANGE_INACTIVE ? "passwordChangeInactiveUSec" : NULL; - assert(field); - if (isempty(optarg)) { - r = drop_from_identity(field); - if (r < 0) - return r; - - break; - } - - r = parse_sec(optarg, &n); + r = parse_time_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %m", field); - - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra, field, n); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return r; break; } @@ -4450,26 +4453,11 @@ static int parse_argv(int argc, char *argv[]) { c == ARG_RATE_LIMIT_INTERVAL ? "rateLimitIntervalUSec" : c == ARG_STOP_DELAY ? "stopDelayUSec" : NULL; - usec_t t; - assert(field); - if (isempty(optarg)) { - r = drop_from_identity(field); - if (r < 0) - return r; - - break; - } - - r = parse_sec(optarg, &t); + r = parse_time_field(match_identity ?: &arg_identity_extra, field, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse %s field: %s", field, optarg); - - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra, field, t); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - + return r; break; } From a161867b0017489d71bb561bc14cee53a46b92bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:33:41 +0100 Subject: [PATCH 17/25] homectl: split out helper parse_auto_resize_mode_field --- src/home/homectl.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 66da4de34e..aa6bbec145 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3357,6 +3357,22 @@ static int parse_nice_field(sd_json_variant **identity, const char *field, const return 0; } +static int parse_auto_resize_mode_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (!isempty(arg)) { + r = auto_resize_mode_from_string(arg); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + arg = auto_resize_mode_to_string(r); + } + + return parse_string_field(identity, field, arg); +} + static int parse_rlimit_field(sd_json_variant **identity, const char *field, const char *arg) { int r; @@ -4614,22 +4630,10 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_AUTO_RESIZE_MODE: - if (isempty(optarg)) { - r = drop_from_identity("autoResizeMode"); - if (r < 0) - return r; - - break; - } - - r = auto_resize_mode_from_string(optarg); + r = parse_auto_resize_mode_field(match_identity ?: &arg_identity_extra, + "autoResizeMode", optarg); if (r < 0) - return log_error_errno(r, "Failed to parse --auto-resize-mode= argument: %s", optarg); - - r = sd_json_variant_set_field_string(match_identity ?: &arg_identity_extra, "autoResizeMode", auto_resize_mode_to_string(r)); - if (r < 0) - return log_error_errno(r, "Failed to set autoResizeMode field: %m"); - + return r; break; case ARG_REBALANCE_WEIGHT: { From 0c2199b4cc37be83d6845b47f32ba1b30a251ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 12 Jan 2026 11:43:06 +0100 Subject: [PATCH 18/25] homectl: split out helper parse_rebalance_weight --- src/home/homectl.c | 69 +++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index aa6bbec145..9df62d6dd7 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3373,6 +3373,41 @@ static int parse_auto_resize_mode_field(sd_json_variant **identity, const char * return parse_string_field(identity, field, arg); } +static int parse_rebalance_weight(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + uint64_t u; + if (streq(arg, "off")) + u = REBALANCE_WEIGHT_OFF; + else { + r = safe_atou64(arg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse rebalance weight parameter: %s", arg); + + if (u < REBALANCE_WEIGHT_MIN || u > REBALANCE_WEIGHT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), + "Rebalancing weight out of valid range %" PRIu64 "%s%" PRIu64 ": %s", + REBALANCE_WEIGHT_MIN, glyph(GLYPH_ELLIPSIS), REBALANCE_WEIGHT_MAX, + arg); + } + + /* Drop from per machine stuff and everywhere */ + r = drop_from_identity(field); + if (r < 0) + return r; + + r = sd_json_variant_set_field_unsigned(identity, field, u); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int parse_rlimit_field(sd_json_variant **identity, const char *field, const char *arg) { int r; @@ -4636,40 +4671,12 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case ARG_REBALANCE_WEIGHT: { - uint64_t u; - - if (isempty(optarg)) { - r = drop_from_identity("rebalanceWeight"); - if (r < 0) - return r; - break; - } - - if (streq(optarg, "off")) - u = REBALANCE_WEIGHT_OFF; - else { - r = safe_atou64(optarg, &u); - if (r < 0) - return log_error_errno(r, "Failed to parse --rebalance-weight= argument: %s", optarg); - - if (u < REBALANCE_WEIGHT_MIN || u > REBALANCE_WEIGHT_MAX) - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Rebalancing weight out of valid range %" PRIu64 "%s%" PRIu64 ": %s", - REBALANCE_WEIGHT_MIN, glyph(GLYPH_ELLIPSIS), REBALANCE_WEIGHT_MAX, optarg); - } - - /* Drop from per machine stuff and everywhere */ - r = drop_from_identity("rebalanceWeight"); + case ARG_REBALANCE_WEIGHT: + r = parse_rebalance_weight(match_identity ?: &arg_identity_extra, + "rebalanceWeight", optarg); if (r < 0) return r; - - /* Add to main identity */ - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra, "rebalanceWeight", u); - if (r < 0) - return log_error_errno(r, "Failed to set rebalanceWeight field: %m"); - break; - } case 'j': arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; From 3f9c1339225dfd7c131f79147ed59ffbd488331a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:35:39 +0100 Subject: [PATCH 19/25] homectl: split out helper parse_capability_set_field --- src/home/homectl.c | 75 ++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 9df62d6dd7..e176f29186 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3694,6 +3694,38 @@ static int parse_group_field( } } +static int parse_capability_set_field( + sd_json_variant **identity, + uint64_t *capability_set, + const char *field, + const char *arg) { + + _cleanup_strv_free_ char **l = NULL; + int r; + + assert(identity); + assert(capability_set); + assert(field); + assert(arg); + + r = parse_capability_set(arg, CAP_MASK_UNSET, capability_set); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capabilities in capability string '%s'.", arg); + if (r < 0) + return log_error_errno(r, "Failed to parse capability string '%s': %m", arg); + + if (*capability_set == CAP_MASK_UNSET) + return drop_from_identity(field); + + if (capability_set_to_strv(*capability_set, &l) < 0) + return log_oom(); + + r = sd_json_variant_set_field_strv(identity, field, l); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -4727,43 +4759,20 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_CAPABILITY_AMBIENT_SET: - case ARG_CAPABILITY_BOUNDING_SET: { - _cleanup_strv_free_ char **l = NULL; - uint64_t *which; - const char *field; - - if (c == ARG_CAPABILITY_AMBIENT_SET) { - which = &arg_capability_ambient_set; - field = "capabilityAmbientSet"; - } else { - assert(c == ARG_CAPABILITY_BOUNDING_SET); - which = &arg_capability_bounding_set; - field = "capabilityBoundingSet"; - } - - r = parse_capability_set(optarg, CAP_MASK_UNSET, which); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capabilities in capability string '%s'.", optarg); + r = parse_capability_set_field(match_identity ?: &arg_identity_extra, + &arg_capability_ambient_set, + "capabilityAmbientSet", optarg); if (r < 0) - return log_error_errno(r, "Failed to parse capability string '%s': %m", optarg); + return r; + break; - if (*which == CAP_MASK_UNSET) { - r = drop_from_identity(field); - if (r < 0) - return r; - - break; - } - - if (capability_set_to_strv(*which, &l) < 0) - return log_oom(); - - r = sd_json_variant_set_field_strv(match_identity ?: &arg_identity_extra, field, l); + case ARG_CAPABILITY_BOUNDING_SET: + r = parse_capability_set_field(match_identity ?: &arg_identity_extra, + &arg_capability_bounding_set, + "capabilityBoundingSet", optarg); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - + return r; break; - } case ARG_PROMPT_NEW_USER: arg_prompt_new_user = true; From b078b7f055cc6000114fc039d6e5315bc5d945f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:39:37 +0100 Subject: [PATCH 20/25] homectl: split out helper parse_tmpfs_limit_field --- src/home/homectl.c | 87 ++++++++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index e176f29186..99fd0c2306 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3726,6 +3726,48 @@ static int parse_capability_set_field( return 0; } +static int parse_tmpfs_limit_field( + sd_json_variant **identity, + const char *field, + const char *field_scale, + const char *arg) { + int r; + + assert(identity); + assert(field); + assert(field_scale); + + if (isempty(arg)) { + FOREACH_STRING(p, field, field_scale) { + r = drop_from_identity(p); + if (r < 0) + return r; + } + return 0; + } + + r = parse_permyriad(arg); + if (r < 0) { + uint64_t u; + + r = parse_size(arg, 1024, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse %s/%s parameter: %s", field, field_scale, arg); + + r = sd_json_variant_set_field_unsigned(identity, field, u); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + + return drop_from_identity(field_scale); + } + + r = sd_json_variant_set_field_unsigned(identity, field_scale, UINT32_SCALE_FROM_PERMYRIAD(r)); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field_scale); + + return drop_from_identity(field); +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -4854,49 +4896,20 @@ static int parse_argv(int argc, char *argv[]) { case ARG_DEV_SHM_LIMIT: { const char *field = c == ARG_TMP_LIMIT ? "tmpLimit" : - c == ARG_DEV_SHM_LIMIT ? "devShmLimit" : NULL; + c == ARG_DEV_SHM_LIMIT ? "devShmLimit" : + NULL; const char *field_scale = c == ARG_TMP_LIMIT ? "tmpLimitScale" : - c == ARG_DEV_SHM_LIMIT ? "devShmLimitScale" : NULL; + c == ARG_DEV_SHM_LIMIT ? "devShmLimitScale" : + NULL; assert(field); assert(field_scale); - if (isempty(optarg)) { - r = drop_from_identity(field); - if (r < 0) - return r; - r = drop_from_identity(field_scale); - if (r < 0) - return r; - break; - } - - r = parse_permyriad(optarg); - if (r < 0) { - uint64_t u; - - r = parse_size(optarg, 1024, &u); - if (r < 0) - return log_error_errno(r, "Failed to parse %s/%s parameter: %s", field, field_scale, optarg); - - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra, field, u); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - - r = drop_from_identity(field_scale); - if (r < 0) - return r; - } else { - r = sd_json_variant_set_field_unsigned(match_identity ?: &arg_identity_extra, field_scale, UINT32_SCALE_FROM_PERMYRIAD(r)); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field_scale); - - r = drop_from_identity(field); - if (r < 0) - return r; - } - + r = parse_tmpfs_limit_field(match_identity ?: &arg_identity_extra, + field, field_scale, optarg); + if (r < 0) + return r; break; } From 47f631fda58cc6431ffb7c65c957529b02ab499f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:42:51 +0100 Subject: [PATCH 21/25] homectl: split out helpers parse_{fido2_device,pkcs11_token_uri}_field Both implement the 'list' verb. --- src/home/homectl.c | 137 ++++++++++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 58 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 99fd0c2306..06bb8e82b4 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3768,6 +3768,81 @@ static int parse_tmpfs_limit_field( return drop_from_identity(field); } +static int parse_pkcs11_token_uri_field(const char *arg) { + int r; + + assert(arg); + + if (streq(arg, "list")) + return pkcs11_list_tokens(); + + /* If --pkcs11-token-uri= is specified we always drop everything old */ + FOREACH_STRING(p, "pkcs11TokenUri", "pkcs11EncryptedKey") { + r = drop_from_identity(p); + if (r < 0) + return r; + } + + if (isempty(arg)) { + arg_pkcs11_token_uri = strv_free(arg_pkcs11_token_uri); + return 1; + } + + if (streq(arg, "auto")) { + char *found; + r = pkcs11_find_token_auto(&found); + if (r < 0) + return r; + + r = strv_consume(&arg_pkcs11_token_uri, found); + } else { + if (!pkcs11_uri_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", arg); + + r = strv_extend(&arg_pkcs11_token_uri, arg); + } + if (r < 0) + return r; + + strv_uniq(arg_pkcs11_token_uri); + return 1; +} + +static int parse_fido2_device_field(const char *arg) { + int r; + + assert(arg); + + if (streq(arg, "list")) + return fido2_list_devices(); + + FOREACH_STRING(p, "fido2HmacCredential", "fido2HmacSalt") { + r = drop_from_identity(p); + if (r < 0) + return r; + } + + if (isempty(arg)) { + arg_fido2_device = strv_free(arg_fido2_device); + return 1; + } + + if (streq(arg, "auto")) { + char *found; + r = fido2_find_device_auto(&found); + if (r < 0) + return r; + + r = strv_consume(&arg_fido2_device, found); + } else + r = strv_extend(&arg_fido2_device, arg); + if (r < 0) + return r; + + strv_uniq(arg_fido2_device); + return 1; +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -4628,38 +4703,9 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_PKCS11_TOKEN_URI: - if (streq(optarg, "list")) - return pkcs11_list_tokens(); - - /* If --pkcs11-token-uri= is specified we always drop everything old */ - FOREACH_STRING(p, "pkcs11TokenUri", "pkcs11EncryptedKey") { - r = drop_from_identity(p); - if (r < 0) - return r; - } - - if (isempty(optarg)) { - arg_pkcs11_token_uri = strv_free(arg_pkcs11_token_uri); - break; - } - - if (streq(optarg, "auto")) { - _cleanup_free_ char *found = NULL; - - r = pkcs11_find_token_auto(&found); - if (r < 0) - return r; - r = strv_consume(&arg_pkcs11_token_uri, TAKE_PTR(found)); - } else { - if (!pkcs11_uri_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg); - - r = strv_extend(&arg_pkcs11_token_uri, optarg); - } - if (r < 0) + r = parse_pkcs11_token_uri_field(optarg); + if (r <= 0) return r; - - strv_uniq(arg_pkcs11_token_uri); break; case ARG_FIDO2_CRED_ALG: @@ -4669,34 +4715,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_FIDO2_DEVICE: - if (streq(optarg, "list")) - return fido2_list_devices(); - - FOREACH_STRING(p, "fido2HmacCredential", "fido2HmacSalt") { - r = drop_from_identity(p); - if (r < 0) - return r; - } - - if (isempty(optarg)) { - arg_fido2_device = strv_free(arg_fido2_device); - break; - } - - if (streq(optarg, "auto")) { - _cleanup_free_ char *found = NULL; - - r = fido2_find_device_auto(&found); - if (r < 0) - return r; - - r = strv_consume(&arg_fido2_device, TAKE_PTR(found)); - } else - r = strv_extend(&arg_fido2_device, optarg); - if (r < 0) + r = parse_fido2_device_field(optarg); + if (r <= 0) return r; - - strv_uniq(arg_fido2_device); break; case ARG_FIDO2_WITH_PIN: From ed646a8d476b201b281274956d9e894c5e77f884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:46:38 +0100 Subject: [PATCH 22/25] homectl: split out helpers parse_uid_field Yet another very similar function, but without generics it's hard to deduplicate those. --- src/home/homectl.c | 59 ++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 06bb8e82b4..6112a3a07c 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3338,6 +3338,34 @@ static int parse_time_field(sd_json_variant **identity, const char *field, const return 0; } +static int parse_uid_field(sd_json_variant **identity, const char *field, const char *arg) { + uid_t uid; + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + r = parse_uid(arg, &uid); + if (r < 0) + return log_error_errno(r, "Failed to parse UID '%s'.", arg); + + const char *bad_range = + uid_is_system(uid) ? "in system range" : + uid_is_greeter(uid) ? "in greeter range" : + uid_is_dynamic(uid) ? "in dynamic ragne" : + uid == UID_NOBODY ? "nobody UID" : NULL; + if (bad_range) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID "UID_FMT" is %s, refusing.", uid, bad_range); + + r = sd_json_variant_set_field_unsigned(identity, field, uid); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} + static int parse_nice_field(sd_json_variant **identity, const char *field, const char *arg) { int nc, r; @@ -4421,36 +4449,11 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - case 'u': { - uid_t uid; - - if (isempty(optarg)) { - r = drop_from_identity("uid"); - if (r < 0) - return r; - - break; - } - - r = parse_uid(optarg, &uid); + case 'u': + r = parse_uid_field(&arg_identity_extra, "uid", optarg); if (r < 0) - return log_error_errno(r, "Failed to parse UID '%s'.", optarg); - - if (uid_is_system(uid)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID " UID_FMT " is in system range, refusing.", uid); - if (uid_is_greeter(uid)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID " UID_FMT " is in greeter range, refusing.", uid); - if (uid_is_dynamic(uid)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID " UID_FMT " is in dynamic range, refusing.", uid); - if (uid == UID_NOBODY) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID " UID_FMT " is nobody UID, refusing.", uid); - - r = sd_json_variant_set_field_unsigned(&arg_identity_extra, "uid", uid); - if (r < 0) - return log_error_errno(r, "Failed to set realm field: %m"); - + return r; break; - } case 'k': case ARG_IMAGE_PATH: { From 37ad11945109402b93dbd7f66a32742d94348f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 15:48:48 +0100 Subject: [PATCH 23/25] homectl: trivial simplification --- src/home/homectl.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index 6112a3a07c..ceb7fae7ee 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -4944,18 +4944,13 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_KEY_NAME: - if (isempty(optarg)) { - arg_key_name = mfree(arg_key_name); - return 0; - } + if (!isempty(optarg) && !filename_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for --key-name= not a valid filename: %s", optarg); - if (!filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified key name not valid: %s", optarg); - - r = free_and_strdup_warn(&arg_key_name, optarg); + r = free_and_strdup_warn(&arg_key_name, empty_to_null(optarg)); if (r < 0) return r; - break; case ARG_SEIZE: From edd2a2a0060b18bbb15c0519dc05d3197d3efebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 10 Dec 2025 16:14:23 +0100 Subject: [PATCH 24/25] homectl: let drop_from_identity take multiple args The only functional change should be that we deduplicate arg_identity_filter. --- src/home/homectl.c | 66 +++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 42 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index ceb7fae7ee..fac6d9482a 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3021,27 +3021,27 @@ static int verb_firstboot(int argc, char *argv[], void *userdata) { return ret; } -static int drop_from_identity(const char *field) { - int r; +#define drop_from_identity(...) _drop_from_identity(STRV_MAKE(__VA_ARGS__)) - assert(field); +static int _drop_from_identity(char **fields) { + int r; /* If we are called to update an identity record and drop some field, let's keep track of what to * remove from the old record */ - r = strv_extend(&arg_identity_filter, field); + r = strv_extend_strv(&arg_identity_filter, fields, /* filter_duplicates= */ true); if (r < 0) return log_oom(); /* Let's also drop the field if it was previously set to a new value on the same command line */ - r = sd_json_variant_filter(&arg_identity_extra, STRV_MAKE(field)); + r = sd_json_variant_filter(&arg_identity_extra, fields); if (r < 0) return log_error_errno(r, "Failed to filter JSON identity data: %m"); - r = sd_json_variant_filter(&arg_identity_extra_this_machine, STRV_MAKE(field)); + r = sd_json_variant_filter(&arg_identity_extra_this_machine, fields); if (r < 0) return log_error_errno(r, "Failed to filter JSON identity data: %m"); - r = sd_json_variant_filter(&arg_identity_extra_privileged, STRV_MAKE(field)); + r = sd_json_variant_filter(&arg_identity_extra_privileged, fields); if (r < 0) return log_error_errno(r, "Failed to filter JSON identity data: %m"); @@ -3510,11 +3510,9 @@ static int parse_disk_size_field(sd_json_variant **identity, const char *arg) { assert(identity); if (isempty(arg)) { - FOREACH_STRING(prop, "diskSize", "diskSizeRelative", "rebalanceWeight") { - r = drop_from_identity(prop); - if (r < 0) - return r; - } + r = drop_from_identity("diskSize", "diskSizeRelative", "rebalanceWeight"); + if (r < 0) + return r; arg_disk_size = arg_disk_size_relative = UINT64_MAX; return 0; @@ -3639,11 +3637,9 @@ static int parse_language_field(char ***languages, const char *arg) { int r; if (isempty(arg)) { - FOREACH_STRING(prop, "preferredLanguage", "additionalLanguages") { - r = drop_from_identity(prop); - if (r < 0) - return r; - } + r = drop_from_identity("preferredLanguage", "additionalLanguages"); + if (r < 0) + return r; strv_freep(languages); return 0; @@ -3765,14 +3761,8 @@ static int parse_tmpfs_limit_field( assert(field); assert(field_scale); - if (isempty(arg)) { - FOREACH_STRING(p, field, field_scale) { - r = drop_from_identity(p); - if (r < 0) - return r; - } - return 0; - } + if (isempty(arg)) + return drop_from_identity(field, field_scale); r = parse_permyriad(arg); if (r < 0) { @@ -3805,11 +3795,9 @@ static int parse_pkcs11_token_uri_field(const char *arg) { return pkcs11_list_tokens(); /* If --pkcs11-token-uri= is specified we always drop everything old */ - FOREACH_STRING(p, "pkcs11TokenUri", "pkcs11EncryptedKey") { - r = drop_from_identity(p); - if (r < 0) - return r; - } + r = drop_from_identity("pkcs11TokenUri", "pkcs11EncryptedKey"); + if (r < 0) + return r; if (isempty(arg)) { arg_pkcs11_token_uri = strv_free(arg_pkcs11_token_uri); @@ -3844,11 +3832,9 @@ static int parse_fido2_device_field(const char *arg) { if (streq(arg, "list")) return fido2_list_devices(); - FOREACH_STRING(p, "fido2HmacCredential", "fido2HmacSalt") { - r = drop_from_identity(p); - if (r < 0) - return r; - } + r = drop_from_identity("fido2HmacCredential", "fido2HmacSalt"); + if (r < 0) + return r; if (isempty(arg)) { arg_fido2_device = strv_free(arg_fido2_device); @@ -4751,15 +4737,11 @@ static int parse_argv(int argc, char *argv[]) { r = parse_boolean(optarg); if (r < 0) return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", optarg); - arg_recovery_key = r; - FOREACH_STRING(p, "recoveryKey", "recoveryKeyType") { - r = drop_from_identity(p); - if (r < 0) - return r; - } - + r = drop_from_identity("recoveryKey", "recoveryKeyType"); + if (r < 0) + return r; break; case ARG_AUTO_RESIZE_MODE: From f2c3ef9678b4046a03f9586ba9164418628c18e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 4 Jan 2026 16:18:25 +0100 Subject: [PATCH 25/25] homectl: add argument comments --- src/home/homectl.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index fac6d9482a..8c263f6caa 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -1507,7 +1507,7 @@ static int create_home_common(sd_json_variant *input, bool show_enforce_password _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(erase_and_freep) char *formatted = NULL; - r = sd_json_variant_format(hr->json, 0, &formatted); + r = sd_json_variant_format(hr->json, /* flags= */ 0, &formatted); if (r < 0) return log_error_errno(r, "Failed to format user record: %m"); @@ -1563,7 +1563,7 @@ static int create_home(int argc, char *argv[], void *userdata) { if (argc >= 2) { /* If a username was specified, use it */ - if (valid_user_group_name(argv[1], 0)) + if (valid_user_group_name(argv[1], /* flags= */ 0)) r = sd_json_variant_set_field_string(&arg_identity_extra, "userName", argv[1]); else { _cleanup_free_ char *un = NULL, *rr = NULL; @@ -1966,7 +1966,7 @@ static int update_home(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_create_error(r); - r = sd_json_variant_format(hr->json, 0, &formatted); + r = sd_json_variant_format(hr->json, /* flags= */ 0, &formatted); if (r < 0) return log_error_errno(r, "Failed to format user record: %m"); @@ -2552,7 +2552,7 @@ static int create_or_register_from_credentials(void) { else continue; - if (!valid_user_group_name(e, 0)) { + if (!valid_user_group_name(e, /* flags= */ 0)) { log_notice("Skipping over credential with name that is not a suitable user name: %s", de->d_name); continue; } @@ -3144,7 +3144,7 @@ static int parse_home_directory_field(sd_json_variant **identity, const char *fi assert(field); if (!isempty(arg)) { - r = parse_path_argument(arg, false, &hd); + r = parse_path_argument(arg, /* suppress_root= */ false, &hd); if (r < 0) return r; @@ -3180,7 +3180,7 @@ static int parse_path_field(sd_json_variant **identity, const char *field, const assert(field); if (!isempty(arg)) { - r = parse_path_argument(arg, false, &v); + r = parse_path_argument(arg, /* suppress_root= */ false, &v); if (r < 0) return r; } @@ -3648,7 +3648,7 @@ static int parse_language_field(char ***languages, const char *arg) { for (const char *p = arg;;) { _cleanup_free_ char *word = NULL; - r = extract_first_word(&p, &word, ",:", 0); + r = extract_first_word(&p, &word, ",:", /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to parse locale list: %m"); if (r == 0) @@ -3685,13 +3685,13 @@ static int parse_group_field( _cleanup_free_ char *word = NULL; _cleanup_strv_free_ char **list = NULL; - r = extract_first_word(&p, &word, ",", 0); + r = extract_first_word(&p, &word, ",", /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to parse group list: %m"); if (r == 0) return 0; - if (!valid_user_group_name(word, 0)) + if (!valid_user_group_name(word, /* flags= */ 0)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word); _cleanup_(sd_json_variant_unrefp) sd_json_variant *mo = @@ -4844,7 +4844,7 @@ static int parse_argv(int argc, char *argv[]) { eq = strrchr(optarg, '='); if (!eq) { /* --blob=/some/path replaces the blob dir */ - r = parse_path_argument(optarg, false, &arg_blob_dir); + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_blob_dir); if (r < 0) return log_error_errno(r, "Failed to parse path %s: %m", optarg); break; @@ -4860,7 +4860,7 @@ static int parse_argv(int argc, char *argv[]) { if (!suitable_blob_filename(filename)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid blob filename: %s", filename); - r = parse_path_argument(eq + 1, false, &path); + r = parse_path_argument(eq + 1, /* suppress_root= */ false, &path); if (r < 0) return log_error_errno(r, "Failed to parse path %s: %m", eq + 1); } else { @@ -4874,7 +4874,7 @@ static int parse_argv(int argc, char *argv[]) { if (!filename) return log_oom(); - r = parse_path_argument(optarg, false, &path); + r = parse_path_argument(optarg, /* suppress_root= */ false, &path); if (r < 0) return log_error_errno(r, "Failed to parse path %s: %m", optarg); }