MINOR: cfgparse/server: try to fix spelling mistakes on server lines
Let's apply the fuzzy match to server keywords so that we can avoid
dumping the huge list of supported keywords each time there is a spelling
mistake, and suggest proper spelling instead:
$ printf "listen f\nserver s 0 sendpx-v2\n" | ./haproxy -c -f /dev/stdin
[NOTICE] 070/095718 (24152) : haproxy version is 2.4-dev11-caa6e3-25
[NOTICE] 070/095718 (24152) : path to executable is ./haproxy
[ALERT] 070/095718 (24152) : parsing [/dev/stdin:2] : 'server s' unknown keyword 'sendpx-v2'; did you mean 'send-proxy-v2' maybe ?
[ALERT] 070/095718 (24152) : Error(s) found in configuration file : /dev/stdin
[ALERT] 070/095718 (24152) : Fatal errors found in configuration.
diff --git a/src/server.c b/src/server.c
index 8eb659d..8b2dd4f 100644
--- a/src/server.c
+++ b/src/server.c
@@ -50,6 +50,19 @@
static int srv_apply_lastaddr(struct server *srv, int *err_code);
static void srv_cleanup_connections(struct server *srv);
+/* some keywords that are still being parsed using strcmp() and are not
+ * registered anywhere. They are used as suggestions for mistyped words.
+ */
+static const char *common_kw_list[] = {
+ "init-addr", "resolvers", "resolve-opts", "resolve-prefer", "ipv4",
+ "ipv6", "resolve-net", "weight", "log-proto", "legacy", "octet-count",
+ "minconn", "maxconn", "maxqueue", "slowstart", "on-error", "fastinter",
+ "fail-check", "sudden-death", "mark-down", "on-marked-down",
+ "shutdown-sessions", "on-marked-up", "shutdown-backup-sessions",
+ "error-limit", "usesrc",
+ NULL /* must be last */
+};
+
/* List head of all known server keywords */
static struct srv_kw_list srv_keywords = {
.list = LIST_HEAD_INIT(srv_keywords.list)
@@ -285,6 +298,50 @@
}
}
}
+}
+
+/* Try to find in srv_keyword the word that looks closest to <word> by counting
+ * transitions between letters, digits and other characters. Will return the
+ * best matching word if found, otherwise NULL. An optional array of extra
+ * words to compare may be passed in <extra>, but it must then be terminated
+ * by a NULL entry. If unused it may be NULL.
+ */
+static const char *srv_find_best_kw(const char *word)
+{
+ uint8_t word_sig[1024];
+ uint8_t list_sig[1024];
+ const struct srv_kw_list *kwl;
+ const char *best_ptr = NULL;
+ int dist, best_dist = INT_MAX;
+ const char **extra;
+ int index;
+
+ make_word_fingerprint(word_sig, word);
+ list_for_each_entry(kwl, &srv_keywords.list, list) {
+ for (index = 0; kwl->kw[index].kw != NULL; index++) {
+ make_word_fingerprint(list_sig, kwl->kw[index].kw);
+ dist = word_fingerprint_distance(word_sig, list_sig);
+ if (dist < best_dist) {
+ best_dist = dist;
+ best_ptr = kwl->kw[index].kw;
+ }
+ }
+ }
+
+ for (extra = common_kw_list; *extra; extra++) {
+ make_word_fingerprint(list_sig, *extra);
+ dist = word_fingerprint_distance(word_sig, list_sig);
+ if (dist < best_dist) {
+ best_dist = dist;
+ best_ptr = *extra;
+ }
+ extra++;
+ }
+
+ if (best_dist > 2 * strlen(word) || (best_ptr && best_dist > 2 * strlen(best_ptr)))
+ best_ptr = NULL;
+
+ return best_ptr;
}
/* Parse the "backup" server keyword */
@@ -2394,9 +2451,8 @@
goto out;
}
else {
- static int srv_dumped;
struct srv_kw *kw;
- char *err;
+ const char *best;
kw = srv_find_kw(args[cur_arg]);
if (kw) {
@@ -2439,17 +2495,13 @@
continue;
}
- err = NULL;
- if (!srv_dumped) {
- srv_dump_kws(&err);
- indent_msg(&err, 4);
- srv_dumped = 1;
- }
-
- ha_alert("parsing [%s:%d] : '%s %s' unknown keyword '%s'.%s%s\n",
- file, linenum, args[0], args[1], args[cur_arg],
- err ? " Registered keywords :" : "", err ? err : "");
- free(err);
+ best = srv_find_best_kw(args[cur_arg]);
+ if (best)
+ ha_alert("parsing [%s:%d] : '%s %s' unknown keyword '%s'; did you mean '%s' maybe ?\n",
+ file, linenum, args[0], args[1], args[cur_arg], best);
+ else
+ ha_alert("parsing [%s:%d] : '%s %s' unknown keyword '%s'.\n",
+ file, linenum, args[0], args[1], args[cur_arg]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;