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