diff --git a/src/basic/strv.c b/src/basic/strv.c index 6c71f990c8..5b71c36dbf 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -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); diff --git a/src/basic/strv.h b/src/basic/strv.h index d38d5bf538..b7845eeecc 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -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_; diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index f4601c0210..3899835d4c 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -187,6 +187,7 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i 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): ", @@ -219,12 +220,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); } }