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