busybox/miscutils/man.c
<<
>>
Prefs
   1/* mini man implementation for busybox
   2 * Copyright (C) 2008 Denys Vlasenko <vda.linux@googlemail.com>
   3 * Licensed under GPLv2, see file LICENSE in this source tree.
   4 */
   5//config:config MAN
   6//config:       bool "man (26 kb)"
   7//config:       default y
   8//config:       help
   9//config:       Format and display manual pages.
  10
  11//applet:IF_MAN(APPLET(man, BB_DIR_USR_BIN, BB_SUID_DROP))
  12
  13//kbuild:lib-$(CONFIG_MAN) += man.o
  14
  15//usage:#define man_trivial_usage
  16//usage:       "[-aw] [SECTION] MANPAGE[.SECTION]..."
  17//usage:#define man_full_usage "\n\n"
  18//usage:       "Display manual page\n"
  19//usage:     "\n        -a      Display all pages"
  20//usage:     "\n        -w      Show page locations"
  21//usage:     "\n"
  22//usage:     "\n$COLUMNS overrides output width"
  23
  24#include "libbb.h"
  25#include "common_bufsiz.h"
  26
  27enum {
  28        OPT_a = 1, /* all */
  29        OPT_w = 2, /* print path */
  30};
  31
  32/* This is what I see on my desktop system being executed:
  33(
  34echo ".ll 12.4i"
  35echo ".nr LL 12.4i"
  36echo ".pl 1100i"
  37gunzip -c '/usr/man/man1/bzip2.1.gz'
  38echo ".\\\""
  39echo ".pl \n(nlu+10"
  40) | gtbl | nroff -Tlatin1 -mandoc | less
  41
  42Some systems use -Tascii.
  43
  44On another system I see this:
  45
  46... | tbl | nroff -mandoc -rLL=<NNN>n -rLT=<NNN>n -Tutf8 | less
  47
  48where <NNN> is screen width minus 5.
  49Replacing "DEFINE nroff nroff -mandoc" in /etc/man_db.conf
  50changes "nroff -mandoc" part; -rLL=<NNN>n, -rLT=<NNN>n and -Tutf8 parts are
  51appended to the user-specified command.
  52
  53Redirecting to a pipe or file sets GROFF_NO_SGR=1 to prevent color escapes,
  54and uses "col -b -p -x" instead of pager, this filters out backspace
  55and underscore tricks.
  56*/
  57
  58struct globals {
  59        const char *col;
  60        const char *tbl;
  61        const char *nroff;
  62        const char *pager;
  63} FIX_ALIASING;
  64#define G (*(struct globals*)bb_common_bufsiz1)
  65#define INIT_G() do { \
  66        setup_common_bufsiz(); \
  67        G.col = "col"; \
  68        G.tbl = "tbl"; \
  69        /* Removed -Tlatin1. Assuming system nroff has suitable default */ \
  70        G.nroff = "nroff -mandoc"; \
  71        G.pager = ENABLE_LESS ? "less" : "more"; \
  72} while (0)
  73
  74static int show_manpage(char *man_filename, int man, int level);
  75
  76static int run_pipe(char *man_filename, int man, int level)
  77{
  78        char *cmd;
  79
  80        /* Prevent man page link loops */
  81        if (level > 10)
  82                return 0;
  83
  84        if (access(man_filename, R_OK) != 0)
  85                return 0;
  86
  87        if (option_mask32 & OPT_w) {
  88                puts(man_filename);
  89                return 1;
  90        }
  91
  92        if (man) { /* man page, not cat page */
  93                /* Is this a link to another manpage? */
  94                /* The link has the following on the first line: */
  95                /* ".so another_man_page" */
  96
  97                struct stat sb;
  98                char *line;
  99                char *linkname, *p;
 100
 101                /* On my system:
 102                 * man1/genhostid.1.gz: 203 bytes - smallest real manpage
 103                 * man2/path_resolution.2.gz: 114 bytes - largest link
 104                 */
 105                xstat(man_filename, &sb);
 106                if (sb.st_size > 300) /* err on the safe side */
 107                        goto ordinary_manpage;
 108
 109                line = xmalloc_open_zipped_read_close(man_filename, NULL);
 110                if (!line || !is_prefixed_with(line, ".so ")) {
 111                        free(line);
 112                        goto ordinary_manpage;
 113                }
 114                /* Example: man2/path_resolution.2.gz contains
 115                 * ".so man7/path_resolution.7\n<junk>"
 116                 */
 117                *strchrnul(line, '\n') = '\0';
 118                linkname = skip_whitespace(&line[4]);
 119
 120                /* If link has no slashes, we just replace man page name.
 121                 * If link has slashes (however many), we go back *once*.
 122                 * ".so zzz/ggg/page.3" does NOT go back two levels. */
 123                p = strrchr(man_filename, '/');
 124                if (!p)
 125                        goto ordinary_manpage;
 126                *p = '\0';
 127                if (strchr(linkname, '/')) {
 128                        p = strrchr(man_filename, '/');
 129                        if (!p)
 130                                goto ordinary_manpage;
 131                        *p = '\0';
 132                }
 133
 134                /* Links do not have .gz extensions, even if manpage
 135                 * is compressed */
 136                man_filename = xasprintf("%s/%s", man_filename, linkname);
 137                free(line);
 138                /* Note: we leak "new" man_filename string as well... */
 139                if (show_manpage(man_filename, man, level + 1))
 140                        return 1;
 141                /* else: show the link, it's better than nothing */
 142        }
 143
 144 ordinary_manpage:
 145        close(STDIN_FILENO);
 146        open_zipped(man_filename, /*fail_if_not_compressed:*/ 0); /* guaranteed to use fd 0 (STDIN_FILENO) */
 147        if (man) {
 148                int w = get_terminal_width(-1);
 149                if (w > 10)
 150                        w -= 2;
 151                /* "2>&1" is added so that nroff errors are shown in pager too.
 152                 * Otherwise it may show just empty screen.
 153                 */
 154                cmd = xasprintf("%s | %s -rLL=%un -rLT=%un 2>&1 | %s",
 155                                G.tbl, G.nroff, w, w,
 156                                G.pager);
 157        } else {
 158                cmd = xstrdup(G.pager);
 159        }
 160        system(cmd);
 161        free(cmd);
 162        return 1;
 163}
 164
 165/* man_filename is of the form "/dir/dir/dir/name.s" */
 166static int show_manpage(char *man_filename, int man, int level)
 167{
 168#if SEAMLESS_COMPRESSION
 169        /* We leak this allocation... */
 170        char *filename_with_zext = xasprintf("%s.lzma", man_filename);
 171        char *ext = strrchr(filename_with_zext, '.') + 1;
 172#endif
 173
 174#if ENABLE_FEATURE_SEAMLESS_LZMA
 175        if (run_pipe(filename_with_zext, man, level))
 176                return 1;
 177#endif
 178#if ENABLE_FEATURE_SEAMLESS_XZ
 179        strcpy(ext, "xz");
 180        if (run_pipe(filename_with_zext, man, level))
 181                return 1;
 182#endif
 183#if ENABLE_FEATURE_SEAMLESS_BZ2
 184        strcpy(ext, "bz2");
 185        if (run_pipe(filename_with_zext, man, level))
 186                return 1;
 187#endif
 188#if ENABLE_FEATURE_SEAMLESS_GZ
 189        strcpy(ext, "gz");
 190        if (run_pipe(filename_with_zext, man, level))
 191                return 1;
 192#endif
 193
 194        return run_pipe(man_filename, man, level);
 195}
 196
 197static char **add_MANPATH(char **man_path_list, int *count_mp, char *path)
 198{
 199        if (path) while (*path) {
 200                char *next_path;
 201                char **path_element;
 202
 203                next_path = strchr(path, ':');
 204                if (next_path) {
 205                        if (next_path == path) /* "::"? */
 206                                goto next;
 207                        *next_path = '\0';
 208                }
 209                /* Do we already have path? */
 210                path_element = man_path_list;
 211                if (path_element) while (*path_element) {
 212                        if (strcmp(*path_element, path) == 0) {
 213                                goto skip;
 214                        }
 215                        path_element++;
 216                }
 217                man_path_list = xrealloc_vector(man_path_list, 4, *count_mp);
 218                man_path_list[*count_mp] = xstrdup(path);
 219                (*count_mp)++;
 220                /* man_path_list is NULL terminated */
 221                /* man_path_list[*count_mp] = NULL; - xrealloc_vector did it */
 222 skip:
 223                if (!next_path)
 224                        break;
 225                /* "path" may be a result of getenv(), be nice and don't mangle it */
 226                *next_path = ':';
 227 next:
 228                path = next_path + 1;
 229        }
 230        return man_path_list;
 231}
 232
 233static const char *if_redefined(const char *var, const char *key, const char *line)
 234{
 235        if (!is_prefixed_with(line, key))
 236                return var;
 237        line += strlen(key);
 238        if (!isspace(line[0]))
 239                return var;
 240        return xstrdup(skip_whitespace(line));
 241}
 242
 243static int is_section_name(const char *sections, const char *str)
 244{
 245        const char *s = strstr(sections, str);
 246        if (s) {
 247                int l = strlen(str);
 248                return (s[l] == ':' || s[l] == '\0');
 249        }
 250        return 0;
 251}
 252
 253int man_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 254int man_main(int argc UNUSED_PARAM, char **argv)
 255{
 256        parser_t *parser;
 257        char *conf_sec_list;
 258        char *sec_list;
 259        char **man_path_list;
 260        int count_mp;
 261        int opt, not_found;
 262        char *token[2];
 263
 264        INIT_G();
 265
 266        opt = getopt32(argv, "^+" "aw" "\0" "-1"/*at least one arg*/);
 267        argv += optind;
 268
 269        conf_sec_list = xstrdup("0p:1:1p:2:3:3p:4:5:6:7:8:9");
 270
 271        count_mp = 0;
 272        man_path_list = add_MANPATH(NULL, &count_mp,
 273                        getenv("MANDATORY_MANPATH"+10) /* "MANPATH" */
 274        );
 275        /* Parse man.conf[ig] or man_db.conf */
 276        /* man version 1.6f uses man.config */
 277        /* man-db implementation of man uses man_db.conf */
 278        parser = config_open2("/etc/man.config", fopen_for_read);
 279        if (!parser)
 280                parser = config_open2("/etc/man.conf", fopen_for_read);
 281        if (!parser)
 282                parser = config_open2("/etc/man_db.conf", fopen_for_read);
 283
 284        while (config_read(parser, token, 2, 0, "# \t", PARSE_NORMAL)) {
 285                if (!token[1])
 286                        continue;
 287                if (strcmp("DEFINE", token[0]) == 0) {
 288                        G.col   = if_redefined(G.col  , "col",   token[1]);
 289                        G.tbl   = if_redefined(G.tbl  , "tbl",   token[1]);
 290                        G.nroff = if_redefined(G.nroff, "nroff", token[1]);
 291                        G.pager = if_redefined(G.pager, "pager", token[1]);
 292                } else
 293                if (strcmp("MANDATORY_MANPATH"+10, token[0]) == 0 /* "MANPATH"? */
 294                 || strcmp("MANDATORY_MANPATH", token[0]) == 0
 295                ) {
 296                        man_path_list = add_MANPATH(man_path_list, &count_mp, token[1]);
 297                }
 298                if (strcmp("MANSECT", token[0]) == 0) {
 299                        free(conf_sec_list);
 300                        conf_sec_list = xstrdup(token[1]);
 301                }
 302        }
 303        config_close(parser);
 304
 305        if (!man_path_list) {
 306                static const char *const mpl[] = { "/usr/man", "/usr/share/man", NULL };
 307                man_path_list = (char**)mpl;
 308                /*count_mp = 2; - not used below anyway */
 309        }
 310
 311        {
 312                /* environment overrides setting from man.config */
 313                char *env_pager = getenv("MANPAGER");
 314                if (!env_pager)
 315                        env_pager = getenv("PAGER");
 316                if (env_pager)
 317                        G.pager = env_pager;
 318        }
 319
 320        if (!isatty(STDOUT_FILENO)) {
 321                putenv((char*)"GROFF_NO_SGR=1");
 322                G.pager = xasprintf("%s -b -p -x", G.col);
 323        }
 324
 325        /* is 1st ARG a SECTION? */
 326        sec_list = conf_sec_list;
 327        if (is_section_name(conf_sec_list, *argv) && argv[1]) {
 328                /* yes */
 329                sec_list = *argv++;
 330        }
 331
 332        not_found = 0;
 333        do { /* for each argv[] */
 334                const char *cur_path;
 335                int cur_mp;
 336                int found = 0;
 337
 338                if (strchr(*argv, '/')) {
 339                        found = show_manpage(*argv, /*man:*/ 1, 0);
 340                        goto check_found;
 341                }
 342
 343                /* for each MANPATH */
 344                cur_mp = 0;
 345                while ((cur_path = man_path_list[cur_mp++]) != NULL) {
 346                        const char *cur_sect = sec_list;
 347
 348                        /* is MANPAGE of the form NAME.SECTION? */
 349                        char *sect_ext = strrchr(*argv, '.');
 350                        if (sect_ext && is_section_name(conf_sec_list, sect_ext + 1)) {
 351                                *sect_ext = '\0';
 352                                cur_sect = sect_ext + 1;
 353                        }
 354
 355                        do { /* for each SECTION in cur_sect */
 356                                char *next_sect = strchrnul(cur_sect, ':');
 357                                int sect_len = next_sect - cur_sect;
 358                                char *man_filename;
 359                                int cat0man1 = 0;
 360
 361                                /* Search for cat, then man page */
 362                                while (cat0man1 < 2) {
 363                                        int found_here;
 364                                        man_filename = xasprintf("%s/%s%.*s/%s.%.*s",
 365                                                        cur_path,
 366                                                        "cat\0man" + (cat0man1 * 4),
 367                                                        sect_len, cur_sect,
 368                                                        *argv,
 369                                                        sect_len, cur_sect);
 370                                        found_here = show_manpage(man_filename, cat0man1, 0);
 371                                        found |= found_here;
 372                                        cat0man1 += found_here + 1;
 373                                        free(man_filename);
 374                                }
 375
 376                                if (found && !(opt & OPT_a))
 377                                        goto next_arg;
 378                                cur_sect = next_sect;
 379                                while (*cur_sect == ':')
 380                                        cur_sect++;
 381                        } while (*cur_sect);
 382
 383                        if (sect_ext) *sect_ext = '.';
 384                }
 385 check_found:
 386                if (!found) {
 387                        bb_error_msg("no manual entry for '%s'", *argv);
 388                        not_found = 1;
 389                }
 390 next_arg:
 391                argv++;
 392        } while (*argv);
 393
 394        return not_found;
 395}
 396