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 16void 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 30void 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 41int 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 48void 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 72void 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 106static 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 128static 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 160static 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 171static 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 179static 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 216void 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 253void 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 286int 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