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++, 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
  82static 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
 104static 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 */
 108        struct winsize win;
 109        int max_cols;
 110        int i, j;
 111
 112        get_term_dimensions(&win);
 113        max_cols = win.ws_col - 1; /* don't print *on* the edge */
 114
 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++) {
 123                        unsigned int n = j * rows + i;
 124                        unsigned int size = space;
 125
 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
 136static 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
 144        return st.st_mode & S_IXUSR;
 145}
 146
 147static 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
 155static 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;
 162        char *buf = NULL;
 163
 164        if (!dir)
 165                return;
 166        if (!prefix)
 167                prefix = "perf-";
 168        prefix_len = strlen(prefix);
 169
 170        astrcatf(&buf, "%s/", path);
 171
 172        while ((de = readdir(dir)) != NULL) {
 173                int entlen;
 174
 175                if (!strstarts(de->d_name, prefix))
 176                        continue;
 177
 178                astrcat(&buf, de->d_name);
 179                if (!is_executable(buf))
 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);
 189        free(buf);
 190}
 191
 192void load_command_list(const char *prefix,
 193                struct cmdnames *main_cmds,
 194                struct cmdnames *other_cmds)
 195{
 196        const char *env_path = getenv("PATH");
 197        char *exec_path = get_argv_exec_path();
 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) {
 210                        if ((colon = strchr(path, ':')))
 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        }
 225        free(exec_path);
 226        exclude_cmds(other_cmds, main_cmds);
 227}
 228
 229void list_commands(const char *title, struct cmdnames *main_cmds,
 230                   struct cmdnames *other_cmds)
 231{
 232        unsigned int i, longest = 0;
 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) {
 242                char *exec_path = get_argv_exec_path();
 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');
 249                free(exec_path);
 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
 262int is_in_cmdlist(struct cmdnames *c, const char *s)
 263{
 264        unsigned int i;
 265
 266        for (i = 0; i < c->cnt; i++)
 267                if (!strcmp(s, c->names[i]->name))
 268                        return 1;
 269        return 0;
 270}
 271