Add a manual test to exercise homectl prompt logic (#41954)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
+25
-256
@@ -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) >= 0;
|
||||
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,38 @@ 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)) {
|
||||
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');
|
||||
|
||||
@@ -51,6 +51,10 @@ homectl_sources = files(
|
||||
'homectl.c',
|
||||
)
|
||||
|
||||
homectl_extract = files(
|
||||
'homectl-prompts.c',
|
||||
)
|
||||
|
||||
pam_systemd_home_sources = files(
|
||||
'home-util.c',
|
||||
'pam_systemd_home.c',
|
||||
@@ -85,6 +89,7 @@ executables += [
|
||||
'name' : 'homectl',
|
||||
'public' : true,
|
||||
'sources' : homectl_sources,
|
||||
'extract' : homectl_extract,
|
||||
'objects' : ['systemd-homed'],
|
||||
'dependencies' : [
|
||||
libdl,
|
||||
@@ -93,6 +98,11 @@ executables += [
|
||||
threads,
|
||||
],
|
||||
},
|
||||
test_template + {
|
||||
'sources' : files('test-homectl-prompts.c'),
|
||||
'objects' : ['homectl'],
|
||||
'type' : 'manual',
|
||||
},
|
||||
test_template + {
|
||||
'sources' : files('test-homed-regression-31896.c'),
|
||||
'type' : 'manual',
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "format-table.h"
|
||||
#include "help-util.h"
|
||||
#include "homectl-prompts.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "tests.h"
|
||||
#include "verbs.h"
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
|
||||
int r;
|
||||
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = verbs_get_help_table(&verbs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(void) table_sync_column_widths(0, options, verbs);
|
||||
|
||||
help_cmdline("[OPTIONS...] VERB [USERNAME]");
|
||||
help_abstract("Exercise homectl prompt functions in isolation.");
|
||||
|
||||
help_section("Verbs");
|
||||
r = table_print_or_warn(verbs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
help_section("Options");
|
||||
return table_print_or_warn(options);
|
||||
}
|
||||
|
||||
VERB(verb_groups, "groups", "[USER]", VERB_ANY, 2, 0, "Select groups");
|
||||
static int verb_groups(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
assert(argv);
|
||||
|
||||
const char *username = argv[1] ?: "test";
|
||||
int r;
|
||||
|
||||
_cleanup_strv_free_ char **t = NULL;
|
||||
|
||||
r = prompt_groups(username, &t);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *s = ASSERT_PTR(strv_join(t, ", "));
|
||||
log_info("groups: %s → %s", username, s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
VERB(verb_shell, "shell", "[USER]", VERB_ANY, 2, 0, "Select shell");
|
||||
static int verb_shell(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
assert(argv);
|
||||
|
||||
const char *username = argv[1] ?: "test";
|
||||
int r;
|
||||
|
||||
_cleanup_free_ char *s = NULL;
|
||||
|
||||
r = prompt_shell(username, &s);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_info("shell: %s → %s", username, strnull(s));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char **argv, char ***remaining_args) {
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
assert(remaining_args);
|
||||
|
||||
OptionParser opts = { argc, argv };
|
||||
|
||||
FOREACH_OPTION_OR_RETURN(c, &opts)
|
||||
switch (c) {
|
||||
|
||||
OPTION_COMMON_HELP:
|
||||
return help();
|
||||
}
|
||||
|
||||
*remaining_args = option_parser_get_args(&opts);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int run(int argc, char **argv) {
|
||||
int r;
|
||||
|
||||
test_setup_logging(LOG_DEBUG);
|
||||
|
||||
char **args = NULL;
|
||||
r = parse_argv(argc, argv, &args);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return dispatch_verb_with_args(args, /* userdata= */ NULL);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
Reference in New Issue
Block a user