]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/man/man.sh
libarchive: merge from vendor branch
[FreeBSD/FreeBSD.git] / usr.bin / man / man.sh
1 #! /bin/sh
2 #
3 # SPDX-License-Identifier: BSD-2-Clause
4 #
5 #  Copyright (c) 2010 Gordon Tetlow
6 #  All rights reserved.
7 #
8 #  Redistribution and use in source and binary forms, with or without
9 #  modification, are permitted provided that the following conditions
10 #  are met:
11 #  1. Redistributions of source code must retain the above copyright
12 #     notice, this list of conditions and the following disclaimer.
13 #  2. Redistributions in binary form must reproduce the above copyright
14 #     notice, this list of conditions and the following disclaimer in the
15 #     documentation and/or other materials provided with the distribution.
16 #
17 #  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 #  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 #  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 #  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 #  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 #  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 #  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 #  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 #  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 #  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 #  SUCH DAMAGE.
28 #
29 # $FreeBSD$
30
31 # Rendering a manual page is fast. Even a manual page several 100k in size
32 # takes less than a CPU second. If it takes much longer, it is very likely
33 # that a tool like mandoc(1) is running in an infinite loop. In this case
34 # it is better to terminate it.
35 ulimit -t 20
36
37 # Usage: add_to_manpath path
38 # Adds a variable to manpath while ensuring we don't have duplicates.
39 # Returns true if we were able to add something. False otherwise.
40 add_to_manpath() {
41         case "$manpath" in
42         *:$1)   decho "  Skipping duplicate manpath entry $1" 2 ;;
43         $1:*)   decho "  Skipping duplicate manpath entry $1" 2 ;;
44         *:$1:*) decho "  Skipping duplicate manpath entry $1" 2 ;;
45         *)      if [ -d "$1" ]; then
46                         decho "  Adding $1 to manpath"
47                         manpath="$manpath:$1"
48                         return 0
49                 fi
50                 ;;
51         esac
52
53         return 1
54 }
55
56 # Usage: build_manlocales
57 # Builds a correct MANLOCALES variable.
58 build_manlocales() {
59         # If the user has set manlocales, who are we to argue.
60         if [ -n "$MANLOCALES" ]; then
61                 return
62         fi
63
64         parse_configs
65
66         # Trim leading colon
67         MANLOCALES=${manlocales#:}
68
69         decho "Available manual locales: $MANLOCALES"
70 }
71
72 # Usage: build_mansect
73 # Builds a correct MANSECT variable.
74 build_mansect() {
75         # If the user has set mansect, who are we to argue.
76         if [ -n "$MANSECT" ]; then
77                 return
78         fi
79
80         parse_configs
81
82         # Trim leading colon
83         MANSECT=${mansect#:}
84
85         if [ -z "$MANSECT" ]; then
86                 MANSECT=$man_default_sections
87         fi
88         decho "Using manual sections: $MANSECT"
89 }
90
91 # Usage: build_manpath
92 # Builds a correct MANPATH variable.
93 build_manpath() {
94         local IFS
95
96         # If the user has set a manpath, who are we to argue.
97         if [ -n "$MANPATH" ]; then
98                 case "$MANPATH" in
99                 *:) PREPEND_MANPATH=${MANPATH} ;;
100                 :*) APPEND_MANPATH=${MANPATH} ;;
101                 *::*)
102                         PREPEND_MANPATH=${MANPATH%%::*}
103                         APPEND_MANPATH=${MANPATH#*::}
104                         ;;
105                 *) return ;;
106                 esac
107         fi
108
109         if [ -n "$PREPEND_MANPATH" ]; then
110                 IFS=:
111                 for path in $PREPEND_MANPATH; do
112                         add_to_manpath "$path"
113                 done
114                 unset IFS
115         fi
116
117         search_path
118
119         decho "Adding default manpath entries"
120         IFS=:
121         for path in $man_default_path; do
122                 add_to_manpath "$path"
123         done
124         unset IFS
125
126         parse_configs
127
128         if [ -n "$APPEND_MANPATH" ]; then
129                 IFS=:
130                 for path in $APPEND_MANPATH; do
131                         add_to_manpath "$path"
132                 done
133                 unset IFS
134         fi
135         # Trim leading colon
136         MANPATH=${manpath#:}
137
138         decho "Using manual path: $MANPATH"
139 }
140
141 # Usage: check_cat catglob
142 # Checks to see if a cat glob is available.
143 check_cat() {
144         if exists "$1"; then
145                 use_cat=yes
146                 catpage=$found
147                 setup_cattool $catpage
148                 decho "    Found catpage $catpage"
149                 return 0
150         else
151                 return 1
152         fi
153 }
154
155 # Usage: check_man manglob catglob
156 # Given 2 globs, figures out if the manglob is available, if so, check to
157 # see if the catglob is also available and up to date.
158 check_man() {
159         if exists "$1"; then
160                 # We have a match, check for a cat page
161                 manpage=$found
162                 setup_cattool $manpage
163                 decho "    Found manpage $manpage"
164
165                 if [ -n "${use_width}" ]; then
166                         # non-standard width
167                         unset use_cat
168                         decho "    Skipping catpage: non-standard page width"
169                 elif exists "$2" && is_newer $found $manpage; then
170                         # cat page found and is newer, use that
171                         use_cat=yes
172                         catpage=$found
173                         setup_cattool $catpage
174                         decho "    Using catpage $catpage"
175                 else
176                         # no cat page or is older
177                         unset use_cat
178                         decho "    Skipping catpage: not found or old"
179                 fi
180                 return 0
181         fi
182
183         return 1
184 }
185
186 # Usage: decho "string" [debuglevel]
187 # Echoes to stderr string prefaced with -- if high enough debuglevel.
188 decho() {
189         if [ $debug -ge ${2:-1} ]; then
190                 echo "-- $1" >&2
191         fi
192 }
193
194 # Usage: exists glob
195 # Returns true if glob resolves to a real file.
196 exists() {
197         local IFS
198
199         # Don't accidentally inherit callers IFS (breaks perl manpages)
200         unset IFS
201
202         # Use some globbing tricks in the shell to determine if a file
203         # exists or not.
204         set +f
205         set -- "$1" $1
206         set -f
207
208         if [ "$1" != "$2" -a -r "$2" ]; then
209                 found="$2"
210                 return 0
211         fi
212
213         return 1
214 }
215
216 # Usage: find_file path section subdir pagename
217 # Returns: true if something is matched and found.
218 # Search the given path/section combo for a given page.
219 find_file() {
220         local manroot catroot mann man0 catn cat0
221
222         manroot="$1/man$2"
223         catroot="$1/cat$2"
224         if [ -n "$3" ]; then
225                 manroot="$manroot/$3"
226                 catroot="$catroot/$3"
227         fi
228
229         if [ ! -d "$manroot" -a ! -d "$catroot" ]; then
230                 return 1
231         fi
232         decho "  Searching directory $manroot" 2
233
234         mann="$manroot/$4.$2*"
235         man0="$manroot/$4.0*"
236         catn="$catroot/$4.$2*"
237         cat0="$catroot/$4.0*"
238
239         # This is the behavior as seen by the original man utility.
240         # Let's not change that which doesn't seem broken.
241         if check_man "$mann" "$catn"; then
242                 return 0
243         elif check_man "$man0" "$cat0"; then
244                 return 0
245         elif check_cat "$catn"; then
246                 return 0
247         elif check_cat "$cat0"; then
248                 return 0
249         fi
250
251         return 1
252 }
253
254 # Usage: is_newer file1 file2
255 # Returns true if file1 is newer than file2 as calculated by mtime.
256 is_newer() {
257         if ! [ "$1" -ot "$2" ]; then
258                 decho "    mtime: $1 not older than $2" 3
259                 return 0
260         else
261                 decho "    mtime: $1 older than $2" 3
262                 return 1
263         fi
264 }
265
266 # Usage: manpath_parse_args "$@"
267 # Parses commandline options for manpath.
268 manpath_parse_args() {
269         local cmd_arg
270
271         OPTIND=1
272         while getopts 'Ldq' cmd_arg; do
273                 case "${cmd_arg}" in
274                 L)      Lflag=Lflag ;;
275                 d)      debug=$(( $debug + 1 )) ;;
276                 q)      qflag=qflag ;;
277                 *)      manpath_usage ;;
278                 esac
279         done >&2
280 }
281
282 # Usage: manpath_usage
283 # Display usage for the manpath(1) utility.
284 manpath_usage() {
285         echo 'usage: manpath [-Ldq]' >&2
286         exit 1
287 }
288
289 # Usage: manpath_warnings
290 # Display some warnings to stderr.
291 manpath_warnings() {
292         if [ -n "$Lflag" -a -n "$MANLOCALES" ]; then
293                 echo "(Warning: MANLOCALES environment variable set)" >&2
294         fi
295 }
296
297 # Usage: man_check_for_so page path
298 # Returns: True if able to resolve the file, false if it ended in tears.
299 # Detects the presence of the .so directive and causes the file to be
300 # redirected to another source file.
301 man_check_for_so() {
302         local IFS line tstr
303
304         unset IFS
305         if [ -n "$catpage" ]; then
306                 return 0
307         fi
308
309         # We need to loop to accommodate multiple .so directives.
310         while true
311         do
312                 line=$($cattool $manpage | head -1)
313                 case "$line" in
314                 .so*)   trim "${line#.so}"
315                         decho "$manpage includes $tstr"
316                         # Glob and check for the file.
317                         if ! check_man "$path/$tstr*" ""; then
318                                 decho "  Unable to find $tstr"
319                                 return 1
320                         fi
321                         ;;
322                 *)      break ;;
323                 esac
324         done
325
326         return 0
327 }
328
329 # Usage: man_display_page
330 # Display either the manpage or catpage depending on the use_cat variable
331 man_display_page() {
332         local IFS pipeline testline
333
334         # We are called with IFS set to colon. This causes really weird
335         # things to happen for the variables that have spaces in them.
336         unset IFS
337
338         # If we are supposed to use a catpage and we aren't using troff(1)
339         # just zcat the catpage and we are done.
340         if [ -z "$tflag" -a -n "$use_cat" ]; then
341                 if [ -n "$wflag" ]; then
342                         echo "$catpage (source: $manpage)"
343                         ret=0
344                 else
345                         if [ $debug -gt 0 ]; then
346                                 decho "Command: $cattool $catpage | $MANPAGER"
347                                 ret=0
348                         else
349                                 eval "$cattool $catpage | $MANPAGER"
350                                 ret=$?
351                         fi
352                 fi
353                 return
354         fi
355
356         # Okay, we are using the manpage, do we just need to output the
357         # name of the manpage?
358         if [ -n "$wflag" ]; then
359                 echo "$manpage"
360                 ret=0
361                 return
362         fi
363
364         if [ -n "$use_width" ]; then
365                 mandoc_args="-O width=${use_width}"
366         fi
367         testline="mandoc -Tlint -Wunsupp >/dev/null 2>&1"
368         if [ -n "$tflag" ]; then
369                 pipeline="mandoc -Tps $mandoc_args"
370         else
371                 pipeline="mandoc $mandoc_args | $MANPAGER"
372         fi
373
374         if ! eval "$cattool $manpage | $testline" ;then
375                 if which -s groff; then
376                         man_display_page_groff
377                 else
378                         echo "This manpage needs groff(1) to be rendered" >&2
379                         echo "First install groff(1): " >&2
380                         echo "pkg install groff " >&2
381                         ret=1
382                 fi
383                 return
384         fi
385
386         if [ $debug -gt 0 ]; then
387                 decho "Command: $cattool $manpage | $pipeline"
388                 ret=0
389         else
390                 eval "$cattool $manpage | $pipeline"
391                 ret=$?
392         fi
393 }
394
395 # Usage: man_display_page_groff
396 # Display the manpage using groff
397 man_display_page_groff() {
398         local EQN NROFF PIC TBL TROFF REFER VGRIND
399         local IFS l nroff_dev pipeline preproc_arg tool
400
401         # So, we really do need to parse the manpage. First, figure out the
402         # device flag (-T) we have to pass to eqn(1) and groff(1). Then,
403         # setup the pipeline of commands based on the user's request.
404
405         # If the manpage is from a particular charset, we need to setup nroff
406         # to properly output for the correct device.
407         case "${manpage}" in
408         *.${man_charset}/*)
409                 # I don't pretend to know this; I'm just copying from the
410                 # previous version of man(1).
411                 case "$man_charset" in
412                 KOI8-R)         nroff_dev="koi8-r" ;;
413                 ISO8859-1)      nroff_dev="latin1" ;;
414                 ISO8859-15)     nroff_dev="latin1" ;;
415                 UTF-8)          nroff_dev="utf8" ;;
416                 *)              nroff_dev="ascii" ;;
417                 esac
418
419                 NROFF="$NROFF -T$nroff_dev"
420                 EQN="$EQN -T$nroff_dev"
421
422                 # Iff the manpage is from the locale and not just the charset,
423                 # then we need to define the locale string.
424                 case "${manpage}" in
425                 */${man_lang}_${man_country}.${man_charset}/*)
426                         NROFF="$NROFF -dlocale=$man_lang.$man_charset"
427                         ;;
428                 */${man_lang}.${man_charset}/*)
429                         NROFF="$NROFF -dlocale=$man_lang.$man_charset"
430                         ;;
431                 esac
432
433                 # Allow language specific calls to override the default
434                 # set of utilities.
435                 l=$(echo $man_lang | tr [:lower:] [:upper:])
436                 for tool in EQN NROFF PIC TBL TROFF REFER VGRIND; do
437                         eval "$tool=\${${tool}_$l:-\$$tool}"
438                 done
439                 ;;
440         *)      NROFF="$NROFF -Tascii"
441                 EQN="$EQN -Tascii"
442                 ;;
443         esac
444
445         if [ -z "$MANCOLOR" ]; then
446                 NROFF="$NROFF -P-c"
447         fi
448
449         if [ -n "${use_width}" ]; then
450                 NROFF="$NROFF -rLL=${use_width}n -rLT=${use_width}n"
451         fi
452
453         if [ -n "$MANROFFSEQ" ]; then
454                 set -- -$MANROFFSEQ
455                 OPTIND=1
456                 while getopts 'egprtv' preproc_arg; do
457                         case "${preproc_arg}" in
458                         e)      pipeline="$pipeline | $EQN" ;;
459                         g)      ;; # Ignore for compatibility.
460                         p)      pipeline="$pipeline | $PIC" ;;
461                         r)      pipeline="$pipeline | $REFER" ;;
462                         t)      pipeline="$pipeline | $TBL" ;;
463                         v)      pipeline="$pipeline | $VGRIND" ;;
464                         *)      usage ;;
465                         esac
466                 done
467                 # Strip the leading " | " from the resulting pipeline.
468                 pipeline="${pipeline#" | "}"
469         else
470                 pipeline="$TBL"
471         fi
472
473         if [ -n "$tflag" ]; then
474                 pipeline="$pipeline | $TROFF"
475         else
476                 pipeline="$pipeline | $NROFF | $MANPAGER"
477         fi
478
479         if [ $debug -gt 0 ]; then
480                 decho "Command: $cattool $manpage | $pipeline"
481                 ret=0
482         else
483                 eval "$cattool $manpage | $pipeline"
484                 ret=$?
485         fi
486 }
487
488 # Usage: man_find_and_display page
489 # Search through the manpaths looking for the given page.
490 man_find_and_display() {
491         local found_page locpath p path sect
492
493         # Check to see if it's a file. But only if it has a '/' in
494         # the filename.
495         case "$1" in
496         */*)    if [ -f "$1" -a -r "$1" ]; then
497                         decho "Found a usable page, displaying that"
498                         unset use_cat
499                         manpage="$1"
500                         setup_cattool $manpage
501                         if man_check_for_so $manpage $(dirname $manpage); then
502                                 found_page=yes
503                                 man_display_page
504                         fi
505                         return
506                 fi
507                 ;;
508         esac
509
510         IFS=:
511         for sect in $MANSECT; do
512                 decho "Searching section $sect" 2
513                 for path in $MANPATH; do
514                         for locpath in $locpaths; do
515                                 p=$path/$locpath
516                                 p=${p%/.} # Rid ourselves of the trailing /.
517
518                                 # Check if there is a MACHINE specific manpath.
519                                 if find_file $p $sect $MACHINE "$1"; then
520                                         if man_check_for_so $manpage $p; then
521                                                 found_page=yes
522                                                 man_display_page
523                                                 if [ -n "$aflag" ]; then
524                                                         continue 2
525                                                 else
526                                                         return
527                                                 fi
528                                         fi
529                                 fi
530
531                                 # Check if there is a MACHINE_ARCH
532                                 # specific manpath.
533                                 if find_file $p $sect $MACHINE_ARCH "$1"; then
534                                         if man_check_for_so $manpage $p; then
535                                                 found_page=yes
536                                                 man_display_page
537                                                 if [ -n "$aflag" ]; then
538                                                         continue 2
539                                                 else
540                                                         return
541                                                 fi
542                                         fi
543                                 fi
544
545                                 # Check plain old manpath.
546                                 if find_file $p $sect '' "$1"; then
547                                         if man_check_for_so $manpage $p; then
548                                                 found_page=yes
549                                                 man_display_page
550                                                 if [ -n "$aflag" ]; then
551                                                         continue 2
552                                                 else
553                                                         return
554                                                 fi
555                                         fi
556                                 fi
557                         done
558                 done
559         done
560         unset IFS
561
562         # Nothing? Well, we are done then.
563         if [ -z "$found_page" ]; then
564                 echo "No manual entry for $1" >&2
565                 ret=1
566                 return
567         fi
568 }
569
570 # Usage: man_parse_opts "$@"
571 # Parses commandline options for man.
572 man_parse_opts() {
573         local cmd_arg
574
575         OPTIND=1
576         while getopts 'K:M:P:S:adfhkm:op:tw' cmd_arg; do
577                 case "${cmd_arg}" in
578                 K)      Kflag=Kflag
579                         REGEXP=$OPTARG ;;
580                 M)      MANPATH=$OPTARG ;;
581                 P)      MANPAGER=$OPTARG ;;
582                 S)      MANSECT=$OPTARG ;;
583                 a)      aflag=aflag ;;
584                 d)      debug=$(( $debug + 1 )) ;;
585                 f)      fflag=fflag ;;
586                 h)      man_usage 0 ;;
587                 k)      kflag=kflag ;;
588                 m)      mflag=$OPTARG ;;
589                 o)      oflag=oflag ;;
590                 p)      MANROFFSEQ=$OPTARG ;;
591                 t)      tflag=tflag ;;
592                 w)      wflag=wflag ;;
593                 *)      man_usage ;;
594                 esac
595         done >&2
596
597         shift $(( $OPTIND - 1 ))
598
599         # Check the args for incompatible options.
600
601         case "${Kflag}${fflag}${kflag}${tflag}${wflag}" in
602         Kflagfflag*)    echo "Incompatible options: -K and -f"; man_usage ;;
603         Kflag*kflag*)   echo "Incompatible options: -K and -k"; man_usage ;;
604         Kflag*tflag)    echo "Incompatible options: -K and -t"; man_usage ;;
605         fflagkflag*)    echo "Incompatible options: -f and -k"; man_usage ;;
606         fflag*tflag*)   echo "Incompatible options: -f and -t"; man_usage ;;
607         fflag*wflag)    echo "Incompatible options: -f and -w"; man_usage ;;
608         *kflagtflag*)   echo "Incompatible options: -k and -t"; man_usage ;;
609         *kflag*wflag)   echo "Incompatible options: -k and -w"; man_usage ;;
610         *tflagwflag)    echo "Incompatible options: -t and -w"; man_usage ;;
611         esac
612
613         # Short circuit for whatis(1) and apropos(1)
614         if [ -n "$fflag" ]; then
615                 do_whatis "$@"
616                 exit
617         fi
618
619         if [ -n "$kflag" ]; then
620                 do_apropos "$@"
621                 exit
622         fi
623 }
624
625 # Usage: man_setup
626 # Setup various trivial but essential variables.
627 man_setup() {
628         # Setup machine and architecture variables.
629         if [ -n "$mflag" ]; then
630                 MACHINE_ARCH=${mflag%%:*}
631                 MACHINE=${mflag##*:}
632         fi
633         if [ -z "$MACHINE_ARCH" ]; then
634                 MACHINE_ARCH=$($SYSCTL -n hw.machine_arch)
635         fi
636         if [ -z "$MACHINE" ]; then
637                 MACHINE=$($SYSCTL -n hw.machine)
638         fi
639         decho "Using architecture: $MACHINE_ARCH:$MACHINE"
640
641         setup_pager
642         build_manpath
643         build_mansect
644         man_setup_locale
645         man_setup_width
646 }
647
648 # Usage: man_setup_width
649 # Set up page width.
650 man_setup_width() {
651         local sizes
652
653         unset use_width
654         case "$MANWIDTH" in
655         [0-9]*)
656                 if [ "$MANWIDTH" -gt 0 2>/dev/null ]; then
657                         use_width=$MANWIDTH
658                 fi
659                 ;;
660         [Tt][Tt][Yy])
661                 if { sizes=$($STTY size 0>&3 2>/dev/null); } 3>&1; then
662                         set -- $sizes
663                         if [ $2 -gt 80 ]; then
664                                 use_width=$(($2-2))
665                         fi
666                 fi
667                 ;;
668         esac
669         if [ -n "$use_width" ]; then
670                 decho "Using non-standard page width: ${use_width}"
671         else
672                 decho 'Using standard page width'
673         fi
674 }
675
676 # Usage: man_setup_locale
677 # Setup necessary locale variables.
678 man_setup_locale() {
679         local lang_cc
680         local locstr
681
682         locpaths='.'
683         man_charset='US-ASCII'
684
685         # Setup locale information.
686         if [ -n "$oflag" ]; then
687                 decho 'Using non-localized manpages'
688         else
689                 # Use the locale tool to give us proper locale information
690                 eval $( $LOCALE )
691
692                 if [ -n "$LANG" ]; then
693                         locstr=$LANG
694                 else
695                         locstr=$LC_CTYPE
696                 fi
697
698                 case "$locstr" in
699                 C)              ;;
700                 C.UTF-8)        ;;
701                 POSIX)          ;;
702                 [a-z][a-z]_[A-Z][A-Z]\.*)
703                                 lang_cc="${locstr%.*}"
704                                 man_lang="${locstr%_*}"
705                                 man_country="${lang_cc#*_}"
706                                 man_charset="${locstr#*.}"
707                                 locpaths="$locstr"
708                                 locpaths="$locpaths:$man_lang.$man_charset"
709                                 if [ "$man_lang" != "en" ]; then
710                                         locpaths="$locpaths:en.$man_charset"
711                                 fi
712                                 locpaths="$locpaths:."
713                                 ;;
714                 *)              echo 'Unknown locale, assuming C' >&2
715                                 ;;
716                 esac
717         fi
718
719         decho "Using locale paths: $locpaths"
720 }
721
722 # Usage: man_usage [exitcode]
723 # Display usage for the man utility.
724 man_usage() {
725         echo 'Usage:'
726         echo ' man [-adho] [-t | -w] [-K regexp] [-M manpath] [-P pager] [-S mansect]'
727         echo '     [-m arch[:machine]] [-p [eprtv]] [mansect] page [...]'
728         echo ' man -f page [...] -- Emulates whatis(1)'
729         echo ' man -k page [...] -- Emulates apropos(1)'
730
731         # When exit'ing with -h, it's not an error.
732         exit ${1:-1}
733 }
734
735 # Usage: parse_configs
736 # Reads the end-user adjustable config files.
737 parse_configs() {
738         local IFS file files
739
740         if [ -n "$parsed_configs" ]; then
741                 return
742         fi
743
744         unset IFS
745
746         # Read the global config first in case the user wants
747         # to override config_local.
748         if [ -r "$config_global" ]; then
749                 parse_file "$config_global"
750         fi
751
752         # Glob the list of files to parse.
753         set +f
754         files=$(echo $config_local)
755         set -f
756
757         for file in $files; do
758                 if [ -r "$file" ]; then
759                         parse_file "$file"
760                 fi
761         done
762
763         parsed_configs='yes'
764 }
765
766 # Usage: parse_file file
767 # Reads the specified config files.
768 parse_file() {
769         local file line tstr var
770
771         file="$1"
772         decho "Parsing config file: $file"
773         while read line; do
774                 decho "  $line" 2
775                 case "$line" in
776                 \#*)            decho "    Comment" 3
777                                 ;;
778                 MANPATH*)       decho "    MANPATH" 3
779                                 trim "${line#MANPATH}"
780                                 add_to_manpath "$tstr"
781                                 ;;
782                 MANLOCALE*)     decho "    MANLOCALE" 3
783                                 trim "${line#MANLOCALE}"
784                                 manlocales="$manlocales:$tstr"
785                                 ;;
786                 MANCONFIG*)     decho "    MANCONFIG" 3
787                                 trim "${line#MANCONFIG}"
788                                 config_local="$tstr"
789                                 ;;
790                 MANSECT*)       decho "    MANSECT" 3
791                                 trim "${line#MANSECT}"
792                                 mansect="$mansect:$tstr"
793                                 ;;
794                 # Set variables in the form of FOO_BAR
795                 *_*[\ \ ]*)     var="${line%%[\ \       ]*}"
796                                 trim "${line#$var}"
797                                 eval "$var=\"$tstr\""
798                                 decho "    Parsed $var" 3
799                                 ;;
800                 esac
801         done < "$file"
802 }
803
804 # Usage: search_path
805 # Traverse $PATH looking for manpaths.
806 search_path() {
807         local IFS p path
808
809         decho "Searching PATH for man directories"
810
811         IFS=:
812         for path in $PATH; do
813                 if add_to_manpath "$path/man"; then
814                         :
815                 elif add_to_manpath "$path/MAN"; then
816                         :
817                 else
818                         case "$path" in
819                         */bin)  p="${path%/bin}/share/man"
820                                 add_to_manpath "$p"
821                                 p="${path%/bin}/man"
822                                 add_to_manpath "$p"
823                                 ;;
824                         esac
825                 fi
826         done
827         unset IFS
828
829         if [ -z "$manpath" ]; then
830                 decho '  Unable to find any manpaths, using default'
831                 manpath=$man_default_path
832         fi
833 }
834
835 # Usage: search_whatis cmd [arglist]
836 # Do the heavy lifting for apropos/whatis
837 search_whatis() {
838         local IFS bad cmd f good key keywords loc opt out path rval wlist
839
840         cmd="$1"
841         shift
842
843         whatis_parse_args "$@"
844
845         build_manpath
846         build_manlocales
847         setup_pager
848
849         if [ "$cmd" = "whatis" ]; then
850                 opt="-w"
851         fi
852
853         f='whatis'
854
855         IFS=:
856         for path in $MANPATH; do
857                 if [ \! -d "$path" ]; then
858                         decho "Skipping non-existent path: $path" 2
859                         continue
860                 fi
861
862                 if [ -f "$path/$f" -a -r "$path/$f" ]; then
863                         decho "Found whatis: $path/$f"
864                         wlist="$wlist $path/$f"
865                 fi
866
867                 for loc in $MANLOCALES; do
868                         if [ -f "$path/$loc/$f" -a -r "$path/$loc/$f" ]; then
869                                 decho "Found whatis: $path/$loc/$f"
870                                 wlist="$wlist $path/$loc/$f"
871                         fi
872                 done
873         done
874         unset IFS
875
876         if [ -z "$wlist" ]; then
877                 echo "$cmd: no whatis databases in $MANPATH" >&2
878                 exit 1
879         fi
880
881         rval=0
882         for key in $keywords; do
883                 out=$(grep -Ehi $opt -- "$key" $wlist)
884                 if [ -n "$out" ]; then
885                         good="$good\\n$out"
886                 else
887                         bad="$bad\\n$key: nothing appropriate"
888                         rval=1
889                 fi
890         done
891
892         # Strip leading carriage return.
893         good=${good#\\n}
894         bad=${bad#\\n}
895
896         if [ -n "$good" ]; then
897                 echo -e "$good" | $MANPAGER
898         fi
899
900         if [ -n "$bad" ]; then
901                 echo -e "$bad" >&2
902         fi
903
904         exit $rval
905 }
906
907 # Usage: setup_cattool page
908 # Finds an appropriate decompressor based on extension
909 setup_cattool() {
910         case "$1" in
911         *.bz)   cattool='/usr/bin/bzcat' ;;
912         *.bz2)  cattool='/usr/bin/bzcat' ;;
913         *.gz)   cattool='/usr/bin/gzcat' ;;
914         *.lzma) cattool='/usr/bin/lzcat' ;;
915         *.xz)   cattool='/usr/bin/xzcat' ;;
916         *.zst)  cattool='/usr/bin/zstdcat' ;;
917         *)      cattool='/usr/bin/zcat -f' ;;
918         esac
919 }
920
921 # Usage: setup_pager
922 # Correctly sets $MANPAGER
923 setup_pager() {
924         # Setup pager.
925         if [ -z "$MANPAGER" ]; then
926                 if [ -n "$MANCOLOR" ]; then
927                         MANPAGER="less -sR"
928                 else
929                         if [ -n "$PAGER" ]; then
930                                 MANPAGER="$PAGER"
931                         else
932                                 MANPAGER="less -s"
933                         fi
934                 fi
935         fi
936         decho "Using pager: $MANPAGER"
937 }
938
939 # Usage: trim string
940 # Trims whitespace from beginning and end of a variable
941 trim() {
942         tstr=$1
943         while true; do
944                 case "$tstr" in
945                 [\ \    ]*)     tstr="${tstr##[\ \      ]}" ;;
946                 *[\ \   ])      tstr="${tstr%%[\ \      ]}" ;;
947                 *)              break ;;
948                 esac
949         done
950 }
951
952 # Usage: whatis_parse_args "$@"
953 # Parse commandline args for whatis and apropos.
954 whatis_parse_args() {
955         local cmd_arg
956         OPTIND=1
957         while getopts 'd' cmd_arg; do
958                 case "${cmd_arg}" in
959                 d)      debug=$(( $debug + 1 )) ;;
960                 *)      whatis_usage ;;
961                 esac
962         done >&2
963
964         shift $(( $OPTIND - 1 ))
965
966         keywords="$*"
967 }
968
969 # Usage: whatis_usage
970 # Display usage for the whatis/apropos utility.
971 whatis_usage() {
972         echo "usage: $cmd [-d] keyword [...]"
973         exit 1
974 }
975
976
977
978 # Supported commands
979 do_apropos() {
980         [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/apropos) ] && \
981                 exec apropos "$@"
982         search_whatis apropos "$@"
983 }
984
985 # Usage: do_full_search reg_exp
986 # Do a full search of the regular expression passed
987 # as parameter in all man pages
988 do_full_search() {
989         local gflags re
990         re=${1}
991
992         # Build grep(1) flags
993         gflags="-H"
994
995         # wflag implies -l for grep(1)
996         if [ -n "$wflag" ]; then
997                 gflags="${gflags} -l"
998         fi
999
1000         gflags="${gflags} --label"
1001
1002         set +f
1003         for mpath in $(echo "${MANPATH}" | tr : '[:blank:]'); do
1004                 for section in $(echo "${MANSECT}" | tr : '[:blank:]'); do
1005                         for manfile in ${mpath}/man${section}/*.${section}*; do
1006                                 mandoc "${manfile}" 2>/dev/null |
1007                                         grep -E ${gflags} "${manfile}" -e "${re}"
1008                         done
1009                 done
1010         done
1011         set -f
1012 }
1013
1014 do_man() {
1015         local IFS
1016
1017         man_parse_opts "$@"
1018         man_setup
1019
1020         shift $(( $OPTIND - 1 ))
1021         IFS=:
1022         for sect in $MANSECT; do
1023                 if [ "$sect" = "$1" ]; then
1024                         decho "Detected manual section as first arg: $1"
1025                         MANSECT="$1"
1026                         shift
1027                         break
1028                 fi
1029         done
1030         unset IFS
1031         pages="$*"
1032
1033         if [ -z "$pages" -a -z "${Kflag}" ]; then
1034                 echo 'What manual page do you want?' >&2
1035                 exit 1
1036         fi
1037
1038         if [ ! -z "${Kflag}" ]; then
1039                 # Short circuit because -K flag does a sufficiently
1040                 # different thing like not showing the man page at all
1041                 do_full_search "${REGEXP}"
1042         fi
1043
1044         for page in $pages; do
1045                 decho "Searching for $page"
1046                 man_find_and_display "$page"
1047         done
1048
1049         exit ${ret:-0}
1050 }
1051
1052 do_manpath() {
1053         manpath_parse_args "$@"
1054         if [ -z "$qflag" ]; then
1055                 manpath_warnings
1056         fi
1057         if [ -n "$Lflag" ]; then
1058                 build_manlocales
1059                 echo $MANLOCALES
1060         else
1061                 build_manpath
1062                 echo $MANPATH
1063         fi
1064         exit 0
1065 }
1066
1067 do_whatis() {
1068         [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/whatis) ] && \
1069                 exec whatis "$@"
1070         search_whatis whatis "$@"
1071 }
1072
1073 # User's PATH setting decides on the groff-suite to pick up.
1074 EQN=eqn
1075 NROFF='groff -S -P-h -Wall -mtty-char -man'
1076 PIC=pic
1077 REFER=refer
1078 TBL=tbl
1079 TROFF='groff -S -man'
1080 VGRIND=vgrind
1081
1082 LOCALE=/usr/bin/locale
1083 STTY=/bin/stty
1084 SYSCTL=/sbin/sysctl
1085
1086 debug=0
1087 man_default_sections='1:8:2:3:3lua:n:4:5:6:7:9:l'
1088 man_default_path='/usr/share/man:/usr/share/openssl/man:/usr/local/share/man:/usr/local/man'
1089 cattool='/usr/bin/zcat -f'
1090
1091 config_global='/etc/man.conf'
1092
1093 # This can be overridden via a setting in /etc/man.conf.
1094 config_local='/usr/local/etc/man.d/*.conf'
1095
1096 # Set noglobbing for now. I don't want spurious globbing.
1097 set -f
1098
1099 case "$0" in
1100 *apropos)       do_apropos "$@" ;;
1101 *manpath)       do_manpath "$@" ;;
1102 *whatis)        do_whatis "$@" ;;
1103 *)              do_man "$@" ;;
1104 esac