man.sh revision 330449
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: stable/11/usr.bin/man/man.sh 330449 2018-03-05 07:26:05Z eadler $
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.
34add_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.
52build_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.
68build_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.
118check_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.
133check_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.
163decho() {
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.
171exists() {
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.
194find_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.
231is_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.
243manpath_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.
258manpath_usage() {
259	echo 'usage: manpath [-Ldq]' >&2
260	exit 1
261}
262
263# Usage: manpath_warnings
264# Display some warnings to stderr.
265manpath_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.
275man_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
305man_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
371man_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.
463man_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.
545man_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.
606man_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.
635man_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.
663man_setup_locale() {
664	local lang_cc
665
666	locpaths='.'
667	man_charset='US-ASCII'
668
669	# Setup locale information.
670	if [ -n "$oflag" ]; then
671		decho 'Using non-localized manpages'
672	else
673		# Use the locale tool to give us the proper LC_CTYPE
674		eval $( $LOCALE )
675
676		case "$LC_CTYPE" in
677		C)		;;
678		POSIX)		;;
679		[a-z][a-z]_[A-Z][A-Z]\.*)
680				lang_cc="${LC_CTYPE%.*}"
681				man_lang="${LC_CTYPE%_*}"
682				man_country="${lang_cc#*_}"
683				man_charset="${LC_CTYPE#*.}"
684				locpaths="$LC_CTYPE"
685				locpaths="$locpaths:$man_lang.$man_charset"
686				if [ "$man_lang" != "en" ]; then
687					locpaths="$locpaths:en.$man_charset"
688				fi
689				locpaths="$locpaths:."
690				;;
691		*)		echo 'Unknown locale, assuming C' >&2
692				;;
693		esac
694	fi
695
696	decho "Using locale paths: $locpaths"
697}
698
699# Usage: man_usage [exitcode]
700# Display usage for the man utility.
701man_usage() {
702	echo 'Usage:'
703	echo ' man [-adho] [-t | -w] [-M manpath] [-P pager] [-S mansect]'
704	echo '     [-m arch[:machine]] [-p [eprtv]] [mansect] page [...]'
705	echo ' man -f page [...] -- Emulates whatis(1)'
706	echo ' man -k page [...] -- Emulates apropos(1)'
707
708	# When exit'ing with -h, it's not an error.
709	exit ${1:-1}
710}
711
712# Usage: parse_configs
713# Reads the end-user adjustable config files.
714parse_configs() {
715	local IFS file files
716
717	if [ -n "$parsed_configs" ]; then
718		return
719	fi
720
721	unset IFS
722
723	# Read the global config first in case the user wants
724	# to override config_local.
725	if [ -r "$config_global" ]; then
726		parse_file "$config_global"
727	fi
728
729	# Glob the list of files to parse.
730	set +f
731	files=$(echo $config_local)
732	set -f
733
734	for file in $files; do
735		if [ -r "$file" ]; then
736			parse_file "$file"
737		fi
738	done
739
740	parsed_configs='yes'
741}
742
743# Usage: parse_file file
744# Reads the specified config files.
745parse_file() {
746	local file line tstr var
747
748	file="$1"
749	decho "Parsing config file: $file"
750	while read line; do
751		decho "  $line" 2
752		case "$line" in
753		\#*)		decho "    Comment" 3
754				;;
755		MANPATH*)	decho "    MANPATH" 3
756				trim "${line#MANPATH}"
757				add_to_manpath "$tstr"
758				;;
759		MANLOCALE*)	decho "    MANLOCALE" 3
760				trim "${line#MANLOCALE}"
761				manlocales="$manlocales:$tstr"
762				;;
763		MANCONFIG*)	decho "    MANCONFIG" 3
764				trim "${line#MANCONFIG}"
765				config_local="$tstr"
766				;;
767		# Set variables in the form of FOO_BAR
768		*_*[\ \	]*)	var="${line%%[\ \	]*}"
769				trim "${line#$var}"
770				eval "$var=\"$tstr\""
771				decho "    Parsed $var" 3
772				;;
773		esac
774	done < "$file"
775}
776
777# Usage: search_path
778# Traverse $PATH looking for manpaths.
779search_path() {
780	local IFS p path
781
782	decho "Searching PATH for man directories"
783
784	IFS=:
785	for path in $PATH; do
786		# Do a little special casing since the base manpages
787		# are in /usr/share/man instead of /usr/man or /man.
788		case "$path" in
789		/bin|/usr/bin)	add_to_manpath "/usr/share/man" ;;
790		*)	if add_to_manpath "$path/man"; then
791				:
792			elif add_to_manpath "$path/MAN"; then
793				:
794			else
795				case "$path" in
796				*/bin)	p="${path%/bin}/man"
797					add_to_manpath "$p"
798					p="${path%/bin}/share/man"
799					add_to_manpath "$p"
800					;;
801				*)	;;
802				esac
803			fi
804			;;
805		esac
806	done
807	unset IFS
808
809	if [ -z "$manpath" ]; then
810		decho '  Unable to find any manpaths, using default'
811		manpath=$man_default_path
812	fi
813}
814
815# Usage: search_whatis cmd [arglist]
816# Do the heavy lifting for apropos/whatis
817search_whatis() {
818	local IFS bad cmd f good key keywords loc opt out path rval wlist
819
820	cmd="$1"
821	shift
822
823	whatis_parse_args "$@"
824
825	build_manpath
826	build_manlocales
827	setup_pager
828
829	if [ "$cmd" = "whatis" ]; then
830		opt="-w"
831	fi
832
833	f='whatis'
834
835	IFS=:
836	for path in $MANPATH; do
837		if [ \! -d "$path" ]; then
838			decho "Skipping non-existent path: $path" 2
839			continue
840		fi
841
842		if [ -f "$path/$f" -a -r "$path/$f" ]; then
843			decho "Found whatis: $path/$f"
844			wlist="$wlist $path/$f"
845		fi
846
847		for loc in $MANLOCALES; do
848			if [ -f "$path/$loc/$f" -a -r "$path/$loc/$f" ]; then
849				decho "Found whatis: $path/$loc/$f"
850				wlist="$wlist $path/$loc/$f"
851			fi
852		done
853	done
854	unset IFS
855
856	if [ -z "$wlist" ]; then
857		echo "$cmd: no whatis databases in $MANPATH" >&2
858		exit 1
859	fi
860
861	rval=0
862	for key in $keywords; do
863		out=$(grep -Ehi $opt -- "$key" $wlist)
864		if [ -n "$out" ]; then
865			good="$good\\n$out"
866		else
867			bad="$bad\\n$key: nothing appropriate"
868			rval=1
869		fi
870	done
871
872	# Strip leading carriage return.
873	good=${good#\\n}
874	bad=${bad#\\n}
875
876	if [ -n "$good" ]; then
877		echo -e "$good" | $MANPAGER
878	fi
879
880	if [ -n "$bad" ]; then
881		echo -e "$bad" >&2
882	fi
883
884	exit $rval
885}
886
887# Usage: setup_cattool page
888# Finds an appropriate decompressor based on extension
889setup_cattool() {
890	case "$1" in
891	*.bz)	cattool='/usr/bin/bzcat' ;;
892	*.bz2)	cattool='/usr/bin/bzcat' ;;
893	*.gz)	cattool='/usr/bin/zcat' ;;
894	*.lzma)	cattool='/usr/bin/lzcat' ;;
895	*.xz)	cattool='/usr/bin/xzcat' ;;
896	*)	cattool='/usr/bin/zcat -f' ;;
897	esac
898}
899
900# Usage: setup_pager
901# Correctly sets $MANPAGER
902setup_pager() {
903	# Setup pager.
904	if [ -z "$MANPAGER" ]; then
905		if [ -n "$MANCOLOR" ]; then
906			MANPAGER="less -sR"
907		else
908			if [ -n "$PAGER" ]; then
909				MANPAGER="$PAGER"
910			else
911				MANPAGER="more -s"
912			fi
913		fi
914	fi
915	decho "Using pager: $MANPAGER"
916}
917
918# Usage: trim string
919# Trims whitespace from beginning and end of a variable
920trim() {
921	tstr=$1
922	while true; do
923		case "$tstr" in
924		[\ \	]*)	tstr="${tstr##[\ \	]}" ;;
925		*[\ \	])	tstr="${tstr%%[\ \	]}" ;;
926		*)		break ;;
927		esac
928	done
929}
930
931# Usage: whatis_parse_args "$@"
932# Parse commandline args for whatis and apropos.
933whatis_parse_args() {
934	local cmd_arg
935	while getopts 'd' cmd_arg; do
936		case "${cmd_arg}" in
937		d)	debug=$(( $debug + 1 )) ;;
938		*)	whatis_usage ;;
939		esac
940	done >&2
941
942	shift $(( $OPTIND - 1 ))
943
944	keywords="$*"
945}
946
947# Usage: whatis_usage
948# Display usage for the whatis/apropos utility.
949whatis_usage() {
950	echo "usage: $cmd [-d] keyword [...]"
951	exit 1
952}
953
954
955
956# Supported commands
957do_apropos() {
958	[ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/apropos) ] && \
959		exec apropos "$@"
960	search_whatis apropos "$@"
961}
962
963do_man() {
964	man_parse_args "$@"
965	if [ -z "$pages" ]; then
966		echo 'What manual page do you want?' >&2
967		exit 1
968	fi
969	man_setup
970
971	for page in $pages; do
972		decho "Searching for $page"
973		man_find_and_display "$page"
974	done
975
976	exit ${ret:-0}
977}
978
979do_manpath() {
980	manpath_parse_args "$@"
981	if [ -z "$qflag" ]; then
982		manpath_warnings
983	fi
984	if [ -n "$Lflag" ]; then
985		build_manlocales
986		echo $MANLOCALES
987	else
988		build_manpath
989		echo $MANPATH
990	fi
991	exit 0
992}
993
994do_whatis() {
995	[ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/whatis) ] && \
996		exec whatis "$@"
997	search_whatis whatis "$@"
998}
999
1000# User's PATH setting decides on the groff-suite to pick up.
1001EQN=eqn
1002NROFF='groff -S -P-h -Wall -mtty-char -man'
1003PIC=pic
1004REFER=refer
1005TBL=tbl
1006TROFF='groff -S -man'
1007VGRIND=vgrind
1008
1009LOCALE=/usr/bin/locale
1010STTY=/bin/stty
1011SYSCTL=/sbin/sysctl
1012
1013debug=0
1014man_default_sections='1:8:2:3:n:4:5:6:7:9:l'
1015man_default_path='/usr/share/man:/usr/share/openssl/man:/usr/local/man'
1016cattool='/usr/bin/zcat -f'
1017
1018config_global='/etc/man.conf'
1019
1020# This can be overridden via a setting in /etc/man.conf.
1021config_local='/usr/local/etc/man.d/*.conf'
1022
1023# Set noglobbing for now. I don't want spurious globbing.
1024set -f
1025
1026case "$0" in
1027*apropos)	do_apropos "$@" ;;
1028*manpath)	do_manpath "$@" ;;
1029*whatis)	do_whatis "$@" ;;
1030*)		do_man "$@" ;;
1031esac
1032