xref: /freebsd/libexec/rc/debug.sh (revision 7d0873ebb83b19ba1e8a89e679470d885efe12e3)
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.46 2024/12/13 03:55:52 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
110# have is handy
111if test -z "$_HAVE_SH"; then
112	_HAVE_SH=:
113
114	##
115	# have that does not rely on return code of type
116	#
117	have() {
118		case `(type "$1") 2>&1` in
119		*" found") return 1;;
120		esac
121		return 0
122	}
123fi
124
125# does local *actually* work?
126local_works() {
127    local _fu
128}
129
130if local_works > /dev/null 2>&1; then
131    _local=local
132else
133    _local=:
134fi
135# for backwards compatability
136local=$_local
137
138if test -z "$isPOSIX_SHELL"; then
139	if (echo ${PATH%:*}) > /dev/null 2>&1; then
140		# true should be a builtin, : certainly is
141		isPOSIX_SHELL=:
142	else
143		isPOSIX_SHELL=false
144		false() {
145			return 1
146		}
147	fi
148fi
149
150is_posix_shell() {
151	$isPOSIX_SHELL
152	return
153}
154
155
156##
157# _debugAdd match
158#
159# Called from _debugOn when $match also appears in $DEBUG_SH with
160# a suffix of :debug_add:tag we will add tag to DEBUG_SH
161#
162_debugAdd() {
163	eval $_local tag
164
165	for tag in `IFS=,; echo $DEBUG_SH`
166	do
167		: tag=$tag
168		case "$tag" in
169		$1:debug_add:*)
170			if is_posix_shell; then
171				tag=${tag#$1:debug_add:}
172			else
173				tag=`expr $tag : '.*:debug_add:\(.*\)'`
174			fi
175			case ",$DEBUG_SH," in
176			*,$tag,*) ;;
177			*)	set -x
178				: _debugAdd $1
179				DEBUG_SH=$DEBUG_SH,$tag
180				set +x
181				;;
182			esac
183			;;
184		esac
185	done
186	export DEBUG_SH
187}
188
189
190##
191# _debugOn match first
192#
193# Actually turn on tracing, set $DEBUG_ON=$match
194#
195# Check if $DEBUG_SH contains $match:debug_add:* and call _debugAdd
196# to add the suffix to DEBUG_SH.  This useful when we only want
197# to trace some script when run under specific circumstances.
198#
199# If we have included hooks.sh $_HOOKS_SH will be set
200# and if $first (the first arg to DebugOn) is suitable as a variable
201# name we will run ${first}_debugOn_hooks.
202#
203# We disable tracing for hooks_run itself but functions can trace
204# if they want based on DEBUG_DO
205#
206_debugOn() {
207	DEBUG_OFF=
208	DEBUG_DO=
209	DEBUG_SKIP=:
210	DEBUG_X=-x
211	# do this firt to reduce noise
212	case ",$DEBUG_SH," in
213	*,$1:debug_add:*) _debugAdd $1;;
214	*,$2:debug_add:*) _debugAdd $2;;
215	esac
216	set -x
217	DEBUG_ON=$1
218	case "$_HOOKS_SH,$2" in
219	,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;;
220	*)	# avoid noise from hooks_run
221		set +x
222		hooks_run ${2}_debugOn_hooks
223		set -x
224		;;
225	esac
226}
227
228##
229# _debugOff match $DEBUG_ON $first
230#
231# Actually turn off tracing, set $DEBUG_OFF=$match
232#
233# If we have included hooks.sh $_HOOKS_SH will be set
234# and if $first (the first arg to DebugOff) is suitable as a variable
235# name we will run ${first}_debugOff_hooks.
236#
237# We do hooks_run after turning off tracing, but before resetting
238# DEBUG_DO so functions can trace if they want
239#
240_debugOff() {
241	DEBUG_OFF=$1
242	set +x
243	case "$_HOOKS_SH,$3" in
244	,*|:,|:,*[${CASE_CLASS_NEG:-!}A-Za-z0-9_]*) ;;
245	*)	hooks_run ${3}_debugOff_hooks;;
246	esac
247	set +x			# just to be sure
248	DEBUG_ON=$2
249	DEBUG_DO=:
250	DEBUG_SKIP=
251	DEBUG_X=
252}
253
254##
255# DebugAdd tag
256#
257# Add tag to DEBUG_SH
258#
259DebugAdd() {
260        DEBUG_SH=${DEBUG_SH:+$DEBUG_SH,}$1
261        export DEBUG_SH
262}
263
264##
265# DebugEcho message
266#
267# Output message if we are debugging
268#
269DebugEcho() {
270	$DEBUG_DO echo "$@"
271}
272
273##
274# Debugging
275#
276# return 0 if we are debugging.
277#
278Debugging() {
279	test "$DEBUG_SKIP"
280}
281
282##
283# DebugLog message
284#
285# Outout message with timestamp if we are debugging
286#
287DebugLog() {
288	$DEBUG_SKIP return 0
289	echo `date '+@ %s [%Y-%m-%d %H:%M:%S %Z]'` "$@"
290}
291
292##
293# DebugTrace message
294#
295# Something hard to miss when wading through huge -x output
296#
297DebugTrace() {
298	$DEBUG_SKIP return 0
299	set +x
300	echo "@ ==================== [ $DEBUG_ON ] ===================="
301	DebugLog "$@"
302	echo "@ ==================== [ $DEBUG_ON ] ===================="
303	set -x
304}
305
306##
307# DebugOn [-e] [-o] match ...
308#
309# Turn on debugging if any $match is found in $DEBUG_SH.
310#
311DebugOn() {
312	eval ${local:-:} _e _match _off _rc
313	_rc=0			# avoid problems with set -e
314	_off=:
315	while :
316	do
317		case "$1" in
318		-e) _rc=1; shift;; # caller ok with return 1
319		-o) _off=; shift;; # off unless we have a match
320		*) break;;
321		esac
322	done
323	case ",${DEBUG_SH:-$DEBUG}," in
324	,,)	return $_rc;;
325	*,[Dd]ebug,*) ;;
326	*) $DEBUG_DO set +x;;		# reduce the noise
327	esac
328	_match=
329	# if debugging is off because of a !e
330	# don't add 'all' to the On list.
331	case "$_off$DEBUG_OFF" in
332	:)	_e=all;;
333	*)	_e=;;
334	esac
335	for _e in ${*:-$Myname} $_e
336	do
337		: $_e in ,${DEBUG_SH:-$DEBUG},
338		case ",${DEBUG_SH:-$DEBUG}," in
339		*,!$_e,*|*,!$Myname:$_e,*)
340			# only turn it off if it was on
341			_rc=0
342			$DEBUG_DO _debugOff $_e $DEBUG_ON $1
343			break
344			;;
345		*,$_e,*|*,$Myname:$_e,*)
346			# only turn it on if it was off
347			_rc=0
348			_match=$_e
349			$DEBUG_SKIP _debugOn $_e $1
350			break
351			;;
352		esac
353	done
354	if test -z "$_off$_match"; then
355		# off unless explicit match, but
356		# only turn it off if it was on
357		$DEBUG_DO _debugOff $_e $DEBUG_ON $1
358	fi
359	DEBUGGING=$DEBUG_SKIP	# backwards compatability
360	$DEBUG_DO set -x	# back on if needed
361	$DEBUG_DO set -x	# make sure we see it in trace
362	return $_rc
363}
364
365##
366# DebugOff [-e] [-o] [rc=$?] match ...
367#
368# Only turn debugging off if one of our args was the reason it
369# was turned on.
370#
371# We normally return 0, but caller can pass rc=$? as first arg
372# so that we preserve the status of last statement.
373#
374# The options '-e' and '-o' are ignored, they just make it easier to
375# keep DebugOn and DebugOff lines in sync.
376#
377DebugOff() {
378	eval ${local:-:} _e _rc
379	case ",${DEBUG_SH:-$DEBUG}," in
380	*,[Dd]ebug,*) ;;
381	*) $DEBUG_DO set +x;;		# reduce the noise
382	esac
383	_rc=0			# always happy
384	while :
385	do
386		case "$1" in
387		-[eo]) shift;;	# ignore it
388		rc=*) eval "_$1"; shift;;
389		*) break;;
390		esac
391	done
392	for _e in $*
393	do
394		: $_e==$DEBUG_OFF DEBUG_OFF
395		case "$DEBUG_OFF" in
396		"")	break;;
397		$_e)	_debugOn $DEBUG_ON $1; return $_rc;;
398		esac
399	done
400	for _e in $*
401	do
402		: $_e==$DEBUG_ON DEBUG_ON
403		case "$DEBUG_ON" in
404		"")	break;;
405		$_e)	_debugOff "" "" $1; return $_rc;;
406		esac
407	done
408	DEBUGGING=$DEBUG_SKIP	# backwards compatability
409	$DEBUG_DO set -x	# back on if needed
410	$DEBUG_DO set -x	# make sure we see it in trace
411	return $_rc
412}
413
414_TTY=${_TTY:-`test -t 0 && tty`}; export _TTY
415
416# override this if you like
417_debugShell() {
418	test "x$_TTY" != x || return 0
419	{
420		echo DebugShell "$@"
421		echo "Type 'exit' to continue..."
422	} > $_TTY
423	${DEBUG_SHELL:-${SHELL:-/bin/sh}} < $_TTY > $_TTY 2>&1
424}
425
426# Run an interactive shell if appropriate
427# Note: you can use $DEBUG_SKIP DebugShell ... to skip unless debugOn
428DebugShell() {
429	eval ${local:-:} _e
430	case "$_TTY%${DEBUG_INTERACTIVE}" in
431	*%|%*) return 0;;	# no tty or no spec
432	esac
433	for _e in ${*:-$Myname} all
434	do
435		case ",${DEBUG_INTERACTIVE}," in
436		*,!$_e,*|*,!$Myname:$_e,*)
437			return 0
438			;;
439		*,$_e,*|*,$Myname:$_e,*)
440			# Provide clues as to why/where
441			_debugShell "$_e: $@"
442			return $?
443			;;
444		esac
445	done
446	return 0
447}
448
449# For backwards compatability
450Debug() {
451	case "${DEBUG_SH:-$DEBUG}" in
452	"")	;;
453	*)	DEBUG_ON=${DEBUG_ON:-_Debug}
454		DebugOn -e $* || DebugOff $DEBUG_LAST
455		DEBUGGING=$DEBUG_SKIP
456		;;
457	esac
458}
459