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