xref: /titanic_50/usr/src/lib/libshell/common/scripts/shman.sh (revision a4aeef46cda1835da2b19f8f62b4526de6521e6c)
1#!/usr/bin/ksh93
2
3#
4# CDDL HEADER START
5#
6# The contents of this file are subject to the terms of the
7# Common Development and Distribution License (the "License").
8# You may not use this file except in compliance with the License.
9#
10# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11# or http://www.opensolaris.org/os/licensing.
12# See the License for the specific language governing permissions
13# and limitations under the License.
14#
15# When distributing Covered Code, include this CDDL HEADER in each
16# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17# If applicable, add the following below this CDDL HEADER, with the
18# fields enclosed by brackets "[]" replaced with your own identifying
19# information: Portions Copyright [yyyy] [name of copyright owner]
20#
21# CDDL HEADER END
22#
23
24#
25# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
26# Use is subject to license terms.
27#
28
29# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant
30export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin
31
32# Make sure all math stuff runs in the "C" locale to avoid problems
33# with alternative # radix point representations (e.g. ',' instead of
34# '.' in de_DE.*-locales). This needs to be set _before_ any
35# floating-point constants are defined in this script).
36if [[ "${LC_ALL}" != "" ]] ; then
37    export \
38        LC_MONETARY="${LC_ALL}" \
39        LC_MESSAGES="${LC_ALL}" \
40        LC_COLLATE="${LC_ALL}" \
41        LC_CTYPE="${LC_ALL}"
42        unset LC_ALL
43fi
44export LC_NUMERIC=C
45
46function fatal_error
47{
48	print -u2 "${progname}: $*"
49	exit 1
50}
51
52function debug_print
53{
54	# don't use "--" here to allow "-f" for formatting
55#	print -u2 "$@"
56	return 0
57}
58
59# Build a list of compound variables calculated from MANPATH and
60# locale which contain...
61# "manpath_element" - the MANPATH element this entry belongs to
62# "dir"             - physical directory of "manpath_element"
63# "sect"            - section (if "manpath_element" is something like /usr/share/man,1b)
64# ... and put the result in the array named by argv[1]
65function enumerate_mandirs
66{
67	nameref md=$1
68	typeset manpath_element dir sect manlang
69	integer i=0
70
71	if [[ "${LC_MESSAGES}" != "" ]] ; then
72		manlang="${LC_MESSAGES}"
73	else
74		manlang="${LANG}"
75	fi
76
77	print -r -- "${MANPATH//:/$'\n'}" | while read manpath_element ; do
78		# strip section from manpath elements like "/usr/share/man,1b"
79		dir="${manpath_element/~(E)(.*),(.*)/\1}"
80		sect="${manpath_element/~(E)(.*),(.*)/\2}"
81		[[ "${sect}" == "${dir}" ]] && sect=""
82
83		if [[ "${manlang}" != "" && -d "${dir}/${manlang}" ]] ; then
84			md+=(
85				manpath_element="${manpath_element}"
86				dir="${dir}/${manlang}"
87				sect="${sect}"
88			)
89		fi
90		if [[ -d "${dir}" ]] ; then
91			md+=(
92				manpath_element="${manpath_element}"
93				dir="${dir}"
94				sect="${sect}"
95			)
96		fi
97	done
98
99	return 0
100}
101
102function enumerate_mansects
103{
104	nameref ms=$1
105	nameref mandir_node=$2
106	typeset mancf="${mandir_node.dir}/man.cf"
107	typeset x s l
108
109	if [[ "${mandir_node.sect}" != "" ]] ; then
110		x="${mandir_node.sect}"
111	elif [[ "${MANSECTS}" != "" ]] ; then
112		x="${MANSECTS//,/$'\n'}"
113	elif [[ -f "${mancf}" && -r "${mancf}" ]] ; then
114		x="$(egrep -v '^#|^[[:space:]]*$' <"${mancf}" | egrep '^MANSECTS=')"
115		x="${x/MANSECTS=}/"
116		x="${x//,/$'\n'}"
117	else
118		x="$(cd "${mandir_node.dir}" ; \
119			ls -1d ~(El)(sman|man).*/ | \
120			while read s ; do \
121				s="${s/~(El)(sman|man)/}" ; \
122				s="${s/~(Er)\//}" ; \
123				print -r -- "$s" ; \
124			done)"
125	fi
126
127	while read l ; do
128		[[ "${l}" != ~(Elr)[[:blank:]]* ]] && ms+=( "${l}" )
129#		print -- "sect=$l"
130	done <<<"${x}"
131
132#	printf "enumerate_mansects: found %d entries.\n" ${#ms[@]}
133
134	return 0
135}
136
137# wrapper around more/less
138function browse_manpage
139{
140	typeset tmpdirname
141	typeset doc_filename="$1"
142	typeset doc_title="$2"
143
144	# squish characters in filename which are not allowed in a filesystem
145	# (currently '/')
146	doc_title="${doc_title//\//}"
147
148	# check if we have "less" installed, if not fall back to /usr/xpg4/bin/more
149	if which less >/dev/null 2>&1 ; then
150		# use "cat" here to avoid that "less" may try funny things
151		cat <"${doc_filename}" | less -I -M $"--prompt=MManual\ page\ ${doc_title}\ ?ltline\ %lt?L/%L.:"
152	else
153		tmpdirname="$(mktemp -d "/tmp/shman_${PPID}_$$_XXXXXX")"
154
155		mkdir -p "${tmpdirname}" || { print -u2 -f $"Couldn't create tmp. dir %s\n" "${tmpdirname}" ; return 1 ; }
156
157		(
158			cd "${tmpdirname}"
159
160			# note: we need to support /dev/stdin
161			cat <"${doc_filename}" >"./${doc_title}"
162
163			/usr/xpg4/bin/more "${doc_title}"
164
165			rm -f "${doc_title}"
166		)
167
168		rmdir "${tmpdirname}"
169	fi
170
171	return 0
172}
173
174# /usr/bin/man <keyword>
175function show_manpage
176{
177	compound -a mandirs
178	integer i
179	integer j
180
181	enumerate_mandirs mandirs
182#	debug_print -- "${mandirs[@]}"
183
184	integer num_mandirs=${#mandirs[@]}
185
186	for ((i=0 ; i < num_mandirs ; i++ )) ; do
187		typeset mandir="${mandirs[i].dir}"
188
189		typeset -a mansects
190		enumerate_mansects mansects "mandirs[$i]"
191
192		integer num_mansects="${#mansects[@]}"
193#		debug_print -- "mansects=${mansects[@]}"
194
195		for ((j=0 ; j < num_mansects ; j++ )) ; do
196			typeset mansect="${mansects[j]}"
197
198			# try 1: SGML manpage
199			typeset match="${mandir}/sman${mansect}/${manname}.${mansect}"
200			if [[ -r "${match}" ]] ; then
201				typeset note nlink
202
203				# follow SGML links if needed (needs rework, including protection against link loops)
204				while true ; do
205					debug_print -f "match: %s\n" "${match}"
206
207					tmp="$(cd "${mandir}" ; LC_MESSAGES=C /usr/lib/sgml/sgml2roff "${match}")"
208					read note nlink <<<"${tmp}"
209
210					if [[ "${note}" == ".so" ]] ; then
211						match="${nlink}"
212					else
213						break
214					fi
215				done
216
217				tbl <<<"${tmp}" | eqn | nroff -u0 -Tlp -man - | col -x | browse_manpage /dev/stdin "${manname}(${mansect})"
218				return 0
219			fi
220
221			# try 2: troff manpage
222			match="${mandir}/man${mansect}/${manname}.${mansect}"
223			if [[ -r "${match}" ]] ; then
224				debug_print -f "match: %s\n" "${match}"
225				tbl <"${match}" | eqn | nroff -u0 -Tlp -man - | col -x | browse_manpage /dev/stdin "${manname}(${mansect})"
226				return 0
227			fi
228		done
229		unset mansects num_mansects
230	done
231
232	printf $"No manual entry for %s.\n" "${manname}"
233	return 0
234}
235
236# /usr/bin/man -l <keyword>
237function list_manpages
238{
239	compound -a mandirs
240
241	enumerate_mandirs mandirs
242	#debug_print -- "${mandirs[@]}"
243
244	integer num_mandirs=${#mandirs[@]}
245
246	for ((i=0 ; i < num_mandirs ; i++ )) ; do
247		typeset mandir="${mandirs[i].dir}"
248
249		typeset -a mansects
250		enumerate_mansects mansects "mandirs[$i]"
251
252		integer num_mansects="${#mansects[@]}"
253#		debug_print -- "mansects=${mansects[@]}"
254
255		for ((j=0 ; j < num_mansects ; j++ )) ; do
256			mansect="${mansects[j]}"
257
258			# try 1: SGML manpage
259			match="${mandir}/sman${mansect}/${manname}.${mansect}"
260			if [[ -r "${match}" ]] ; then
261				printf "%s (%s)\t-M %s\n" "${manname}" "${mansect}" "${mandir}"
262				continue
263			fi
264
265			# try 2: troff manpage
266			match="${mandir}/man${mansect}/${manname}.${mansect}"
267			if [[ -r "${match}" ]] ; then
268				printf "%s (%s)\t-M %s\n" "${manname}" "${mansect}" "${mandir}"
269				continue
270			fi
271		done
272		unset mansects num_mansects
273	done
274
275	return 0
276}
277
278# /usr/bin/appropos
279function list_keywords
280{
281	typeset -a mandirs
282	typeset name namesec title
283
284	enumerate_mandirs mandirs
285	#debug_print -- "${mandirs[@]}"
286
287	integer num_mandirs=${#mandirs[@]}
288
289	for ((i=0 ; i < num_mandirs ; i++ )) ; do
290		typeset mandir="${mandirs[i].dir}"
291		typeset windexfile="${mandir}/windex"
292
293		if [[ ! -r "${windexfile}" ]] ; then
294			print -u2 -f $"%s: Can't open %s.\n" "${progname}" "${windexfile}"
295			continue
296		fi
297
298		while IFS=$'\t' read name namesec title ; do
299			if [[ "${name}${namesec}${title}" == ~(Fi)${manname} ]] ; then
300				printf "%s\t%s\t%s\n" "${name}" "${namesec}" "${title}"
301			fi
302		done <"${windexfile}"
303	done
304
305	return 0
306}
307
308function usage
309{
310	OPTIND=0
311	getopts -a "${progname}" "${man_usage}" OPT '-?'
312	exit 2
313}
314
315# program start
316builtin basename
317builtin cat
318builtin date
319
320typeset progname="$(basename "${0}")"
321
322typeset -r man_usage=$'+
323[-?\n@(#)\$Id: shman (Roland Mainz) 2009-06-26 \$\n]
324[-author?Roland Mainz <roland.mainz@nrubsig.org>]
325[-author?Roland Mainz <roland.mainz@sun.com>]
326[+NAME?man - find and display reference manual pages]
327[+DESCRIPTION?The man command displays information from the reference
328	manuals. It displays complete manual pages that you select
329	by name, or one-line summaries selected  either  by  keyword
330	(-k), or by the name of an associated file (-f). If no
331	manual page is located, man prints an error message.]
332[+?write me.]
333[k:keyword?Prints out one-line summaries from the windex database (table of contents) that
334	contain any of the given  keywords. The windex database is created using
335	catman(1M).]
336[l:list?Lists all manual  pages  found  matching name within the search path.]
337[M:mpath?Specifies an alternate search  path  for manual  pages. path is a colon-separated
338	list of directories that contain  manual page directory subtrees. For example, if
339	path  is  /usr/share/man:/usr/local/man, man  searches  for  name in the standard
340	location, and then /usr/local/man.  When used  with  the -k or -f options, the -M
341	option must appear first. Each directory in  the  path is assumed to contain subdirectories of the form man* or sman*  ,
342	one  for each section. This option overrides the MANPATH environment variable.]:[path]
343[s:section?Specifies sections of the manual for man to search. The directories searched for
344	name are limited to those specified by section. section can be a numerical
345	digit, perhaps followed by one or more letters to match the desired section of
346	the manual, for example,  "3libucb". Also, section can be a word, for example,
347	local, new, old, public. section can also be a letter.
348	To specify multiple sections, separate each section with
349	a comma. This option overrides the MANPATH environment variable and the man.cf
350	file.
351	See Search Path below for an explanation of how man conducts its search.]:[section]
352
353name
354
355[+SEE ALSO?\bksh93\b(1), \bman\b(1)]
356'
357
358typeset do_list=false
359typeset do_keyword=false
360
361while getopts -a "${progname}" "${man_usage}" OPT ; do
362#    printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|"
363	case ${OPT} in
364		M)	MANPATH="${OPTARG}"	;;
365		l)	do_list=true		;;
366		k)	do_keyword=true		;;
367		s)	MANSECTS="${OPTARG}" 	;;
368		*)	usage			;;
369	esac
370done
371shift $((OPTIND-1))
372
373# cd /usr/man; LC_MESSAGES=C /usr/lib/sgml/sgml2roff /usr/man/sman1as/asadmin-list-timers.1as  | tbl | eqn | nroff -u0 -Tlp -man -  | col -x > /tmp/mpLQaqac
374
375# prechecks
376(( $# > 0 )) || usage
377
378# process arguments
379while (( $# > 0 )) ; do
380	typeset manname="$1"
381	shift
382
383	debug_print -f "# searching for %s ...\n" "${manname}"
384
385	if ${do_keyword} ; then
386		list_keywords
387	elif ${do_list} ; then
388		list_manpages
389	else
390		show_manpage
391	fi
392done
393
394# todo: better exit codes
395exit 0
396# EOF.
397