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