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