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