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