xref: /freebsd/usr.bin/man/man.sh (revision b71a5db306ab1ad1e23c7f9e3c949c6218f43455)
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
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		if add_to_manpath "$path/man"; then
787			:
788		elif add_to_manpath "$path/MAN"; then
789			:
790		else
791			case "$path" in
792			*/bin)	p="${path%/bin}/share/man"
793				add_to_manpath "$p"
794				p="${path%/bin}/man"
795				add_to_manpath "$p"
796				;;
797			esac
798		fi
799	done
800	unset IFS
801
802	if [ -z "$manpath" ]; then
803		decho '  Unable to find any manpaths, using default'
804		manpath=$man_default_path
805	fi
806}
807
808# Usage: search_whatis cmd [arglist]
809# Do the heavy lifting for apropos/whatis
810search_whatis() {
811	local IFS bad cmd f good key keywords loc opt out path rval wlist
812
813	cmd="$1"
814	shift
815
816	whatis_parse_args "$@"
817
818	build_manpath
819	build_manlocales
820	setup_pager
821
822	if [ "$cmd" = "whatis" ]; then
823		opt="-w"
824	fi
825
826	f='whatis'
827
828	IFS=:
829	for path in $MANPATH; do
830		if [ \! -d "$path" ]; then
831			decho "Skipping non-existent path: $path" 2
832			continue
833		fi
834
835		if [ -f "$path/$f" -a -r "$path/$f" ]; then
836			decho "Found whatis: $path/$f"
837			wlist="$wlist $path/$f"
838		fi
839
840		for loc in $MANLOCALES; do
841			if [ -f "$path/$loc/$f" -a -r "$path/$loc/$f" ]; then
842				decho "Found whatis: $path/$loc/$f"
843				wlist="$wlist $path/$loc/$f"
844			fi
845		done
846	done
847	unset IFS
848
849	if [ -z "$wlist" ]; then
850		echo "$cmd: no whatis databases in $MANPATH" >&2
851		exit 1
852	fi
853
854	rval=0
855	for key in $keywords; do
856		out=$(grep -Ehi $opt -- "$key" $wlist)
857		if [ -n "$out" ]; then
858			good="$good\\n$out"
859		else
860			bad="$bad\\n$key: nothing appropriate"
861			rval=1
862		fi
863	done
864
865	# Strip leading carriage return.
866	good=${good#\\n}
867	bad=${bad#\\n}
868
869	if [ -n "$good" ]; then
870		echo -e "$good" | $MANPAGER
871	fi
872
873	if [ -n "$bad" ]; then
874		echo -e "$bad" >&2
875	fi
876
877	exit $rval
878}
879
880# Usage: setup_cattool page
881# Finds an appropriate decompressor based on extension
882setup_cattool() {
883	case "$1" in
884	*.bz)	cattool='/usr/bin/bzcat' ;;
885	*.bz2)	cattool='/usr/bin/bzcat' ;;
886	*.gz)	cattool='/usr/bin/zcat' ;;
887	*.lzma)	cattool='/usr/bin/lzcat' ;;
888	*.xz)	cattool='/usr/bin/xzcat' ;;
889	*)	cattool='/usr/bin/zcat -f' ;;
890	esac
891}
892
893# Usage: setup_pager
894# Correctly sets $MANPAGER
895setup_pager() {
896	# Setup pager.
897	if [ -z "$MANPAGER" ]; then
898		if [ -n "$MANCOLOR" ]; then
899			MANPAGER="less -sR"
900		else
901			if [ -n "$PAGER" ]; then
902				MANPAGER="$PAGER"
903			else
904				MANPAGER="more -s"
905			fi
906		fi
907	fi
908	decho "Using pager: $MANPAGER"
909}
910
911# Usage: trim string
912# Trims whitespace from beginning and end of a variable
913trim() {
914	tstr=$1
915	while true; do
916		case "$tstr" in
917		[\ \	]*)	tstr="${tstr##[\ \	]}" ;;
918		*[\ \	])	tstr="${tstr%%[\ \	]}" ;;
919		*)		break ;;
920		esac
921	done
922}
923
924# Usage: whatis_parse_args "$@"
925# Parse commandline args for whatis and apropos.
926whatis_parse_args() {
927	local cmd_arg
928	while getopts 'd' cmd_arg; do
929		case "${cmd_arg}" in
930		d)	debug=$(( $debug + 1 )) ;;
931		*)	whatis_usage ;;
932		esac
933	done >&2
934
935	shift $(( $OPTIND - 1 ))
936
937	keywords="$*"
938}
939
940# Usage: whatis_usage
941# Display usage for the whatis/apropos utility.
942whatis_usage() {
943	echo "usage: $cmd [-d] keyword [...]"
944	exit 1
945}
946
947
948
949# Supported commands
950do_apropos() {
951	[ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/apropos) ] && \
952		exec apropos "$@"
953	search_whatis apropos "$@"
954}
955
956do_man() {
957	man_parse_args "$@"
958	if [ -z "$pages" ]; then
959		echo 'What manual page do you want?' >&2
960		exit 1
961	fi
962	man_setup
963
964	for page in $pages; do
965		decho "Searching for $page"
966		man_find_and_display "$page"
967	done
968
969	exit ${ret:-0}
970}
971
972do_manpath() {
973	manpath_parse_args "$@"
974	if [ -z "$qflag" ]; then
975		manpath_warnings
976	fi
977	if [ -n "$Lflag" ]; then
978		build_manlocales
979		echo $MANLOCALES
980	else
981		build_manpath
982		echo $MANPATH
983	fi
984	exit 0
985}
986
987do_whatis() {
988	[ $(stat -f %i /usr/bin/man) -ne $(stat -f %i /usr/bin/whatis) ] && \
989		exec whatis "$@"
990	search_whatis whatis "$@"
991}
992
993# User's PATH setting decides on the groff-suite to pick up.
994EQN=eqn
995NROFF='groff -S -P-h -Wall -mtty-char -man'
996PIC=pic
997REFER=refer
998TBL=tbl
999TROFF='groff -S -man'
1000VGRIND=vgrind
1001
1002LOCALE=/usr/bin/locale
1003STTY=/bin/stty
1004SYSCTL=/sbin/sysctl
1005
1006debug=0
1007man_default_sections='1:8:2:3:n:4:5:6:7:9:l'
1008man_default_path='/usr/share/man:/usr/share/openssl/man:/usr/local/share/man:/usr/local/man'
1009cattool='/usr/bin/zcat -f'
1010
1011config_global='/etc/man.conf'
1012
1013# This can be overridden via a setting in /etc/man.conf.
1014config_local='/usr/local/etc/man.d/*.conf'
1015
1016# Set noglobbing for now. I don't want spurious globbing.
1017set -f
1018
1019case "$0" in
1020*apropos)	do_apropos "$@" ;;
1021*manpath)	do_manpath "$@" ;;
1022*whatis)	do_whatis "$@" ;;
1023*)		do_man "$@" ;;
1024esac
1025