1 // SPDX-License-Identifier: GPL-2.0 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <linux/string.h> 6 #include <termios.h> 7 #include <sys/ioctl.h> 8 #include <sys/types.h> 9 #include <sys/stat.h> 10 #include <unistd.h> 11 #include <dirent.h> 12 #include "subcmd-util.h" 13 #include "help.h" 14 #include "exec-cmd.h" 15 16 void add_cmdname(struct cmdnames *cmds, const char *name, size_t len) 17 { 18 struct cmdname *ent = malloc(sizeof(*ent) + len + 1); 19 if (!ent) 20 return; 21 22 ent->len = len; 23 memcpy(ent->name, name, len); 24 ent->name[len] = 0; 25 26 ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc); 27 cmds->names[cmds->cnt++] = ent; 28 } 29 30 void clean_cmdnames(struct cmdnames *cmds) 31 { 32 unsigned int i; 33 34 for (i = 0; i < cmds->cnt; ++i) 35 zfree(&cmds->names[i]); 36 zfree(&cmds->names); 37 cmds->cnt = 0; 38 cmds->alloc = 0; 39 } 40 41 int cmdname_compare(const void *a_, const void *b_) 42 { 43 struct cmdname *a = *(struct cmdname **)a_; 44 struct cmdname *b = *(struct cmdname **)b_; 45 return strcmp(a->name, b->name); 46 } 47 48 void uniq(struct cmdnames *cmds) 49 { 50 unsigned int i, j; 51 52 if (!cmds->cnt) 53 return; 54 55 for (i = 1; i < cmds->cnt; i++) { 56 if (!strcmp(cmds->names[i]->name, cmds->names[i-1]->name)) 57 zfree(&cmds->names[i - 1]); 58 } 59 for (i = 0, j = 0; i < cmds->cnt; i++) { 60 if (cmds->names[i]) { 61 if (i == j) 62 j++; 63 else 64 cmds->names[j++] = cmds->names[i]; 65 } 66 } 67 cmds->cnt = j; 68 while (j < i) 69 cmds->names[j++] = NULL; 70 } 71 72 void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes) 73 { 74 size_t ci, cj, ei; 75 int cmp; 76 77 ci = cj = ei = 0; 78 while (ci < cmds->cnt && ei < excludes->cnt) { 79 cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name); 80 if (cmp < 0) { 81 if (ci == cj) { 82 ci++; 83 cj++; 84 } else { 85 zfree(&cmds->names[cj]); 86 cmds->names[cj++] = cmds->names[ci++]; 87 } 88 } else if (cmp == 0) { 89 ci++; 90 ei++; 91 } else if (cmp > 0) { 92 ei++; 93 } 94 } 95 if (ci != cj) { 96 while (ci < cmds->cnt) { 97 zfree(&cmds->names[cj]); 98 cmds->names[cj++] = cmds->names[ci++]; 99 } 100 } 101 for (ci = cj; ci < cmds->cnt; ci++) 102 zfree(&cmds->names[ci]); 103 cmds->cnt = cj; 104 } 105 106 static void get_term_dimensions(struct winsize *ws) 107 { 108 char *s = getenv("LINES"); 109 110 if (s != NULL) { 111 ws->ws_row = atoi(s); 112 s = getenv("COLUMNS"); 113 if (s != NULL) { 114 ws->ws_col = atoi(s); 115 if (ws->ws_row && ws->ws_col) 116 return; 117 } 118 } 119 #ifdef TIOCGWINSZ 120 if (ioctl(1, TIOCGWINSZ, ws) == 0 && 121 ws->ws_row && ws->ws_col) 122 return; 123 #endif 124 ws->ws_row = 25; 125 ws->ws_col = 80; 126 } 127 128 static void pretty_print_string_list(struct cmdnames *cmds, int longest) 129 { 130 int cols = 1, rows; 131 int space = longest + 1; /* min 1 SP between words */ 132 struct winsize win; 133 int max_cols; 134 int i, j; 135 136 get_term_dimensions(&win); 137 max_cols = win.ws_col - 1; /* don't print *on* the edge */ 138 139 if (space < max_cols) 140 cols = max_cols / space; 141 rows = (cmds->cnt + cols - 1) / cols; 142 143 for (i = 0; i < rows; i++) { 144 printf(" "); 145 146 for (j = 0; j < cols; j++) { 147 unsigned int n = j * rows + i; 148 unsigned int size = space; 149 150 if (n >= cmds->cnt) 151 break; 152 if (j == cols-1 || n + rows >= cmds->cnt) 153 size = 1; 154 printf("%-*s", size, cmds->names[n]->name); 155 } 156 putchar('\n'); 157 } 158 } 159 160 static int is_executable(const char *name) 161 { 162 struct stat st; 163 164 if (stat(name, &st) || /* stat, not lstat */ 165 !S_ISREG(st.st_mode)) 166 return 0; 167 168 return st.st_mode & S_IXUSR; 169 } 170 171 static int has_extension(const char *filename, const char *ext) 172 { 173 size_t len = strlen(filename); 174 size_t extlen = strlen(ext); 175 176 return len > extlen && !memcmp(filename + len - extlen, ext, extlen); 177 } 178 179 static void list_commands_in_dir(struct cmdnames *cmds, 180 const char *path, 181 const char *prefix) 182 { 183 int prefix_len; 184 DIR *dir = opendir(path); 185 struct dirent *de; 186 char *buf = NULL; 187 188 if (!dir) 189 return; 190 if (!prefix) 191 prefix = "perf-"; 192 prefix_len = strlen(prefix); 193 194 astrcatf(&buf, "%s/", path); 195 196 while ((de = readdir(dir)) != NULL) { 197 int entlen; 198 199 if (!strstarts(de->d_name, prefix)) 200 continue; 201 202 astrcat(&buf, de->d_name); 203 if (!is_executable(buf)) 204 continue; 205 206 entlen = strlen(de->d_name) - prefix_len; 207 if (has_extension(de->d_name, ".exe")) 208 entlen -= 4; 209 210 add_cmdname(cmds, de->d_name + prefix_len, entlen); 211 } 212 closedir(dir); 213 free(buf); 214 } 215 216 void load_command_list(const char *prefix, 217 struct cmdnames *main_cmds, 218 struct cmdnames *other_cmds) 219 { 220 const char *env_path = getenv("PATH"); 221 char *exec_path = get_argv_exec_path(); 222 223 if (exec_path) { 224 list_commands_in_dir(main_cmds, exec_path, prefix); 225 qsort(main_cmds->names, main_cmds->cnt, 226 sizeof(*main_cmds->names), cmdname_compare); 227 uniq(main_cmds); 228 } 229 230 if (env_path) { 231 char *paths, *path, *colon; 232 path = paths = strdup(env_path); 233 while (1) { 234 if ((colon = strchr(path, ':'))) 235 *colon = 0; 236 if (!exec_path || strcmp(path, exec_path)) 237 list_commands_in_dir(other_cmds, path, prefix); 238 239 if (!colon) 240 break; 241 path = colon + 1; 242 } 243 free(paths); 244 245 qsort(other_cmds->names, other_cmds->cnt, 246 sizeof(*other_cmds->names), cmdname_compare); 247 uniq(other_cmds); 248 } 249 free(exec_path); 250 exclude_cmds(other_cmds, main_cmds); 251 } 252 253 void list_commands(const char *title, struct cmdnames *main_cmds, 254 struct cmdnames *other_cmds) 255 { 256 unsigned int i, longest = 0; 257 258 for (i = 0; i < main_cmds->cnt; i++) 259 if (longest < main_cmds->names[i]->len) 260 longest = main_cmds->names[i]->len; 261 for (i = 0; i < other_cmds->cnt; i++) 262 if (longest < other_cmds->names[i]->len) 263 longest = other_cmds->names[i]->len; 264 265 if (main_cmds->cnt) { 266 char *exec_path = get_argv_exec_path(); 267 printf("available %s in '%s'\n", title, exec_path); 268 printf("----------------"); 269 mput_char('-', strlen(title) + strlen(exec_path)); 270 putchar('\n'); 271 pretty_print_string_list(main_cmds, longest); 272 putchar('\n'); 273 free(exec_path); 274 } 275 276 if (other_cmds->cnt) { 277 printf("%s available from elsewhere on your $PATH\n", title); 278 printf("---------------------------------------"); 279 mput_char('-', strlen(title)); 280 putchar('\n'); 281 pretty_print_string_list(other_cmds, longest); 282 putchar('\n'); 283 } 284 } 285 286 int is_in_cmdlist(struct cmdnames *c, const char *s) 287 { 288 unsigned int i; 289 290 for (i = 0; i < c->cnt; i++) 291 if (!strcmp(s, c->names[i]->name)) 292 return 1; 293 return 0; 294 } 295