From: Zbigniew Jędrzejewski-Szmek Date: Tue, 5 May 2026 11:01:16 +0000 (+0200) Subject: homectl: split out two prompt functions X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=764d9d5ddbcc355c2f895b6e87c6916e5495ddca;p=thirdparty%2Fsystemd.git homectl: split out two prompt functions homectl.c is too long… --- diff --git a/src/home/homectl-prompts.c b/src/home/homectl-prompts.c new file mode 100644 index 00000000000..71640377e38 --- /dev/null +++ b/src/home/homectl-prompts.c @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "bitfield.h" +#include "chase.h" +#include "glyph-util.h" +#include "group-record.h" +#include "homectl-prompts.h" +#include "log.h" +#include "parse-util.h" +#include "prompt-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" +#include "userdb.h" + +static int acquire_group_list(char ***ret) { + _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; + _cleanup_strv_free_ char **groups = NULL; + UserDBMatch match = USERDB_MATCH_NULL; + int r; + + assert(ret); + + match.disposition_mask = INDEXES_TO_MASK(uint64_t, USER_REGULAR, USER_SYSTEM); + + r = groupdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator); + if (r == -ENOLINK) + log_debug_errno(r, "No groups found. (Didn't check via Varlink.)"); + else if (r == -ESRCH) + log_debug_errno(r, "No groups found."); + else if (r < 0) + return log_debug_errno(r, "Failed to enumerate groups, ignoring: %m"); + else + for (;;) { + _cleanup_(group_record_unrefp) GroupRecord *gr = NULL; + + r = groupdb_iterator_get(iterator, &match, &gr); + if (r == -ESRCH) + break; + if (r < 0) + return log_debug_errno(r, "Failed to acquire next group: %m"); + + if (group_record_disposition(gr) == USER_REGULAR) { + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + + /* Filter groups here that belong to a specific user, and are named like them */ + + UserDBMatch user_match = USERDB_MATCH_NULL; + user_match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR); + + r = userdb_by_name(gr->group_name, &user_match, USERDB_SUPPRESS_SHADOW, &ur); + if (r < 0 && r != -ESRCH) + return log_debug_errno(r, "Failed to check if matching user exists for group '%s': %m", gr->group_name); + + if (r >= 0 && user_record_gid(ur) == gr->gid) + continue; + } + + r = strv_extend(&groups, gr->group_name); + if (r < 0) + return log_oom(); + } + + strv_sort(groups); + + *ret = TAKE_PTR(groups); + return !!*ret; +} + +static int group_completion_callback(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata) { + char ***available = userdata; + int r; + + if (!*available) { + r = acquire_group_list(available); + if (r < 0) + log_debug_errno(r, "Failed to enumerate available groups, ignoring: %m"); + } + + _cleanup_strv_free_ char **l = strv_copy(*available); + if (!l) + return -ENOMEM; + + if (!FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT)) { + r = strv_extend(&l, "list"); + if (r < 0) + return r; + } + + *ret_list = TAKE_PTR(l); + return 0; +} + +int prompt_groups(const char *username, char ***ret_groups) { + int r; + + assert(username); + assert(ret_groups); + + _cleanup_strv_free_ char **available = NULL, **groups = NULL; + for (;;) { + strv_sort_uniq(groups); + + if (!strv_isempty(groups)) { + _cleanup_free_ char *j = strv_join(groups, ", "); + if (!j) + return log_oom(); + + log_info("Currently selected groups: %s", j); + } + + _cleanup_free_ char *s = NULL; + r = ask_string_full( + &s, + group_completion_callback, + &available, + "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", + glyph(GLYPH_LABEL), + username); + if (r < 0) + return log_error_errno(r, "Failed to query user for auxiliary group: %m"); + + if (isempty(s)) + break; + + if (streq(s, "list")) { + if (!available) { + r = acquire_group_list(&available); + if (r < 0) + log_warning_errno(r, "Failed to enumerate available groups, ignoring: %m"); + if (r == 0) + log_notice("Did not find any available groups"); + if (r <= 0) + continue; + } + + r = show_menu(available, + /* n_columns= */ 3, + /* column_width= */ 20, + /* ellipsize_percentage= */ 60, + /* grey_prefix= */ NULL, + /* with_numbers= */ true); + if (r < 0) + return log_error_errno(r, "Failed to show menu: %m"); + + putchar('\n'); + continue; + } + + if (!strv_isempty(available)) { + unsigned u; + r = safe_atou(s, &u); + if (r >= 0) { + if (u <= 0 || u > strv_length(available)) { + log_error("Specified entry number out of range."); + continue; + } + + log_info("Selected '%s'.", available[u-1]); + + r = strv_extend(&groups, available[u-1]); + if (r < 0) + return log_oom(); + + continue; + } + } + + if (!valid_user_group_name(s, /* flags= */ 0)) { + log_notice("Specified group name is not a valid UNIX group name, try again: %s", s); + continue; + } + + r = groupdb_by_name(s, /* match= */ NULL, USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, /* ret= */ NULL); + if (r == -ESRCH) { + log_notice("Specified auxiliary group does not exist, try again: %s", s); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to check if specified group '%s' already exists: %m", s); + + log_info("Selected '%s'.", s); + + r = strv_extend(&groups, s); + if (r < 0) + return log_oom(); + } + + *ret_groups = TAKE_PTR(groups); + return 0; +} + +static int shell_is_ok(const char *path, void *userdata) { + int r; + + assert(path); + + if (!valid_shell(path)) { + log_error("String '%s' is not a valid path to a shell, refusing.", path); + return false; + } + + r = chase_and_access(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, X_OK, /* ret_path= */ NULL); + if (r == -ENOENT) { + log_error_errno(r, "Shell '%s' does not exist, try again.", path); + return false; + } + if (ERRNO_IS_NEG_PRIVILEGE(r)) { + log_error_errno(r, "File '%s' is not executable, try again.", path); + return false; + } + if (r < 0) + return log_error_errno(r, "Failed to check if shell '%s' exists and is executable: %m", path); + + return true; +} + +int prompt_shell(const char *username, char **ret_shell) { + assert(username); + assert(ret_shell); + + _cleanup_free_ char *q = strjoin("Please enter the shell to use for user ", username); + if (!q) + return log_oom(); + + return prompt_loop( + q, + GLYPH_SHELL, + /* menu= */ NULL, + /* accepted= */ NULL, + /* ellipsize_percentage= */ 0, + /* n_columns= */ 3, + /* column_width= */ 20, + shell_is_ok, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE, + ret_shell); +} diff --git a/src/home/homectl-prompts.h b/src/home/homectl-prompts.h new file mode 100644 index 00000000000..04d64600582 --- /dev/null +++ b/src/home/homectl-prompts.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int prompt_groups(const char *username, char ***ret_groups); +int prompt_shell(const char *username, char **ret_shell); diff --git a/src/home/homectl.c b/src/home/homectl.c index 454aa5bfe6b..56e723fbfef 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -16,7 +16,6 @@ #include "capability-list.h" #include "capability-util.h" #include "cgroup-util.h" -#include "chase.h" #include "creds-util.h" #include "crypto-util.h" #include "dirent-util.h" @@ -35,6 +34,7 @@ #include "home-util.h" #include "homectl-fido2.h" #include "homectl-pkcs11.h" +#include "homectl-prompts.h" #include "homectl-recovery-key.h" #include "json-util.h" #include "libfido2-util.h" @@ -2643,245 +2643,6 @@ static int has_regular_user(void) { return true; } -static int acquire_group_list(char ***ret) { - _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; - _cleanup_strv_free_ char **groups = NULL; - UserDBMatch match = USERDB_MATCH_NULL; - int r; - - assert(ret); - - match.disposition_mask = INDEXES_TO_MASK(uint64_t, USER_REGULAR, USER_SYSTEM); - - r = groupdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator); - if (r == -ENOLINK) - log_debug_errno(r, "No groups found. (Didn't check via Varlink.)"); - else if (r == -ESRCH) - log_debug_errno(r, "No groups found."); - else if (r < 0) - return log_debug_errno(r, "Failed to enumerate groups, ignoring: %m"); - else - for (;;) { - _cleanup_(group_record_unrefp) GroupRecord *gr = NULL; - - r = groupdb_iterator_get(iterator, &match, &gr); - if (r == -ESRCH) - break; - if (r < 0) - return log_debug_errno(r, "Failed to acquire next group: %m"); - - if (group_record_disposition(gr) == USER_REGULAR) { - _cleanup_(user_record_unrefp) UserRecord *ur = NULL; - - /* Filter groups here that belong to a specific user, and are named like them */ - - UserDBMatch user_match = USERDB_MATCH_NULL; - user_match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR); - - r = userdb_by_name(gr->group_name, &user_match, USERDB_SUPPRESS_SHADOW, &ur); - if (r < 0 && r != -ESRCH) - return log_debug_errno(r, "Failed to check if matching user exists for group '%s': %m", gr->group_name); - - if (r >= 0 && user_record_gid(ur) == gr->gid) - continue; - } - - r = strv_extend(&groups, gr->group_name); - if (r < 0) - return log_oom(); - } - - strv_sort(groups); - - *ret = TAKE_PTR(groups); - return !!*ret; -} - -static int group_completion_callback(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata) { - char ***available = userdata; - int r; - - if (!*available) { - r = acquire_group_list(available); - if (r < 0) - log_debug_errno(r, "Failed to enumerate available groups, ignoring: %m"); - } - - _cleanup_strv_free_ char **l = strv_copy(*available); - if (!l) - return -ENOMEM; - - if (!FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT)) { - r = strv_extend(&l, "list"); - if (r < 0) - return r; - } - - *ret_list = TAKE_PTR(l); - return 0; -} - -static int prompt_groups(const char *username, char ***ret_groups) { - int r; - - assert(username); - assert(ret_groups); - - if (!arg_prompt_groups) { - *ret_groups = NULL; - return 0; - } - - putchar('\n'); - - _cleanup_strv_free_ char **available = NULL, **groups = NULL; - for (;;) { - strv_sort_uniq(groups); - - if (!strv_isempty(groups)) { - _cleanup_free_ char *j = strv_join(groups, ", "); - if (!j) - return log_oom(); - - log_info("Currently selected groups: %s", j); - } - - _cleanup_free_ char *s = NULL; - r = ask_string_full( - &s, - group_completion_callback, - &available, - "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", - glyph(GLYPH_LABEL), - username); - if (r < 0) - return log_error_errno(r, "Failed to query user for auxiliary group: %m"); - - if (isempty(s)) - break; - - if (streq(s, "list")) { - if (!available) { - r = acquire_group_list(&available); - if (r < 0) - log_warning_errno(r, "Failed to enumerate available groups, ignoring: %m"); - if (r == 0) - log_notice("Did not find any available groups"); - if (r <= 0) - continue; - } - - r = show_menu(available, - /* n_columns= */ 3, - /* column_width= */ 20, - /* ellipsize_percentage= */ 60, - /* grey_prefix= */ NULL, - /* with_numbers= */ true); - if (r < 0) - return log_error_errno(r, "Failed to show menu: %m"); - - putchar('\n'); - continue; - }; - - if (!strv_isempty(available)) { - unsigned u; - r = safe_atou(s, &u); - if (r >= 0) { - if (u <= 0 || u > strv_length(available)) { - log_error("Specified entry number out of range."); - continue; - } - - log_info("Selected '%s'.", available[u-1]); - - r = strv_extend(&groups, available[u-1]); - if (r < 0) - return log_oom(); - - continue; - } - } - - if (!valid_user_group_name(s, /* flags= */ 0)) { - log_notice("Specified group name is not a valid UNIX group name, try again: %s", s); - continue; - } - - r = groupdb_by_name(s, /* match= */ NULL, USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, /* ret= */ NULL); - if (r == -ESRCH) { - log_notice("Specified auxiliary group does not exist, try again: %s", s); - continue; - } - if (r < 0) - return log_error_errno(r, "Failed to check if specified group '%s' already exists: %m", s); - - log_info("Selected '%s'.", s); - - r = strv_extend(&groups, s); - if (r < 0) - return log_oom(); - } - - *ret_groups = TAKE_PTR(groups); - return 0; -} - -static int shell_is_ok(const char *path, void *userdata) { - int r; - - assert(path); - - if (!valid_shell(path)) { - log_error("String '%s' is not a valid path to a shell, refusing.", path); - return false; - } - - r = chase_and_access(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, X_OK, /* ret_path= */ NULL); - if (r == -ENOENT) { - log_error_errno(r, "Shell '%s' does not exist, try again.", path); - return false; - } - if (ERRNO_IS_NEG_PRIVILEGE(r)) { - log_error_errno(r, "File '%s' is not executable, try again.", path); - return false; - } - if (r < 0) - return log_error_errno(r, "Failed to check if shell '%s' exists and is executable: %m", path); - - return true; -} - -static int prompt_shell(const char *username, char **ret_shell) { - assert(username); - assert(ret_shell); - - if (!arg_prompt_shell) { - *ret_shell = NULL; - return 0; - } - - putchar('\n'); - - _cleanup_free_ char *q = strjoin("Please enter the shell to use for user ", username); - if (!q) - return log_oom(); - - return prompt_loop( - q, - GLYPH_SHELL, - /* menu= */ NULL, - /* accepted= */ NULL, - /* ellipsize_percentage= */ 0, - /* n_columns= */ 3, - /* column_width= */ 20, - shell_is_ok, - /* refresh= */ NULL, - /* userdata= */ NULL, - PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE, - ret_shell); -} - static int username_is_ok(const char *name, void *userdata) { int r; @@ -2963,30 +2724,40 @@ static int create_interactively(void) { if (r < 0) return log_error_errno(r, "Failed to set enforcePasswordPolicy field: %m"); - _cleanup_strv_free_ char **groups = NULL; - r = prompt_groups(username, &groups); - if (r < 0) - return r; + if (arg_prompt_groups) { + _cleanup_strv_free_ char **groups = NULL; - if (!strv_isempty(groups)) { - strv_sort_uniq(groups); + putchar('\n'); - r = sd_json_variant_set_field_strv(&arg_identity_extra, "memberOf", groups); + r = prompt_groups(username, &groups); if (r < 0) - return log_error_errno(r, "Failed to set memberOf field: %m"); + return r; + + if (!strv_isempty(groups)) { + strv_sort_uniq(groups); + + r = sd_json_variant_set_field_strv(&arg_identity_extra, "memberOf", groups); + if (r < 0) + return log_error_errno(r, "Failed to set memberOf field: %m"); + } } - _cleanup_free_ char *shell = NULL; - r = prompt_shell(username, &shell); - if (r < 0) - return r; + if (arg_prompt_shell) { + _cleanup_free_ char *shell = NULL; - if (!isempty(shell)) { - log_info("Selected %s as the shell for user %s", shell, username); + putchar('\n'); - r = sd_json_variant_set_field_string(&arg_identity_extra, "shell", shell); + r = prompt_shell(username, &shell); if (r < 0) - return log_error_errno(r, "Failed to set shell field: %m"); + return r; + + if (!isempty(shell)) { + log_info("Selected %s as the shell for user %s", shell, username); + + r = sd_json_variant_set_field_string(&arg_identity_extra, "shell", shell); + if (r < 0) + return log_error_errno(r, "Failed to set shell field: %m"); + } } putchar('\n'); diff --git a/src/home/meson.build b/src/home/meson.build index 53c5675c83f..8c644842c14 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -47,6 +47,7 @@ systemd_homed_sources += [homed_gperf_c] homectl_sources = files( 'homectl-fido2.c', 'homectl-pkcs11.c', + 'homectl-prompts.c', 'homectl-recovery-key.c', 'homectl.c', )