295 lines · c
1// SPDX-License-Identifier: GPL-2.02#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 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 30void 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 41int 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 48void 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 else64 cmds->names[j++] = cmds->names[i];65 }66 }67 cmds->cnt = j;68 while (j < i)69 cmds->names[j++] = NULL;70}71 72void 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 106static 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 TIOCGWINSZ120 if (ioctl(1, TIOCGWINSZ, ws) == 0 &&121 ws->ws_row && ws->ws_col)122 return;123#endif124 ws->ws_row = 25;125 ws->ws_col = 80;126}127 128static 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(&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 160static 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 171static 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(filename + len - extlen, ext, extlen);177}178 179static 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(de->d_name, prefix))200 continue;201 202 astrcat(&buf, de->d_name);203 if (!is_executable(buf))204 continue;205 206 entlen = strlen(de->d_name) - prefix_len;207 if (has_extension(de->d_name, ".exe"))208 entlen -= 4;209 210 add_cmdname(cmds, de->d_name + prefix_len, entlen);211 }212 closedir(dir);213 free(buf);214}215 216void 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(main_cmds, exec_path, prefix);225 qsort(main_cmds->names, main_cmds->cnt,226 sizeof(*main_cmds->names), cmdname_compare);227 uniq(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(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(other_cmds);248 }249 free(exec_path);250 exclude_cmds(other_cmds, main_cmds);251}252 253void 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('-', strlen(title) + strlen(exec_path));270 putchar('\n');271 pretty_print_string_list(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('-', strlen(title));280 putchar('\n');281 pretty_print_string_list(other_cmds, longest);282 putchar('\n');283 }284}285 286int 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