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