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