Compare commits

...

3 Commits

Author SHA1 Message Date
Michael Ferrari 25e9f95960
Merge 17393505b1 into 099b16c3e7 2024-09-17 23:09:51 +02:00
Michael Ferrari 17393505b1
firstboot: add similar input suggestion
This uses the same logic as similar verb suggestion for command line
utilities. Try to be helpful when the user entered something invalid
instead of just showing the prompt again.
2024-09-14 02:01:52 +02:00
Michael Ferrari 17c53ebf20
firstboot: use default value when empty
This results in the "default" pathway having sane options set instead
of skipping the question, as it was explicitly requested to prompt the
user. Entering `-` allows skipping a prompt without writing any value.
2024-09-14 01:35:41 +02:00
3 changed files with 77 additions and 18 deletions

View File

@ -66,6 +66,33 @@ char* strv_find_startswith(char * const *l, const char *name) {
return NULL;
}
char* strv_find_closest_by_levenshtein(char * const *l, const char *name) {
ssize_t best_distance = SSIZE_MAX;
char *best = NULL;
assert(name);
STRV_FOREACH(i, l) {
ssize_t distance;
distance = strlevenshtein(*i, name);
if (distance < 0) {
log_debug_errno(distance, "Failed to determine Levenshtein distance between %s and %s: %m", *i, name);
return NULL;
}
if (distance > 5) /* If the distance is just too far off, don't make a bad suggestion */
continue;
if (distance < best_distance) {
best_distance = distance;
best = *i;
}
}
return best;
}
char* strv_find_first_field(char * const *needles, char * const *haystack) {
STRV_FOREACH(k, needles) {
char *value = strv_env_pairs_get((char **)haystack, *k);

View File

@ -17,6 +17,7 @@ char* strv_find(char * const *l, const char *name) _pure_;
char* strv_find_case(char * const *l, const char *name) _pure_;
char* strv_find_prefix(char * const *l, const char *name) _pure_;
char* strv_find_startswith(char * const *l, const char *name) _pure_;
char* strv_find_closest_by_levenshtein(char * const *l, const char *name) _pure_;
/* Given two vectors, the first a list of keys and the second a list of key-value pairs, returns the value
* of the first key from the first vector that is found in the second vector. */
char* strv_find_first_field(char * const *needles, char * const *haystack) _pure_;

View File

@ -178,24 +178,31 @@ static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned perc
return 0;
}
static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*is_valid)(const char *name), char **ret) {
static int prompt_loop(const char *text, char **l, const char* fallback, unsigned percentage, bool (*is_valid)(const char *name), char **ret) {
int r;
assert(text);
assert(is_valid);
assert(is_valid(fallback));
assert(ret);
for (;;) {
_cleanup_free_ char *p = NULL;
char *best_match = NULL;
unsigned u;
r = ask_string(&p, "%s %s (empty to skip, \"list\" to list options): ",
special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), text);
r = ask_string(&p, "%s %s (empty to default to \"%s\", \"-\" to skip, \"list\" to list options): ",
special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), text, fallback);
if (r < 0)
return log_error_errno(r, "Failed to query user: %m");
if (isempty(p)) {
log_info("No data entered, skipping.");
log_info("No data entered, using default.");
return free_and_strdup_warn(ret, fallback);
}
if (streq(p, "-")) {
log_info("Skip requested, continuing.");
return 0;
}
@ -219,12 +226,20 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i
return free_and_strdup_warn(ret, l[u-1]);
}
if (!is_valid(p)) {
log_error("Entered data invalid.");
continue;
}
if (is_valid(p))
return free_and_replace(*ret, p);
return free_and_replace(*ret, p);
/* Be helperful to the user, and give a hint what the user might have wanted to
* type. We search with two mechanisms: a simple prefix match and if that didn't
* yield results , a Levenshtein word distance based match. */
best_match = strv_find_prefix(l, p);
if (!best_match)
best_match = strv_find_closest_by_levenshtein(l, p);
if (best_match)
log_error("Invalid data '%s', did you mean '%s'?", p, best_match);
else
log_error("Invalid data '%s'.", p);
}
}
@ -365,7 +380,7 @@ static int prompt_locale(int rfd) {
print_welcome(rfd);
r = prompt_loop("Please enter system locale name or number",
locales, 60, is_valid, &arg_locale);
locales, SYSTEMD_DEFAULT_LOCALE, 60, is_valid, &arg_locale);
if (r < 0)
return r;
@ -373,7 +388,7 @@ static int prompt_locale(int rfd) {
return 0;
r = prompt_loop("Please enter system message locale name or number",
locales, 60, is_valid, &arg_locale_messages);
locales, arg_locale, 60, is_valid, &arg_locale_messages);
if (r < 0)
return r;
@ -488,7 +503,7 @@ static int prompt_keymap(int rfd) {
print_welcome(rfd);
return prompt_loop("Please enter system keymap name or number",
kmaps, 60, determine_keymap_validity_func(rfd), &arg_keymap);
kmaps, SYSTEMD_DEFAULT_KEYMAP, 60, determine_keymap_validity_func(rfd), &arg_keymap);
}
static int process_keymap(int rfd) {
@ -578,7 +593,7 @@ static int prompt_timezone(int rfd) {
print_welcome(rfd);
r = prompt_loop("Please enter timezone name or number",
zones, 30, timezone_is_valid_log_debug, &arg_timezone);
zones, "UTC", 30, timezone_is_valid_log_debug, &arg_timezone);
if (r < 0)
return r;
@ -660,14 +675,24 @@ static int prompt_hostname(int rfd) {
putchar('\n');
for (;;) {
_cleanup_free_ char *h = NULL;
_cleanup_free_ char *h = NULL, *fallback = NULL;
r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET));
fallback = get_default_hostname();
if (!fallback)
return log_oom();
r = ask_string(&h, "%s Please enter hostname for new system (empty to default to \"%s\", \"-\" to skip): ",
special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), fallback);
if (r < 0)
return log_error_errno(r, "Failed to query hostname: %m");
if (isempty(h)) {
log_info("No hostname entered, skipping.");
log_info("No hostname entered, using default.");
return free_and_strdup_warn(&arg_hostname, fallback);
}
if (streq(h, "-")) {
log_info("Skip requested, continuing.");
break;
}
@ -869,12 +894,18 @@ static int prompt_root_shell(int rfd) {
for (;;) {
_cleanup_free_ char *s = NULL;
r = ask_string(&s, "%s Please enter root shell for new system (empty to skip): ", special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET));
r = ask_string(&s, "%s Please enter root shell for new system (empty to default to \"%s\", \"-\" to skip): ",
special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), DEFAULT_USER_SHELL);
if (r < 0)
return log_error_errno(r, "Failed to query root shell: %m");
if (isempty(s)) {
log_info("No shell entered, skipping.");
log_info("No shell entered, using default.");
return free_and_strdup_warn(&arg_root_shell, DEFAULT_USER_SHELL);
}
if (streq(s, "-")) {
log_info("Skip requested, continuing.");
break;
}