1 // SPDX-License-Identifier: GPL-2.0
5 #include <linux/string.h>
12 #include "subcmd-util.h"
16 void add_cmdname(struct cmdnames
*cmds
, const char *name
, size_t len
)
18 struct cmdname
*ent
= malloc(sizeof(*ent
) + len
+ 1);
23 memcpy(ent
->name
, name
, len
);
26 ALLOC_GROW(cmds
->names
, cmds
->cnt
+ 1, cmds
->alloc
);
27 cmds
->names
[cmds
->cnt
++] = ent
;
30 void clean_cmdnames(struct cmdnames
*cmds
)
34 for (i
= 0; i
< cmds
->cnt
; ++i
)
35 zfree(&cmds
->names
[i
]);
41 int cmdname_compare(const void *a_
, const void *b_
)
43 struct cmdname
*a
= *(struct cmdname
**)a_
;
44 struct cmdname
*b
= *(struct cmdname
**)b_
;
45 return strcmp(a
->name
, b
->name
);
48 void uniq(struct cmdnames
*cmds
)
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]);
59 for (i
= 0, j
= 0; i
< cmds
->cnt
; i
++) {
64 cmds
->names
[j
++] = cmds
->names
[i
];
69 cmds
->names
[j
++] = NULL
;
72 void exclude_cmds(struct cmdnames
*cmds
, struct cmdnames
*excludes
)
78 while (ci
< cmds
->cnt
&& ei
< excludes
->cnt
) {
79 cmp
= strcmp(cmds
->names
[ci
]->name
, excludes
->names
[ei
]->name
);
85 zfree(&cmds
->names
[cj
]);
86 cmds
->names
[cj
++] = cmds
->names
[ci
++];
88 } else if (cmp
== 0) {
96 while (ci
< cmds
->cnt
) {
97 zfree(&cmds
->names
[cj
]);
98 cmds
->names
[cj
++] = cmds
->names
[ci
++];
101 for (ci
= cj
; ci
< cmds
->cnt
; ci
++)
102 zfree(&cmds
->names
[ci
]);
106 static void get_term_dimensions(struct winsize
*ws
)
108 char *s
= getenv("LINES");
111 ws
->ws_row
= atoi(s
);
112 s
= getenv("COLUMNS");
114 ws
->ws_col
= atoi(s
);
115 if (ws
->ws_row
&& ws
->ws_col
)
120 if (ioctl(1, TIOCGWINSZ
, ws
) == 0 &&
121 ws
->ws_row
&& ws
->ws_col
)
128 static void pretty_print_string_list(struct cmdnames
*cmds
, int longest
)
131 int space
= longest
+ 1; /* min 1 SP between words */
136 get_term_dimensions(&win
);
137 max_cols
= win
.ws_col
- 1; /* don't print *on* the edge */
139 if (space
< max_cols
)
140 cols
= max_cols
/ space
;
141 rows
= (cmds
->cnt
+ cols
- 1) / cols
;
143 for (i
= 0; i
< rows
; i
++) {
146 for (j
= 0; j
< cols
; j
++) {
147 unsigned int n
= j
* rows
+ i
;
148 unsigned int size
= space
;
152 if (j
== cols
-1 || n
+ rows
>= cmds
->cnt
)
154 printf("%-*s", size
, cmds
->names
[n
]->name
);
160 static int is_executable(const char *name
)
164 if (stat(name
, &st
) || /* stat, not lstat */
165 !S_ISREG(st
.st_mode
))
168 return st
.st_mode
& S_IXUSR
;
171 static int has_extension(const char *filename
, const char *ext
)
173 size_t len
= strlen(filename
);
174 size_t extlen
= strlen(ext
);
176 return len
> extlen
&& !memcmp(filename
+ len
- extlen
, ext
, extlen
);
179 static void list_commands_in_dir(struct cmdnames
*cmds
,
184 DIR *dir
= opendir(path
);
192 prefix_len
= strlen(prefix
);
194 astrcatf(&buf
, "%s/", path
);
196 while ((de
= readdir(dir
)) != NULL
) {
199 if (!strstarts(de
->d_name
, prefix
))
202 astrcat(&buf
, de
->d_name
);
203 if (!is_executable(buf
))
206 entlen
= strlen(de
->d_name
) - prefix_len
;
207 if (has_extension(de
->d_name
, ".exe"))
210 add_cmdname(cmds
, de
->d_name
+ prefix_len
, entlen
);
216 void load_command_list(const char *prefix
,
217 struct cmdnames
*main_cmds
,
218 struct cmdnames
*other_cmds
)
220 const char *env_path
= getenv("PATH");
221 char *exec_path
= get_argv_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
);
231 char *paths
, *path
, *colon
;
232 path
= paths
= strdup(env_path
);
234 if ((colon
= strchr(path
, ':')))
236 if (!exec_path
|| strcmp(path
, exec_path
))
237 list_commands_in_dir(other_cmds
, path
, prefix
);
245 qsort(other_cmds
->names
, other_cmds
->cnt
,
246 sizeof(*other_cmds
->names
), cmdname_compare
);
250 exclude_cmds(other_cmds
, main_cmds
);
253 void list_commands(const char *title
, struct cmdnames
*main_cmds
,
254 struct cmdnames
*other_cmds
)
256 unsigned int i
, longest
= 0;
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
;
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
));
271 pretty_print_string_list(main_cmds
, longest
);
276 if (other_cmds
->cnt
) {
277 printf("%s available from elsewhere on your $PATH\n", title
);
278 printf("---------------------------------------");
279 mput_char('-', strlen(title
));
281 pretty_print_string_list(other_cmds
, longest
);
286 int is_in_cmdlist(struct cmdnames
*c
, const char *s
)
290 for (i
= 0; i
< c
->cnt
; i
++)
291 if (!strcmp(s
, c
->names
[i
]->name
))