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