1213317Sgordon#! /bin/sh
2213317Sgordon#
3213317Sgordon#  Copyright (c) 2010 Gordon Tetlow
4213317Sgordon#  All rights reserved.
5213317Sgordon#
6213317Sgordon#  Redistribution and use in source and binary forms, with or without
7213317Sgordon#  modification, are permitted provided that the following conditions
8213317Sgordon#  are met:
9213317Sgordon#  1. Redistributions of source code must retain the above copyright
10213317Sgordon#     notice, this list of conditions and the following disclaimer.
11213317Sgordon#  2. Redistributions in binary form must reproduce the above copyright
12213317Sgordon#     notice, this list of conditions and the following disclaimer in the
13213317Sgordon#     documentation and/or other materials provided with the distribution.
14213317Sgordon#
15213317Sgordon#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16213317Sgordon#  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17213317Sgordon#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18213317Sgordon#  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19213317Sgordon#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20213317Sgordon#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21213317Sgordon#  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22213317Sgordon#  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23213317Sgordon#  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24213317Sgordon#  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25213317Sgordon#  SUCH DAMAGE.
26213317Sgordon#
27213317Sgordon# $FreeBSD$
28213317Sgordon
29213317Sgordon# Usage: add_to_manpath path
30213317Sgordon# Adds a variable to manpath while ensuring we don't have duplicates.
31213317Sgordon# Returns true if we were able to add something. False otherwise.
32213317Sgordonadd_to_manpath() {
33213317Sgordon	case "$manpath" in
34213317Sgordon	*:$1)	decho "  Skipping duplicate manpath entry $1" 2 ;;
35213317Sgordon	$1:*)	decho "  Skipping duplicate manpath entry $1" 2 ;;
36213317Sgordon	*:$1:*)	decho "  Skipping duplicate manpath entry $1" 2 ;;
37213317Sgordon	*)	if [ -d "$1" ]; then
38213317Sgordon			decho "  Adding $1 to manpath"
39213317Sgordon			manpath="$manpath:$1"
40213317Sgordon			return 0
41213317Sgordon		fi
42213317Sgordon		;;
43213317Sgordon	esac
44213317Sgordon
45213317Sgordon	return 1
46213317Sgordon}
47213317Sgordon
48213317Sgordon# Usage: build_manlocales
49213317Sgordon# Builds a correct MANLOCALES variable.
50213317Sgordonbuild_manlocales() {
51213317Sgordon	# If the user has set manlocales, who are we to argue.
52213317Sgordon	if [ -n "$MANLOCALES" ]; then
53213317Sgordon		return
54213317Sgordon	fi
55213317Sgordon
56213317Sgordon	parse_configs
57213317Sgordon
58213317Sgordon	# Trim leading colon
59213317Sgordon	MANLOCALES=${manlocales#:}
60213317Sgordon
61213317Sgordon	decho "Available manual locales: $MANLOCALES"
62213317Sgordon}
63213317Sgordon
64213317Sgordon# Usage: build_manpath
65213317Sgordon# Builds a correct MANPATH variable.
66213317Sgordonbuild_manpath() {
67213317Sgordon	local IFS
68213317Sgordon
69213317Sgordon	# If the user has set a manpath, who are we to argue.
70213317Sgordon	if [ -n "$MANPATH" ]; then
71213317Sgordon		return
72213317Sgordon	fi
73213317Sgordon
74213317Sgordon	search_path
75213317Sgordon
76213317Sgordon	decho "Adding default manpath entries"
77213317Sgordon	IFS=:
78213317Sgordon	for path in $man_default_path; do
79213317Sgordon		add_to_manpath "$path"
80213317Sgordon	done
81213317Sgordon	unset IFS
82213317Sgordon
83213317Sgordon	parse_configs
84213317Sgordon
85213317Sgordon	# Trim leading colon
86213317Sgordon	MANPATH=${manpath#:}
87213317Sgordon
88213317Sgordon	decho "Using manual path: $MANPATH"
89213317Sgordon}
90213317Sgordon
91213317Sgordon# Usage: check_cat catglob
92213317Sgordon# Checks to see if a cat glob is available.
93213317Sgordoncheck_cat() {
94213317Sgordon	if exists "$1"; then
95213317Sgordon		use_cat=yes
96213317Sgordon		catpage=$found
97216140Sgordon		setup_cattool $catpage
98213317Sgordon		decho "    Found catpage $catpage"
99213317Sgordon		return 0
100213317Sgordon	else
101213317Sgordon		return 1
102213317Sgordon	fi
103213317Sgordon}
104213317Sgordon
105213317Sgordon# Usage: check_man manglob catglob
106213317Sgordon# Given 2 globs, figures out if the manglob is available, if so, check to
107213317Sgordon# see if the catglob is also available and up to date.
108213317Sgordoncheck_man() {
109213317Sgordon	if exists "$1"; then
110213317Sgordon		# We have a match, check for a cat page
111213317Sgordon		manpage=$found
112216140Sgordon		setup_cattool $manpage
113213317Sgordon		decho "    Found manpage $manpage"
114213317Sgordon
115222635Sru		if [ -n "${use_width}" ]; then
116222635Sru			# non-standard width
117222635Sru			unset use_cat
118222635Sru			decho "    Skipping catpage: non-standard page width"
119222635Sru		elif exists "$2" && is_newer $found $manpage; then
120213317Sgordon			# cat page found and is newer, use that
121213317Sgordon			use_cat=yes
122213317Sgordon			catpage=$found
123216140Sgordon			setup_cattool $catpage
124213317Sgordon			decho "    Using catpage $catpage"
125213317Sgordon		else
126213317Sgordon			# no cat page or is older
127213317Sgordon			unset use_cat
128213317Sgordon			decho "    Skipping catpage: not found or old"
129213317Sgordon		fi
130213317Sgordon		return 0
131213317Sgordon	fi
132213317Sgordon
133213317Sgordon	return 1
134213317Sgordon}
135213317Sgordon
136213317Sgordon# Usage: decho "string" [debuglevel]
137213317Sgordon# Echoes to stderr string prefaced with -- if high enough debuglevel.
138213317Sgordondecho() {
139213317Sgordon	if [ $debug -ge ${2:-1} ]; then
140213317Sgordon		echo "-- $1" >&2
141213317Sgordon	fi
142213317Sgordon}
143213317Sgordon
144213317Sgordon# Usage: exists glob
145213317Sgordon# Returns true if glob resolves to a real file.
146213317Sgordonexists() {
147213317Sgordon	local IFS
148213317Sgordon
149213317Sgordon	# Don't accidentally inherit callers IFS (breaks perl manpages)
150213317Sgordon	unset IFS
151213317Sgordon
152213317Sgordon	# Use some globbing tricks in the shell to determine if a file
153213317Sgordon	# exists or not.
154213317Sgordon	set +f
155213317Sgordon	set -- "$1" $1
156213317Sgordon	set -f
157213317Sgordon
158213317Sgordon	if [ "$1" != "$2" -a -r "$2" ]; then
159213317Sgordon		found="$2"
160213317Sgordon		return 0
161213317Sgordon	fi
162213317Sgordon
163213317Sgordon	return 1
164213317Sgordon}
165213317Sgordon
166213317Sgordon# Usage: find_file path section subdir pagename
167213317Sgordon# Returns: true if something is matched and found.
168213317Sgordon# Search the given path/section combo for a given page.
169213317Sgordonfind_file() {
170213317Sgordon	local manroot catroot mann man0 catn cat0
171213317Sgordon
172213317Sgordon	manroot="$1/man$2"
173213317Sgordon	catroot="$1/cat$2"
174213317Sgordon	if [ -n "$3" ]; then
175213317Sgordon		manroot="$manroot/$3"
176213317Sgordon		catroot="$catroot/$3"
177213317Sgordon	fi
178213317Sgordon
179213317Sgordon	if [ ! -d "$manroot" ]; then
180213317Sgordon		return 1
181213317Sgordon	fi
182213317Sgordon	decho "  Searching directory $manroot" 2
183213317Sgordon
184213317Sgordon	mann="$manroot/$4.$2*"
185213317Sgordon	man0="$manroot/$4.0*"
186213317Sgordon	catn="$catroot/$4.$2*"
187213317Sgordon	cat0="$catroot/$4.0*"
188213317Sgordon
189213317Sgordon	# This is the behavior as seen by the original man utility.
190213317Sgordon	# Let's not change that which doesn't seem broken.
191213317Sgordon	if check_man "$mann" "$catn"; then
192213317Sgordon		return 0
193213317Sgordon	elif check_man "$man0" "$cat0"; then
194213317Sgordon		return 0
195213317Sgordon	elif check_cat "$catn"; then
196213317Sgordon		return 0
197213317Sgordon	elif check_cat "$cat0"; then
198213317Sgordon		return 0
199213317Sgordon	fi
200213317Sgordon
201213317Sgordon	return 1
202213317Sgordon}
203213317Sgordon
204213317Sgordon# Usage: is_newer file1 file2
205213317Sgordon# Returns true if file1 is newer than file2 as calculated by mtime.
206213317Sgordonis_newer() {
207217831Suqs	if ! [ "$1" -ot "$2" ]; then
208217831Suqs		decho "    mtime: $1 not older than $2" 3
209213317Sgordon		return 0
210213317Sgordon	else
211213317Sgordon		decho "    mtime: $1 older than $2" 3
212213317Sgordon		return 1
213213317Sgordon	fi
214213317Sgordon}
215213317Sgordon
216213317Sgordon# Usage: manpath_parse_args "$@"
217213317Sgordon# Parses commandline options for manpath.
218213317Sgordonmanpath_parse_args() {
219213317Sgordon	local cmd_arg
220213317Sgordon
221213317Sgordon	while getopts 'Ldq' cmd_arg; do
222213317Sgordon		case "${cmd_arg}" in
223213317Sgordon		L)	Lflag=Lflag ;;
224213317Sgordon		d)	debug=$(( $debug + 1 )) ;;
225213317Sgordon		q)	qflag=qflag ;;
226213317Sgordon		*)	manpath_usage ;;
227213317Sgordon		esac
228213317Sgordon	done >&2
229213317Sgordon}
230213317Sgordon
231213317Sgordon# Usage: manpath_usage
232213317Sgordon# Display usage for the manpath(1) utility.
233213317Sgordonmanpath_usage() {
234213317Sgordon	echo 'usage: manpath [-Ldq]' >&2
235213317Sgordon	exit 1
236213317Sgordon}
237213317Sgordon
238213317Sgordon# Usage: manpath_warnings
239213317Sgordon# Display some warnings to stderr.
240213317Sgordonmanpath_warnings() {
241213317Sgordon	if [ -z "$Lflag" -a -n "$MANPATH" ]; then
242213317Sgordon		echo "(Warning: MANPATH environment variable set)" >&2
243213317Sgordon	fi
244213317Sgordon
245213317Sgordon	if [ -n "$Lflag" -a -n "$MANLOCALES" ]; then
246213317Sgordon		echo "(Warning: MANLOCALES environment variable set)" >&2
247213317Sgordon	fi
248213317Sgordon}
249213317Sgordon
250216140Sgordon# Usage: man_check_for_so page path
251216140Sgordon# Returns: True if able to resolve the file, false if it ended in tears.
252216140Sgordon# Detects the presence of the .so directive and causes the file to be
253216140Sgordon# redirected to another source file.
254216140Sgordonman_check_for_so() {
255216140Sgordon	local IFS line tstr
256216140Sgordon
257216140Sgordon	unset IFS
258216140Sgordon
259216140Sgordon	# We need to loop to accommodate multiple .so directives.
260216140Sgordon	while true
261216140Sgordon	do
262216140Sgordon		line=$($cattool $manpage | head -1)
263216140Sgordon		case "$line" in
264216140Sgordon		.so*)	trim "${line#.so}"
265216140Sgordon			decho "$manpage includes $tstr"
266216140Sgordon			# Glob and check for the file.
267216140Sgordon			if ! check_man "$path/$tstr*" ""; then
268216140Sgordon				decho "  Unable to find $tstr"
269216140Sgordon				return 1
270216140Sgordon			fi
271216140Sgordon			;;
272216140Sgordon		*)	break ;;
273216140Sgordon		esac
274216140Sgordon	done
275216140Sgordon
276216140Sgordon	return 0
277216140Sgordon}
278216140Sgordon
279213317Sgordon# Usage: man_display_page
280213317Sgordon# Display either the manpage or catpage depending on the use_cat variable
281213317Sgordonman_display_page() {
282222650Sru	local EQN NROFF PIC TBL TROFF REFER VGRIND
283213317Sgordon	local IFS l nroff_dev pipeline preproc_arg tool
284213317Sgordon
285213317Sgordon	# We are called with IFS set to colon. This causes really weird
286213317Sgordon	# things to happen for the variables that have spaces in them.
287213317Sgordon	unset IFS
288213317Sgordon
289213317Sgordon	# If we are supposed to use a catpage and we aren't using troff(1)
290213317Sgordon	# just zcat the catpage and we are done.
291213317Sgordon	if [ -z "$tflag" -a -n "$use_cat" ]; then
292213317Sgordon		if [ -n "$wflag" ]; then
293213317Sgordon			echo "$catpage (source: $manpage)"
294213317Sgordon			ret=0
295213317Sgordon		else
296213317Sgordon			if [ $debug -gt 0 ]; then
297222653Sru				decho "Command: $cattool $catpage | $MANPAGER"
298213317Sgordon				ret=0
299213317Sgordon			else
300222653Sru				eval "$cattool $catpage | $MANPAGER"
301213317Sgordon				ret=$?
302213317Sgordon			fi
303213317Sgordon		fi
304213317Sgordon		return
305213317Sgordon	fi
306213317Sgordon
307213317Sgordon	# Okay, we are using the manpage, do we just need to output the
308213317Sgordon	# name of the manpage?
309213317Sgordon	if [ -n "$wflag" ]; then
310213317Sgordon		echo "$manpage"
311213317Sgordon		ret=0
312213317Sgordon		return
313213317Sgordon	fi
314213317Sgordon
315213317Sgordon	# So, we really do need to parse the manpage. First, figure out the
316213317Sgordon	# device flag (-T) we have to pass to eqn(1) and groff(1). Then,
317213317Sgordon	# setup the pipeline of commands based on the user's request.
318213317Sgordon
319220261Sgordon	# If the manpage is from a particular charset, we need to setup nroff
320220261Sgordon	# to properly output for the correct device.
321220261Sgordon	case "${manpage}" in
322220261Sgordon	*.${man_charset}/*)
323213317Sgordon		# I don't pretend to know this; I'm just copying from the
324213317Sgordon		# previous version of man(1).
325213317Sgordon		case "$man_charset" in
326213317Sgordon		KOI8-R)		nroff_dev="koi8-r" ;;
327213317Sgordon		ISO8859-1)	nroff_dev="latin1" ;;
328213317Sgordon		ISO8859-15)	nroff_dev="latin1" ;;
329213317Sgordon		UTF-8)		nroff_dev="utf8" ;;
330213317Sgordon		*)		nroff_dev="ascii" ;;
331213317Sgordon		esac
332213317Sgordon
333220261Sgordon		NROFF="$NROFF -T$nroff_dev"
334213317Sgordon		EQN="$EQN -T$nroff_dev"
335213317Sgordon
336220261Sgordon		# Iff the manpage is from the locale and not just the charset,
337220261Sgordon		# then we need to define the locale string.
338220261Sgordon		case "${manpage}" in
339220261Sgordon		*/${man_lang}_${man_country}.${man_charset}/*)
340220261Sgordon			NROFF="$NROFF -dlocale=$man_lang.$man_charset"
341220261Sgordon			;;
342220261Sgordon		*/${man_lang}.${man_charset}/*)
343220261Sgordon			NROFF="$NROFF -dlocale=$man_lang.$man_charset"
344220261Sgordon			;;
345220261Sgordon		esac
346220261Sgordon
347213317Sgordon		# Allow language specific calls to override the default
348213317Sgordon		# set of utilities.
349213317Sgordon		l=$(echo $man_lang | tr [:lower:] [:upper:])
350222650Sru		for tool in EQN NROFF PIC TBL TROFF REFER VGRIND; do
351213317Sgordon			eval "$tool=\${${tool}_$l:-\$$tool}"
352213317Sgordon		done
353213317Sgordon		;;
354213317Sgordon	*)	NROFF="$NROFF -Tascii"
355213317Sgordon		EQN="$EQN -Tascii"
356213317Sgordon		;;
357213317Sgordon	esac
358213317Sgordon
359222653Sru	if [ -z "$MANCOLOR" ]; then
360222653Sru		NROFF="$NROFF -P-c"
361222653Sru	fi
362222653Sru
363222635Sru	if [ -n "${use_width}" ]; then
364222635Sru		NROFF="$NROFF -rLL=${use_width}n -rLT=${use_width}n"
365222635Sru	fi
366222635Sru
367213317Sgordon	if [ -n "$MANROFFSEQ" ]; then
368213317Sgordon		set -- -$MANROFFSEQ
369213317Sgordon		while getopts 'egprtv' preproc_arg; do
370213317Sgordon			case "${preproc_arg}" in
371213317Sgordon			e)	pipeline="$pipeline | $EQN" ;;
372228992Suqs			g)	;; # Ignore for compatibility.
373213317Sgordon			p)	pipeline="$pipeline | $PIC" ;;
374213317Sgordon			r)	pipeline="$pipeline | $REFER" ;;
375222650Sru			t)	pipeline="$pipeline | $TBL" ;;
376213317Sgordon			v)	pipeline="$pipeline | $VGRIND" ;;
377213317Sgordon			*)	usage ;;
378213317Sgordon			esac
379213317Sgordon		done
380213317Sgordon		# Strip the leading " | " from the resulting pipeline.
381213317Sgordon		pipeline="${pipeline#" | "}"
382213317Sgordon	else
383213317Sgordon		pipeline="$TBL"
384213317Sgordon	fi
385213317Sgordon
386213317Sgordon	if [ -n "$tflag" ]; then
387213317Sgordon		pipeline="$pipeline | $TROFF"
388213317Sgordon	else
389222653Sru		pipeline="$pipeline | $NROFF | $MANPAGER"
390213317Sgordon	fi
391213317Sgordon
392213317Sgordon	if [ $debug -gt 0 ]; then
393216140Sgordon		decho "Command: $cattool $manpage | $pipeline"
394213317Sgordon		ret=0
395213317Sgordon	else
396216140Sgordon		eval "$cattool $manpage | $pipeline"
397213317Sgordon		ret=$?
398213317Sgordon	fi
399213317Sgordon}
400213317Sgordon
401213317Sgordon# Usage: man_find_and_display page
402213317Sgordon# Search through the manpaths looking for the given page.
403213317Sgordonman_find_and_display() {
404213317Sgordon	local found_page locpath p path sect
405213317Sgordon
406213507Sgordon	# Check to see if it's a file. But only if it has a '/' in
407213507Sgordon	# the filename.
408213507Sgordon	case "$1" in
409213507Sgordon	*/*)	if [ -f "$1" -a -r "$1" ]; then
410213507Sgordon			decho "Found a usable page, displaying that"
411213507Sgordon			unset use_cat
412213507Sgordon			manpage="$1"
413216140Sgordon			setup_cattool $manpage
414216140Sgordon			if man_check_for_so $manpage $(dirname $manpage); then
415216140Sgordon				found_page=yes
416216140Sgordon				man_display_page
417216140Sgordon			fi
418213507Sgordon			return
419213507Sgordon		fi
420213507Sgordon		;;
421213507Sgordon	esac
422213507Sgordon
423213317Sgordon	IFS=:
424213317Sgordon	for sect in $MANSECT; do
425213317Sgordon		decho "Searching section $sect" 2
426213317Sgordon		for path in $MANPATH; do
427213317Sgordon			for locpath in $locpaths; do
428213317Sgordon				p=$path/$locpath
429213317Sgordon				p=${p%/.} # Rid ourselves of the trailing /.
430213317Sgordon
431213317Sgordon				# Check if there is a MACHINE specific manpath.
432213317Sgordon				if find_file $p $sect $MACHINE "$1"; then
433216140Sgordon					if man_check_for_so $manpage $p; then
434216140Sgordon						found_page=yes
435216140Sgordon						man_display_page
436216140Sgordon						if [ -n "$aflag" ]; then
437216140Sgordon							continue 2
438216140Sgordon						else
439216140Sgordon							return
440216140Sgordon						fi
441213317Sgordon					fi
442213317Sgordon				fi
443213317Sgordon
444213317Sgordon				# Check if there is a MACHINE_ARCH
445213317Sgordon				# specific manpath.
446213317Sgordon				if find_file $p $sect $MACHINE_ARCH "$1"; then
447216140Sgordon					if man_check_for_so $manpage $p; then
448216140Sgordon						found_page=yes
449216140Sgordon						man_display_page
450216140Sgordon						if [ -n "$aflag" ]; then
451216140Sgordon							continue 2
452216140Sgordon						else
453216140Sgordon							return
454216140Sgordon						fi
455213317Sgordon					fi
456213317Sgordon				fi
457213317Sgordon
458213317Sgordon				# Check plain old manpath.
459213317Sgordon				if find_file $p $sect '' "$1"; then
460216140Sgordon					if man_check_for_so $manpage $p; then
461216140Sgordon						found_page=yes
462216140Sgordon						man_display_page
463216140Sgordon						if [ -n "$aflag" ]; then
464216140Sgordon							continue 2
465216140Sgordon						else
466216140Sgordon							return
467216140Sgordon						fi
468213317Sgordon					fi
469213317Sgordon				fi
470213317Sgordon			done
471213317Sgordon		done
472213317Sgordon	done
473213317Sgordon	unset IFS
474213317Sgordon
475213317Sgordon	# Nothing? Well, we are done then.
476213317Sgordon	if [ -z "$found_page" ]; then
477213317Sgordon		echo "No manual entry for $1" >&2
478213317Sgordon		ret=1
479213317Sgordon		return
480213317Sgordon	fi
481213317Sgordon}
482213317Sgordon
483213317Sgordon# Usage: man_parse_args "$@"
484213317Sgordon# Parses commandline options for man.
485213317Sgordonman_parse_args() {
486213317Sgordon	local IFS cmd_arg
487213317Sgordon
488213317Sgordon	while getopts 'M:P:S:adfhkm:op:tw' cmd_arg; do
489213317Sgordon		case "${cmd_arg}" in
490213317Sgordon		M)	MANPATH=$OPTARG ;;
491222653Sru		P)	MANPAGER=$OPTARG ;;
492213317Sgordon		S)	MANSECT=$OPTARG ;;
493213317Sgordon		a)	aflag=aflag ;;
494213317Sgordon		d)	debug=$(( $debug + 1 )) ;;
495213317Sgordon		f)	fflag=fflag ;;
496213317Sgordon		h)	man_usage 0 ;;
497213317Sgordon		k)	kflag=kflag ;;
498213317Sgordon		m)	mflag=$OPTARG ;;
499213317Sgordon		o)	oflag=oflag ;;
500213317Sgordon		p)	MANROFFSEQ=$OPTARG ;;
501213317Sgordon		t)	tflag=tflag ;;
502213317Sgordon		w)	wflag=wflag ;;
503213317Sgordon		*)	man_usage ;;
504213317Sgordon		esac
505213317Sgordon	done >&2
506213317Sgordon
507213317Sgordon	shift $(( $OPTIND - 1 ))
508213317Sgordon
509213317Sgordon	# Check the args for incompatible options.
510213317Sgordon	case "${fflag}${kflag}${tflag}${wflag}" in
511213317Sgordon	fflagkflag*)	echo "Incompatible options: -f and -k"; man_usage ;;
512213317Sgordon	fflag*tflag*)	echo "Incompatible options: -f and -t"; man_usage ;;
513213317Sgordon	fflag*wflag)	echo "Incompatible options: -f and -w"; man_usage ;;
514213317Sgordon	*kflagtflag*)	echo "Incompatible options: -k and -t"; man_usage ;;
515213317Sgordon	*kflag*wflag)	echo "Incompatible options: -k and -w"; man_usage ;;
516213317Sgordon	*tflagwflag)	echo "Incompatible options: -t and -w"; man_usage ;;
517213317Sgordon	esac
518213317Sgordon
519213317Sgordon	# Short circuit for whatis(1) and apropos(1)
520213317Sgordon	if [ -n "$fflag" ]; then
521213317Sgordon		do_whatis "$@"
522213317Sgordon		exit
523213317Sgordon	fi
524213317Sgordon
525213317Sgordon	if [ -n "$kflag" ]; then
526213317Sgordon		do_apropos "$@"
527213317Sgordon		exit
528213317Sgordon	fi
529213317Sgordon
530213317Sgordon	IFS=:
531213317Sgordon	for sect in $man_default_sections; do
532213317Sgordon		if [ "$sect" = "$1" ]; then
533213317Sgordon			decho "Detected manual section as first arg: $1"
534213317Sgordon			MANSECT="$1"
535213317Sgordon			shift
536213317Sgordon			break
537213317Sgordon		fi
538213317Sgordon	done
539213317Sgordon	unset IFS
540213317Sgordon
541213317Sgordon	pages="$*"
542213317Sgordon}
543213317Sgordon
544213317Sgordon# Usage: man_setup
545213317Sgordon# Setup various trivial but essential variables.
546213317Sgordonman_setup() {
547213317Sgordon	# Setup machine and architecture variables.
548213317Sgordon	if [ -n "$mflag" ]; then
549213317Sgordon		MACHINE_ARCH=${mflag%%:*}
550213317Sgordon		MACHINE=${mflag##*:}
551213317Sgordon	fi
552213317Sgordon	if [ -z "$MACHINE_ARCH" ]; then
553216426Sgordon		MACHINE_ARCH=$($SYSCTL -n hw.machine_arch)
554213317Sgordon	fi
555213317Sgordon	if [ -z "$MACHINE" ]; then
556216426Sgordon		MACHINE=$($SYSCTL -n hw.machine)
557213317Sgordon	fi
558213317Sgordon	decho "Using architecture: $MACHINE_ARCH:$MACHINE"
559213317Sgordon
560213317Sgordon	setup_pager
561213317Sgordon
562213317Sgordon	# Setup manual sections to search.
563213317Sgordon	if [ -z "$MANSECT" ]; then
564213317Sgordon		MANSECT=$man_default_sections
565213317Sgordon	fi
566213317Sgordon	decho "Using manual sections: $MANSECT"
567213317Sgordon
568213317Sgordon	build_manpath
569213317Sgordon	man_setup_locale
570222635Sru	man_setup_width
571213317Sgordon}
572213317Sgordon
573222635Sru# Usage: man_setup_width
574222635Sru# Set up page width.
575222635Sruman_setup_width() {
576222635Sru	local sizes
577222635Sru
578222635Sru	unset use_width
579222635Sru	case "$MANWIDTH" in
580222635Sru	[0-9]*)
581222635Sru		if [ "$MANWIDTH" -gt 0 2>/dev/null ]; then
582222635Sru			use_width=$MANWIDTH
583222635Sru		fi
584222635Sru		;;
585222635Sru	[Tt][Tt][Yy])
586222635Sru		if { sizes=$($STTY size 0>&3 2>/dev/null); } 3>&1; then
587222635Sru			set -- $sizes
588222635Sru			if [ $2 -gt 80 ]; then
589222635Sru				use_width=$(($2-2))
590222635Sru			fi
591222635Sru		fi
592222635Sru		;;
593222635Sru	esac
594222635Sru	if [ -n "$use_width" ]; then
595222635Sru		decho "Using non-standard page width: ${use_width}"
596222635Sru	else
597222635Sru		decho 'Using standard page width'
598222635Sru	fi
599222635Sru}
600222635Sru
601213317Sgordon# Usage: man_setup_locale
602213317Sgordon# Setup necessary locale variables.
603213317Sgordonman_setup_locale() {
604220261Sgordon	local lang_cc
605220261Sgordon
606220261Sgordon	locpaths='.'
607220261Sgordon	man_charset='US-ASCII'
608220261Sgordon
609213317Sgordon	# Setup locale information.
610213317Sgordon	if [ -n "$oflag" ]; then
611220261Sgordon		decho 'Using non-localized manpages'
612220261Sgordon	else
613220261Sgordon		# Use the locale tool to give us the proper LC_CTYPE
614220261Sgordon		eval $( $LOCALE )
615220261Sgordon
616220261Sgordon		case "$LC_CTYPE" in
617220261Sgordon		C)		;;
618220261Sgordon		POSIX)		;;
619220261Sgordon		[a-z][a-z]_[A-Z][A-Z]\.*)
620220261Sgordon				lang_cc="${LC_CTYPE%.*}"
621220261Sgordon				man_lang="${LC_CTYPE%_*}"
622220261Sgordon				man_country="${lang_cc#*_}"
623220261Sgordon				man_charset="${LC_CTYPE#*.}"
624220261Sgordon				locpaths="$LC_CTYPE"
625220261Sgordon				locpaths="$locpaths:$man_lang.$man_charset"
626220261Sgordon				if [ "$man_lang" != "en" ]; then
627220261Sgordon					locpaths="$locpaths:en.$man_charset"
628220261Sgordon				fi
629220261Sgordon				locpaths="$locpaths:."
630220261Sgordon				;;
631220261Sgordon		*)		echo 'Unknown locale, assuming C' >&2
632220261Sgordon				;;
633220261Sgordon		esac
634213317Sgordon	fi
635213317Sgordon
636213317Sgordon	decho "Using locale paths: $locpaths"
637213317Sgordon}
638213317Sgordon
639213317Sgordon# Usage: man_usage [exitcode]
640213317Sgordon# Display usage for the man utility.
641213317Sgordonman_usage() {
642213317Sgordon	echo 'Usage:'
643213317Sgordon	echo ' man [-adho] [-t | -w] [-M manpath] [-P pager] [-S mansect]'
644213317Sgordon	echo '     [-m arch[:machine]] [-p [eprtv]] [mansect] page [...]'
645213317Sgordon	echo ' man -f page [...] -- Emulates whatis(1)'
646213317Sgordon	echo ' man -k page [...] -- Emulates apropos(1)'
647213317Sgordon
648213317Sgordon	# When exit'ing with -h, it's not an error.
649213317Sgordon	exit ${1:-1}
650213317Sgordon}
651213317Sgordon
652213317Sgordon# Usage: parse_configs
653213317Sgordon# Reads the end-user adjustable config files.
654213317Sgordonparse_configs() {
655213317Sgordon	local IFS file files
656213317Sgordon
657213317Sgordon	if [ -n "$parsed_configs" ]; then
658213317Sgordon		return
659213317Sgordon	fi
660213317Sgordon
661213317Sgordon	unset IFS
662213317Sgordon
663213317Sgordon	# Read the global config first in case the user wants
664213317Sgordon	# to override config_local.
665213317Sgordon	if [ -r "$config_global" ]; then
666213317Sgordon		parse_file "$config_global"
667213317Sgordon	fi
668213317Sgordon
669213317Sgordon	# Glob the list of files to parse.
670213317Sgordon	set +f
671213317Sgordon	files=$(echo $config_local)
672213317Sgordon	set -f
673213317Sgordon
674213317Sgordon	for file in $files; do
675213317Sgordon		if [ -r "$file" ]; then
676213317Sgordon			parse_file "$file"
677213317Sgordon		fi
678213317Sgordon	done
679213317Sgordon
680213317Sgordon	parsed_configs='yes'
681213317Sgordon}
682213317Sgordon
683213317Sgordon# Usage: parse_file file
684213317Sgordon# Reads the specified config files.
685213317Sgordonparse_file() {
686213317Sgordon	local file line tstr var
687213317Sgordon
688213317Sgordon	file="$1"
689213317Sgordon	decho "Parsing config file: $file"
690213317Sgordon	while read line; do
691213317Sgordon		decho "  $line" 2
692213317Sgordon		case "$line" in
693213317Sgordon		\#*)		decho "    Comment" 3
694213317Sgordon				;;
695213317Sgordon		MANPATH*)	decho "    MANPATH" 3
696213317Sgordon				trim "${line#MANPATH}"
697213317Sgordon				add_to_manpath "$tstr"
698213317Sgordon				;;
699213317Sgordon		MANLOCALE*)	decho "    MANLOCALE" 3
700213317Sgordon				trim "${line#MANLOCALE}"
701213317Sgordon				manlocales="$manlocales:$tstr"
702213317Sgordon				;;
703213317Sgordon		MANCONFIG*)	decho "    MANCONFIG" 3
704222638Sru				trim "${line#MANCONFIG}"
705213317Sgordon				config_local="$tstr"
706213317Sgordon				;;
707213317Sgordon		# Set variables in the form of FOO_BAR
708213317Sgordon		*_*[\ \	]*)	var="${line%%[\ \	]*}"
709213317Sgordon				trim "${line#$var}"
710213317Sgordon				eval "$var=\"$tstr\""
711213317Sgordon				decho "    Parsed $var" 3
712213317Sgordon				;;
713213317Sgordon		esac
714213317Sgordon	done < "$file"
715213317Sgordon}
716213317Sgordon
717213317Sgordon# Usage: search_path
718213317Sgordon# Traverse $PATH looking for manpaths.
719213317Sgordonsearch_path() {
720213317Sgordon	local IFS p path
721213317Sgordon
722213317Sgordon	decho "Searching PATH for man directories"
723213317Sgordon
724213317Sgordon	IFS=:
725213317Sgordon	for path in $PATH; do
726213317Sgordon		# Do a little special casing since the base manpages
727213317Sgordon		# are in /usr/share/man instead of /usr/man or /man.
728213317Sgordon		case "$path" in
729213317Sgordon		/bin|/usr/bin)	add_to_manpath "/usr/share/man" ;;
730213317Sgordon		*)	if add_to_manpath "$path/man"; then
731213317Sgordon				:
732213317Sgordon			elif add_to_manpath "$path/MAN"; then
733213317Sgordon				:
734213317Sgordon			else
735213317Sgordon				case "$path" in
736213317Sgordon				*/bin)	p="${path%/bin}/man"
737213317Sgordon					add_to_manpath "$p"
738213317Sgordon					;;
739213317Sgordon				*)	;;
740213317Sgordon				esac
741213317Sgordon			fi
742213317Sgordon			;;
743213317Sgordon		esac
744213317Sgordon	done
745213317Sgordon	unset IFS
746213317Sgordon
747213317Sgordon	if [ -z "$manpath" ]; then
748213317Sgordon		decho '  Unable to find any manpaths, using default'
749213317Sgordon		manpath=$man_default_path
750213317Sgordon	fi
751213317Sgordon}
752213317Sgordon
753213317Sgordon# Usage: search_whatis cmd [arglist]
754213317Sgordon# Do the heavy lifting for apropos/whatis
755213317Sgordonsearch_whatis() {
756213317Sgordon	local IFS bad cmd f good key keywords loc opt out path rval wlist
757213317Sgordon
758213317Sgordon	cmd="$1"
759213317Sgordon	shift
760213317Sgordon
761213317Sgordon	whatis_parse_args "$@"
762213317Sgordon
763213317Sgordon	build_manpath
764213317Sgordon	build_manlocales
765213317Sgordon	setup_pager
766213317Sgordon
767213317Sgordon	if [ "$cmd" = "whatis" ]; then
768213317Sgordon		opt="-w"
769213317Sgordon	fi
770213317Sgordon
771213317Sgordon	f='whatis'
772213317Sgordon
773213317Sgordon	IFS=:
774213317Sgordon	for path in $MANPATH; do
775213317Sgordon		if [ \! -d "$path" ]; then
776213317Sgordon			decho "Skipping non-existent path: $path" 2
777213317Sgordon			continue
778213317Sgordon		fi
779213317Sgordon
780213317Sgordon		if [ -f "$path/$f" -a -r "$path/$f" ]; then
781213317Sgordon			decho "Found whatis: $path/$f"
782213317Sgordon			wlist="$wlist $path/$f"
783213317Sgordon		fi
784213317Sgordon
785213317Sgordon		for loc in $MANLOCALES; do
786213317Sgordon			if [ -f "$path/$loc/$f" -a -r "$path/$loc/$f" ]; then
787213317Sgordon				decho "Found whatis: $path/$loc/$f"
788213317Sgordon				wlist="$wlist $path/$loc/$f"
789213317Sgordon			fi
790213317Sgordon		done
791213317Sgordon	done
792213317Sgordon	unset IFS
793213317Sgordon
794213317Sgordon	if [ -z "$wlist" ]; then
795213317Sgordon		echo "$cmd: no whatis databases in $MANPATH" >&2
796213317Sgordon		exit 1
797213317Sgordon	fi
798213317Sgordon
799213317Sgordon	rval=0
800213317Sgordon	for key in $keywords; do
801213317Sgordon		out=$(grep -Ehi $opt -- "$key" $wlist)
802213317Sgordon		if [ -n "$out" ]; then
803213317Sgordon			good="$good\\n$out"
804213317Sgordon		else
805213317Sgordon			bad="$bad\\n$key: nothing appropriate"
806213317Sgordon			rval=1
807213317Sgordon		fi
808213317Sgordon	done
809213317Sgordon
810213317Sgordon	# Strip leading carriage return.
811213317Sgordon	good=${good#\\n}
812213317Sgordon	bad=${bad#\\n}
813213317Sgordon
814213317Sgordon	if [ -n "$good" ]; then
815222653Sru		echo -e "$good" | $MANPAGER
816213317Sgordon	fi
817213317Sgordon
818213317Sgordon	if [ -n "$bad" ]; then
819213349Sgordon		echo -e "$bad" >&2
820213317Sgordon	fi
821213317Sgordon
822213317Sgordon	exit $rval
823213317Sgordon}
824213317Sgordon
825216140Sgordon# Usage: setup_cattool page
826216140Sgordon# Finds an appropriate decompressor based on extension
827216140Sgordonsetup_cattool() {
828216140Sgordon	case "$1" in
829216140Sgordon	*.bz)	cattool='/usr/bin/bzcat' ;;
830216140Sgordon	*.bz2)	cattool='/usr/bin/bzcat' ;;
831216140Sgordon	*.gz)	cattool='/usr/bin/zcat' ;;
832216140Sgordon	*.lzma)	cattool='/usr/bin/lzcat' ;;
833216140Sgordon	*.xz)	cattool='/usr/bin/xzcat' ;;
834216140Sgordon	*)	cattool='/usr/bin/zcat -f' ;;
835216140Sgordon	esac
836216140Sgordon}
837216140Sgordon
838213317Sgordon# Usage: setup_pager
839222653Sru# Correctly sets $MANPAGER
840213317Sgordonsetup_pager() {
841213317Sgordon	# Setup pager.
842222653Sru	if [ -z "$MANPAGER" ]; then
843222653Sru		if [ -n "$MANCOLOR" ]; then
844222653Sru			MANPAGER="less -sR"
845222653Sru		else
846222653Sru			if [ -n "$PAGER" ]; then
847222653Sru				MANPAGER="$PAGER"
848222653Sru			else
849222653Sru				MANPAGER="more -s"
850222653Sru			fi
851222653Sru		fi
852213317Sgordon	fi
853222653Sru	decho "Using pager: $MANPAGER"
854213317Sgordon}
855213317Sgordon
856213317Sgordon# Usage: trim string
857213317Sgordon# Trims whitespace from beginning and end of a variable
858213317Sgordontrim() {
859213317Sgordon	tstr=$1
860213317Sgordon	while true; do
861213317Sgordon		case "$tstr" in
862213317Sgordon		[\ \	]*)	tstr="${tstr##[\ \	]}" ;;
863213317Sgordon		*[\ \	])	tstr="${tstr%%[\ \	]}" ;;
864213317Sgordon		*)		break ;;
865213317Sgordon		esac
866213317Sgordon	done
867213317Sgordon}
868213317Sgordon
869213317Sgordon# Usage: whatis_parse_args "$@"
870213317Sgordon# Parse commandline args for whatis and apropos.
871213317Sgordonwhatis_parse_args() {
872213317Sgordon	local cmd_arg
873213317Sgordon	while getopts 'd' cmd_arg; do
874213317Sgordon		case "${cmd_arg}" in
875213317Sgordon		d)	debug=$(( $debug + 1 )) ;;
876213317Sgordon		*)	whatis_usage ;;
877213317Sgordon		esac
878213317Sgordon	done >&2
879213317Sgordon
880213317Sgordon	shift $(( $OPTIND - 1 ))
881213317Sgordon
882213317Sgordon	keywords="$*"
883213317Sgordon}
884213317Sgordon
885213317Sgordon# Usage: whatis_usage
886213317Sgordon# Display usage for the whatis/apropos utility.
887213317Sgordonwhatis_usage() {
888213317Sgordon	echo "usage: $cmd [-d] keyword [...]"
889213317Sgordon	exit 1
890213317Sgordon}
891213317Sgordon
892213317Sgordon
893213317Sgordon
894213317Sgordon# Supported commands
895213317Sgordondo_apropos() {
896213317Sgordon	search_whatis apropos "$@"
897213317Sgordon}
898213317Sgordon
899213317Sgordondo_man() {
900213317Sgordon	man_parse_args "$@"
901213317Sgordon	if [ -z "$pages" ]; then
902213317Sgordon		echo 'What manual page do you want?' >&2
903213317Sgordon		exit 1
904213317Sgordon	fi
905213317Sgordon	man_setup
906213317Sgordon
907213317Sgordon	for page in $pages; do
908213317Sgordon		decho "Searching for $page"
909213317Sgordon		man_find_and_display "$page"
910213317Sgordon	done
911213317Sgordon
912213317Sgordon	exit ${ret:-0}
913213317Sgordon}
914213317Sgordon
915213317Sgordondo_manpath() {
916213317Sgordon	manpath_parse_args "$@"
917213317Sgordon	if [ -z "$qflag" ]; then
918213317Sgordon		manpath_warnings
919213317Sgordon	fi
920213317Sgordon	if [ -n "$Lflag" ]; then
921213317Sgordon		build_manlocales
922213317Sgordon		echo $MANLOCALES
923213317Sgordon	else
924213317Sgordon		build_manpath
925213317Sgordon		echo $MANPATH
926213317Sgordon	fi
927213317Sgordon	exit 0
928213317Sgordon}
929213317Sgordon
930213317Sgordondo_whatis() {
931213317Sgordon	search_whatis whatis "$@"
932213317Sgordon}
933213317Sgordon
934221303Suqs# User's PATH setting decides on the groff-suite to pick up.
935221303SuqsEQN=eqn
936222653SruNROFF='groff -S -P-h -Wall -mtty-char -man'
937221303SuqsPIC=pic
938221303SuqsREFER=refer
939221303SuqsTBL=tbl
940222601SuqsTROFF='groff -S -man'
941221303SuqsVGRIND=vgrind
942221303Suqs
943220261SgordonLOCALE=/usr/bin/locale
944222635SruSTTY=/bin/stty
945216426SgordonSYSCTL=/sbin/sysctl
946213317Sgordon
947213317Sgordondebug=0
948245514Sbrooksman_default_sections='1:8:2:3:n:4:5:6:7:9:l'
949213317Sgordonman_default_path='/usr/share/man:/usr/share/openssl/man:/usr/local/man'
950216140Sgordoncattool='/usr/bin/zcat -f'
951213317Sgordon
952213317Sgordonconfig_global='/etc/man.conf'
953213317Sgordon
954213317Sgordon# This can be overridden via a setting in /etc/man.conf.
955213317Sgordonconfig_local='/usr/local/etc/man.d/*.conf'
956213317Sgordon
957213317Sgordon# Set noglobbing for now. I don't want spurious globbing.
958213317Sgordonset -f
959213317Sgordon
960213317Sgordoncase "$0" in
961213317Sgordon*apropos)	do_apropos "$@" ;;
962213317Sgordon*manpath)	do_manpath "$@" ;;
963213317Sgordon*whatis)	do_whatis "$@" ;;
964213317Sgordon*)		do_man "$@" ;;
965213317Sgordonesac
966