xref: /freebsd/usr.bin/man/man.sh (revision 73577bf01de5c4677dc54d97f93310286c254780)
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.
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	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.
709man_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.
722parse_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.
753parse_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.
787search_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
818search_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
890setup_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
903setup_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
921trim() {
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.
934whatis_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.
950whatis_usage() {
951	echo "usage: $cmd [-d] keyword [...]"
952	exit 1
953}
954
955
956
957# Supported commands
958do_apropos() {
959	[ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/apropos) ] && \
960		exec apropos "$@"
961	search_whatis apropos "$@"
962}
963
964do_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
980do_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
995do_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.
1002EQN=eqn
1003NROFF='groff -S -P-h -Wall -mtty-char -man'
1004PIC=pic
1005REFER=refer
1006TBL=tbl
1007TROFF='groff -S -man'
1008VGRIND=vgrind
1009
1010LOCALE=/usr/bin/locale
1011STTY=/bin/stty
1012SYSCTL=/sbin/sysctl
1013
1014debug=0
1015man_default_sections='1:8:2:3:3lua:n:4:5:6:7:9:l'
1016man_default_path='/usr/share/man:/usr/share/openssl/man:/usr/local/share/man:/usr/local/man'
1017cattool='/usr/bin/zcat -f'
1018
1019config_global='/etc/man.conf'
1020
1021# This can be overridden via a setting in /etc/man.conf.
1022config_local='/usr/local/etc/man.d/*.conf'
1023
1024# Set noglobbing for now. I don't want spurious globbing.
1025set -f
1026
1027case "$0" in
1028*apropos)	do_apropos "$@" ;;
1029*manpath)	do_manpath "$@" ;;
1030*whatis)	do_whatis "$@" ;;
1031*)		do_man "$@" ;;
1032esac
1033