xref: /freebsd/libexec/rc/debug.sh (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
1:
2# SPDX-License-Identifier: BSD-2-Clause
3
4# NAME:
5#	debug.sh - selectively debug scripts
6#
7# SYNOPSIS:
8#	$_DEBUG_SH . debug.sh
9#	DebugOn [-eo] "tag" ...
10#	DebugOff [-eo] [rc="rc"] "tag" ...
11#	Debugging
12#	DebugAdd "tag"
13#	DebugEcho ...
14#	DebugLog ...
15#	DebugShell "tag" ...
16#	DebugTrace ...
17#	Debug "tag" ...
18#
19#	$DEBUG_SKIP echo skipped when Debug "tag" is true.
20#	$DEBUG_DO echo only done when Debug "tag" is true.
21#
22# DESCRIPTION:
23#	debug.sh provides the following functions to facilitate
24#	flexible run-time tracing of complicated shell scripts.
25#
26#	DebugOn turns tracing on if any "tag" is found in "DEBUG_SH".
27#	It turns tracing off if "!tag" is found in "DEBUG_SH".
28#	It also sets "DEBUG_ON" to the "tag" that caused tracing to be
29#	enabled, or "DEBUG_OFF" if we matched "!tag".
30#	If '-e' option given returns 1 if no "tag" matched.
31#	If the '-o' flag is given, tracing is turned off unless there
32#	was a matched "tag", useful for functions too noisy to tace.
33#
34#	Further; when we set "DEBUG_ON" if we find
35#	"$DEBUG_ON:debug_add:tag" in "DEBUG_SH" we will
36#	add the new "tag" to "DEBUG_SH" so it only has effect after that
37#	point.
38#
39#	DebugOff turns tracing on if any "tag" matches "DEBUG_OFF" or
40#	off if any "tag" matches "DEBUG_ON". This allows nested
41#	functions to not interfere with each other.
42#
43#	DebugOff accepts but ignores the '-e' and '-o' options.
44#	The optional "rc" value will be returned rather than the
45#	default of 0. Thus if DebugOff is the last operation in a
46#	function, "rc" will be the return code of that function.
47#
48#	DebugAdd allows adding a "tag" to "DEBUG_SH" to influence
49#	later events, possibly in a child process.
50#
51#	DebugEcho is just shorthand for:
52#.nf
53#	$DEBUG_DO echo "$@"
54#.fi
55#
56#	Debugging returns true if tracing is enabled.
57#	It is useful for bounding complex debug actions, rather than
58#	using lots of "DEBUG_DO" lines.
59#
60#	DebugShell runs an interactive shell if any "tag" is found in
61#	"DEBUG_INTERACTIVE", and there is a tty available.
62#	The shell used is defined by "DEBUG_SHELL" or "SHELL" and
63#	defaults to '/bin/sh'.
64#
65#	Debug calls DebugOn and if that does not turn tracing on, it
66#	calls DebugOff to turn it off.
67#
68#	The variables "DEBUG_SKIP" and "DEBUG_DO" are set so as to
69#	enable/disable code that should be skipped/run when debugging
70#	is turned on. "DEBUGGING" is the same as "DEBUG_SKIP" for
71#	backwards compatability.
72#
73#	The use of $_DEBUG_SH is to prevent multiple inclusion, though
74#	it does no harm in this case.
75#
76# BUGS:
77#	Does not work with some versions of ksh.
78#	If a function turns tracing on, ksh turns it off when the
79#	function returns - useless.
80#	PD ksh works ok ;-)
81#
82# AUTHOR:
83#	Simon J. Gerraty <sjg@crufty.net>
84
85# RCSid:
86#	$Id: debug.sh,v 1.42 2024/10/30 18:23:19 sjg Exp $
87#
88#	@(#) Copyright (c) 1994-2024 Simon J. Gerraty
89#
90#	This file is provided in the hope that it will
91#	be of use.  There is absolutely NO WARRANTY.
92#	Permission to copy, redistribute or otherwise
93#	use this file is hereby granted provided that
94#	the above copyright notice and this notice are
95#	left intact.
96#
97#	Please send copies of changes and bug-fixes to:
98#	sjg@crufty.net
99#
100
101_DEBUG_SH=:
102
103Myname=${Myname:-`basename $0 .sh`}
104
105DEBUGGING=
106DEBUG_DO=:
107DEBUG_SKIP=
108export DEBUGGING DEBUG_DO DEBUG_SKIP
109
110case "$isPOSIX_SHELL,$local" in
111:,:|:,local|false,:) ;;		# sane
112*)	# this is the bulk of isposix-shell.sh
113	if (echo ${PATH%:*}) > /dev/null 2>&1; then
114		# true should be a builtin, : certainly is
115		isPOSIX_SHELL=:
116		# you need to eval $local var
117		local=local
118		: KSH_VERSION=$KSH_VERSION
119		case "$KSH_VERSION" in
120		Version*) local=: ;; # broken
121		esac
122	else
123		isPOSIX_SHELL=false
124		local=:
125		false() {
126			return 1
127		}
128	fi
129	;;
130esac
131
132is_posix_shell() {
133	$isPOSIX_SHELL
134	return
135}
136
137
138##
139# _debugAdd match
140#
141# Called from _debugOn when $match also appears in $DEBUG_SH with
142# a suffix of :debug_add:tag we will add tag to DEBUG_SH
143#
144_debugAdd() {
145	eval $local tag
146
147	for tag in `IFS=,; echo $DEBUG_SH`
148	do
149		: tag=$tag
150		case "$tag" in
151		$1:debug_add:*)
152			if is_posix_shell; then
153				tag=${tag#$1:debug_add:}
154			else
155				tag=`expr $tag : '.*:debug_add:\(.*\)'`
156			fi
157			case ",$DEBUG_SH," in
158			*,$tag,*) ;;
159			*)	set -x
160				: _debugAdd $1
161				DEBUG_SH=$DEBUG_SH,$tag
162				set +x
163				;;
164			esac
165			;;
166		esac
167	done
168	export DEBUG_SH
169}
170
171
172##
173# _debugOn match first
174#
175# Actually turn on tracing, set $DEBUG_ON=$match
176#
177# Check if $DEBUG_SH contains $match:debug_add:* and call _debugAdd
178# to add the suffix to DEBUG_SH.  This useful when we only want
179# to trace some script when run under specific circumstances.
180#
181# If we have included hooks.sh $_HOOKS_SH will be set
182# and if $first (the first arg to DebugOn) is suitable as a variable
183# name we will run ${first}_debugOn_hooks.
184#
185# We disable tracing for hooks_run itself but functions can trace
186# if they want based on DEBUG_DO
187#
188_debugOn() {
189	DEBUG_OFF=
190	DEBUG_DO=
191	DEBUG_SKIP=:
192	DEBUG_X=-x
193	# do this firt to reduce noise
194	case ",$DEBUG_SH," in
195	*,$1:debug_add:*) _debugAdd $1;;
196	*,$2:debug_add:*) _debugAdd $2;;
197	esac
198	set -x
199	DEBUG_ON=$1
200	case "$_HOOKS_SH,$2" in
201	,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;;
202	*)	# avoid noise from hooks_run
203		set +x
204		hooks_run ${2}_debugOn_hooks
205		set -x
206		;;
207	esac
208}
209
210##
211# _debugOff match $DEBUG_ON $first
212#
213# Actually turn off tracing, set $DEBUG_OFF=$match
214#
215# If we have included hooks.sh $_HOOKS_SH will be set
216# and if $first (the first arg to DebugOff) is suitable as a variable
217# name we will run ${first}_debugOff_hooks.
218#
219# We do hooks_run after turning off tracing, but before resetting
220# DEBUG_DO so functions can trace if they want
221#
222_debugOff() {
223	DEBUG_OFF=$1
224	set +x
225	case "$_HOOKS_SH,$3" in
226	,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;;
227	*)	hooks_run ${3}_debugOff_hooks;;
228	esac
229	set +x			# just to be sure
230	DEBUG_ON=$2
231	DEBUG_DO=:
232	DEBUG_SKIP=
233	DEBUG_X=
234}
235
236##
237# DebugAdd tag
238#
239# Add tag to DEBUG_SH
240#
241DebugAdd() {
242        DEBUG_SH=${DEBUG_SH:+$DEBUG_SH,}$1
243        export DEBUG_SH
244}
245
246##
247# DebugEcho message
248#
249# Output message if we are debugging
250#
251DebugEcho() {
252	$DEBUG_DO echo "$@"
253}
254
255##
256# Debugging
257#
258# return 0 if we are debugging.
259#
260Debugging() {
261	test "$DEBUG_SKIP"
262}
263
264##
265# DebugLog message
266#
267# Outout message with timestamp if we are debugging
268#
269DebugLog() {
270	$DEBUG_SKIP return 0
271	echo `date '+@ %s [%Y-%m-%d %H:%M:%S %Z]'` "$@"
272}
273
274##
275# DebugTrace message
276#
277# Something hard to miss when wading through huge -x output
278#
279DebugTrace() {
280	$DEBUG_SKIP return 0
281	set +x
282	echo "@ ==================== [ $DEBUG_ON ] ===================="
283	DebugLog "$@"
284	echo "@ ==================== [ $DEBUG_ON ] ===================="
285	set -x
286}
287
288##
289# DebugOn [-e] [-o] match ...
290#
291# Turn on debugging if any $match is found in $DEBUG_SH.
292#
293DebugOn() {
294	eval ${local:-:} _e _match _off _rc
295	_rc=0			# avoid problems with set -e
296	_off=:
297	while :
298	do
299		case "$1" in
300		-e) _rc=1; shift;; # caller ok with return 1
301		-o) _off=; shift;; # off unless we have a match
302		*) break;;
303		esac
304	done
305	case ",${DEBUG_SH:-$DEBUG}," in
306	,,)	return $_rc;;
307	*,[Dd]ebug,*) ;;
308	*) $DEBUG_DO set +x;;		# reduce the noise
309	esac
310	_match=
311	# if debugging is off because of a !e
312	# don't add 'all' to the On list.
313	case "$_off$DEBUG_OFF" in
314	:)	_e=all;;
315	*)	_e=;;
316	esac
317	for _e in ${*:-$Myname} $_e
318	do
319		: $_e in ,${DEBUG_SH:-$DEBUG},
320		case ",${DEBUG_SH:-$DEBUG}," in
321		*,!$_e,*|*,!$Myname:$_e,*)
322			# only turn it off if it was on
323			_rc=0
324			$DEBUG_DO _debugOff $_e $DEBUG_ON $1
325			break
326			;;
327		*,$_e,*|*,$Myname:$_e,*)
328			# only turn it on if it was off
329			_rc=0
330			_match=$_e
331			$DEBUG_SKIP _debugOn $_e $1
332			break
333			;;
334		esac
335	done
336	if test -z "$_off$_match"; then
337		# off unless explicit match, but
338		# only turn it off if it was on
339		$DEBUG_DO _debugOff $_e $DEBUG_ON $1
340	fi
341	DEBUGGING=$DEBUG_SKIP	# backwards compatability
342	$DEBUG_DO set -x	# back on if needed
343	$DEBUG_DO set -x	# make sure we see it in trace
344	return $_rc
345}
346
347##
348# DebugOff [-e] [-o] [rc=$?] match ...
349#
350# Only turn debugging off if one of our args was the reason it
351# was turned on.
352#
353# We normally return 0, but caller can pass rc=$? as first arg
354# so that we preserve the status of last statement.
355#
356# The options '-e' and '-o' are ignored, they just make it easier to
357# keep DebugOn and DebugOff lines in sync.
358#
359DebugOff() {
360	eval ${local:-:} _e _rc
361	case ",${DEBUG_SH:-$DEBUG}," in
362	*,[Dd]ebug,*) ;;
363	*) $DEBUG_DO set +x;;		# reduce the noise
364	esac
365	_rc=0			# always happy
366	while :
367	do
368		case "$1" in
369		-[eo]) shift;;	# ignore it
370		rc=*) eval "_$1"; shift;;
371		*) break;;
372		esac
373	done
374	for _e in $*
375	do
376		: $_e==$DEBUG_OFF DEBUG_OFF
377		case "$DEBUG_OFF" in
378		"")	break;;
379		$_e)	_debugOn $DEBUG_ON $1; return $_rc;;
380		esac
381	done
382	for _e in $*
383	do
384		: $_e==$DEBUG_ON DEBUG_ON
385		case "$DEBUG_ON" in
386		"")	break;;
387		$_e)	_debugOff "" "" $1; return $_rc;;
388		esac
389	done
390	DEBUGGING=$DEBUG_SKIP	# backwards compatability
391	$DEBUG_DO set -x	# back on if needed
392	$DEBUG_DO set -x	# make sure we see it in trace
393	return $_rc
394}
395
396_TTY=${_TTY:-`test -t 0 && tty`}; export _TTY
397
398# override this if you like
399_debugShell() {
400	test "x$_TTY" != x || return 0
401	{
402		echo DebugShell "$@"
403		echo "Type 'exit' to continue..."
404	} > $_TTY
405	${DEBUG_SHELL:-${SHELL:-/bin/sh}} < $_TTY > $_TTY 2>&1
406}
407
408# Run an interactive shell if appropriate
409# Note: you can use $DEBUG_SKIP DebugShell ... to skip unless debugOn
410DebugShell() {
411	eval ${local:-:} _e
412	case "$_TTY%${DEBUG_INTERACTIVE}" in
413	*%|%*) return 0;;	# no tty or no spec
414	esac
415	for _e in ${*:-$Myname} all
416	do
417		case ",${DEBUG_INTERACTIVE}," in
418		*,!$_e,*|*,!$Myname:$_e,*)
419			return 0
420			;;
421		*,$_e,*|*,$Myname:$_e,*)
422			# Provide clues as to why/where
423			_debugShell "$_e: $@"
424			return $?
425			;;
426		esac
427	done
428	return 0
429}
430
431# For backwards compatability
432Debug() {
433	case "${DEBUG_SH:-$DEBUG}" in
434	"")	;;
435	*)	DEBUG_ON=${DEBUG_ON:-_Debug}
436		DebugOn -e $* || DebugOff $DEBUG_LAST
437		DEBUGGING=$DEBUG_SKIP
438		;;
439	esac
440}
441