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