1 // SPDX-License-Identifier: GPL-2.0 2 #include "cache.h" 3 #include "config.h" 4 #include <poll.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <subcmd/help.h> 8 #include "../builtin.h" 9 #include "levenshtein.h" 10 #include <linux/zalloc.h> 11 12 static int autocorrect; 13 14 static int perf_unknown_cmd_config(const char *var, const char *value, 15 void *cb __maybe_unused) 16 { 17 if (!strcmp(var, "help.autocorrect")) 18 return perf_config_int(&autocorrect, var,value); 19 20 return 0; 21 } 22 23 static int levenshtein_compare(const void *p1, const void *p2) 24 { 25 const struct cmdname *const *c1 = p1, *const *c2 = p2; 26 const char *s1 = (*c1)->name, *s2 = (*c2)->name; 27 int l1 = (*c1)->len; 28 int l2 = (*c2)->len; 29 return l1 != l2 ? l1 - l2 : strcmp(s1, s2); 30 } 31 32 static int add_cmd_list(struct cmdnames *cmds, struct cmdnames *old) 33 { 34 unsigned int i, nr = cmds->cnt + old->cnt; 35 void *tmp; 36 37 if (nr > cmds->alloc) { 38 /* Choose bigger one to alloc */ 39 if (alloc_nr(cmds->alloc) < nr) 40 cmds->alloc = nr; 41 else 42 cmds->alloc = alloc_nr(cmds->alloc); 43 tmp = realloc(cmds->names, cmds->alloc * sizeof(*cmds->names)); 44 if (!tmp) 45 return -1; 46 cmds->names = tmp; 47 } 48 for (i = 0; i < old->cnt; i++) 49 cmds->names[cmds->cnt++] = old->names[i]; 50 zfree(&old->names); 51 old->cnt = 0; 52 return 0; 53 } 54 55 const char *help_unknown_cmd(const char *cmd, struct cmdnames *main_cmds) 56 { 57 unsigned int i, n = 0, best_similarity = 0; 58 struct cmdnames other_cmds; 59 60 memset(&other_cmds, 0, sizeof(other_cmds)); 61 62 perf_config(perf_unknown_cmd_config, NULL); 63 64 load_command_list("perf-", main_cmds, &other_cmds); 65 66 if (add_cmd_list(main_cmds, &other_cmds) < 0) { 67 fprintf(stderr, "ERROR: Failed to allocate command list for unknown command.\n"); 68 goto end; 69 } 70 qsort(main_cmds->names, main_cmds->cnt, 71 sizeof(main_cmds->names), cmdname_compare); 72 uniq(main_cmds); 73 74 if (main_cmds->cnt) { 75 /* This reuses cmdname->len for similarity index */ 76 for (i = 0; i < main_cmds->cnt; ++i) { 77 main_cmds->names[i]->len = 78 levenshtein(cmd, main_cmds->names[i]->name, 79 /*swap_penalty=*/0, 80 /*substition_penality=*/2, 81 /*insertion_penality=*/1, 82 /*deletion_penalty=*/1); 83 } 84 qsort(main_cmds->names, main_cmds->cnt, 85 sizeof(*main_cmds->names), levenshtein_compare); 86 87 best_similarity = main_cmds->names[0]->len; 88 n = 1; 89 while (n < main_cmds->cnt && best_similarity == main_cmds->names[n]->len) 90 ++n; 91 } 92 93 if (autocorrect && n == 1) { 94 const char *assumed = main_cmds->names[0]->name; 95 96 main_cmds->names[0] = NULL; 97 clean_cmdnames(&other_cmds); 98 fprintf(stderr, "WARNING: You called a perf program named '%s', " 99 "which does not exist.\n" 100 "Continuing under the assumption that you meant '%s'\n", 101 cmd, assumed); 102 if (autocorrect > 0) { 103 fprintf(stderr, "in %0.1f seconds automatically...\n", 104 (float)autocorrect/10.0); 105 poll(NULL, 0, autocorrect * 100); 106 } 107 return assumed; 108 } 109 110 fprintf(stderr, "perf: '%s' is not a perf-command. See 'perf --help'.\n", cmd); 111 112 if (main_cmds->cnt && best_similarity < 6) { 113 fprintf(stderr, "\nDid you mean %s?\n", 114 n < 2 ? "this": "one of these"); 115 116 for (i = 0; i < n; i++) 117 fprintf(stderr, "\t%s\n", main_cmds->names[i]->name); 118 } 119 end: 120 clean_cmdnames(&other_cmds); 121 return NULL; 122 } 123