diff options
author | Colin Watson <[email protected]> | 2021-12-31 18:15:08 +0000 |
---|---|---|
committer | Colin Watson <[email protected]> | 2021-12-31 18:15:08 +0000 |
commit | 5b273ae106744bbe93341d6ad3aac1980ac97b51 (patch) | |
tree | 611a6d9fc4fd524f8a69e9d54bb732d61faf2dde /src/man.c | |
parent | a8bb42d31ff69e735192dd1f8f89c09f2dec855d (diff) | |
download | man-db-master.tar.gz |
Diffstat (limited to 'src/man.c')
-rw-r--r-- | src/man.c | 4386 |
1 files changed, 0 insertions, 4386 deletions
diff --git a/src/man.c b/src/man.c deleted file mode 100644 index 6d1cba74..00000000 --- a/src/man.c +++ /dev/null @@ -1,4386 +0,0 @@ -/* - * man.c: The manual pager - * - * Copyright (C) 1990, 1991 John W. Eaton. - * Copyright (C) 1994, 1995 Graeme W. Wilford. (Wilf.) - * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, - * 2011, 2012 Colin Watson. - * - * This file is part of man-db. - * - * man-db is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * man-db is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with man-db; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * John W. Eaton - * Department of Chemical Engineering - * The University of Texas at Austin - * Austin, Texas 78712 - * - * Mostly written/re-written by Wilf, some routines by Markus Armbruster. - * - * CJW: Various robustness, security, and internationalization fixes. - * Improved HTML support (originally written by Fabrizio Polacco). - * Rewrite of page location routines for improved maintainability and - * accuracy. - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif /* HAVE_CONFIG_H */ - -#include <stdbool.h> -#include <string.h> -#include <stdlib.h> -#include <stdio.h> -#include <stdarg.h> -#include <assert.h> -#include <errno.h> -#include <termios.h> -#include <unistd.h> -#include <limits.h> -#include <fcntl.h> -#include <ctype.h> -#include <signal.h> -#include <time.h> -#include <sys/types.h> -#include <sys/stat.h> - -#include "argp.h" -#include "dirname.h" -#include "gl_array_list.h" -#include "gl_hash_map.h" -#include "gl_list.h" -#include "gl_xlist.h" -#include "gl_xmap.h" -#include "minmax.h" -#include "progname.h" -#include "regex.h" -#include "stat-time.h" -#include "utimens.h" -#include "xgetcwd.h" -#include "xvasprintf.h" -#include "xstdopen.h" - -#include "gettext.h" -#include <locale.h> -#define _(String) gettext (String) -#define N_(String) gettext_noop (String) - -#include "manconfig.h" - -#include "error.h" -#include "cleanup.h" -#include "glcontainers.h" -#include "pipeline.h" -#include "pathsearch.h" -#include "linelength.h" -#include "decompress.h" -#include "xregcomp.h" -#include "security.h" -#include "encodings.h" -#include "orderfiles.h" -#include "sandbox.h" - -#include "mydbm.h" -#include "db_storage.h" - -#include "filenames.h" -#include "globbing.h" -#include "ult_src.h" -#include "manp.h" -#include "zsoelim.h" -#include "manconv_client.h" - -#ifdef MAN_OWNER -extern uid_t ruid; -extern uid_t euid; -#endif /* MAN_OWNER */ - -/* the default preprocessor sequence */ -#ifndef DEFAULT_MANROFFSEQ -# define DEFAULT_MANROFFSEQ "" -#endif - -/* placeholder for the manual page name in the less prompt string */ -#define MAN_PN "$MAN_PN" - -/* Some systems lack these */ -#ifndef STDIN_FILENO -# define STDIN_FILENO 0 -#endif -#ifndef STDOUT_FILENO -# define STDOUT_FILENO 1 -#endif -#ifndef STDERR_FILENO -# define STDERR_FILENO 2 -#endif - -char *lang; - -/* external formatter programs, one for use without -t, and one with -t */ -#define NFMT_PROG "mandb_nfmt" -#define TFMT_PROG "mandb_tfmt" -#undef ALT_EXT_FORMAT /* allow external formatters located in cat hierarchy */ - -static bool global_manpath; /* global or user manual page hierarchy? */ -static int skip; /* page exists but has been skipped */ - -#if defined _AIX || defined __sgi -char **global_argv; -#endif - -struct candidate { - const char *req_name; - char from_db; - char cat; - const char *path; - char *ult; - struct mandata *source; - int add_index; /* for sort stabilisation */ - struct candidate *next; -}; - -#define CANDIDATE_FILESYSTEM 0 -#define CANDIDATE_DATABASE 1 - -static void gripe_system (pipeline *p, int status) -{ - error (CHILD_FAIL, 0, _("command exited with status %d: %s"), - status, pipeline_tostring (p)); -} - -enum opts { - OPT_WARNINGS = 256, - OPT_REGEX, - OPT_WILDCARD, - OPT_NAMES, - OPT_NO_HYPHENATION, - OPT_NO_JUSTIFICATION, - OPT_NO_SUBPAGES, - OPT_MAX -}; - -static gl_list_t manpathlist; - -/* globals */ -int quiet = 1; -char *database = NULL; -extern const char *extension; /* for globbing.c */ -extern char *user_config_file; /* defined in manp.c */ -extern bool disable_cache; -extern int min_cat_width, max_cat_width, cat_width; -man_sandbox *sandbox; - -/* locals */ -static const char *alt_system_name; -static gl_list_t section_list; -static const char *section; -static char *colon_sep_section_list; -static const char *preprocessors; -static const char *pager; -static const char *locale; -static char *internal_locale, *multiple_locale; -static const char *prompt_string; -static char *less; -static const char *std_sections[] = STD_SECTIONS; -static char *manp; -static const char *external; -static gl_map_t db_map = NULL; - -static bool troff; -static const char *roff_device = NULL; -static const char *want_encoding = NULL; -#ifdef NROFF_WARNINGS -static const char default_roff_warnings[] = "mac"; -static gl_list_t roff_warnings; -#endif /* NROFF_WARNINGS */ -static bool global_apropos; -static bool print_where, print_where_cat; -static bool catman; -static bool local_man_file; -static bool findall; -static bool update; -static bool match_case; -static bool regex_opt; -static bool wildcard; -static bool names_only; -static int ult_flags = SO_LINK | SOFT_LINK | HARD_LINK; -static const char *recode = NULL; -static bool no_hyphenation; -static bool no_justification; -static bool subpages = true; - -static bool ascii; /* insert tr in the output pipe */ -static bool save_cat; /* security breach? Can we save the cat? */ - -static int first_arg; - -static int found_a_stray; /* found a straycat */ - -#ifdef MAN_CATS -static char *tmp_cat_file; /* for open_cat_stream(), close_cat_stream() */ -static int created_tmp_cat; /* dto. */ -#endif -static int tmp_cat_fd; -static struct timespec man_modtime; /* modtime of man page, for - * commit_tmp_cat() */ - -# ifdef TROFF_IS_GROFF -static bool ditroff; -static const char *gxditview; -static bool htmlout; -static const char *html_pager; -# endif /* TROFF_IS_GROFF */ - -const char *argp_program_version = "man " PACKAGE_VERSION; -const char *argp_program_bug_address = PACKAGE_BUGREPORT; -error_t argp_err_exit_status = FAIL; - -static const char args_doc[] = N_("[SECTION] PAGE..."); - -# ifdef NROFF_WARNINGS -# define ONLY_NROFF_WARNINGS 0 -# else -# define ONLY_NROFF_WARNINGS OPTION_HIDDEN -# endif - -# ifdef TROFF_IS_GROFF -# define ONLY_TROFF_IS_GROFF 0 -# else -# define ONLY_TROFF_IS_GROFF OPTION_HIDDEN -# endif - -/* Please keep these options in the same order as in parse_opt below. */ -static struct argp_option options[] = { - { "config-file", 'C', N_("FILE"), 0, N_("use this user configuration file") }, - { "debug", 'd', 0, 0, N_("emit debugging messages") }, - { "default", 'D', 0, 0, N_("reset all options to their default values") }, - { "warnings", OPT_WARNINGS, N_("WARNINGS"), ONLY_NROFF_WARNINGS | OPTION_ARG_OPTIONAL, - N_("enable warnings from groff") }, - - { 0, 0, 0, 0, N_("Main modes of operation:"), 10 }, - { "whatis", 'f', 0, 0, N_("equivalent to whatis") }, - { "apropos", 'k', 0, 0, N_("equivalent to apropos") }, - { "global-apropos", 'K', 0, 0, N_("search for text in all pages") }, - { "where", 'w', 0, 0, N_("print physical location of man page(s)") }, - { "path", 0, 0, OPTION_ALIAS }, - { "location", 0, 0, OPTION_ALIAS }, - { "where-cat", 'W', 0, 0, N_("print physical location of cat file(s)") }, - { "location-cat", 0, 0, OPTION_ALIAS }, - { "local-file", 'l', 0, 0, N_("interpret PAGE argument(s) as local filename(s)") }, - { "catman", 'c', 0, 0, N_("used by catman to reformat out of date cat pages"), 11 }, - { "recode", 'R', N_("ENCODING"), 0, N_("output source page encoded in ENCODING") }, - - { 0, 0, 0, 0, N_("Finding manual pages:"), 20 }, - { "locale", 'L', N_("LOCALE"), 0, N_("define the locale for this particular man search") }, - { "systems", 'm', N_("SYSTEM"), 0, N_("use manual pages from other systems") }, - { "manpath", 'M', N_("PATH"), 0, N_("set search path for manual pages to PATH") }, - { "sections", 'S', N_("LIST"), 0, N_("use colon separated section list"), 21 }, - { 0, 's', 0, OPTION_ALIAS }, - { "extension", 'e', N_("EXTENSION"), - 0, N_("limit search to extension type EXTENSION"), 22 }, - { "ignore-case", 'i', 0, 0, N_("look for pages case-insensitively (default)"), 23 }, - { "match-case", 'I', 0, 0, N_("look for pages case-sensitively") }, - { "regex", OPT_REGEX, 0, 0, N_("show all pages matching regex"), 24 }, - { "wildcard", OPT_WILDCARD, 0, 0, N_("show all pages matching wildcard") }, - { "names-only", OPT_NAMES, 0, 0, N_("make --regex and --wildcard match page names only, not " - "descriptions"), 25 }, - { "all", 'a', 0, 0, N_("find all matching manual pages"), 26 }, - { "update", 'u', 0, 0, N_("force a cache consistency check") }, - { "no-subpages", - OPT_NO_SUBPAGES, 0, 0, N_("don't try subpages, e.g. 'man foo bar' => 'man foo-bar'"), 27 }, - - { 0, 0, 0, 0, N_("Controlling formatted output:"), 30 }, - { "pager", 'P', N_("PAGER"), 0, N_("use program PAGER to display output") }, - { "prompt", 'r', N_("STRING"), 0, N_("provide the `less' pager with a prompt") }, - { "ascii", '7', 0, 0, N_("display ASCII translation of certain latin1 chars"), 31 }, - { "encoding", 'E', N_("ENCODING"), 0, N_("use selected output encoding") }, - { "no-hyphenation", - OPT_NO_HYPHENATION, 0, 0, N_("turn off hyphenation") }, - { "nh", 0, 0, OPTION_ALIAS }, - { "no-justification", - OPT_NO_JUSTIFICATION, 0, 0, N_("turn off justification") }, - { "nj", 0, 0, OPTION_ALIAS }, - { "preprocessor", 'p', N_("STRING"), 0, N_("STRING indicates which preprocessors to run:\n" - "e - [n]eqn, p - pic, t - tbl,\n" - "g - grap, r - refer, v - vgrind") }, -#ifdef HAS_TROFF - { "troff", 't', 0, 0, N_("use %s to format pages"), 32 }, - { "troff-device", 'T', N_("DEVICE"), OPTION_ARG_OPTIONAL, - N_("use %s with selected device") }, - { "html", 'H', N_("BROWSER"), ONLY_TROFF_IS_GROFF | OPTION_ARG_OPTIONAL, - N_("use %s or BROWSER to display HTML output"), 33 }, - { "gxditview", 'X', N_("RESOLUTION"), - ONLY_TROFF_IS_GROFF | OPTION_ARG_OPTIONAL, - N_("use groff and display through gxditview (X11):\n" - "-X = -TX75, -X100 = -TX100, -X100-12 = -TX100-12") }, - { "ditroff", 'Z', 0, ONLY_TROFF_IS_GROFF, N_("use groff and force it to produce ditroff") }, -#endif /* HAS_TROFF */ - - { 0, 'h', 0, OPTION_HIDDEN, 0 }, /* compatibility for --help */ - { 0 } -}; - -static void init_html_pager (void) -{ - html_pager = getenv ("BROWSER"); - if (!html_pager) - html_pager = WEB_BROWSER; -} - -static error_t parse_opt (int key, char *arg, struct argp_state *state) -{ - static bool apropos, whatis; /* retain values between calls */ - - /* Please keep these keys in the same order as in options above. */ - switch (key) { - case 'C': - user_config_file = arg; - return 0; - case 'd': - debug_level = true; - return 0; - case 'D': - /* discard all preset options */ - local_man_file = findall = update = catman = - debug_level = troff = global_apropos = - print_where = print_where_cat = - ascii = match_case = - regex_opt = wildcard = names_only = - no_hyphenation = no_justification = false; -#ifdef TROFF_IS_GROFF - ditroff = false; - gxditview = NULL; - htmlout = false; - init_html_pager (); -#endif - roff_device = want_encoding = extension = pager = - locale = alt_system_name = external = - preprocessors = NULL; - colon_sep_section_list = manp = NULL; - return 0; - - case OPT_WARNINGS: -#ifdef NROFF_WARNINGS - { - char *s = xstrdup - (arg ? arg : default_roff_warnings); - const char *warning; - - for (warning = strtok (s, ","); warning; - warning = strtok (NULL, ",")) - gl_list_add_last (roff_warnings, - xstrdup (warning)); - - free (s); - } -#endif /* NROFF_WARNINGS */ - return 0; - - case 'f': - external = WHATIS; - whatis = true; - return 0; - case 'k': - external = APROPOS; - apropos = true; - return 0; - case 'K': - global_apropos = true; - return 0; - case 'w': - print_where = true; - return 0; - case 'W': - print_where_cat = true; - return 0; - case 'l': - local_man_file = true; - return 0; - case 'c': - catman = true; - return 0; - case 'R': - recode = arg; - ult_flags &= ~SO_LINK; - return 0; - - case 'L': - locale = arg; - return 0; - case 'm': - alt_system_name = arg; - return 0; - case 'M': - manp = arg; - return 0; - case 'S': - case 's': - if (*arg) - colon_sep_section_list = arg; - return 0; - case 'e': - extension = arg; - return 0; - case 'i': - match_case = false; - return 0; - case 'I': - match_case = true; - return 0; - case OPT_REGEX: - regex_opt = true; - findall = true; - return 0; - case OPT_WILDCARD: - wildcard = true; - findall = true; - return 0; - case OPT_NAMES: - names_only = true; - return 0; - case 'a': - findall = true; - return 0; - case 'u': - update = true; - return 0; - case OPT_NO_SUBPAGES: - subpages = false; - return 0; - - case 'P': - pager = arg; - return 0; - case 'r': - prompt_string = arg; - return 0; - case '7': - ascii = true; - return 0; - case 'E': - want_encoding = arg; - if (is_roff_device (want_encoding)) - roff_device = want_encoding; - return 0; - case OPT_NO_HYPHENATION: - no_hyphenation = true; - return 0; - case OPT_NO_JUSTIFICATION: - no_justification = true; - return 0; - case 'p': - preprocessors = arg; - return 0; -#ifdef HAS_TROFF - case 't': - troff = true; - return 0; - case 'T': - /* Traditional nroff knows -T; troff does not (gets - * ignored). All incarnations of groff know it. Why - * does -T imply -t? - */ - roff_device = (arg ? arg : "ps"); - troff = true; - return 0; - case 'H': -# ifdef TROFF_IS_GROFF - if (arg) - html_pager = arg; - htmlout = true; - troff = true; - roff_device = "html"; -# endif /* TROFF_IS_GROFF */ - return 0; - case 'X': -# ifdef TROFF_IS_GROFF - troff = true; - gxditview = (arg ? arg : "75"); -# endif /* TROFF_IS_GROFF */ - return 0; - case 'Z': -# ifdef TROFF_IS_GROFF - ditroff = true; - troff = true; -# endif /* TROFF_IS_GROFF */ - return 0; -#endif /* HAS_TROFF */ - - case 'h': - argp_state_help (state, state->out_stream, - ARGP_HELP_STD_HELP); - break; - case ARGP_KEY_SUCCESS: - /* check for incompatible options */ - if ((int) troff + (int) whatis + (int) apropos + - (int) catman + - (int) (print_where || print_where_cat) > 1) { - char *badopts = xasprintf - ("%s%s%s%s%s%s", - troff ? "-[tTZH] " : "", - whatis ? "-f " : "", - apropos ? "-k " : "", - catman ? "-c " : "", - print_where ? "-w " : "", - print_where_cat ? "-W " : ""); - argp_error (state, - _("%s: incompatible options"), - badopts); - } - if ((int) regex_opt + (int) wildcard > 1) { - char *badopts = xasprintf - ("%s%s", - regex_opt ? "--regex " : "", - wildcard ? "--wildcard " : ""); - argp_error (state, - _("%s: incompatible options"), - badopts); - } - return 0; - } - return ARGP_ERR_UNKNOWN; -} - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -static char *help_filter (int key, const char *text, void *input _GL_UNUSED) -{ -#ifdef HAS_TROFF -# ifdef TROFF_IS_GROFF - static const char formatter[] = "groff"; - const char *browser; -# else - static const char formatter[] = "troff"; -# endif /* TROFF_IS_GROFF */ -#endif /* HAS_TROFF */ - - switch (key) { -#ifdef HAS_TROFF - case 't': - case 'T': - return xasprintf (text, formatter); -# ifdef TROFF_IS_GROFF - case 'H': - browser = html_pager; - assert (browser); - if (STRNEQ (browser, "exec ", 5)) - browser += 5; - return xasprintf (text, browser); -# endif /* TROFF_IS_GROFF */ -#endif /* HAS_TROFF */ - default: - return (char *) text; - } -} -#pragma GCC diagnostic pop - -static struct argp argp = { options, parse_opt, args_doc, 0, 0, help_filter }; - -/* - * changed these messages from stdout to stderr, - * (Fabrizio Polacco) Fri, 14 Feb 1997 01:30:07 +0200 - */ -static void gripe_no_name (const char *sect) -{ - if (sect) { - fprintf (stderr, _("No manual entry for %s\n"), sect); - fprintf (stderr, - _("(Alternatively, what manual page do you want from " - "section %s?)\n"), - sect); - } else - fputs (_("What manual page do you want?\n"), stderr); - fputs (_("For example, try 'man man'.\n"), stderr); - - exit (FAIL); -} - -static struct termios tms; -static int tms_set = 0; -static pid_t tms_pid = 0; - -static void set_term (void) -{ - if (tms_set && getpid () == tms_pid) - tcsetattr (STDIN_FILENO, TCSANOW, &tms); -} - -static void get_term (void) -{ - if (isatty (STDOUT_FILENO)) { - debug ("is a tty\n"); - tcgetattr (STDIN_FILENO, &tms); - if (!tms_set++) { - /* Work around pipecmd_exec calling exit(3) rather - * than _exit(2), which means our atexit-registered - * functions are called at the end of each child - * process created using pipecmd_new_function and - * friends. It would probably be good to fix this - * in libpipeline at some point, but it would - * require care to avoid breaking compatibility. - */ - tms_pid = getpid (); - atexit (set_term); - } - } -} - -#if defined(TROFF_IS_GROFF) || defined(HEIRLOOM_NROFF) -static int get_roff_line_length (void) -{ - int line_length = cat_width ? cat_width : get_line_length (); - - /* groff >= 1.18 defaults to 78. */ - if ((!troff || ditroff) && line_length != 80) { - int length = line_length * 39 / 40; - if (length > line_length - 2) - return line_length - 2; - else - return length; - } else - return 0; -} - -#ifdef HEIRLOOM_NROFF -static void heirloom_line_length (void *data) -{ - printf (".ll %sn\n", (const char *) data); - /* TODO: This fails to do anything useful. Why? */ - printf (".lt %sn\n", (const char *) data); - printf (".lf 1\n"); -} -#endif /* HEIRLOOM_NROFF */ - -static pipecmd *add_roff_line_length (pipecmd *cmd, bool *save_cat_p) -{ - int length; - pipecmd *ret = NULL; - - if (!catman) { - int line_length = get_line_length (); - debug ("Terminal width %d\n", line_length); - if (line_length >= min_cat_width && - line_length <= max_cat_width) - debug ("Terminal width %d within cat page range " - "[%d, %d]\n", - line_length, min_cat_width, max_cat_width); - else { - debug ("Terminal width %d not within cat page range " - "[%d, %d]\n", - line_length, min_cat_width, max_cat_width); - *save_cat_p = false; - } - } - - length = get_roff_line_length (); - if (length) { -#ifdef HEIRLOOM_NROFF - char *name; - char *lldata; - pipecmd *llcmd; -#endif /* HEIRLOOM_NROFF */ - - debug ("Using %d-character lines\n", length); -#if defined(TROFF_IS_GROFF) - pipecmd_argf (cmd, "-rLL=%dn", length); - pipecmd_argf (cmd, "-rLT=%dn", length); -#elif defined(HEIRLOOM_NROFF) - name = xasprintf ("echo .ll %dn && echo .lt %dn", - length, length); - lldata = xasprintf ("%d", length); - llcmd = pipecmd_new_function (name, heirloom_line_length, free, - lldata); - ret = pipecmd_new_sequence ("line-length", llcmd, - pipecmd_new_passthrough (), NULL); - free (name); -#endif /* HEIRLOOM_NROFF */ - } - - return ret; -} -#endif /* TROFF_IS_GROFF || HEIRLOOM_NROFF */ - -static void gripe_no_man (const char *name, const char *sec) -{ - /* On AIX and IRIX, fall back to the vendor supplied browser. */ -#if defined _AIX || defined __sgi - if (!troff) { - pipecmd *vendor_man; - int i; - - vendor_man = pipecmd_new ("/usr/bin/man"); - for (i = 1; i < argc; ++i) - pipecmd_arg (vendor_man, global_argv[i]); - pipecmd_unsetenv (vendor_man, "MANPATH"); - pipecmd_exec (vendor_man); - } -#endif - - if (sec) - fprintf (stderr, _("No manual entry for %s in section %s\n"), - name, sec); - else - fprintf (stderr, _("No manual entry for %s\n"), name); - -#ifdef UNDOC_COMMAND - if (getenv ("MAN_TEST_DISABLE_UNDOCUMENTED") == NULL && - pathsearch_executable (name)) - fprintf (stderr, - _("See '%s' for help when manual pages are not " - "available.\n"), UNDOC_COMMAND); -#endif -} - -/* fire up the appropriate external program */ -static void do_extern (int argc, char *argv[]) -{ - pipeline *p; - pipecmd *cmd; - - cmd = pipecmd_new (external); - /* Please keep these in the same order as they are in whatis.c. */ - if (debug_level) - pipecmd_arg (cmd, "-d"); - if (local_man_file) /* actually apropos/whatis --long */ - pipecmd_arg (cmd, "-l"); - if (colon_sep_section_list) - pipecmd_args (cmd, "-s", colon_sep_section_list, (void *) 0); - if (alt_system_name) - pipecmd_args (cmd, "-m", alt_system_name, (void *) 0); - if (manp) - pipecmd_args (cmd, "-M", manp, (void *) 0); - if (locale) - pipecmd_args (cmd, "-L", locale, (void *) 0); - if (user_config_file) - pipecmd_args (cmd, "-C", user_config_file, (void *) 0); - while (first_arg < argc) - pipecmd_arg (cmd, argv[first_arg++]); - p = pipeline_new_commands (cmd, (void *) 0); - - /* privs are already dropped */ - exit (pipeline_run (p)); -} - -/* lookup $MANOPT and if available, put in *argv[] format for argp */ -static char **manopt_to_env (int *argc) -{ - char *manopt, *manopt_copy, *opt_start, **argv; - - manopt = getenv ("MANOPT"); - if (manopt == NULL || *manopt == '\0') - return NULL; - - opt_start = manopt = manopt_copy = xstrdup (manopt); - - /* allocate space for the program name */ - *argc = 0; - argv = XNMALLOC (*argc + 3, char *); - argv[(*argc)++] = base_name (program_name); - - /* for each [ \t]+ delimited string, allocate an array space and fill - it in. An escaped space is treated specially */ - while (*manopt) { - switch (*manopt) { - case ' ': - case '\t': - if (manopt != opt_start) { - *manopt = '\0'; - argv = xnrealloc (argv, *argc + 3, - sizeof (char *)); - argv[(*argc)++] = xstrdup (opt_start); - } - while (CTYPE (isspace, *(manopt + 1))) - *++manopt = '\0'; - opt_start = manopt + 1; - break; - case '\\': - if (*(manopt + 1) == ' ') - manopt++; - break; - default: - break; - } - manopt++; - } - - if (*opt_start) - argv[(*argc)++] = xstrdup (opt_start); - argv[*argc] = NULL; - - free (manopt_copy); - return argv; -} - -/* Return char array with 'less' special chars escaped. Uses static storage. */ -static const char *escape_less (const char *string) -{ - static char *escaped_string; - char *ptr; - - /* 2*strlen will always be long enough to hold the escaped string */ - ptr = escaped_string = xrealloc (escaped_string, - 2 * strlen (string) + 1); - - while (*string) { - if (*string == '?' || - *string == ':' || - *string == '.' || - *string == '%' || - *string == '\\') - *ptr++ = '\\'; - - *ptr++ = *string++; - } - - *ptr = *string; - return escaped_string; -} - -#if defined(MAN_DB_CREATES) || defined(MAN_DB_UPDATES) -/* Run mandb to ensure databases are up to date. Only used with -u. - * Returns the exit status of mandb. - * - * If filename is non-NULL, uses mandb's -f option to update a single file. - */ -static int run_mandb (int create, const char *manpath, const char *filename) -{ - pipeline *mandb_pl = pipeline_new (); - pipecmd *mandb_cmd = pipecmd_new ("mandb"); - - if (debug_level) - pipecmd_arg (mandb_cmd, "-d"); - else - pipecmd_arg (mandb_cmd, "-q"); - - if (user_config_file) - pipecmd_args (mandb_cmd, "-C", user_config_file, (void *) 0); - - if (filename) - pipecmd_args (mandb_cmd, "-f", filename, (void *) 0); - else if (create) { - pipecmd_arg (mandb_cmd, "-c"); - pipecmd_setenv (mandb_cmd, "MAN_MUST_CREATE", "1"); - } else - pipecmd_arg (mandb_cmd, "-p"); - - if (manpath) - pipecmd_arg (mandb_cmd, manpath); - - pipeline_command (mandb_pl, mandb_cmd); - - if (debug_level) { - debug ("running mandb: "); - pipeline_dump (mandb_pl, stderr); - } - - return pipeline_run (mandb_pl); -} -#endif /* MAN_DB_CREATES || MAN_DB_UPDATES */ - - -static char *locale_manpath (const char *manpath) -{ - char *all_locales; - char *new_manpath; - - if (multiple_locale && *multiple_locale) { - if (internal_locale && *internal_locale) - all_locales = xasprintf ("%s:%s", multiple_locale, - internal_locale); - else - all_locales = xstrdup (multiple_locale); - } else { - if (internal_locale && *internal_locale) - all_locales = xstrdup (internal_locale); - else - all_locales = NULL; - } - - new_manpath = add_nls_manpaths (manpath, all_locales); - free (all_locales); - - return new_manpath; -} - -/* - * Check to see if the argument is a valid section number. - * If the name matches one of - * the sections listed in section_list, we'll assume that it's a section. - * The list of sections in config.h simply allows us to specify oddly - * named directories like .../man3f. Yuk. - */ -static const char *is_section (const char *name) -{ - const char *vs; - - GL_LIST_FOREACH_START (section_list, vs) { - if (STREQ (vs, name)) - return name; - /* allow e.g. 3perl but disallow 8139too and libfoo */ - if (strlen (vs) == 1 && CTYPE (isdigit, *vs) && - strlen (name) > 1 && !CTYPE (isdigit, name[1]) && - STRNEQ (vs, name, 1)) - return name; - } GL_LIST_FOREACH_END (section_list); - return NULL; -} - -/* Snarf pre-processors from file, return string or NULL on failure */ -static char *get_preprocessors_from_file (pipeline *decomp, int prefixes) -{ - const size_t block = 4096; - int i; - char *line = NULL; - size_t previous_len = 0; - - if (!decomp) - return NULL; - - /* Prefixes are inserted into the stream by man itself, and we must - * skip over them to find any preprocessors line that exists. Each - * one ends with an .lf macro. - */ - for (i = 0; ; ++i) { - size_t len = block * (i + 1); - const char *buffer, *scan, *end; - int j; - - scan = buffer = pipeline_peek (decomp, &len); - if (!buffer || len == 0) - return NULL; - - for (j = 0; j < prefixes; ++j) { - scan = memmem (scan, len - (scan - buffer), - "\n.lf ", strlen ("\n.lf ")); - if (!scan) - break; - ++scan; - scan = memchr (scan, '\n', len - (scan - buffer)); - if (!scan) - break; - ++scan; - } - if (!scan) - continue; - - end = memchr (scan, '\n', len - (scan - buffer)); - if (!end && len == previous_len) - /* end of file, no newline found */ - end = buffer + len - 1; - if (end) { - line = xstrndup (scan, end - scan + 1); - break; - } - previous_len = len; - } - if (!line) - return NULL; - - if (!strncmp (line, PP_COOKIE, 4)) { - const char *newline = strchr (line, '\n'); - if (newline) - return xstrndup (line + 4, newline - (line + 4)); - else - return xstrdup (line + 4); - } - return NULL; -} - - -/* Determine pre-processors, set save_cat and return string */ -static char *get_preprocessors (pipeline *decomp, const char *dbfilters, - int prefixes) -{ - char *pp_string; - const char *pp_source; - const char *env; - - /* try in order: database, command line, file, environment, default */ - /* command line overrides the database, but database empty overrides default */ - if (dbfilters && (dbfilters[0] != '-') && !preprocessors) { - pp_string = xstrdup (dbfilters); - pp_source = "database"; - save_cat = true; - } else if (preprocessors) { - pp_string = xstrdup (preprocessors); - pp_source = "command line"; - save_cat = false; - } else if ((pp_string = get_preprocessors_from_file (decomp, - prefixes))) { - pp_source = "file"; - save_cat = true; - } else if ((env = getenv ("MANROFFSEQ"))) { - pp_string = xstrdup (env); - pp_source = "environment"; - save_cat = false; - } else if (!dbfilters) { - pp_string = xstrdup (DEFAULT_MANROFFSEQ); - pp_source = "default"; - save_cat = true; - } else { - pp_string = xstrdup (""); - pp_source = "no filters"; - save_cat = true; - } - - debug ("pre-processors `%s' from %s\n", pp_string, pp_source); - return pp_string; -} - -static const char *my_locale_charset (void) -{ - if (want_encoding && !is_roff_device (want_encoding)) - return want_encoding; - else - return get_locale_charset (); -} - -static void add_col (pipeline *p, const char *locale_charset, ...) -{ - pipecmd *cmd; - va_list argv; - char *col_locale = NULL; - - cmd = pipecmd_new (COL); - va_start (argv, locale_charset); - pipecmd_argv (cmd, argv); - va_end (argv); - pipecmd_pre_exec (cmd, sandbox_load, sandbox_free, sandbox); - - if (locale_charset) - col_locale = find_charset_locale (locale_charset); - if (col_locale) { - pipecmd_setenv (cmd, "LC_CTYPE", col_locale); - free (col_locale); - } - - pipeline_command (p, cmd); -} - -/* Return pipeline to format file to stdout. */ -static pipeline *make_roff_command (const char *dir, const char *file, - pipeline *decomp, const char *pp_string, - char **result_encoding) -{ - const char *roff_opt; - char *fmt_prog = NULL; - pipeline *p = pipeline_new (); - pipecmd *cmd; - char *page_encoding = NULL; - const char *output_encoding = NULL; - const char *locale_charset = NULL; - - *result_encoding = xstrdup ("UTF-8"); /* optimistic default */ - - roff_opt = getenv ("MANROFFOPT"); - if (!roff_opt) - roff_opt = ""; - - if (dir && !recode) { -#ifdef ALT_EXT_FORMAT - char *catpath = get_catpath - (dir, global_manpath ? SYSTEM_CAT : USER_CAT); - - /* If we have an alternate catpath, look for an external - * formatter there. - */ - if (catpath) { - fmt_prog = appendstr (catpath, "/", - troff ? TFMT_PROG : NFMT_PROG, - (void *) 0); - if (!CAN_ACCESS (fmt_prog, X_OK)) { - free (fmt_prog); - fmt_prog = NULL; - } - } -#endif /* ALT_EXT_FORMAT */ - - /* If the page is in a proper manual page hierarchy (as - * opposed to being read using --local-file or similar), - * look for an external formatter there. - */ - if (!fmt_prog) { - fmt_prog = appendstr (NULL, dir, "/", - troff ? TFMT_PROG : NFMT_PROG, - (void *) 0); - if (!CAN_ACCESS (fmt_prog, X_OK)) { - free (fmt_prog); - fmt_prog = NULL; - } - } - } - - if (fmt_prog) - debug ("External formatter %s\n", fmt_prog); - - if (!fmt_prog) { - /* we don't have an external formatter script */ - const char *source_encoding, *roff_encoding; - const char *groff_preconv; - - if (!recode) { - struct zsoelim_stdin_data *zsoelim_data; - - zsoelim_data = zsoelim_stdin_data_new (dir, - manpathlist); - cmd = pipecmd_new_function (ZSOELIM, &zsoelim_stdin, - zsoelim_stdin_data_free, - zsoelim_data); - pipecmd_pre_exec (cmd, sandbox_load, sandbox_free, - sandbox); - pipeline_command (p, cmd); - } - - page_encoding = check_preprocessor_encoding - (decomp, NULL, NULL); - if (!page_encoding) - page_encoding = get_page_encoding (lang); - if (page_encoding && !STREQ (page_encoding, "UTF-8")) - source_encoding = page_encoding; - else - source_encoding = get_source_encoding (lang); - debug ("page_encoding = %s\n", page_encoding); - debug ("source_encoding = %s\n", source_encoding); - - /* Load the roff_device value dependent on the language dir - * in the path. - */ - if (!troff) { -#define STRC(s, otherwise) ((s) ? (s) : (otherwise)) - - locale_charset = my_locale_charset (); - debug ("locale_charset = %s\n", - STRC (locale_charset, "NULL")); - - /* Pick the default device for this locale if there - * wasn't one selected explicitly. - */ - if (!roff_device) { - roff_device = - get_default_device (locale_charset, - source_encoding); -#ifdef HEIRLOOM_NROFF - /* In Heirloom, if LC_CTYPE is a UTF-8 - * locale, then -Tlocale will be equivalent - * to -Tutf8 except that it will do a - * slightly better job of rendering some - * special characters. - */ - if (STREQ (roff_device, "utf8")) { - const char *real_locale_charset = - get_locale_charset (); - if (real_locale_charset && - STREQ (real_locale_charset, - "UTF-8")) - roff_device = "locale"; - } -#endif /* HEIRLOOM_NROFF */ - debug ("roff_device (locale) = %s\n", - STRC (roff_device, "NULL")); - } - } - - roff_encoding = get_roff_encoding (roff_device, - source_encoding); - debug ("roff_encoding = %s\n", roff_encoding); - - /* We may need to recode: - * from page_encoding to roff_encoding on input; - * from output_encoding to locale_charset on output - * (if not troff). - * If we have preconv, then use it to recode the - * input to a safe escaped form. - * The --recode option overrides everything else. - */ - groff_preconv = get_groff_preconv (); - if (recode) - add_manconv (p, page_encoding, recode); - else if (groff_preconv) { - pipecmd *preconv_cmd; - add_manconv (p, page_encoding, "UTF-8"); - preconv_cmd = pipecmd_new_args - (groff_preconv, "-e", "UTF-8", (void *) 0); - pipecmd_pre_exec (preconv_cmd, sandbox_load, - sandbox_free, sandbox); - pipeline_command (p, preconv_cmd); - } else if (roff_encoding) - add_manconv (p, page_encoding, roff_encoding); - else - add_manconv (p, page_encoding, page_encoding); - - if (!troff && !recode) { - output_encoding = get_output_encoding (roff_device); - if (!output_encoding) - output_encoding = source_encoding; - debug ("output_encoding = %s\n", output_encoding); - free (*result_encoding); - *result_encoding = xstrdup (output_encoding); - - if (!getenv ("LESSCHARSET")) { - const char *less_charset = - get_less_charset (locale_charset); - debug ("less_charset = %s\n", less_charset); - setenv ("LESSCHARSET", less_charset, 1); - } - - if (!getenv ("JLESSCHARSET")) { - const char *jless_charset = - get_jless_charset (locale_charset); - if (jless_charset) { - debug ("jless_charset = %s\n", - jless_charset); - setenv ("JLESSCHARSET", - jless_charset, 1); - } - } - } - } - - if (recode) - ; - else if (!fmt_prog) { -#ifndef GNU_NROFF - int using_tbl = 0; -#endif /* GNU_NROFF */ - - do { -#ifdef NROFF_WARNINGS - const char *warning; -#endif /* NROFF_WARNINGS */ - int wants_dev = 0; /* filter wants a dev argument */ - int wants_post = 0; /* postprocessor arguments */ - - cmd = NULL; - /* set cmd according to *pp_string, on - errors leave cmd as NULL */ - switch (*pp_string) { - case 'e': - if (troff) - cmd = pipecmd_new_argstr - (get_def ("eqn", EQN)); - else - cmd = pipecmd_new_argstr - (get_def ("neqn", NEQN)); - wants_dev = 1; - break; - case 'g': - cmd = pipecmd_new_argstr - (get_def ("grap", GRAP)); - break; - case 'p': - cmd = pipecmd_new_argstr - (get_def ("pic", PIC)); - break; - case 't': - cmd = pipecmd_new_argstr - (get_def ("tbl", TBL)); -#ifndef GNU_NROFF - using_tbl = 1; -#endif /* GNU_NROFF */ - break; - case 'v': - cmd = pipecmd_new_argstr - (get_def ("vgrind", VGRIND)); - break; - case 'r': - cmd = pipecmd_new_argstr - (get_def ("refer", REFER)); - break; - case ' ': - case '-': - case 0: - /* done with preprocessors, now add roff */ - if (troff) { - cmd = pipecmd_new_argstr - (get_def ("troff", TROFF)); - save_cat = false; - } else - cmd = pipecmd_new_argstr - (get_def ("nroff", NROFF)); - -#ifdef TROFF_IS_GROFF - if (troff && ditroff) - pipecmd_arg (cmd, "-Z"); -#endif /* TROFF_IS_GROFF */ - -#if defined(TROFF_IS_GROFF) || defined(HEIRLOOM_NROFF) - { - pipecmd *seq = add_roff_line_length - (cmd, &save_cat); - if (seq) - pipeline_command (p, seq); - } -#endif /* TROFF_IS_GROFF || HEIRLOOM_NROFF */ - -#ifdef NROFF_WARNINGS - GL_LIST_FOREACH_START (roff_warnings, warning) - pipecmd_argf (cmd, "-w%s", warning); - GL_LIST_FOREACH_END (roff_warnings); -#endif /* NROFF_WARNINGS */ - -#ifdef HEIRLOOM_NROFF - if (running_setuid ()) - pipecmd_unsetenv (cmd, "TROFFMACS"); -#endif /* HEIRLOOM_NROFF */ - - pipecmd_argstr (cmd, roff_opt); - - wants_dev = 1; - wants_post = 1; - break; - } - - if (!cmd) { - assert (*pp_string); /* didn't fail on roff */ - error (0, 0, - _("ignoring unknown preprocessor `%c'"), - *pp_string); - continue; - } - - if (wants_dev) { - if (roff_device) - pipecmd_argf (cmd, - "-T%s", roff_device); -#ifdef TROFF_IS_GROFF - else if (gxditview) { - pipecmd_argf (cmd, "-TX%s", gxditview); - if (strstr (gxditview, "-12")) - pipecmd_argf (cmd, "-rS12"); - } -#endif /* TROFF_IS_GROFF */ - } - - if (wants_post) { -#ifdef TROFF_IS_GROFF - if (gxditview) - /* -X arranges for the correct - * options to be passed to troff. - * Normally it would run gxditview - * as well, but we suppress that - * with -Z so that we can do it - * ourselves; this lets us set a - * better window title, and means - * that we don't have to worry about - * sandboxing text processing and an - * X program in the same way. - */ - pipecmd_args (cmd, "-X", "-Z", - (void *) 0); -#endif /* TROFF_IS_GROFF */ - - if (roff_device && STREQ (roff_device, "ps")) - /* Tell grops to guess the page - * size. - */ - pipecmd_arg (cmd, "-P-g"); - } - - pipecmd_pre_exec (cmd, sandbox_load_permissive, - sandbox_free, sandbox); - pipeline_command (p, cmd); - - if (*pp_string == ' ' || *pp_string == '-') - break; - } while (*pp_string++); - - if (!troff && *COL) { - const char *man_keep_formatting = - getenv ("MAN_KEEP_FORMATTING"); - if ((!man_keep_formatting || !*man_keep_formatting) && - !isatty (STDOUT_FILENO)) - /* we'll run col later, but prepare for it */ - setenv ("GROFF_NO_SGR", "1", 1); -#ifndef GNU_NROFF - /* tbl needs col */ - else if (using_tbl && !troff && *COL) - add_col (p, locale_charset, (void *) 0); -#endif /* GNU_NROFF */ - } - } else { - /* use external formatter script, it takes arguments - input file, preprocessor string, and (optional) - output device */ - cmd = pipecmd_new_args (fmt_prog, file, pp_string, (void *) 0); - if (roff_device) - pipecmd_arg (cmd, roff_device); - pipeline_command (p, cmd); - } - - free (fmt_prog); - free (page_encoding); - return p; -} - -#ifdef TROFF_IS_GROFF -/* Return pipeline to run a browser on a given file, observing - * http://www.tuxedo.org/~esr/BROWSER/. - * - * (Actually, I really implement - * https://www.dwheeler.com/browse/secure_browser.html, but it's - * backward-compatible.) - * - * TODO: Is there any way to use the pipeline library better here? - */ -static pipeline *make_browser (const char *pattern, const char *file) -{ - pipeline *p; - pipecmd *cmd; - char *browser = xmalloc (1); - bool found_percent_s = false; - char *percent; - char *esc_file; - - *browser = '\0'; - - percent = strchr (pattern, '%'); - while (percent) { - size_t len = strlen (browser); - browser = xrealloc (browser, len + 1 + (percent - pattern)); - strncat (browser, pattern, percent - pattern); - switch (*(percent + 1)) { - case '\0': - case '%': - browser = appendstr (browser, "%", (void *) 0); - break; - case 'c': - browser = appendstr (browser, ":", (void *) 0); - break; - case 's': - esc_file = escape_shell (file); - browser = appendstr (browser, esc_file, - (void *) 0); - free (esc_file); - found_percent_s = true; - break; - default: - len = strlen (browser); /* cannot be NULL */ - browser = xrealloc (browser, len + 3); - strncat (browser, percent, 2); - break; - } - if (*(percent + 1)) - pattern = percent + 2; - else - pattern = percent + 1; - percent = strchr (pattern, '%'); - } - browser = appendstr (browser, pattern, (void *) 0); - if (!found_percent_s) { - esc_file = escape_shell (file); - browser = appendstr (browser, " ", esc_file, (void *) 0); - free (esc_file); - } - - cmd = pipecmd_new_args ("/bin/sh", "-c", browser, (void *) 0); - pipecmd_pre_exec (cmd, drop_privs, NULL, NULL); - p = pipeline_new_commands (cmd, (void *) 0); - pipeline_ignore_signals (p, 1); - free (browser); - - return p; -} -#endif /* TROFF_IS_GROFF */ - -static void setenv_less (pipecmd *cmd, const char *title) -{ - const char *esc_title; - char *less_opts, *man_pn; - - esc_title = escape_less (title); - less_opts = xasprintf (LESS_OPTS, prompt_string, prompt_string); - less_opts = appendstr (less_opts, less, (void *) 0); - man_pn = strstr (less_opts, MAN_PN); - while (man_pn) { - char *subst_opts = - xmalloc (strlen (less_opts) - strlen (MAN_PN) + - strlen (esc_title) + 1); - strncpy (subst_opts, less_opts, man_pn - less_opts); - subst_opts[man_pn - less_opts] = '\0'; - strcat (subst_opts, esc_title); - strcat (subst_opts, man_pn + strlen (MAN_PN)); - free (less_opts); - less_opts = subst_opts; - man_pn = strstr (less_opts, MAN_PN); - } - - debug ("Setting LESS to %s\n", less_opts); - pipecmd_setenv (cmd, "LESS", less_opts); - - debug ("Setting MAN_PN to %s\n", esc_title); - pipecmd_setenv (cmd, "MAN_PN", esc_title); - - free (less_opts); -} - -static void add_output_iconv (pipeline *p, - const char *source, const char *target) -{ - debug ("add_output_iconv: source %s, target %s\n", source, target); - if (source && target && !STREQ (source, target)) { - char *target_translit = xasprintf ("%s//TRANSLIT", target); - pipecmd *iconv_cmd; - iconv_cmd = pipecmd_new_args - ("iconv", "-c", "-f", source, "-t", target_translit, - (void *) 0); - pipecmd_pre_exec (iconv_cmd, sandbox_load, sandbox_free, - sandbox); - pipeline_command (p, iconv_cmd); - free (target_translit); - } -} - -/* Pipeline command to squeeze multiple blank lines into one. - * - */ -static void squeeze_blank_lines (void *data _GL_UNUSED) -{ - char *line = NULL; - size_t len = 0; - - while (getline (&line, &len, stdin) != -1) { - int in_blank_line = 1; - int got_blank_line = 0; - - while (in_blank_line) { - char *p; - for (p = line; *p; ++p) { - if (!CTYPE (isspace, *p)) { - in_blank_line = 0; - break; - } - } - - if (in_blank_line) { - got_blank_line = 1; - free (line); - line = NULL; - len = 0; - if (getline (&line, &len, stdin) == -1) - break; - } - } - - if (got_blank_line && putchar ('\n') < 0) - break; - - if (!in_blank_line && fputs (line, stdout) < 0) - break; - - free (line); - line = NULL; - len = 0; - } - - free (line); - return; -} - -/* Return pipeline to display file provided on stdin. - * - * TODO: htmlout case is pretty weird now. I'd like the intelligence to be - * somewhere other than format_display. - */ -static pipeline *make_display_command (const char *encoding, const char *title) -{ - pipeline *p = pipeline_new (); - const char *locale_charset = NULL; - pipecmd *pager_cmd = NULL; - - locale_charset = my_locale_charset (); - - if (!troff && (!want_encoding || !is_roff_device (want_encoding))) - add_output_iconv (p, encoding, locale_charset); - - if (!troff && *COL) { - /* get rid of special characters if not writing to a - * terminal - */ - const char *man_keep_formatting = - getenv ("MAN_KEEP_FORMATTING"); - if ((!man_keep_formatting || !*man_keep_formatting) && - !isatty (STDOUT_FILENO)) - add_col (p, locale_charset, "-b", "-p", "-x", - (void *) 0); - } - -#ifdef TROFF_IS_GROFF - if (gxditview) { - char *x_resource = xasprintf ("*iconName:%s", title); - pipeline_command_args - (p, "gxditview", - "-title", title, "-xrm", x_resource, "-", - (void *) 0); - free (x_resource); - return p; - } -#endif /* TROFF_IS_GROFF */ - - /* emulate pager -s, the sed code is just for information */ - { - pipecmd *cmd; - const char *name = "sed -e '/^[[:space:]]*$/{ N; /^[[:space:]]*\\n[[:space:]]*$/D; }'"; - cmd = pipecmd_new_function (name, &squeeze_blank_lines, NULL, NULL); - pipeline_command (p, cmd); - } - - if (isatty (STDOUT_FILENO)) { - if (ascii) { - pipecmd *tr_cmd; - tr_cmd = pipecmd_new_argstr - (get_def_user ("tr", TR TR_SET1 TR_SET2)); - pipecmd_pre_exec (tr_cmd, sandbox_load, sandbox_free, - sandbox); - pipeline_command (p, tr_cmd); - pager_cmd = pipecmd_new_argstr (pager); - } else -#ifdef TROFF_IS_GROFF - if (!htmlout) - /* format_display deals with html_pager */ -#endif - pager_cmd = pipecmd_new_argstr (pager); - } - - if (pager_cmd) { - setenv_less (pager_cmd, title); - pipeline_command (p, pager_cmd); - } - pipeline_ignore_signals (p, 1); - - if (!pipeline_get_ncommands (p)) - /* Always return at least a dummy pipeline. */ - pipeline_command (p, pipecmd_new_passthrough ()); - return p; -} - - -/* return a (malloced) temporary name in cat_file's directory */ -static char *tmp_cat_filename (const char *cat_file) -{ - char *name; - - if (debug_level) { - name = xstrdup ("/dev/null"); - tmp_cat_fd = open (name, O_WRONLY); - } else { - char *slash; - name = xstrdup (cat_file); - slash = strrchr (name, '/'); - if (slash) - *(slash + 1) = '\0'; - else - *name = '\0'; - name = appendstr (name, "catXXXXXX", (void *) 0); - tmp_cat_fd = mkstemp (name); - } - - if (tmp_cat_fd == -1) { - free (name); - return NULL; - } else - return name; -} - - -/* If delete unlink tmp_cat, else commit tmp_cat to cat_file. - Return non-zero on error. - */ -static int commit_tmp_cat (const char *cat_file, const char *tmp_cat, - int delete) -{ - int status = 0; - -#ifdef MAN_OWNER - if (!delete && global_manpath && euid == 0) { - if (debug_level) { - debug ("fixing temporary cat's ownership\n"); - status = 0; - } else { - struct passwd *man_owner = get_man_owner (); - status = chown (tmp_cat, man_owner->pw_uid, - man_owner->pw_gid); - if (status) - error (0, errno, _("can't chown %s"), tmp_cat); - } - } -#endif /* MAN_OWNER */ - - if (!delete && !status) { - if (debug_level) { - debug ("fixing temporary cat's mode\n"); - status = 0; - } else { - status = chmod (tmp_cat, CATMODE); - if (status) - error (0, errno, _("can't chmod %s"), tmp_cat); - } - } - - if (!delete && !status) { - if (debug_level) { - debug ("renaming temporary cat to %s\n", cat_file); - status = 0; - } else { - status = rename (tmp_cat, cat_file); - if (status) - error (0, errno, _("can't rename %s to %s"), - tmp_cat, cat_file); - } - } - - if (!delete && !status) { - if (debug_level) { - debug ("setting modtime on cat file %s\n", cat_file); - status = 0; - } else { - struct timespec times[2]; - - times[0].tv_sec = 0; - times[0].tv_nsec = UTIME_NOW; - times[1] = man_modtime; - status = utimens (cat_file, times); - if (status) - error (0, errno, _("can't set times on %s"), - cat_file); - } - } - - if (delete || status) { - if (debug_level) - debug ("unlinking temporary cat\n"); - else if (unlink (tmp_cat)) - error (0, errno, _("can't unlink %s"), tmp_cat); - } - - return status; -} - -/* TODO: This should all be refactored after work on the decompression - * library is complete. - */ -static void discard_stderr (pipeline *p) -{ - int i; - - for (i = 0; i < pipeline_get_ncommands (p); ++i) - pipecmd_discard_err (pipeline_get_command (p, i), 1); -} - -static void maybe_discard_stderr (pipeline *p) -{ - const char *man_keep_stderr = getenv ("MAN_KEEP_STDERR"); - if ((!man_keep_stderr || !*man_keep_stderr) && isatty (STDOUT_FILENO)) - discard_stderr (p); -} - -static void chdir_commands (pipeline *p, const char *dir) -{ - int i; - - for (i = 0; i < pipeline_get_ncommands (p); ++i) - pipecmd_chdir (pipeline_get_command (p, i), dir); -} - -static void cleanup_unlink (void *arg) -{ - const char *path = arg; - - if (unlink (path)) - error (0, errno, _("can't unlink %s"), path); -} - -#ifdef MAN_CATS - -/* Return pipeline to write formatted manual page to for saving as cat file. */ -static pipeline *open_cat_stream (const char *cat_file, const char *encoding) -{ - pipeline *cat_p; -# ifdef COMP_CAT - pipecmd *comp_cmd; -# endif - - created_tmp_cat = 0; - - debug ("creating temporary cat for %s\n", cat_file); - - tmp_cat_file = tmp_cat_filename (cat_file); - if (tmp_cat_file) - created_tmp_cat = 1; - else { - if (!debug_level && (errno == EACCES || errno == EROFS)) { - /* No permission to write to the cat file. Oh well, - * return NULL and let the caller sort it out. - */ - debug ("can't write to temporary cat for %s\n", - cat_file); - return NULL; - } else - error (FATAL, errno, - _("can't create temporary cat for %s"), - cat_file); - } - - if (!debug_level) - push_cleanup (cleanup_unlink, tmp_cat_file, 1); - - cat_p = pipeline_new (); - add_output_iconv (cat_p, encoding, "UTF-8"); -# ifdef COMP_CAT - /* fork the compressor */ - comp_cmd = pipecmd_new_argstr (get_def ("compressor", COMPRESSOR)); - pipecmd_nice (comp_cmd, 10); - pipecmd_pre_exec (comp_cmd, sandbox_load, sandbox_free, sandbox); - pipeline_command (cat_p, comp_cmd); -# endif - /* pipeline_start will close tmp_cat_fd */ - pipeline_want_out (cat_p, tmp_cat_fd); - - return cat_p; -} - -/* Close the cat page stream, return non-zero on error. - If delete don't update the cat file. - */ -static int close_cat_stream (pipeline *cat_p, const char *cat_file, - int delete) -{ - int status; - - status = pipeline_wait (cat_p); - debug ("cat-saver exited with status %d\n", status); - - pipeline_free (cat_p); - - if (created_tmp_cat) { - status |= commit_tmp_cat (cat_file, tmp_cat_file, - delete || status); - if (!debug_level) - pop_cleanup (cleanup_unlink, tmp_cat_file); - } - free (tmp_cat_file); - return status; -} - -/* - * format a manual page with format_cmd, display it with disp_cmd, and - * save it to cat_file - */ -static int format_display_and_save (pipeline *decomp, - pipeline *format_cmd, - pipeline *disp_cmd, - const char *cat_file, const char *encoding) -{ - pipeline *sav_p = open_cat_stream (cat_file, encoding); - int instat; - - if (global_manpath) - drop_effective_privs (); - - maybe_discard_stderr (format_cmd); - - pipeline_connect (decomp, format_cmd, (void *) 0); - if (sav_p) { - pipeline_connect (format_cmd, disp_cmd, sav_p, (void *) 0); - pipeline_pump (decomp, format_cmd, disp_cmd, sav_p, - (void *) 0); - } else { - pipeline_connect (format_cmd, disp_cmd, (void *) 0); - pipeline_pump (decomp, format_cmd, disp_cmd, (void *) 0); - } - - if (global_manpath) - regain_effective_privs (); - - pipeline_wait (decomp); - instat = pipeline_wait (format_cmd); - if (sav_p) - close_cat_stream (sav_p, cat_file, instat); - pipeline_wait (disp_cmd); - return instat; -} -#endif /* MAN_CATS */ - -/* Format a manual page with format_cmd and display it with disp_cmd. - * Handle temporary file creation if necessary. - * TODO: merge with format_display_and_save - */ -static void format_display (pipeline *decomp, - pipeline *format_cmd, pipeline *disp_cmd, - const char *man_file) -{ - int format_status = 0, disp_status = 0; -#ifdef TROFF_IS_GROFF - char *htmldir = NULL, *htmlfile = NULL; -#endif /* TROFF_IS_GROFF */ - - if (format_cmd) - maybe_discard_stderr (format_cmd); - - drop_effective_privs (); - -#ifdef TROFF_IS_GROFF - if (format_cmd && htmlout) { - char *man_base, *man_ext; - int htmlfd; - - htmldir = create_tempdir ("hman"); - if (!htmldir) - error (FATAL, errno, - _("can't create temporary directory")); - chdir_commands (format_cmd, htmldir); - chdir_commands (disp_cmd, htmldir); - man_base = base_name (man_file); - man_ext = strchr (man_base, '.'); - if (man_ext) - *man_ext = '\0'; - htmlfile = xasprintf ("%s/%s.html", htmldir, man_base); - free (man_base); - htmlfd = open (htmlfile, O_CREAT | O_EXCL | O_WRONLY, 0644); - if (htmlfd == -1) - error (FATAL, errno, _("can't open temporary file %s"), - htmlfile); - pipeline_want_out (format_cmd, htmlfd); - pipeline_connect (decomp, format_cmd, (void *) 0); - pipeline_pump (decomp, format_cmd, (void *) 0); - pipeline_wait (decomp); - format_status = pipeline_wait (format_cmd); - } else -#endif /* TROFF_IS_GROFF */ - if (format_cmd) { - pipeline_connect (decomp, format_cmd, (void *) 0); - pipeline_connect (format_cmd, disp_cmd, (void *) 0); - pipeline_pump (decomp, format_cmd, disp_cmd, (void *) 0); - pipeline_wait (decomp); - format_status = pipeline_wait (format_cmd); - disp_status = pipeline_wait (disp_cmd); - } else { - pipeline_connect (decomp, disp_cmd, (void *) 0); - pipeline_pump (decomp, disp_cmd, (void *) 0); - pipeline_wait (decomp); - disp_status = pipeline_wait (disp_cmd); - } - -#ifdef TROFF_IS_GROFF - if (format_cmd && htmlout) { - char *browser_list, *candidate; - - if (format_status) { - if (remove_directory (htmldir, 0) == -1) - error (0, errno, - _("can't remove directory %s"), - htmldir); - free (htmlfile); - free (htmldir); - gripe_system (format_cmd, format_status); - } - - browser_list = xstrdup (html_pager); - for (candidate = strtok (browser_list, ":"); candidate; - candidate = strtok (NULL, ":")) { - pipeline *browser; - debug ("Trying browser: %s\n", candidate); - browser = make_browser (candidate, htmlfile); - disp_status = pipeline_run (browser); - if (!disp_status) - break; - } - if (!candidate) { - if (html_pager && *html_pager) - error (CHILD_FAIL, 0, - "couldn't execute any browser from %s", - html_pager); - else - error (CHILD_FAIL, 0, - "no browser configured, so cannot show " - "HTML output"); - } - free (browser_list); - if (remove_directory (htmldir, 0) == -1) - error (0, errno, _("can't remove directory %s"), - htmldir); - free (htmlfile); - free (htmldir); - } else -#endif /* TROFF_IS_GROFF */ - { - if (format_status && format_status != (SIGPIPE + 0x80) * 256) - gripe_system (format_cmd, format_status); - if (disp_status && disp_status != (SIGPIPE + 0x80) * 256) - gripe_system (disp_cmd, disp_status); - } - - regain_effective_privs (); -} - -/* "Display" a page in catman mode, which amounts to saving it. */ -/* TODO: merge with format_display_and_save? */ -static void display_catman (const char *cat_file, pipeline *decomp, - pipeline *format_cmd, const char *encoding) -{ - char *tmpcat = tmp_cat_filename (cat_file); -#ifdef COMP_CAT - pipecmd *comp_cmd; -#endif /* COMP_CAT */ - int status; - - add_output_iconv (format_cmd, encoding, "UTF-8"); - -#ifdef COMP_CAT - comp_cmd = pipecmd_new_argstr (get_def ("compressor", COMPRESSOR)); - pipecmd_pre_exec (comp_cmd, sandbox_load, sandbox_free, sandbox); - pipeline_command (format_cmd, comp_cmd); -#endif /* COMP_CAT */ - - maybe_discard_stderr (format_cmd); - pipeline_want_out (format_cmd, tmp_cat_fd); - - push_cleanup (cleanup_unlink, tmpcat, 1); - - /* save the cat as real user - * (1) required for user man hierarchy - * (2) else depending on ruid's privs is ok, effectively disables - * catman for non-root. - */ - drop_effective_privs (); - pipeline_connect (decomp, format_cmd, (void *) 0); - pipeline_pump (decomp, format_cmd, (void *) 0); - pipeline_wait (decomp); - status = pipeline_wait (format_cmd); - regain_effective_privs (); - if (status) - gripe_system (format_cmd, status); - - close (tmp_cat_fd); - commit_tmp_cat (cat_file, tmpcat, status); - pop_cleanup (cleanup_unlink, tmpcat); - free (tmpcat); -} - -static void disable_hyphenation (void *data _GL_UNUSED) -{ - fputs (".nh\n" - ".de hy\n" - "..\n" - ".lf 1\n", stdout); -} - -static void disable_justification (void *data _GL_UNUSED) -{ - fputs (".na\n" - ".de ad\n" - "..\n" - ".lf 1\n", stdout); -} - -#ifdef TROFF_IS_GROFF -static void locale_macros (void *data) -{ - const char *macro_lang = data; - const char *hyphen_lang = STREQ (lang, "en") ? "us" : macro_lang; - - debug ("Macro language %s; hyphenation language %s\n", - macro_lang, hyphen_lang); - - printf ( - /* If we're using groff >= 1.20.2 (for the 'file' warning - * category): - */ - ".if \\n[.g] \\{\\\n" - ". ds Ystring \\n[.Y]\n" - ". while (\\B'\\*[Ystring]' = 0) .chop Ystring\n" - ". if ((\\n[.x] > 1) :" - " ((\\n[.x] == 1) & (\\n[.y] > 20)) :" - " ((\\n[.x] == 1) & (\\n[.y] == 20) & (\\*[Ystring] >= 2))) " - "\\{\\\n" - /* disable warnings of category 'file' */ - ". warn (\\n[.warn] -" - " (\\n[.warn] / 1048576 %% 2 * 1048576))\n" - /* and load the appropriate per-locale macros */ - ". mso %s.tmac\n" - ". \\}\n" - ". rm Ystring\n" - ".\\}\n" - /* set the hyphenation language anyway, to make sure groff - * only hyphenates languages it knows about - */ - ".hla %s\n" - ".lf 1\n", macro_lang, hyphen_lang); -} -#endif /* TROFF_IS_GROFF */ - -/* allow user to skip a page or quit after viewing desired page - return 1 to skip - return 0 to view - */ -static int do_prompt (const char *name) -{ - int ch; - FILE *tty = NULL; - - skip = 0; - if (!isatty (STDOUT_FILENO) || !isatty (STDIN_FILENO)) - return 0; /* noninteractive */ - tty = fopen ("/dev/tty", "r+"); - if (!tty) - return 0; - - fprintf (tty, _( - "--Man-- next: %s " - "[ view (return) | skip (Ctrl-D) | quit (Ctrl-C) ]\n"), - name); - fflush (tty); - - do { - ch = getc (tty); - switch (ch) { - case '\n': - fclose (tty); - return 0; - case EOF: - skip = 1; - fclose (tty); - return 1; - default: - break; - } - } while (1); - - fclose (tty); - return 0; -} - -/* - * optionally chdir to dir, if necessary update cat_file from man_file - * and display it. if man_file is NULL cat_file is a stray cat. If - * !save_cat or cat_file is NULL we must not save the formatted cat. - * If man_file is "" this is a special case -- we expect the man page - * on standard input. - */ -static int display (const char *dir, const char *man_file, - const char *cat_file, const char *title, - const char *dbfilters) -{ - int found; - static int prompt; - int prefixes = 0; - pipeline *format_cmd; /* command to format man_file to stdout */ - char *formatted_encoding = NULL; - bool display_to_stdout; - pipeline *decomp = NULL; - int decomp_errno = 0; - - /* define format_cmd */ - if (man_file) { - pipecmd *seq = pipecmd_new_sequence ("decompressor", - (void *) 0); - - if (*man_file) - decomp = decompress_open (man_file); - else - decomp = decompress_fdopen (dup (STDIN_FILENO)); - - if (!recode && no_hyphenation) { - pipecmd *hcmd = pipecmd_new_function ( - "echo .nh && echo .de hy && echo ..", - disable_hyphenation, NULL, NULL); - pipecmd_sequence_command (seq, hcmd); - ++prefixes; - } - - if (!recode && no_justification) { - pipecmd *jcmd = pipecmd_new_function ( - "echo .na && echo .de ad && echo ..", - disable_justification, NULL, NULL); - pipecmd_sequence_command (seq, jcmd); - ++prefixes; - } - -#ifdef TROFF_IS_GROFF - /* This only works with preconv, since the per-locale macros - * may change the assumed input encoding. - */ - if (!recode && *man_file && get_groff_preconv ()) { - char *page_lang = lang_dir (man_file); - - if (page_lang && *page_lang && - !STREQ (page_lang, "C")) { - struct locale_bits bits; - char *name; - pipecmd *lcmd; - - unpack_locale_bits (page_lang, &bits); - name = xasprintf ("echo .mso %s.tmac", - bits.language); - lcmd = pipecmd_new_function ( - name, locale_macros, free, - xstrdup (bits.language)); - pipecmd_sequence_command (seq, lcmd); - ++prefixes; - free (name); - free_locale_bits (&bits); - } - free (page_lang); - } -#endif /* TROFF_IS_GROFF */ - - if (prefixes) { - assert (pipeline_get_ncommands (decomp) <= 1); - if (pipeline_get_ncommands (decomp)) { - pipecmd_sequence_command - (seq, - pipeline_get_command (decomp, 0)); - pipeline_set_command (decomp, 0, seq); - } else { - pipecmd_sequence_command - (seq, pipecmd_new_passthrough ()); - pipeline_command (decomp, seq); - } - } else - pipecmd_free (seq); - } - - if (decomp) { - char *pp_string; - - pipeline_start (decomp); - pp_string = get_preprocessors (decomp, dbfilters, prefixes); - format_cmd = make_roff_command (dir, man_file, decomp, - pp_string, - &formatted_encoding); - if (dir) - chdir_commands (format_cmd, dir); - debug ("formatted_encoding = %s\n", formatted_encoding); - free (pp_string); - } else { - format_cmd = NULL; - decomp_errno = errno; - } - - /* Get modification time, for commit_tmp_cat(). */ - if (man_file && *man_file) { - struct stat stb; - if (stat (man_file, &stb)) { - man_modtime.tv_sec = 0; - man_modtime.tv_nsec = 0; - } else - man_modtime = get_stat_mtime (&stb); - } - - display_to_stdout = troff; -#ifdef TROFF_IS_GROFF - if (htmlout || gxditview) - display_to_stdout = false; -#endif - if (recode) - display_to_stdout = true; - - if (display_to_stdout) { - /* If we're reading stdin via '-l -', man_file is "". See - * below. - */ - assert (man_file); - if (!decomp) { - assert (!format_cmd); /* no need to free it */ - error (0, decomp_errno, _("can't open %s"), man_file); - return 0; - } - if (*man_file == '\0') - found = 1; - else - found = CAN_ACCESS (man_file, R_OK); - if (found) { - int status; - if (prompt && do_prompt (title)) { - pipeline_free (format_cmd); - pipeline_free (decomp); - free (formatted_encoding); - return 0; - } - drop_effective_privs (); - pipeline_connect (decomp, format_cmd, (void *) 0); - pipeline_pump (decomp, format_cmd, (void *) 0); - pipeline_wait (decomp); - status = pipeline_wait (format_cmd); - regain_effective_privs (); - if (status != 0) - gripe_system (format_cmd, status); - } - } else { - int format = 1; - int status; - - /* The caller should already have checked for any - * FSSTND-style (same hierarchy) cat page that may be - * present, and we don't expect to have to update the cat - * page in that case. If by some chance we do have to update - * it, then there's no harm trying; open_cat_stream() will - * refuse gracefully if the file isn't writeable. - */ - - /* In theory we might be able to get away with saving cats - * for want_encoding, but it does change the roff device so - * perhaps that's best avoided. - */ - if (want_encoding -#ifdef TROFF_IS_GROFF - || htmlout - || gxditview -#endif - || local_man_file - || recode - || disable_cache - || no_hyphenation - || no_justification) - save_cat = false; - - if (!man_file) { - /* Stray cat. */ - assert (cat_file); - format = 0; - } else if (!cat_file) { - assert (man_file); - save_cat = false; - format = 1; - } else if (format && save_cat) { - char *cat_dir; - char *tmp; - - status = is_changed (man_file, cat_file); - format = (status == -2) || ((status & 1) == 1); - - /* don't save if we haven't a cat directory */ - cat_dir = xstrdup (cat_file); - tmp = strrchr (cat_dir, '/'); - if (tmp) - *tmp = 0; - save_cat = is_directory (cat_dir) == 1; - if (!save_cat) - debug ("cat dir %s does not exist\n", cat_dir); - free (cat_dir); - } - - if (format && (!format_cmd || !decomp)) { - assert (man_file); - /* format_cmd is NULL iff decomp is NULL; no need to - * free either of them. - */ - assert (!format_cmd); - assert (!decomp); - error (0, decomp_errno, _("can't open %s"), man_file); - return 0; - } - - /* if we're trying to read stdin via '-l -' then man_file - * will be "" which access() obviously barfs on, but all is - * well because the format_cmd will have been created to - * expect input via stdin. So we special-case this to avoid - * the bogus access() check. - */ - if (format == 1 && *man_file == '\0') - found = 1; - else - found = CAN_ACCESS - (format ? man_file : cat_file, R_OK); - - debug ("format: %d, save_cat: %d, found: %d\n", - format, (int) save_cat, found); - - if (!found) { - pipeline_free (format_cmd); - pipeline_free (decomp); - return found; - } - - if (print_where || print_where_cat) { - int printed = 0; - if (print_where && man_file) { - printf ("%s", man_file); - printed = 1; - } - if (print_where_cat && cat_file && !format) { - if (printed) - putchar (' '); - printf ("%s", cat_file); - printed = 1; - } - if (printed) - putchar ('\n'); - } else if (catman) { - if (format) { - if (!save_cat) - error (0, 0, - _("\ncannot write to " - "%s in catman mode"), - cat_file); - else - display_catman (cat_file, decomp, - format_cmd, - formatted_encoding); - } - } else if (format) { - /* no cat or out of date */ - pipeline *disp_cmd; - - if (prompt && do_prompt (title)) { - pipeline_free (format_cmd); - pipeline_free (decomp); - free (formatted_encoding); - if (local_man_file) - return 1; - else - return 0; - } - - disp_cmd = make_display_command (formatted_encoding, - title); - -#ifdef MAN_CATS - if (save_cat) { - /* save cat */ - assert (disp_cmd); /* not htmlout for now */ - format_display_and_save (decomp, - format_cmd, - disp_cmd, - cat_file, - formatted_encoding); - } else -#endif /* MAN_CATS */ - /* don't save cat */ - format_display (decomp, format_cmd, disp_cmd, - man_file); - - pipeline_free (disp_cmd); - - } else { - /* display preformatted cat */ - pipeline *disp_cmd; - pipeline *decomp_cat; - - if (prompt && do_prompt (title)) { - pipeline_free (format_cmd); - pipeline_free (decomp); - return 0; - } - - decomp_cat = decompress_open (cat_file); - if (!decomp_cat) { - error (0, errno, _("can't open %s"), cat_file); - pipeline_free (format_cmd); - pipeline_free (decomp); - return 0; - } - disp_cmd = make_display_command ("UTF-8", title); - format_display (decomp_cat, NULL, disp_cmd, man_file); - pipeline_free (disp_cmd); - pipeline_free (decomp_cat); - } - } - - free (formatted_encoding); - - pipeline_free (format_cmd); - pipeline_free (decomp); - - if (!prompt) - prompt = found; - - return found; -} - -static _Noreturn void gripe_converting_name (const char *name) -{ - error (FATAL, 0, _("Can't convert %s to cat name"), name); - abort (); /* error should have exited; help compilers prove noreturn */ -} - -/* Convert the trailing part of 'name' to be a cat page path by altering its - * extension appropriately. If fsstnd is set, also try converting the - * containing directory name from "man1" to "cat1" etc., returning NULL if - * that doesn't work. - * - * fsstnd should only be set if name is the original path of a man page - * found in a man hierarchy, not something like a symlink target or a file - * named with 'man -l'. Otherwise, a symlink to "/home/manuel/foo.1.gz" - * would be converted to "/home/catuel/foo.1.gz", which would be bad. - */ -static char *convert_name (const char *name, int fsstnd) -{ - char *to_name, *t1 = NULL; - char *t2 = NULL; - struct compression *comp; - char *namestem; - - comp = comp_info (name, 1); - if (comp) - namestem = comp->stem; - else - namestem = xstrdup (name); - -#ifdef COMP_CAT - /* TODO: BSD layout requires .0. */ - to_name = xasprintf ("%s.%s", namestem, COMPRESS_EXT); -#else /* !COMP_CAT */ - to_name = xstrdup (namestem); -#endif /* COMP_CAT */ - free (namestem); - - if (fsstnd) { - t1 = strrchr (to_name, '/'); - if (!t1) - gripe_converting_name (name); - *t1 = '\0'; - - t2 = strrchr (to_name, '/'); - if (!t2) - gripe_converting_name (name); - ++t2; - *t1 = '/'; - - if (STRNEQ (t2, "man", 3)) { - /* If the second-last component starts with "man", - * replace "man" with "cat". - */ - *t2 = 'c'; - *(t2 + 2) = 't'; - } else { - free (to_name); - debug ("couldn't convert %s to FSSTND cat file\n", - name); - return NULL; - } - } - - debug ("converted %s to %s\n", name, to_name); - - return to_name; -} - -static char *find_cat_file (const char *path, const char *original, - const char *man_file) -{ - size_t path_len = strlen (path); - char *cat_file, *cat_path; - int status; - - /* Try the FSSTND way first, namely a cat page in the same hierarchy - * as the original path to the man page. We don't create these - * unless no alternate cat hierarchy is available, but will use them - * if they happen to exist already and have the same timestamp as - * the corresponding man page. (In practice I'm betting that this - * means we'll hardly ever use them at all except for user - * hierarchies; but compatibility, eh?) - */ - cat_file = convert_name (original, 1); - if (cat_file) { - status = is_changed (original, cat_file); - if (status != -2 && (!(status & 1)) == 1) { - debug ("found valid FSSTND cat file %s\n", cat_file); - return cat_file; - } - free (cat_file); - } - - /* Otherwise, find the cat page we actually want to use or create, - * taking any alternate cat hierarchy into account. If the original - * path and man_file differ (i.e. original was a symlink or .so - * link), try the link target and then the source. - */ - if (!STREQ (man_file, original)) { - global_manpath = is_global_mandir (man_file); - cat_path = get_catpath - (man_file, global_manpath ? SYSTEM_CAT : USER_CAT); - - if (cat_path) { - cat_file = convert_name (cat_path, 0); - free (cat_path); - } else if (STRNEQ (man_file, path, path_len) && - man_file[path_len] == '/') - cat_file = convert_name (man_file, 1); - else - cat_file = NULL; - - if (cat_file) { - char *cat_dir = xstrdup (cat_file); - char *tmp = strrchr (cat_dir, '/'); - if (tmp) - *tmp = 0; - if (is_directory (cat_dir)) { - debug ("will try cat file %s\n", cat_file); - free (cat_dir); - return cat_file; - } else - debug ("cat dir %s does not exist\n", cat_dir); - free (cat_dir); - } else - debug ("no cat path for %s\n", man_file); - } - - global_manpath = is_global_mandir (original); - cat_path = get_catpath - (original, global_manpath ? SYSTEM_CAT : USER_CAT); - - if (cat_path) { - cat_file = convert_name (cat_path, 0); - free (cat_path); - } else - cat_file = convert_name (original, 1); - - if (cat_file) - debug ("will try cat file %s\n", cat_file); - else - debug ("no cat path for %s\n", original); - - return cat_file; -} - -static int get_ult_flags (char from_db, char id) -{ - if (!from_db) - return ult_flags; - else if (id == ULT_MAN) - /* Checking .so links is expensive, as we have to open the - * file. Therefore, if the database lists it as ULT_MAN, - * that's good enough for us and we won't recheck that. This - * does mean that if a page changes from ULT_MAN to SO_MAN - * then you might get duplicates until mandb is next run, - * but that isn't a big deal. - */ - return ult_flags & ~SO_LINK; - else - return ult_flags; -} - -/* Is this candidate substantially a duplicate of a previous one? - * Returns true if so, otherwise false. - */ -static bool duplicate_candidates (struct candidate *left, - struct candidate *right) -{ - const char *slash1, *slash2; - struct locale_bits bits1, bits2; - bool ret; - - if (left->ult && right->ult && STREQ (left->ult, right->ult)) - return true; /* same ultimate source file */ - - if (!STREQ (left->source->name, right->source->name) || - !STREQ (left->source->sec, right->source->sec) || - !STREQ (left->source->ext, right->source->ext)) - return false; /* different name/section/extension */ - - if (STREQ (left->path, right->path)) - return true; /* same path */ - - /* Figure out if we've had a sufficiently similar candidate for this - * language already. - */ - slash1 = strrchr (left->path, '/'); - slash2 = strrchr (right->path, '/'); - if (!slash1 || !slash2 || - !STRNEQ (left->path, right->path, - MAX (slash1 - left->path, slash2 - right->path))) - return false; /* different path base */ - - unpack_locale_bits (++slash1, &bits1); - unpack_locale_bits (++slash2, &bits2); - - if (!STREQ (bits1.language, bits2.language) || - !STREQ (bits1.territory, bits2.territory) || - !STREQ (bits1.modifier, bits2.modifier)) - ret = false; /* different language/territory/modifier */ - else - /* Everything seems to be the same; we can find nothing to - * choose between them. - */ - ret = true; - - free_locale_bits (&bits1); - free_locale_bits (&bits2); - return ret; -} - -static int compare_candidates (const struct candidate *left, - const struct candidate *right) -{ - const struct mandata *lsource = left->source, *rsource = right->source; - int cmp; - const char *slash1, *slash2; - - /* If one candidate matches the requested name exactly, sort it - * first. This makes --ignore-case behave more sensibly. - */ - /* name is never NULL here, see add_candidate() */ - if (STREQ (lsource->name, left->req_name)) { - if (!STREQ (rsource->name, right->req_name)) - return -1; - } else { - if (STREQ (rsource->name, right->req_name)) - return 1; - } - - /* Compare pure sections first, then ids, then extensions. - * Rationale: whatis refs get the same section and extension as - * their source, but may be supplanted by a real page with a - * slightly different extension, possibly in another hierarchy (!); - * see Debian bug #204249 for the gory details. - * - * Any extension spelt out in full in section_list effectively - * becomes a pure section; this allows extensions to be selectively - * moved out of order with respect to their parent sections. - */ - if (strcmp (lsource->ext, rsource->ext)) { - size_t index_left, index_right; - - /* If the user asked for an explicit section, sort exact - * matches first. - */ - if (section) { - if (STREQ (lsource->ext, section)) { - if (!STREQ (rsource->ext, section)) - return -1; - } else { - if (STREQ (rsource->ext, section)) - return 1; - } - } - - /* Find out whether lsource->ext is ahead of rsource->ext in - * section_list. Sections missing from section_list are - * sorted to the end. - */ - index_left = gl_list_indexof (section_list, lsource->ext); - if (index_left == (size_t) -1 && strlen (lsource->ext) > 1) { - char *sec_left = xstrndup (lsource->ext, 1); - index_left = gl_list_indexof (section_list, sec_left); - free (sec_left); - if (index_left == (size_t) -1) - index_left = gl_list_size (section_list); - } - index_right = gl_list_indexof (section_list, rsource->ext); - if (index_right == (size_t) -1 && strlen (rsource->ext) > 1) { - char *sec_right = xstrndup (rsource->ext, 1); - index_right = gl_list_indexof (section_list, - sec_right); - free (sec_right); - if (index_right == (size_t) -1) - index_right = gl_list_size (section_list); - } - if (index_left < index_right) - return -1; - else if (index_left > index_right) - return 1; - - cmp = strcmp (lsource->sec, rsource->sec); - if (cmp) - return cmp; - } - - /* ULT_MAN comes first, etc. Consider SO_MAN equivalent to ULT_MAN. */ - cmp = compare_ids (lsource->id, rsource->id, 1); - if (cmp) - return cmp; - - /* The order in section_list has already been compared above. For - * everything not mentioned explicitly there, we just compare - * lexically. - */ - cmp = strcmp (lsource->ext, rsource->ext); - if (cmp) - return cmp; - - /* Try comparing based on language. We used to prefer to display a - * page in the user's preferred language than a page from a better - * section, but that attracted objections, so now we prefer to get - * the section right. See Debian bug #519547. - */ - slash1 = strrchr (left->path, '/'); - slash2 = strrchr (right->path, '/'); - if (slash1 && slash2) { - char *locale_copy, *p; - struct locale_bits bits1, bits2, lbits; - const char *codeset1, *codeset2; - - unpack_locale_bits (++slash1, &bits1); - unpack_locale_bits (++slash2, &bits2); - - /* We need the current locale as well. */ - locale_copy = xstrdup (internal_locale); - p = strchr (locale_copy, ':'); - if (p) - *p = '\0'; - unpack_locale_bits (locale_copy, &lbits); - free (locale_copy); - -#define COMPARE_LOCALE_ELEMENTS(elt) do { \ - /* For different elements, prefer one that matches the locale if - * possible. - */ \ - if (*lbits.elt) { \ - if (STREQ (lbits.elt, bits1.elt)) { \ - if (!STREQ (lbits.elt, bits2.elt)) { \ - cmp = -1; \ - goto out; \ - } \ - } else { \ - if (STREQ (lbits.elt, bits2.elt)) { \ - cmp = 1; \ - goto out; \ - } \ - } \ - } \ - cmp = strcmp (bits1.elt, bits2.elt); \ - if (cmp) \ - /* No help from locale; might as well sort lexically. */ \ - goto out; \ -} while (0) - - COMPARE_LOCALE_ELEMENTS (language); - COMPARE_LOCALE_ELEMENTS (territory); - COMPARE_LOCALE_ELEMENTS (modifier); - -#undef COMPARE_LOCALE_ELEMENTS - - /* Prefer UTF-8 if available. Otherwise, consider them - * equal. - */ - codeset1 = get_canonical_charset_name (bits1.codeset); - codeset2 = get_canonical_charset_name (bits2.codeset); - if (STREQ (codeset1, "UTF-8")) { - if (!STREQ (codeset2, "UTF-8")) { - cmp = -1; - goto out; - } - } else { - if (STREQ (codeset2, "UTF-8")) { - cmp = 1; - goto out; - } - } - -out: - free_locale_bits (&lbits); - free_locale_bits (&bits1); - free_locale_bits (&bits2); - if (cmp) - return cmp; - } - - /* Explicitly stabilise the sort as a last resort, so that manpath - * ordering (e.g. language-specific hierarchies) works. - */ - if (left->add_index < right->add_index) - return -1; - else if (left->add_index > right->add_index) - return 1; - else - return 0; - - return 0; -} - -static int compare_candidates_qsort (const void *l, const void *r) -{ - const struct candidate *left = *(const struct candidate **)l; - const struct candidate *right = *(const struct candidate **)r; - - return compare_candidates (left, right); -} - -static void free_candidate (struct candidate *candidate) -{ - if (candidate) - free (candidate->ult); - free (candidate); -} - -/* Add an entry to the list of candidates. */ -static int add_candidate (struct candidate **head, char from_db, char cat, - const char *req_name, const char *path, - const char *ult, struct mandata *source) -{ - struct candidate *search, *prev, *insert, *candp; - static int add_index = 0; - - if (!ult) { - const char *name; - char *filename; - - if (*source->pointer != '-') - name = source->pointer; - else if (source->name) - name = source->name; - else - name = req_name; - - filename = make_filename (path, name, source, cat ? "cat" : "man"); - if (!filename) - return 0; - ult = ult_src (filename, path, NULL, - get_ult_flags (from_db, source->id), NULL); - free (filename); - } - - debug ("candidate: %d %d %s %s %s %c %s %s %s\n", - from_db, cat, req_name, path, ult, - source->id, source->name ? source->name : "-", - source->sec, source->ext); - - if (!source->name) - source->name = xstrdup (req_name); - - candp = XMALLOC (struct candidate); - candp->req_name = req_name; - candp->from_db = from_db; - candp->cat = cat; - candp->path = path; - candp->ult = ult ? xstrdup (ult) : NULL; - candp->source = source; - candp->add_index = add_index++; - candp->next = NULL; - - /* insert will be NULL (insert at start) or a pointer to the element - * after which this element should be inserted. - */ - insert = NULL; - search = *head; - prev = NULL; - /* This search produces quadratic-time behaviour, although in - * practice it doesn't seem to be too bad at the moment since the - * run-time is dominated by calls to ult_src. In future it might be - * worth optimising this; the reason I haven't done this yet is that - * it involves quite a bit of tedious bookkeeping. A practical - * approach would be to keep two hashes, one that's just a set to - * keep track of whether candp->ult has been seen already, and one - * that keeps a list of candidates for each candp->name that could - * then be quickly checked by brute force. - */ - while (search) { - int dupcand = duplicate_candidates (candp, search); - - debug ("search: %d %d %s %s %s %c %s %s %s " - "(dup: %d)\n", - search->from_db, search->cat, search->req_name, - search->path, search->ult, search->source->id, - search->source->name ? search->source->name : "-", - search->source->sec, search->source->ext, dupcand); - - /* Check for duplicates. */ - if (dupcand) { - int cmp = compare_candidates (candp, search); - - if (cmp >= 0) { - debug ("other duplicate is at least as " - "good\n"); - free_candidate (candp); - return 0; - } else { - debug ("this duplicate is better; removing " - "old one\n"); - if (prev) { - prev->next = search->next; - free_candidate (search); - search = prev->next; - } else { - *head = search->next; - free_candidate (search); - search = *head; - } - continue; - } - } - - prev = search; - if (search->next) - search = search->next; - else - break; - } - /* Insert the new candidate at the end of the list (having had to go - * through them all looking for duplicates anyway); we'll sort it - * into place later. - */ - insert = prev; - - candp->next = insert ? insert->next : *head; - if (insert) - insert->next = candp; - else - *head = candp; - - return 1; -} - -/* Sort the entire list of candidates. */ -static void sort_candidates (struct candidate **candidates) -{ - struct candidate *cand, **allcands; - size_t count = 0, i; - - for (cand = *candidates; cand; cand = cand->next) - ++count; - - if (count == 0) - return; - - allcands = XNMALLOC (count, struct candidate *); - i = 0; - for (cand = *candidates; cand; cand = cand->next) { - assert (i < count); - allcands[i++] = cand; - } - assert (i == count); - - qsort (allcands, count, sizeof *allcands, compare_candidates_qsort); - - *candidates = cand = allcands[0]; - for (i = 1; i < count; ++i) { - cand->next = allcands[i]; - cand = cand->next; - } - cand->next = NULL; - - free (allcands); -} - -/* - * See if the preformatted man page or the source exists in the given - * section. - */ -static int try_section (const char *path, const char *sec, const char *name, - struct candidate **cand_head) -{ - int found = 0; - gl_list_t names = NULL; - const char *found_name; - char cat = 0; - int lff_opts = (match_case ? LFF_MATCHCASE : 0) | - (regex_opt ? LFF_REGEX : 0) | - (wildcard ? LFF_WILDCARD : 0); - - debug ("trying section %s with globbing\n", sec); - -#ifndef NROFF_MISSING /* #ifdef NROFF */ - /* - * Look for man page source files. - */ - - names = look_for_file (path, sec, name, 0, lff_opts); - if (!gl_list_size (names)) - /* - * No files match. - * See if there's a preformatted page around that - * we can display. - */ -#endif /* NROFF_MISSING */ - { - if (catman) - return 1; - - if (!troff && !want_encoding && !recode) { - gl_list_free (names); - names = look_for_file (path, sec, name, 1, lff_opts); - cat = 1; - } - } - - order_files (path, &names); - - GL_LIST_FOREACH_START (names, found_name) { - struct mandata *info = infoalloc (); - char *info_buffer = filename_info (found_name, info, name); - const char *ult; - int f; - - if (!info_buffer) { - free_mandata_struct (info); - continue; - } - info->addr = info_buffer; - - /* What kind of page is this? Since it's a real file, it - * must be either ULT_MAN or SO_MAN. ult_src() can tell us - * which. - */ - ult = ult_src (found_name, path, NULL, ult_flags, NULL); - if (!ult) { - /* already warned */ - debug ("try_section(): bad link %s\n", found_name); - free (info_buffer); - info->addr = NULL; - free_mandata_struct (info); - continue; - } - if (STREQ (ult, found_name)) - info->id = ULT_MAN; - else - info->id = SO_MAN; - - f = add_candidate (cand_head, CANDIDATE_FILESYSTEM, - cat, name, path, ult, info); - found += f; - /* Free info and info_buffer if they weren't added to the - * candidates. - */ - if (f == 0) { - free (info_buffer); - info->addr = NULL; - free_mandata_struct (info); - } - /* Don't free info and info_buffer here. */ - } GL_LIST_FOREACH_END (names); - - gl_list_free (names); - return found; -} - -static int display_filesystem (struct candidate *candp) -{ - char *filename = make_filename (candp->path, NULL, candp->source, - candp->cat ? "cat" : "man"); - char *title; - int found = 0; - - if (!filename) - return 0; - /* source->name is never NULL thanks to add_candidate() */ - title = xasprintf ("%s(%s)", candp->source->name, candp->source->ext); - - if (candp->cat) { - if (troff || want_encoding || recode) - goto out; - found = display (candp->path, NULL, filename, title, NULL); - } else { - const char *man_file; - char *cat_file; - - man_file = ult_src (filename, candp->path, NULL, ult_flags, - NULL); - if (man_file == NULL) - goto out; - - debug ("found ultimate source file %s\n", man_file); - lang = lang_dir (man_file); - - cat_file = find_cat_file (candp->path, filename, man_file); - found = display (candp->path, man_file, cat_file, title, NULL); - free (cat_file); - free (lang); - lang = NULL; - } - -out: - free (title); - free (filename); - return found; -} - -#ifdef MAN_DB_UPDATES -/* wrapper to dbdelete which deals with opening/closing the db */ -static void dbdelete_wrapper (const char *page, struct mandata *info) -{ - if (!catman) { - MYDBM_FILE dbf; - - dbf = MYDBM_RWOPEN (database); - if (dbf) { - if (dbdelete (dbf, page, info) == 1) - debug ("%s(%s) not in db!\n", page, info->ext); - MYDBM_CLOSE (dbf); - } - } -} -#endif /* MAN_DB_UPDATES */ - -/* This started out life as try_section, but a lot of that routine is - redundant wrt the db cache. */ -static int display_database (struct candidate *candp) -{ - int found = 0; - char *file; - const char *name; - char *title; - struct mandata *in = candp->source; - - debug ("trying a db located file.\n"); - dbprintf (in); - - /* if the pointer holds some data, this is a reference to the - real page, use that instead. */ - if (*in->pointer != '-') - name = in->pointer; - else if (in->name) - name = in->name; - else - name = candp->req_name; - - if (in->id == WHATIS_MAN || in->id == WHATIS_CAT) - debug (_("%s: relying on whatis refs is deprecated\n"), name); - - title = xasprintf ("%s(%s)", - in->name ? in->name : candp->req_name, in->ext); - -#ifndef NROFF_MISSING /* #ifdef NROFF */ - /* - * Look for man page source files. - */ - - if (in->id < STRAY_CAT) { /* There should be a src page */ - file = make_filename (candp->path, name, in, "man"); - if (file) { - const char *man_file; - char *cat_file; - - man_file = ult_src (file, candp->path, NULL, - get_ult_flags (1, in->id), NULL); - if (man_file == NULL) { - free (title); - return found; /* zero */ - } - - debug ("found ultimate source file %s\n", man_file); - lang = lang_dir (man_file); - - cat_file = find_cat_file (candp->path, file, man_file); - found += display (candp->path, man_file, cat_file, - title, in->filter); - free (cat_file); - free (lang); - lang = NULL; - free (file); - } /* else {drop through to the bottom and return 0 anyway} */ - } else - -#endif /* NROFF_MISSING */ - - if (in->id <= WHATIS_CAT) { - /* The db says we have a stray cat or whatis ref */ - - if (catman) { - free (title); - return ++found; - } - - /* show this page but force an update later to make sure - we haven't just added the new page */ - found_a_stray = 1; - - /* If explicitly asked for troff or a different encoding, - * don't show a stray cat. - */ - if (troff || want_encoding || recode) { - free (title); - return found; - } - - file = make_filename (candp->path, name, in, "cat"); - if (!file) { - char *catpath; - catpath = get_catpath (candp->path, - global_manpath ? SYSTEM_CAT - : USER_CAT); - - if (catpath && strcmp (catpath, candp->path) != 0) { - file = make_filename (catpath, name, - in, "cat"); - free (catpath); - if (!file) { - /* don't delete here, - return==0 will do that */ - free (title); - return found; /* zero */ - } - } else { - free (catpath); - free (title); - return found; /* zero */ - } - } - - found += display (candp->path, NULL, file, title, in->filter); - free (file); - } - free (title); - return found; -} - -/* test for existence, if fail: call dbdelete_wrapper, else return amount */ -static int display_database_check (struct candidate *candp) -{ - int exists = display_database (candp); - -#ifdef MAN_DB_UPDATES - if (!exists && !skip) { - debug ("dbdelete_wrapper (%s, %p)\n", - candp->req_name, candp->source); - dbdelete_wrapper (candp->req_name, candp->source); - } -#endif /* MAN_DB_UPDATES */ - - return exists; -} - -#ifdef MAN_DB_UPDATES -static int maybe_update_file (const char *manpath, const char *name, - struct mandata *info) -{ - const char *real_name; - char *file; - struct stat buf; - struct timespec file_mtime; - int status; - - if (!update) - return 0; - - /* If the pointer holds some data, then we need to look at that - * name in the filesystem instead. - */ - if (!STRNEQ (info->pointer, "-", 1)) - real_name = info->pointer; - else if (info->name) - real_name = info->name; - else - real_name = name; - - file = make_filename (manpath, real_name, info, "man"); - if (!file) - return 0; - if (lstat (file, &buf) != 0) - return 0; - file_mtime = get_stat_mtime (&buf); - if (timespec_cmp (file_mtime, info->mtime) == 0) - return 0; - - debug ("%s needs to be recached: %ld.%09ld %ld.%09ld\n", - file, - (long) info->mtime.tv_sec, (long) info->mtime.tv_nsec, - (long) file_mtime.tv_sec, (long) file_mtime.tv_nsec); - status = run_mandb (0, manpath, file); - if (status) - error (0, 0, _("mandb command failed with exit status %d"), - status); - free (file); - - return 1; -} -#endif /* MAN_DB_UPDATES */ - -/* Special return values from try_db(). */ - -#define TRY_DATABASE_OPEN_FAILED -1 - -#ifdef MAN_DB_CREATES -#define TRY_DATABASE_CREATED -2 -#endif /* MAN_DB_CREATES */ - -#ifdef MAN_DB_UPDATES -#define TRY_DATABASE_UPDATED -3 -#endif /* MAN_DB_UPDATES */ - -static void db_map_value_free (const void *value) -{ - /* The value may be NULL to indicate that opening the database at - * this location already failed. - */ - if (value) - gl_list_free ((gl_list_t) value); -} - -/* Look for a page in the database. If db not accessible, return -1, - otherwise return number of pages found. */ -static int try_db (const char *manpath, const char *sec, const char *name, - struct candidate **cand_head) -{ - gl_list_t matches; - struct mandata *loc; - char *catpath; - int found = 0; -#ifdef MAN_DB_UPDATES - bool found_stale = false; -#endif /* MAN_DB_UPDATES */ - - /* find out where our db for this manpath should be */ - - catpath = get_catpath (manpath, global_manpath ? SYSTEM_CAT : USER_CAT); - free (database); - if (catpath) { - database = mkdbname (catpath); - free (catpath); - } else - database = mkdbname (manpath); - - if (!db_map) - db_map = new_string_map (GL_HASH_MAP, db_map_value_free); - - /* If we haven't looked here already, do so now. */ - if (!gl_map_search (db_map, manpath, (const void **) &matches)) { - MYDBM_FILE dbf; - - dbf = MYDBM_RDOPEN (database); - if (dbf && dbver_rd (dbf)) { - MYDBM_CLOSE (dbf); - dbf = NULL; - } - if (dbf) { - debug ("Succeeded in opening %s O_RDONLY\n", database); - - /* if section is set, only return those that match, - otherwise NULL retrieves all available */ - if (regex_opt || wildcard) - matches = dblookup_pattern - (dbf, name, section, match_case, - regex_opt, !names_only); - else - matches = dblookup_all (dbf, name, section, - match_case); - gl_map_put (db_map, xstrdup (manpath), matches); - MYDBM_CLOSE (dbf); - dbf = NULL; -#ifdef MAN_DB_CREATES - } else if (!global_manpath) { - /* create one */ - debug ("Failed to open %s O_RDONLY\n", database); - if (run_mandb (1, manpath, NULL)) { - gl_map_put (db_map, xstrdup (manpath), NULL); - return TRY_DATABASE_OPEN_FAILED; - } - return TRY_DATABASE_CREATED; -#endif /* MAN_DB_CREATES */ - } else { - debug ("Failed to open %s O_RDONLY\n", database); - gl_map_put (db_map, xstrdup (manpath), NULL); - return TRY_DATABASE_OPEN_FAILED; - } - assert (matches != NULL); - } - - /* We already tried (and failed) to open this db before. */ - if (!matches) - return TRY_DATABASE_OPEN_FAILED; - -#ifdef MAN_DB_UPDATES - /* Check that all the entries found are up to date. If not, the - * caller should try again. - */ - GL_LIST_FOREACH_START (matches, loc) - if (STREQ (sec, loc->sec) && - (!extension || STREQ (extension, loc->ext) - || STREQ (extension, loc->ext + strlen (sec)))) - if (maybe_update_file (manpath, name, loc)) - found_stale = true; - GL_LIST_FOREACH_END (matches); - - if (found_stale) { - gl_map_remove (db_map, manpath); - return TRY_DATABASE_UPDATED; - } -#endif /* MAN_DB_UPDATES */ - - /* cycle through the mandata structures (there's usually only - 1 or 2) and see what we have w.r.t. the current section */ - GL_LIST_FOREACH_START (matches, loc) - if (STREQ (sec, loc->sec) && - (!extension || STREQ (extension, loc->ext) - || STREQ (extension, loc->ext + strlen (sec)))) - found += add_candidate (cand_head, CANDIDATE_DATABASE, - 0, name, manpath, NULL, loc); - GL_LIST_FOREACH_END (matches); - - return found; -} - -/* Try to locate the page under the specified manpath, in the desired section, - * with the supplied name. Glob if necessary. Initially search the filesystem; - * if that fails, try finding it via a db cache access. */ -static int locate_page (const char *manpath, const char *sec, const char *name, - struct candidate **candidates) -{ - int found, db_ok; - - /* sort out whether we want to treat this hierarchy as - global or user. Differences: - - global: if setuid, use privs; don't create db. - user : if setuid, drop privs; allow db creation. */ - - global_manpath = is_global_mandir (manpath); - if (!global_manpath) - drop_effective_privs (); - - debug ("searching in %s, section %s\n", manpath, sec); - - found = try_section (manpath, sec, name, candidates); - - if ((!found || findall) && !global_apropos) { - db_ok = try_db (manpath, sec, name, candidates); - -#ifdef MAN_DB_CREATES - if (db_ok == TRY_DATABASE_CREATED) - /* we created a db in the last call */ - db_ok = try_db (manpath, sec, name, candidates); -#endif /* MAN_DB_CREATES */ - -#ifdef MAN_DB_UPDATES - if (db_ok == TRY_DATABASE_UPDATED) - /* We found some outdated entries and rebuilt the - * database in the last call. If this keeps - * happening, though, give up and punt to the - * filesystem. - */ - db_ok = try_db (manpath, sec, name, candidates); -#endif /* MAN_DB_UPDATES */ - - if (db_ok > 0) /* we found/opened a db and found something */ - found += db_ok; - } - - if (!global_manpath) - regain_effective_privs (); - - return found; -} - -static int display_pages (struct candidate *candidates) -{ - struct candidate *candp; - int found = 0; - - for (candp = candidates; candp; candp = candp->next) { - global_manpath = is_global_mandir (candp->path); - if (!global_manpath) - drop_effective_privs (); - - switch (candp->from_db) { - case CANDIDATE_FILESYSTEM: - found += display_filesystem (candp); - break; - case CANDIDATE_DATABASE: - found += display_database_check (candp); - break; - default: - error (0, 0, - _("internal error: candidate type %d " - "out of range"), candp->from_db); - } - - if (!global_manpath) - regain_effective_privs (); - - if (found && !findall) - return found; - } - - return found; -} - -/* - * Search for text in all manual pages. - * - * This is not a real full-text search, but a brute-force on-demand search. - * The idea, name, and approach originate in the 'man' package, added (I - * believe) by Andries Brouwer, although the implementation is new for - * man-db and much faster due to running in-process. - * - * Conceptually, this really belongs in whatis.c, as part of apropos. - * However, the implementation in 'man' offers pages for immediate display - * on request rather than simply listing them, which is currently awkward to - * do in apropos. If we ever add support to apropos/whatis for either - * calling back to man or displaying pages directly, we should revisit this. - */ -static int grep (const char *file, const char *string, const regex_t *search) -{ - struct stat st; - pipeline *decomp; - const char *line; - int ret = 0; - - /* pipeline_start makes file open failures unconditionally fatal. - * Here, we'd rather just ignore any such files. - */ - if (stat (file, &st) < 0) - return 0; - - decomp = decompress_open (file); - if (!decomp) - return 0; - pipeline_start (decomp); - while ((line = pipeline_readline (decomp)) != NULL) { - if (regex_opt) { - if (regexec (search, line, - 0, (regmatch_t *) 0, 0) == 0) { - ret = 1; - break; - } - } else { - if (match_case ? - strstr (line, string) : - strcasestr (line, string)) { - ret = 1; - break; - } - } - } - - pipeline_free (decomp); - return ret; -} - -static int do_global_apropos_section (const char *path, const char *sec, - const char *name) -{ - int found = 0; - gl_list_t names; - const char *found_name; - regex_t search; - - global_manpath = is_global_mandir (path); - if (!global_manpath) - drop_effective_privs (); - - debug ("searching in %s, section %s\n", path, sec); - - names = look_for_file (path, sec, "*", 0, LFF_WILDCARD); - - if (regex_opt) - xregcomp (&search, name, - REG_EXTENDED | REG_NOSUB | - (match_case ? 0 : REG_ICASE)); - else - memset (&search, 0, sizeof search); - - order_files (path, &names); - - GL_LIST_FOREACH_START (names, found_name) { - struct mandata *info; - char *info_buffer; - char *title = NULL; - const char *man_file; - char *cat_file = NULL; - - if (!grep (found_name, name, &search)) - continue; - - info = infoalloc (); - info_buffer = filename_info (found_name, info, NULL); - if (!info_buffer) - goto next; - info->addr = info_buffer; - - title = xasprintf ("%s(%s)", strchr (info_buffer, '\0') + 1, - info->ext); - man_file = ult_src (found_name, path, NULL, ult_flags, NULL); - if (!man_file) - goto next; - lang = lang_dir (man_file); - cat_file = find_cat_file (path, found_name, man_file); - if (display (path, man_file, cat_file, title, NULL)) - found = 1; - free (lang); - lang = NULL; - -next: - free (cat_file); - free (title); - free_mandata_struct (info); - } GL_LIST_FOREACH_END (names); - - gl_list_free (names); - - if (regex_opt) - regfree (&search); - - if (!global_manpath) - regain_effective_privs (); - - return found; -} - -static int do_global_apropos (const char *name, int *found) -{ - gl_list_t my_section_list; - const char *sec; - - if (section) { - my_section_list = gl_list_create_empty (GL_ARRAY_LIST, NULL, - NULL, NULL, false); - gl_list_add_last (my_section_list, section); - } else - my_section_list = section_list; - - GL_LIST_FOREACH_START (my_section_list, sec) { - char *mp; - - GL_LIST_FOREACH_START (manpathlist, mp) - *found += do_global_apropos_section (mp, sec, name); - GL_LIST_FOREACH_END (manpathlist); - } GL_LIST_FOREACH_END (my_section_list); - - if (section) - gl_list_free (my_section_list); - - return *found ? OK : NOT_FOUND; -} - -/* Each of local_man_loop and man sometimes calls the other. */ -static int man (const char *name, int *found); - -/* man issued with `-l' option */ -static int local_man_loop (const char *argv) -{ - int exit_status = OK; - bool local_mf = local_man_file; - - drop_effective_privs (); - local_man_file = true; - if (strcmp (argv, "-") == 0) - display (NULL, "", NULL, "(stdin)", NULL); - else { - struct stat st; - - /* Check that the file exists and isn't e.g. a directory */ - if (stat (argv, &st)) { - error (0, errno, "%s", argv); - return NOT_FOUND; - } - - if (S_ISDIR (st.st_mode)) { - error (0, EISDIR, "%s", argv); - return NOT_FOUND; - } - - if (S_ISCHR (st.st_mode) || S_ISBLK (st.st_mode)) { - /* EINVAL is about the best I can do. */ - error (0, EINVAL, "%s", argv); - return NOT_FOUND; - } - - if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { - /* Perhaps an executable. If its directory is on - * $PATH, then we want to look up the corresponding - * manual page in the appropriate hierarchy rather - * than displaying the executable. - */ - char *argv_dir = dir_name (argv); - int found = 0; - - if (directory_on_path (argv_dir)) { - char *argv_base = base_name (argv); - char *new_manp, *nm; - gl_list_t old_manpathlist; - - debug ("recalculating manpath for executable " - "in %s\n", argv_dir); - - new_manp = get_manpath_from_path (argv_dir, 0); - if (!new_manp || !*new_manp) { - debug ("no useful manpath for " - "executable\n"); - goto executable_out; - } - nm = locale_manpath (new_manp); - free (new_manp); - new_manp = nm; - - old_manpathlist = manpathlist; - manpathlist = create_pathlist (new_manp); - - man (argv_base, &found); - - free_pathlist (manpathlist); - manpathlist = old_manpathlist; -executable_out: - free (new_manp); - free (argv_base); - } - free (argv_dir); - - if (found) - return OK; - } - - if (exit_status == OK) { - char *argv_base = base_name (argv); - char *argv_abs; - if (argv[0] == '/') - argv_abs = xstrdup (argv); - else { - argv_abs = xgetcwd (); - if (argv_abs) - argv_abs = appendstr (argv_abs, "/", - argv, - (void *) 0); - else - argv_abs = xstrdup (argv); - } - lang = lang_dir (argv_abs); - free (argv_abs); - if (!display (NULL, argv, NULL, argv_base, NULL)) { - if (local_mf) - error (0, errno, "%s", argv); - exit_status = NOT_FOUND; - } - free (lang); - lang = NULL; - free (argv_base); - } - } - local_man_file = local_mf; - regain_effective_privs (); - return exit_status; -} - -/* - * Splits a "name[.section]" or "name(section)" into { "name", "section" }. - * Section would be NULL if not present. - * The caller is responsible for freeing *ret_name and *ret_section. - * */ -static void split_page_name (const char *page_name, - char **ret_name, - char **ret_section) -{ - char *dot, *lparen, *rparen; - - dot = strrchr (page_name, '.'); - if (dot && is_section (dot + 1)) { - *ret_name = xstrndup (page_name, dot - page_name); - *ret_section = xstrdup (dot + 1); - return; - } - - lparen = strrchr (page_name, '('); - rparen = strrchr (page_name, ')'); - if (lparen && rparen && rparen > lparen) { - char *paren_section = xstrndup - (lparen + 1, rparen - lparen - 1); - if (is_section (paren_section)) { - *ret_name = xstrndup (page_name, lparen - page_name); - *ret_section = paren_section; /* steal memory */ - return; - } - free (paren_section); - } - - *ret_name = xstrdup (page_name); - *ret_section = NULL; -} - -static void locate_page_in_manpath (const char *page_section, - const char *page_name, - struct candidate **candidates, - int *found) -{ - char *mp; - - GL_LIST_FOREACH_START (manpathlist, mp) - *found += locate_page (mp, page_section, page_name, - candidates); - GL_LIST_FOREACH_END (manpathlist); -} - -/* - * Search for manual pages. - * - * If preformatted manual pages are supported, look for the formatted - * file first, then the man page source file. If they both exist and - * the man page source file is newer, or only the source file exists, - * try to reformat it and write the results in the cat directory. If - * it is not possible to write the cat file, simply format and display - * the man file. - * - * If preformatted pages are not supported, or the troff option is - * being used, only look for the man page source file. - * - */ -static int man (const char *name, int *found) -{ - char *page_name, *page_section; - struct candidate *candidates = NULL, *cand, *candnext; - - *found = 0; - fflush (stdout); - - if (strchr (name, '/')) { - int status = local_man_loop (name); - if (status == OK) - *found = 1; - return status; - } - - if (section) - locate_page_in_manpath (section, name, &candidates, found); - else { - const char *sec; - - GL_LIST_FOREACH_START (section_list, sec) - locate_page_in_manpath (sec, name, &candidates, found); - GL_LIST_FOREACH_END (section_list); - } - - split_page_name (name, &page_name, &page_section); - - if (!*found && page_section) - locate_page_in_manpath (page_section, page_name, &candidates, - found); - - free (page_name); - free (page_section); - - sort_candidates (&candidates); - - if (*found) - *found = display_pages (candidates); - - for (cand = candidates; cand; cand = candnext) { - candnext = cand->next; - free_candidate (cand); - } - - return *found ? OK : NOT_FOUND; -} - - -static gl_list_t get_section_list (void) -{ - gl_list_t config_sections, sections; - const char *sec; - - /* Section list from configuration file, or STD_SECTIONS if it's - * empty. - */ - config_sections = get_sections (); - if (!gl_list_size (config_sections)) { - int i; - for (i = 0; std_sections[i]; ++i) - gl_list_add_last (config_sections, - xstrdup (std_sections[i])); - } - - if (colon_sep_section_list == NULL) - colon_sep_section_list = getenv ("MANSECT"); - if (colon_sep_section_list == NULL || *colon_sep_section_list == '\0') - return config_sections; - - /* Although this is documented as colon-separated, at least Solaris - * man's -s option takes a comma-separated list, so we accept that - * too for compatibility. - */ - sections = new_string_list (GL_ARRAY_LIST, true); - for (sec = strtok (colon_sep_section_list, ":,"); sec; - sec = strtok (NULL, ":,")) - gl_list_add_last (sections, xstrdup (sec)); - - if (gl_list_size (sections)) { - gl_list_free (config_sections); - return sections; - } else { - gl_list_free (sections); - return config_sections; - } -} - -/* - * Returns the first token of a libpipeline/sh-style command. See SUSv4TC2: - * 2.2 Shell Command Language: Quoting. - * - * Free the returned value. - * - * Examples: - * sh_lang_first_word ("echo 3") returns "echo" - * sh_lang_first_word ("'e ho' 3") returns "e ho" - * sh_lang_first_word ("e\\cho 3") returns "echo" - * sh_lang_first_word ("e\\\ncho 3") returns "echo" - * sh_lang_first_word ("\"echo t\" 3") returns "echo t" - * sh_lang_first_word ("\"ech\\o t\" 3") returns "ech\\o t" - * sh_lang_first_word ("\"ech\\\\o t\" 3") returns "ech\\o t" - * sh_lang_first_word ("\"ech\\\no t\" 3") returns "echo t" - * sh_lang_first_word ("\"ech\\$ t\" 3") returns "ech$ t" - * sh_lang_first_word ("\"ech\\` t\" 3") returns "ech` t" - * sh_lang_first_word ("e\"ch\"o 3") returns "echo" - * sh_lang_first_word ("e'ch'o 3") returns "echo" - */ -static char *sh_lang_first_word (const char *cmd) -{ - int i, o = 0; - char *ret = xmalloc (strlen (cmd) + 1); - - for (i = 0; cmd[i] != '\0'; i++) { - if (cmd[i] == '\\') { - /* Escape Character (Backslash) */ - i++; - if (cmd[i] == '\0') - break; - if (cmd[i] != '\n') - ret[o++] = cmd[i]; - } else if (cmd[i] == '\'') { - /* Single-Quotes */ - i++; - while (cmd[i] != '\0' && cmd[i] != '\'') - ret[o++] = cmd[i++]; - } else if (cmd[i] == '"') { - /* Double-Quotes */ - i++; - while (cmd[i] != '\0' && cmd[i] != '"') { - if (cmd[i] == '\\') { - if (cmd[i + 1] == '$' || - cmd[i + 1] == '`' || - cmd[i + 1] == '"' || - cmd[i + 1] == '\\') - ret[o++] = cmd[++i]; - else if (cmd[i + 1] == '\n') - i++; - else - ret[o++] = cmd[i]; - } else - ret[o++] = cmd[i]; - - i++; - } - } else if (cmd[i] == '\t' || cmd[i] == ' ' || cmd[i] == '\n' || - cmd[i] == '#') - break; - else - ret[o++] = cmd[i]; - } - - ret[o] = '\0'; - - return ret; -} - -int main (int argc, char *argv[]) -{ - int argc_env, exit_status = OK; - char **argv_env; - const char *tmp; - - set_program_name (argv[0]); - - init_debug (); - pipeline_install_post_fork (pop_all_cleanups); - sandbox = sandbox_init (); - - umask (022); - init_locale (); - - internal_locale = setlocale (LC_MESSAGES, NULL); - /* Use LANGUAGE only when LC_MESSAGES locale category is - * neither "C" nor "POSIX". */ - if (internal_locale && strcmp (internal_locale, "C") && - strcmp (internal_locale, "POSIX")) - multiple_locale = getenv ("LANGUAGE"); - internal_locale = xstrdup (internal_locale ? internal_locale : "C"); - - xstdopen (); - -/* export argv, it might be needed when invoking the vendor supplied browser */ -#if defined _AIX || defined __sgi - global_argv = argv; -#endif - -#ifdef TROFF_IS_GROFF - /* used in --help, so initialise early */ - if (!html_pager) - init_html_pager (); -#endif /* TROFF_IS_GROFF */ - -#ifdef NROFF_WARNINGS - roff_warnings = new_string_list (GL_ARRAY_LIST, true); -#endif /* NROFF_WARNINGS */ - - /* First of all, find out if $MANOPT is set. If so, put it in - *argv[] format for argp to play with. */ - argv_env = manopt_to_env (&argc_env); - if (argv_env) - if (argp_parse (&argp, argc_env, argv_env, ARGP_NO_ARGS, 0, 0)) - exit (FAIL); - - /* parse the actual program args */ - if (argp_parse (&argp, argc, argv, ARGP_NO_ARGS, &first_arg, 0)) - exit (FAIL); - - /* record who we are and drop effective privs for later use */ - init_security (); - - read_config_file (local_man_file || user_config_file); - - /* if the user wants whatis or apropos, give it to them... */ - if (external) - do_extern (argc, argv); - - get_term (); /* stores terminal settings */ - - /* close this locale and reinitialise if a new locale was - issued as an argument or in $MANOPT */ - if (locale) { - free (internal_locale); - internal_locale = setlocale (LC_ALL, locale); - if (internal_locale) - internal_locale = xstrdup (internal_locale); - else - internal_locale = xstrdup (locale); - - debug ("main(): locale = %s, internal_locale = %s\n", - locale, internal_locale); - if (internal_locale) { - setenv ("LANGUAGE", internal_locale, 1); - locale_changed (); - multiple_locale = NULL; - } - } - -#ifdef TROFF_IS_GROFF - if (htmlout) - pager = html_pager; -#endif /* TROFF_IS_GROFF */ - - if (pager == NULL) - pager = getenv ("MANPAGER"); - if (pager == NULL) - pager = getenv ("PAGER"); - if (pager == NULL) - pager = get_def_user ("pager", NULL); - if (pager == NULL) { - char *pager_program = sh_lang_first_word (PAGER); - if (pathsearch_executable (pager_program)) - pager = PAGER; - else - pager = ""; - free (pager_program); - } - if (*pager == '\0') - pager = get_def_user ("cat", CAT); - - if (prompt_string == NULL) - prompt_string = getenv ("MANLESS"); - - if (prompt_string == NULL) -#ifdef LESS_PROMPT - prompt_string = LESS_PROMPT; -#else - prompt_string = _( - " Manual page " MAN_PN - " ?ltline %lt?L/%L.:byte %bB?s/%s..?e (END):" - "?pB %pB\\%.. " - "(press h for help or q to quit)"); -#endif - - /* Restore and save $LESS in $MAN_ORIG_LESS so that recursive uses - * of man work as expected. - */ - less = getenv ("MAN_ORIG_LESS"); - if (less == NULL) - less = getenv ("LESS"); - setenv ("MAN_ORIG_LESS", less ? less : "", 1); - - debug ("using %s as pager\n", pager); - - if (first_arg == argc) { - if (print_where) { - manp = get_manpath (""); - printf ("%s\n", manp); - exit (OK); - } else { - free (internal_locale); - gripe_no_name (NULL); - } - } - - section_list = get_section_list (); - - if (manp == NULL) { - char *mp = get_manpath (alt_system_name); - manp = locale_manpath (mp); - free (mp); - } else - free (get_manpath (NULL)); - - manpathlist = create_pathlist (manp); - - /* man issued with `-l' option */ - if (local_man_file) { - while (first_arg < argc) { - exit_status = local_man_loop (argv[first_arg]); - ++first_arg; - } - free (internal_locale); - exit (exit_status); - } - - /* finished manpath processing, regain privs */ - regain_effective_privs (); - -#ifdef MAN_DB_UPDATES - /* If `-u', do it now. */ - if (update) { - int status = run_mandb (0, NULL, NULL); - if (status) - error (0, 0, - _("mandb command failed with exit status %d"), - status); - } -#endif /* MAN_DB_UPDATES */ - - while (first_arg < argc) { - int status = OK; - int found = 0; - static int maybe_section = 0; - const char *nextarg = argv[first_arg++]; - - /* - * See if this argument is a valid section name. If not, - * is_section returns NULL. - */ - if (!catman) { - tmp = is_section (nextarg); - if (tmp) { - section = tmp; - debug ("\nsection: %s\n", section); - maybe_section = 1; - } - } - - if (maybe_section) { - if (first_arg < argc) - /* e.g. 'man 3perl Shell' */ - nextarg = argv[first_arg++]; - else - /* e.g. 'man 9wm' */ - section = NULL; - /* ... but leave maybe_section set so we can - * tell later that this happened. - */ - } - - /* this is where we actually start looking for the man page */ - skip = 0; - if (global_apropos) - status = do_global_apropos (nextarg, &found); - else { - bool found_subpage = false; - if (subpages && first_arg < argc) { - char *subname = xasprintf ( - "%s-%s", nextarg, argv[first_arg]); - status = man (subname, &found); - free (subname); - if (status == OK) { - found_subpage = true; - ++first_arg; - } - } - if (!found_subpage && subpages && first_arg < argc) { - char *subname = xasprintf ( - "%s_%s", nextarg, argv[first_arg]); - status = man (subname, &found); - free (subname); - if (status == OK) { - found_subpage = true; - ++first_arg; - } - } - if (!found_subpage) - status = man (nextarg, &found); - } - - /* clean out the cache of database lookups for each man page */ - if (db_map) { - gl_map_free (db_map); - db_map = NULL; - } - - if (section && maybe_section) { - if (status != OK && !catman) { - /* Maybe the section wasn't a section after - * all? e.g. 'man 9wm fvwm'. - */ - bool found_subpage = false; - debug ("\nRetrying section %s as name\n", - section); - tmp = section; - section = NULL; - if (subpages) { - char *subname = xasprintf ( - "%s-%s", tmp, nextarg); - status = man (subname, &found); - free (subname); - if (status == OK) { - found_subpage = true; - ++first_arg; - } - } - if (!found_subpage) - status = man (tmp, &found); - if (db_map) { - gl_map_free (db_map); - db_map = NULL; - } - /* ... but don't gripe about it if it doesn't - * work! - */ - if (status == OK) { - /* It was a name after all, so arrange - * to try the next page again with a - * null section. - */ - nextarg = tmp; - --first_arg; - } else - /* No go, it really was a section. */ - section = tmp; - } - } - - if (status != OK && !catman) { - if (!skip) { - exit_status = status; - if (exit_status == NOT_FOUND) { - if (!section && maybe_section && - CTYPE (isdigit, nextarg[0])) - gripe_no_name (nextarg); - else - gripe_no_man (nextarg, section); - } - } - } else { - debug ("\nFound %d man pages\n", found); - if (catman) { - printf ("%s", nextarg); - if (section) - printf ("(%s)", section); - if (first_arg != argc) - fputs (", ", stdout); - else - fputs (".\n", stdout); - } - } - - maybe_section = 0; - - chkr_garbage_detector (); - } - if (db_map) { - gl_map_free (db_map); - db_map = NULL; - } - - drop_effective_privs (); - - free (database); - gl_list_free (section_list); - free_pathlist (manpathlist); - free (internal_locale); - sandbox_free (sandbox); - exit (exit_status); -} |