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