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(ws: &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(p: filename + len - extlen, q: ext, size: 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(str: de->d_name, prefix)) |
200 | continue; |
201 | |
202 | astrcat(out: &buf, add: de->d_name); |
203 | if (!is_executable(name: buf)) |
204 | continue; |
205 | |
206 | entlen = strlen(de->d_name) - prefix_len; |
207 | if (has_extension(filename: de->d_name, ext: ".exe" )) |
208 | entlen -= 4; |
209 | |
210 | add_cmdname(cmds, name: de->d_name + prefix_len, 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(cmds: main_cmds, path: exec_path, prefix); |
225 | qsort(main_cmds->names, main_cmds->cnt, |
226 | sizeof(*main_cmds->names), cmdname_compare); |
227 | uniq(cmds: 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(cmds: 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(cmds: other_cmds); |
248 | } |
249 | free(exec_path); |
250 | exclude_cmds(cmds: other_cmds, excludes: 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(c: '-', strlen(title) + strlen(exec_path)); |
270 | putchar('\n'); |
271 | pretty_print_string_list(cmds: 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(c: '-', strlen(title)); |
280 | putchar('\n'); |
281 | pretty_print_string_list(cmds: 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 | |