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