]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homectl: split out two prompt functions
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Tue, 5 May 2026 11:01:16 +0000 (13:01 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Tue, 5 May 2026 18:53:23 +0000 (20:53 +0200)
homectl.c is too long…

src/home/homectl-prompts.c [new file with mode: 0644]
src/home/homectl-prompts.h [new file with mode: 0644]
src/home/homectl.c
src/home/meson.build

diff --git a/src/home/homectl-prompts.c b/src/home/homectl-prompts.c
new file mode 100644 (file)
index 0000000..7164037
--- /dev/null
@@ -0,0 +1,244 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stdio.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..04d6460
--- /dev/null
@@ -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);
index 454aa5bfe6bb31718f36923713a9612abb8d06a6..56e723fbfef9f34c110b5ec1e7eb8cb4d1a8022e 100644 (file)
@@ -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');
index 53c5675c83f881196f1f2c509f2b4392eb9837de..8c644842c1470b05928a32529ece551577144340 100644 (file)
@@ -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',
 )