3 # SPDX-License-Identifier: BSD-2-Clause
5 # Copyright (c) 2010 Gordon Tetlow
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
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.
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
30 # Rendering a manual page is fast. Even a manual page several 100k in size
31 # takes less than a CPU second. If it takes much longer, it is very likely
32 # that a tool like mandoc(1) is running in an infinite loop. In this case
33 # it is better to terminate it.
36 # Usage: add_to_manpath path
37 # Adds a variable to manpath while ensuring we don't have duplicates.
38 # Returns true if we were able to add something. False otherwise.
41 *:$1) decho " Skipping duplicate manpath entry $1" 2 ;;
42 $1:*) decho " Skipping duplicate manpath entry $1" 2 ;;
43 *:$1:*) decho " Skipping duplicate manpath entry $1" 2 ;;
44 *) if [ -d "$1" ]; then
45 decho " Adding $1 to manpath"
55 # Usage: build_manlocales
56 # Builds a correct MANLOCALES variable.
58 # If the user has set manlocales, who are we to argue.
59 if [ -n "$MANLOCALES" ]; then
66 MANLOCALES=${manlocales#:}
68 decho "Available manual locales: $MANLOCALES"
71 # Usage: build_mansect
72 # Builds a correct MANSECT variable.
74 # If the user has set mansect, who are we to argue.
75 if [ -n "$MANSECT" ]; then
84 if [ -z "$MANSECT" ]; then
85 MANSECT=$man_default_sections
87 decho "Using manual sections: $MANSECT"
90 # Usage: build_manpath
91 # Builds a correct MANPATH variable.
95 # If the user has set a manpath, who are we to argue.
96 if [ -n "$MANPATH" ]; then
98 *:) PREPEND_MANPATH=${MANPATH} ;;
99 :*) APPEND_MANPATH=${MANPATH} ;;
101 PREPEND_MANPATH=${MANPATH%%::*}
102 APPEND_MANPATH=${MANPATH#*::}
108 if [ -n "$PREPEND_MANPATH" ]; then
110 for path in $PREPEND_MANPATH; do
111 add_to_manpath "$path"
118 decho "Adding default manpath entries"
120 for path in $man_default_path; do
121 add_to_manpath "$path"
127 if [ -n "$APPEND_MANPATH" ]; then
129 for path in $APPEND_MANPATH; do
130 add_to_manpath "$path"
137 decho "Using manual path: $MANPATH"
140 # Usage: check_cat catglob
141 # Checks to see if a cat glob is available.
146 setup_cattool "$catpage"
147 decho " Found catpage \"$catpage\""
154 # Usage: check_man manglob catglob
155 # Given 2 globs, figures out if the manglob is available, if so, check to
156 # see if the catglob is also available and up to date.
159 # We have a match, check for a cat page
161 setup_cattool "$manpage"
162 decho " Found manpage \"$manpage\""
164 if [ -n "${use_width}" ]; then
167 decho " Skipping catpage: non-standard page width"
168 elif exists "$2" && is_newer $found "$manpage"; then
169 # cat page found and is newer, use that
172 setup_cattool "$catpage"
173 decho " Using catpage \"$catpage\""
175 # no cat page or is older
177 decho " Skipping catpage: not found or old"
185 # Usage: decho "string" [debuglevel]
186 # Echoes to stderr string prefaced with -- if high enough debuglevel.
188 if [ $debug -ge ${2:-1} ]; then
195 # Returns true if glob resolves to a real file and store the first
196 # found filename in the variable $found
200 # Don't accidentally inherit callers IFS (breaks perl manpages)
203 # Use some globbing tricks in the shell to determine if a file
208 if [ -r "$file" ]; then
219 # Usage: find_file path section subdir pagename
220 # Returns: true if something is matched and found.
221 # Search the given path/section combo for a given page.
223 local manroot catroot mann man0 catn cat0
228 manroot="$manroot/$3"
229 catroot="$catroot/$3"
232 if [ ! -d "$manroot" -a ! -d "$catroot" ]; then
235 decho " Searching directory $manroot" 2
237 mann="$manroot/$4.$2"
239 catn="$catroot/$4.$2"
242 # This is the behavior as seen by the original man utility.
243 # Let's not change that which doesn't seem broken.
244 if check_man "$mann" "$catn"; then
246 elif check_man "$man0" "$cat0"; then
248 elif check_cat "$catn"; then
250 elif check_cat "$cat0"; then
257 # Usage: is_newer file1 file2
258 # Returns true if file1 is newer than file2 as calculated by mtime.
260 if ! [ "$1" -ot "$2" ]; then
261 decho " mtime: $1 not older than $2" 3
264 decho " mtime: $1 older than $2" 3
269 # Usage: manpath_parse_args "$@"
270 # Parses commandline options for manpath.
271 manpath_parse_args() {
275 while getopts 'Ldq' cmd_arg; do
278 d) debug=$(( $debug + 1 )) ;;
285 # Usage: manpath_usage
286 # Display usage for the manpath(1) utility.
288 echo 'usage: manpath [-Ldq]' >&2
292 # Usage: manpath_warnings
293 # Display some warnings to stderr.
295 if [ -n "$Lflag" -a -n "$MANLOCALES" ]; then
296 echo "(Warning: MANLOCALES environment variable set)" >&2
300 # Usage: man_check_for_so path
301 # Returns: True if able to resolve the file, false if it ended in tears.
302 # Detects the presence of the .so directive and causes the file to be
303 # redirected to another source file.
308 if [ -n "$catpage" ]; then
312 # We need to loop to accommodate multiple .so directives.
315 line=$($cattool "$manpage" | head -n1)
317 .so*) trim "${line#.so}"
318 decho "$manpage includes $tstr"
319 # Glob and check for the file.
320 if ! check_man "$1/$tstr" ""; then
321 decho " Unable to find $tstr"
332 # Usage: man_display_page
333 # Display either the manpage or catpage depending on the use_cat variable
335 local IFS pipeline testline
337 # We are called with IFS set to colon. This causes really weird
338 # things to happen for the variables that have spaces in them.
341 # If we are supposed to use a catpage and we aren't using troff(1)
342 # just zcat the catpage and we are done.
343 if [ -z "$tflag" -a -n "$use_cat" ]; then
344 if [ -n "$wflag" ]; then
345 echo "$catpage (source: \"$manpage\")"
348 if [ $debug -gt 0 ]; then
349 decho "Command: $cattool \"$catpage\" | $MANPAGER"
352 $cattool "$catpage" | $MANPAGER
359 # Okay, we are using the manpage, do we just need to output the
360 # name of the manpage?
361 if [ -n "$wflag" ]; then
367 if [ -n "$use_width" ]; then
368 mandoc_args="-O width=${use_width}"
370 testline="mandoc -Tlint -Wunsupp >/dev/null 2>&1"
371 if [ -n "$tflag" ]; then
372 pipeline="mandoc -Tps $mandoc_args"
374 pipeline="mandoc $mandoc_args | $MANPAGER"
377 if ! $cattool "$manpage" | eval "$testline"; then
378 if which -s groff; then
379 man_display_page_groff
381 echo "This manpage needs groff(1) to be rendered" >&2
382 echo "First install groff(1): " >&2
383 echo "pkg install groff " >&2
389 if [ $debug -gt 0 ]; then
390 decho "Command: $cattool \"$manpage\" | eval \"$pipeline\""
393 $cattool "$manpage" | eval "$pipeline"
398 # Usage: man_display_page_groff
399 # Display the manpage using groff
400 man_display_page_groff() {
401 local EQN NROFF PIC TBL TROFF REFER VGRIND
402 local IFS l nroff_dev pipeline preproc_arg tool
404 # So, we really do need to parse the manpage. First, figure out the
405 # device flag (-T) we have to pass to eqn(1) and groff(1). Then,
406 # setup the pipeline of commands based on the user's request.
408 # If the manpage is from a particular charset, we need to setup nroff
409 # to properly output for the correct device.
412 # I don't pretend to know this; I'm just copying from the
413 # previous version of man(1).
414 case "$man_charset" in
415 KOI8-R) nroff_dev="koi8-r" ;;
416 ISO8859-1) nroff_dev="latin1" ;;
417 ISO8859-15) nroff_dev="latin1" ;;
418 UTF-8) nroff_dev="utf8" ;;
419 *) nroff_dev="ascii" ;;
422 NROFF="$NROFF -T$nroff_dev"
423 EQN="$EQN -T$nroff_dev"
425 # Iff the manpage is from the locale and not just the charset,
426 # then we need to define the locale string.
428 */${man_lang}_${man_country}.${man_charset}/*)
429 NROFF="$NROFF -dlocale=$man_lang.$man_charset"
431 */${man_lang}.${man_charset}/*)
432 NROFF="$NROFF -dlocale=$man_lang.$man_charset"
436 # Allow language specific calls to override the default
438 l=$(echo $man_lang | tr [:lower:] [:upper:])
439 for tool in EQN NROFF PIC TBL TROFF REFER VGRIND; do
440 eval "$tool=\${${tool}_$l:-\$$tool}"
443 *) NROFF="$NROFF -Tascii"
448 if [ -z "$MANCOLOR" ]; then
452 if [ -n "${use_width}" ]; then
453 NROFF="$NROFF -rLL=${use_width}n -rLT=${use_width}n"
456 if [ -n "$MANROFFSEQ" ]; then
459 while getopts 'egprtv' preproc_arg; do
460 case "${preproc_arg}" in
461 e) pipeline="$pipeline | $EQN" ;;
462 g) ;; # Ignore for compatibility.
463 p) pipeline="$pipeline | $PIC" ;;
464 r) pipeline="$pipeline | $REFER" ;;
465 t) pipeline="$pipeline | $TBL" ;;
466 v) pipeline="$pipeline | $VGRIND" ;;
470 # Strip the leading " | " from the resulting pipeline.
471 pipeline="${pipeline#" | "}"
476 if [ -n "$tflag" ]; then
477 pipeline="$pipeline | $TROFF"
479 pipeline="$pipeline | $NROFF | $MANPAGER"
482 if [ $debug -gt 0 ]; then
483 decho "Command: $cattool \"$manpage\" | eval \"$pipeline\""
486 $cattool "$manpage" | eval "$pipeline"
491 # Usage: man_find_and_display page
492 # Search through the manpaths looking for the given page.
493 man_find_and_display() {
494 local found_page locpath p path sect
496 # Check to see if it's a file. But only if it has a '/' in
499 */*) if [ -f "$1" -a -r "$1" ]; then
500 decho "Found a usable page, displaying that"
503 setup_cattool "$manpage"
504 p=$(cd "$(dirname "$manpage")" && pwd)
505 case "$(basename "$p")" in
506 man*|cat*) p=$p/.. ;;
509 if man_check_for_so "$p"; then
519 for sect in $MANSECT; do
520 decho "Searching section $sect" 2
521 for path in $MANPATH; do
522 for locpath in $locpaths; do
524 p=${p%/.} # Rid ourselves of the trailing /.
526 # Check if there is a MACHINE specific manpath.
527 if find_file $p $sect $MACHINE "$1"; then
528 if man_check_for_so $p; then
531 if [ -n "$aflag" ]; then
539 # Check if there is a MACHINE_ARCH
541 if find_file $p $sect $MACHINE_ARCH "$1"; then
542 if man_check_for_so $p; then
545 if [ -n "$aflag" ]; then
553 # Check plain old manpath.
554 if find_file $p $sect '' "$1"; then
555 if man_check_for_so $p; then
558 if [ -n "$aflag" ]; then
570 # Nothing? Well, we are done then.
571 if [ -z "$found_page" ]; then
572 echo "No manual entry for \"$1\"" >&2
578 # Usage: man_parse_opts "$@"
579 # Parses commandline options for man.
584 while getopts 'K:M:P:S:adfhkm:op:tw' cmd_arg; do
588 M) MANPATH=$OPTARG ;;
589 P) MANPAGER=$OPTARG ;;
590 S) MANSECT=$OPTARG ;;
592 d) debug=$(( $debug + 1 )) ;;
598 p) MANROFFSEQ=$OPTARG ;;
605 shift $(( $OPTIND - 1 ))
607 # Check the args for incompatible options.
609 case "${Kflag}${fflag}${kflag}${tflag}${wflag}" in
610 Kflagfflag*) echo "Incompatible options: -K and -f"; man_usage ;;
611 Kflag*kflag*) echo "Incompatible options: -K and -k"; man_usage ;;
612 Kflag*tflag) echo "Incompatible options: -K and -t"; man_usage ;;
613 fflagkflag*) echo "Incompatible options: -f and -k"; man_usage ;;
614 fflag*tflag*) echo "Incompatible options: -f and -t"; man_usage ;;
615 fflag*wflag) echo "Incompatible options: -f and -w"; man_usage ;;
616 *kflagtflag*) echo "Incompatible options: -k and -t"; man_usage ;;
617 *kflag*wflag) echo "Incompatible options: -k and -w"; man_usage ;;
618 *tflagwflag) echo "Incompatible options: -t and -w"; man_usage ;;
621 # Short circuit for whatis(1) and apropos(1)
622 if [ -n "$fflag" ]; then
627 if [ -n "$kflag" ]; then
634 # Setup various trivial but essential variables.
636 # Setup machine and architecture variables.
637 if [ -n "$mflag" ]; then
638 MACHINE_ARCH=${mflag%%:*}
641 if [ -z "$MACHINE_ARCH" ]; then
642 MACHINE_ARCH=$($SYSCTL -n hw.machine_arch)
644 if [ -z "$MACHINE" ]; then
645 MACHINE=$($SYSCTL -n hw.machine)
647 decho "Using architecture: $MACHINE_ARCH:$MACHINE"
656 # Usage: man_setup_width
664 if [ "$MANWIDTH" -gt 0 2>/dev/null ]; then
669 if { sizes=$($STTY size 0>&3 2>/dev/null); } 3>&1; then
671 if [ $2 -gt 80 ]; then
677 if [ -n "$use_width" ]; then
678 decho "Using non-standard page width: ${use_width}"
680 decho 'Using standard page width'
684 # Usage: man_setup_locale
685 # Setup necessary locale variables.
691 man_charset='US-ASCII'
693 # Setup locale information.
694 if [ -n "$oflag" ]; then
695 decho 'Using non-localized manpages'
697 # Use the locale tool to give us proper locale information
700 if [ -n "$LANG" ]; then
710 [a-z][a-z]_[A-Z][A-Z]\.*)
711 lang_cc="${locstr%.*}"
712 man_lang="${locstr%_*}"
713 man_country="${lang_cc#*_}"
714 man_charset="${locstr#*.}"
716 locpaths="$locpaths:$man_lang.$man_charset"
717 if [ "$man_lang" != "en" ]; then
718 locpaths="$locpaths:en.$man_charset"
720 locpaths="$locpaths:."
722 *) echo 'Unknown locale, assuming C' >&2
727 decho "Using locale paths: $locpaths"
730 # Usage: man_usage [exitcode]
731 # Display usage for the man utility.
734 echo ' man [-adho] [-t | -w] [-K regexp] [-M manpath] [-P pager] [-S mansect]'
735 echo ' [-m arch[:machine]] [-p [eprtv]] [mansect] page [...]'
736 echo ' man -f page [...] -- Emulates whatis(1)'
737 echo ' man -k page [...] -- Emulates apropos(1)'
739 # When exit'ing with -h, it's not an error.
743 # Usage: parse_configs
744 # Reads the end-user adjustable config files.
748 if [ -n "$parsed_configs" ]; then
754 # Read the global config first in case the user wants
755 # to override config_local.
756 if [ -r "$config_global" ]; then
757 parse_file "$config_global"
760 # Glob the list of files to parse.
762 files=$(echo $config_local)
765 for file in $files; do
766 if [ -r "$file" ]; then
774 # Usage: parse_file file
775 # Reads the specified config files.
777 local file line tstr var
780 decho "Parsing config file: $file"
784 \#*) decho " Comment" 3
786 MANPATH*) decho " MANPATH" 3
787 trim "${line#MANPATH}"
788 add_to_manpath "$tstr"
790 MANLOCALE*) decho " MANLOCALE" 3
791 trim "${line#MANLOCALE}"
792 manlocales="$manlocales:$tstr"
794 MANCONFIG*) decho " MANCONFIG" 3
795 trim "${line#MANCONFIG}"
798 MANSECT*) decho " MANSECT" 3
799 trim "${line#MANSECT}"
800 mansect="$mansect:$tstr"
802 # Set variables in the form of FOO_BAR
803 *_*[\ \ ]*) var="${line%%[\ \ ]*}"
805 eval "$var=\"$tstr\""
806 decho " Parsed $var" 3
813 # Traverse $PATH looking for manpaths.
817 decho "Searching PATH for man directories"
820 for path in $PATH; do
821 if add_to_manpath "$path/man"; then
823 elif add_to_manpath "$path/MAN"; then
827 */bin) p="${path%/bin}/share/man"
837 if [ -z "$manpath" ]; then
838 decho ' Unable to find any manpaths, using default'
839 manpath=$man_default_path
843 # Usage: search_whatis cmd [arglist]
844 # Do the heavy lifting for apropos/whatis
846 local IFS bad cmd f good key keywords loc opt out path rval wlist
851 whatis_parse_args "$@"
857 if [ "$cmd" = "whatis" ]; then
864 for path in $MANPATH; do
865 if [ \! -d "$path" ]; then
866 decho "Skipping non-existent path: $path" 2
870 if [ -f "$path/$f" -a -r "$path/$f" ]; then
871 decho "Found whatis: $path/$f"
872 wlist="$wlist $path/$f"
875 for loc in $MANLOCALES; do
876 if [ -f "$path/$loc/$f" -a -r "$path/$loc/$f" ]; then
877 decho "Found whatis: $path/$loc/$f"
878 wlist="$wlist $path/$loc/$f"
884 if [ -z "$wlist" ]; then
885 echo "$cmd: no whatis databases in $MANPATH" >&2
890 for key in $keywords; do
891 out=$(grep -Ehi $opt -- "$key" $wlist)
892 if [ -n "$out" ]; then
895 bad="$bad\\n$key: nothing appropriate"
900 # Strip leading carriage return.
904 if [ -n "$good" ]; then
905 printf '%b\n' "$good" | $MANPAGER
908 if [ -n "$bad" ]; then
909 printf '%b\n' "$bad" >&2
915 # Usage: setup_cattool page
916 # Finds an appropriate decompressor based on extension
919 *.bz) cattool='/usr/bin/bzcat' ;;
920 *.bz2) cattool='/usr/bin/bzcat' ;;
921 *.gz) cattool='/usr/bin/gzcat' ;;
922 *.lzma) cattool='/usr/bin/lzcat' ;;
923 *.xz) cattool='/usr/bin/xzcat' ;;
924 *.zst) cattool='/usr/bin/zstdcat' ;;
925 *) cattool='/usr/bin/zcat -f' ;;
930 # Correctly sets $MANPAGER
933 if [ -z "$MANPAGER" ]; then
934 if [ -n "$MANCOLOR" ]; then
937 if [ -n "$PAGER" ]; then
944 decho "Using pager: $MANPAGER"
948 # Trims whitespace from beginning and end of a variable
953 [\ \ ]*) tstr="${tstr##[\ \ ]}" ;;
954 *[\ \ ]) tstr="${tstr%%[\ \ ]}" ;;
960 # Usage: whatis_parse_args "$@"
961 # Parse commandline args for whatis and apropos.
962 whatis_parse_args() {
965 while getopts 'd' cmd_arg; do
967 d) debug=$(( $debug + 1 )) ;;
972 shift $(( $OPTIND - 1 ))
977 # Usage: whatis_usage
978 # Display usage for the whatis/apropos utility.
980 echo "usage: $cmd [-d] keyword [...]"
988 [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/apropos) ] && \
990 search_whatis apropos "$@"
993 # Usage: do_full_search reg_exp
994 # Do a full search of the regular expression passed
995 # as parameter in all man pages
1000 # Build grep(1) flags
1003 # wflag implies -l for grep(1)
1004 if [ -n "$wflag" ]; then
1005 gflags="${gflags} -l"
1008 gflags="${gflags} --label"
1011 for mpath in $(echo "${MANPATH}" | tr : '[:blank:]'); do
1012 for section in $(echo "${MANSECT}" | tr : '[:blank:]'); do
1013 for manfile in ${mpath}/man${section}/*.${section}*; do
1014 mandoc "${manfile}" 2>/dev/null |
1015 grep -E ${gflags} "${manfile}" -e "${re}"
1028 shift $(( $OPTIND - 1 ))
1030 for sect in $MANSECT; do
1031 if [ "$sect" = "$1" ]; then
1032 decho "Detected manual section as first arg: $1"
1041 if [ -z "$pages" -a -z "${Kflag}" ]; then
1042 echo 'What manual page do you want?' >&2
1046 if [ ! -z "${Kflag}" ]; then
1047 # Short circuit because -K flag does a sufficiently
1048 # different thing like not showing the man page at all
1049 do_full_search "${REGEXP}"
1052 for page in "$@"; do
1053 decho "Searching for \"$page\""
1054 man_find_and_display "$page"
1061 manpath_parse_args "$@"
1062 if [ -z "$qflag" ]; then
1065 if [ -n "$Lflag" ]; then
1076 [ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/whatis) ] && \
1078 search_whatis whatis "$@"
1081 # User's PATH setting decides on the groff-suite to pick up.
1083 NROFF='groff -S -P-h -Wall -mtty-char -mandoc'
1087 TROFF='groff -S -mandoc'
1090 LOCALE=/usr/bin/locale
1095 man_default_sections='1:8:2:3:3lua:n:4:5:6:7:9:l'
1096 man_default_path='/usr/share/man:/usr/share/openssl/man:/usr/local/share/man:/usr/local/man'
1097 cattool='/usr/bin/zcat -f'
1099 config_global='/etc/man.conf'
1101 # This can be overridden via a setting in /etc/man.conf.
1102 config_local='/usr/local/etc/man.d/*.conf'
1104 # Set noglobbing for now. I don't want spurious globbing.
1108 *apropos) do_apropos "$@" ;;
1109 *manpath) do_manpath "$@" ;;
1110 *whatis) do_whatis "$@" ;;