1#!/bin/sh
2#-
3# Copyright (c) 2014-2018 Devin Teske
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26#
27############################################################ IDENT(1)
28#
29# $Title: Watch processes as they trigger a particular DTrace probe $
30#
31############################################################ CONFIGURATION
32
33#
34# DTrace pragma settings
35#
36DTRACE_PRAGMA="
37	option quiet
38	option dynvarsize=16m
39	option switchrate=10hz
40" # END-QUOTE
41
42#
43# Profiles
44#
45: ${DWATCH_PROFILES_PATH="/usr/libexec/dwatch:/usr/local/libexec/dwatch"}
46
47############################################################ GLOBALS
48
49VERSION='$Version: 1.4 $' # -V
50
51pgm="${0##*/}" # Program basename
52
53#
54# Command-line arguments
55#
56PROBE_ARG=
57
58#
59# Command-line defaults
60#
61_MAX_ARGS=64		# -B num
62_MAX_DEPTH=64		# -K num
63
64#
65# Command-line options
66#
67CONSOLE=		# -y
68CONSOLE_FORCE=		# -y
69[ -t 1 ] && CONSOLE=1	# -y
70COUNT=0			# -N count
71CUSTOM_DETAILS=		# -E code
72CUSTOM_TEST=		# -t test
73DEBUG=			# -d
74DESTRUCTIVE_ACTIONS=	# -w
75DEVELOPER=		# -dev
76EXECNAME=		# -k name
77EXECREGEX=		# -z regex
78EXIT_AFTER_COMPILE=	# -e
79FILTER=			# -r regex
80PROBE_COALESCE=		# -F
81GROUP=			# -g group
82JID=			# -j jail
83LIST=			# -l
84LIST_PROFILES=		# -Q
85MAX_ARGS=$_MAX_ARGS	# -B num
86MAX_DEPTH=$_MAX_DEPTH	# -K num
87ONELINE=		# -1
88OUTPUT=			# -o file
89OUTPUT_CMD=		# -O cmd
90PID=			# -p pid
91PROBE_TYPE=		# -f -m -n -P
92PROFILE=		# -X profile
93PSTREE=			# -R
94QUIET=			# -q
95TIMEOUT=		# -T time
96TRACE=			# -x
97USER=			# -u user
98USE_PROFILE=		# -X profile
99VERBOSE=		# -v
100
101#
102# Global exit status
103#
104SUCCESS=0
105FAILURE=1
106
107#
108# Miscellaneous
109#
110ACTIONS=
111EVENT_DETAILS=
112EVENT_TAG='printf("%d.%d %s[%d]: ",
113		this->uid0, this->gid0, execname, this->pid0);'
114EVENT_TEST=
115FILE=
116ID=3
117MODULE_CHECKED=
118PROBE=
119PSARGS=1
120RGID=
121RUID=
122SUDO=
123export SUDO_PROMPT="[sudo] Password:"
124TITLE=\$Title:
125
126############################################################ FUNCTIONS
127
128ansi() { local fmt="$2 $4"; [ "$CONSOLE" ] && fmt="\\033[$1m$2\\033[$3m $4";
129	shift 4; printf "$fmt\n" "$@"; }
130die() { exec >&2; [ "$*" ] && echo "$pgm:" "$@"; exit $FAILURE; }
131info() { [ "$QUIET" ] || ansi 35 "INFO" 39 "$@" >&2; }
132
133usage()
134{
135	local optfmt="\t%-10s %s\n"
136	exec >&2
137	[ "$*" ] && printf "%s: %s\n" "$pgm" "$*"
138	printf "Usage: %s [-1defFmnPqRvVwxy] [%s] [%s] [%s] [%s]\n" "$pgm" \
139		"-B num" "-E code" "-g group" "-j jail"
140	printf "\t      [%s] [%s] [%s] [%s] [%s] [%s]\n" \
141		"-k name" "-K num" "-N count" "-o file" "-O cmd" "-p pid"
142	printf "\t      [%s] [%s] [%s] [%s] [%s] [%s]\n" \
143		"-r regex" "-t test" "-T time" "-u user" "-X profile" \
144		"-z regex"
145	printf "\t      probe[,...] [args ...]\n"
146	printf "       %s -l [-fmnPqy] [-r regex] [probe ...]\n" "$pgm"
147	printf "       %s -Q [-1qy] [-r regex]\n" "$pgm"
148	printf "\n"
149	printf "$optfmt" "-1" \
150		"Print one line per process/profile (Default; disables \`-R')."
151	printf "$optfmt" "-B num" \
152		"Maximum process arguments to display (Default $_MAX_ARGS)."
153	printf "$optfmt" "-d" \
154		"Debug. Send dtrace(1) script to stdout instead of executing."
155	printf "$optfmt" "-e" \
156		"Exit after compiling request but prior to enabling probes."
157	printf "$optfmt" "-E code" \
158		"DTrace code for event details. If \`-', read from stdin."
159	printf "$optfmt" "-f" \
160		"Enable probe matching the specified function name."
161	printf "$optfmt" "-F" \
162		"Coalesce trace output by function."
163	printf "$optfmt" "-g group" \
164		"Group filter. Only show processes matching group name/gid."
165	printf "$optfmt" "-j jail" \
166		"Jail filter. Only show processes matching jail name/jid."
167	printf "$optfmt" "-k name" \
168		"Only show processes matching name."
169	printf "$optfmt" "-K num" \
170		"Maximum directory depth to display (Default $_MAX_DEPTH)."
171	printf "$optfmt" "-l" \
172		"List available probes on standard output and exit."
173	printf "$optfmt" "-m" \
174		"Enable probe matching the specified module name."
175	printf "$optfmt" "-n" \
176		"Enable probe matching the specified probe name."
177	printf "$optfmt" "-N count" \
178		"Exit after count matching entries (Default 0 for disabled)."
179	printf "$optfmt" "-o file" \
180		"Set output file. If \`-', the path \`/dev/stdout' is used."
181	printf "$optfmt" "-O cmd" \
182		"Execute cmd for each event."
183	printf "$optfmt" "-p pid" \
184		"Process id filter. Only show processes with matching pid."
185	printf "$optfmt" "-P" \
186		"Enable probe matching the specified provider name."
187	printf "$optfmt" "-q" \
188		"Quiet. Hide informational messages and all dtrace(1) errors."
189	printf "$optfmt" "-Q" \
190		"List available profiles in DWATCH_PROFILES_PATH and exit."
191	printf "$optfmt" "-r regex" \
192		"Filter. Only show blocks matching awk(1) regular expression."
193	printf "$optfmt" "-R" \
194		"Show parent, grandparent, and ancestor of process."
195	printf "$optfmt" "-t test" \
196		"Test clause (predicate) to limit events (Default none)."
197	printf "$optfmt" "-T time" \
198		"Timeout. Format is \`\#[smhd]' or simply \`\#' for seconds."
199	printf "$optfmt" "-u user" \
200		"User filter. Only show processes matching user name/uid."
201	printf "$optfmt" "-v" \
202		"Verbose. Show all errors from dtrace(1)."
203	printf "$optfmt" "-V" \
204		"Report dwatch version on standard output and exit."
205	printf "$optfmt" "-w" \
206		"Permit destructive actions (copyout*, stop, panic, etc.)."
207	printf "$optfmt" "-x" \
208		"Trace. Print \`<probe-id>' when a probe is triggered."
209	printf "$optfmt" "-X profile" \
210		"Load profile name from DWATCH_PROFILES_PATH."
211	printf "$optfmt" "-y" \
212		"Always treat stdout as console (enable colors/columns/etc.)."
213	printf "$optfmt" "-z regex" \
214		"Only show processes matching awk(1) regular expression."
215	die
216}
217
218dtrace_cmd()
219{
220	local status stdout
221	local timeout=
222
223	if [ "$1" = "-t" ]; then
224		shift
225		[ "$TIMEOUT" ] && timeout=1
226	fi
227
228	exec 3>&1
229	stdout=3
230
231	#
232	# Filter dtrace(1) stderr while preserving exit status
233	#
234	status=$(
235		exec 4>&1
236		to_status=4
237		( trap 'echo $? >&$to_status' EXIT
238			eval $SUDO ${timeout:+timeout \"\$TIMEOUT\"} dtrace \
239				\"\$@\" 2>&1 ${QUIET:+2> /dev/null} >&$stdout
240		) | dtrace_stderr_filter >&2
241	)
242
243	return $status
244}
245
246dtrace_stderr_filter()
247{
248	if [ "$VERBOSE" ]; then
249		cat
250		return
251		# NOTREACHED
252	fi
253
254	awk ' # Start awk(1) stderr-filter
255	/[[:digit:]]+ drops? on CPU [[:digit:]]+/ { next }
256	/failed to write to <stdout>: No such file or directory/ { next }
257	/failed to write to <stdout>: Broken pipe/ { next }
258	/processing aborted: Broken pipe/ { next }
259	/invalid address \(0x[[:xdigit:]]+\) in action #[[:digit:]]+/ { next }
260	/out of scratch space in action #[[:digit:]]+/ { next }
261	/^Bus error$/ { next }
262	{ print; fflush() }
263	' # END-QUOTE
264}
265
266expand_probe()
267{
268	local OPTIND=1 OPTARG flag
269	local type=
270
271	while getopts t: flag; do
272		case "$flag" in
273		t) type="$OPTARG" ;;
274		esac
275	done
276	shift $(( $OPTIND - 1 ))
277
278	local probe="$1"
279	case "$probe" in
280	*:*)
281		echo "$probe"
282		return $SUCCESS
283		;;
284	esac
285
286	dtrace_cmd -l | awk -v probe="$probe" -v type="$type" '
287	# Start awk(1) processor
288	#################################################### BEGIN
289	BEGIN { getline dtrace_header }
290	#################################################### FUNCTIONS
291	function dump(unused1,unused2) {
292		if (n) {
293			if (NcF[n] == 1) f = N2F[n]
294			if (NcM[n] == 1) m = N2M[n]
295			if (NcP[n] == 1) p = N2P[n]
296		} else if (f) {
297			if (FcM[f] == 1) m = F2M[f]
298			if (FcP[f] == 1) p = F2P[f]
299			if (FcN[f] == 0 && found) n = "entry"
300		} else if (m) {
301			if (McP[m] == 1) p = M2P[m]
302		}
303		printf "%s:%s:%s:%s\n", p, m, f, n
304		exit !found
305	}
306	function inFMP() { return probe in F || probe in M || probe in P }
307	function inNMP() { return probe in N || probe in M || probe in P }
308	function inNFP() { return probe in N || probe in F || probe in P }
309	function inNFM() { return probe in N || probe in F || probe in M }
310	function diva(value, peerA, peerB, peerC) {
311		return value >= peerA && value >= peerB && value >= peerC
312	}
313	#################################################### MAIN
314	type == "name" && $NF != probe { next }
315	type == "function" && NF >=4 && $(NF-1) != probe { next }
316	type == "module" && NF == 5 && $(NF-2) != probe { next }
317	type == "provider" && $2 != probe { next }
318	type || $2 == probe || $3 == probe || $4 == probe || $5 == probe {
319		P[_p = $2]++
320		M[_m = (NF >= 5 ? $(NF-2) : "")]++
321		F[_f = (NF >= 4 ? $(NF-1) : "")]++
322		N[_n = $NF]++
323		if (N2F[_n] != _f) NcF[_n]++; N2F[_n] = _f
324		if (N2M[_n] != _m) NcM[_n]++; N2M[_n] = _m
325		if (N2P[_n] != _p) NcP[_n]++; N2P[_n] = _p
326		if (_n !~ /entry|return/) {
327			if (F2N[_f] != _n) FcN[_f]++
328			F2N[_f] = _n
329		}
330		if (F2M[_f] != _m) FcM[_f]++; F2M[_f] = _m
331		if (F2P[_f] != _p) FcP[_f]++; F2P[_f] = _p
332		if (M2P[_m] != _p) McP[_m]++; M2P[_m] = _p
333	}
334	#################################################### END
335	END {
336		if (type == "name")     dump(n = probe, found = probe in N)
337		if (type == "function") dump(f = probe, found = probe in F)
338		if (type == "module")   dump(m = probe, found = probe in M)
339		if (type == "provider") dump(p = probe, found = probe in P)
340		if (probe in N) {
341			found = 1
342			if (!inFMP()) dump(n = probe)
343			if (diva(F[probe], N[probe], M[probe], P[probe]))
344				dump(f = probe)
345			if (diva(M[probe], N[probe], F[probe], P[probe]))
346				dump(m = probe)
347			if (diva(P[probe], N[probe], F[probe], M[probe]))
348				dump(p = probe)
349			dump(n = probe) # N is the diva
350		} else if (probe in F) {
351			found = 1
352			if (!inNMP()) dump(f = probe)
353			if (diva(N[probe], F[probe], M[probe], P[probe]))
354				dump(n = probe)
355			if (diva(M[probe], F[probe], N[probe], P[probe]))
356				dump(m = probe)
357			if (diva(P[probe], F[probe], N[probe], M[probe]))
358				dump(p = probe)
359			dump(f = probe) # F is the diva
360		} else if (probe in M) {
361			found = 1
362			if (!inNFP()) dump(m = probe)
363			if (diva(N[probe], M[probe], F[probe], P[probe]))
364				dump(n = probe)
365			if (diva(F[probe], M[probe], N[probe], P[probe]))
366				dump(f = probe)
367			if (diva(P[probe], M[probe], N[probe], F[probe]))
368				dump(p = probe)
369			dump(m = probe) # M is the diva
370		} else if (probe in P) {
371			found = 1
372			if (!inNFM()) dump(p = probe)
373			if (diva(N[probe], P[probe], F[probe], M[probe]))
374				dump(n = probe)
375			if (diva(F[probe], P[probe], N[probe], M[probe]))
376				dump(f = probe)
377			if (diva(M[probe], P[probe], N[probe], F[probe]))
378				dump(m = probe)
379			dump(p = probe) # P is the diva
380		}
381		if (!found) print probe
382		exit !found
383	}
384	' # END-QUOTE
385}
386
387list_probes()
388{
389	local OPTIND=1 OPTARG flag
390	local column=0 header="PROVIDER:MODULE:FUNCTION:NAME"
391	local filter= quiet= type=
392
393	while getopts f:qt: flag; do
394		case "$flag" in
395		f) filter="$OPTARG" ;;
396		q) quiet=1 ;;
397		t) type="$OPTARG" ;;
398		esac
399	done
400	shift $(( $OPTIND - 1 ))
401
402	if [ $# -eq 0 ]; then
403		case "$type" in
404		provider) column=1 header="PROVIDER" ;;
405		module)   column=2 header="MODULE" ;;
406		function) column=3 header="FUNCTION" ;;
407		name)     column=4 header="NAME" ;;
408		esac
409	fi
410
411	[ "$quiet" ] || echo "$header"
412
413	local arg probe=
414	for arg in "$@"; do
415		arg=$( expand_probe -t "$type" -- "$arg" )
416		probe="$probe${probe:+, }$arg"
417	done
418
419	dtrace_cmd -l${probe:+n "$probe"} | awk -v pattern="$(
420		# Prevent backslashes from being lost
421		echo "$filter" | awk 'gsub(/\\/,"&&")||1'
422	)" -v want="$column" -v console="$CONSOLE" '
423		BEGIN { getline dtrace_header }
424		function ans(seq) { return console ? "\033[" seq "m" : "" }
425		NF > 3 && $(NF-1) ~ /^#/ { next }
426		!_[$0 = column[0] = sprintf("%s:%s:%s:%s",
427			column[1] = $2,
428			column[2] = (NF >= 5 ? $(NF-2) : ""),
429			column[3] = (NF >= 4 ? $(NF-1) : ""),
430			column[4] = $NF)]++ &&
431			!__[$0 = column[want]]++ &&
432			gsub(pattern, ans("31;1") "&" ans("39;22")) {
433				print | "sort"
434			}
435		END { close("sort") }
436	' # END-QUOTE
437
438	exit $SUCCESS
439}
440
441list_profiles()
442{
443	local OPTIND=1 OPTARG flag
444	local filter= oneline= quiet=
445
446	while getopts 1f:q flag; do
447		case "$flag" in
448		1) oneline=1 ;;
449		f) filter="$OPTARG" ;;
450		q) quiet=1 ;;
451		esac
452	done
453	shift $(( $OPTIND - 1 ))
454
455	# Prevent backslashes from being lost
456	filter=$( echo "$filter" | awk 'gsub(/\\/,"&&")||1' )
457
458	# Build a list of profiles available
459	local profiles
460	profiles=$( { IFS=:
461		for dir in $DWATCH_PROFILES_PATH; do
462			[ -d "$dir" ] || continue
463			for path in $dir/*; do
464				[ -f "$path" ] || continue
465				name="${path##*/}"
466				[ "$name" = "${name%%[!0-9A-Za-z_-]*}" ] ||
467					continue
468				echo $name
469			done
470		done
471	} | sort -u )
472
473	# Get the longest profile name
474	local longest_profile_name
475	longest_profile_name=$( echo "$profiles" |
476		awk -v N=0 '(L = length($0)) > N { N = L } END { print N }' )
477
478	# Get the width of the terminal
479	local max_size="$( stty size 2> /dev/null )"
480	: ${max_size:=24 80}
481	local max_width="${max_size#*[$IFS]}"
482
483	# Determine how many columns we can display
484	local x=$longest_profile_name ncols=1
485	[ "$QUIET" ] || x=$(( $x + 8 )) # Accommodate leading tab character
486	x=$(( $x + 3 + $longest_profile_name )) # Preload end of next column
487	while [ $x -lt $max_width ]; do
488		ncols=$(( $ncols + 1 ))
489		x=$(( $x + 3 + $longest_profile_name ))
490	done
491
492	# Output single lines if sent to a pipe
493	if [ "$oneline" ]; then
494		echo "$profiles" | awk -v filter="$filter" -v cons="$CONSOLE" '
495			function ans(s) { return cons ? "\033[" s "m" : "" }
496			gsub(filter, ans("31;1") "&" ans("39;22"))
497		' # END-QUOTE
498		exit $SUCCESS
499	fi
500
501	[ "$quiet" ] || echo PROFILES:
502	echo "$profiles" | awk \
503		-v colsize=$longest_profile_name \
504		-v console="$CONSOLE" \
505		-v ncols=$ncols \
506		-v quiet="$quiet" \
507		-v filter="$filter" \
508	' # Begin awk(1) processor
509		function ans(seq) { return console ? "\033[" seq "m" : "" }
510		BEGIN {
511			row_item[1] = ""
512			replace = ans("31;1") "&" ans("39;22")
513			ansi_offset = length(replace) - 1
514		}
515		function print_row()
516		{
517			cs = colsize + ansi_offset * \
518				gsub(filter, replace, row_item[1])
519			printf "%s%-*s", quiet ? "" : "\t", cs, row_item[1]
520			for (i = 2; i <= cur_col; i++) {
521				cs = colsize + ansi_offset * \
522					gsub(filter, replace, row_item[i])
523				printf "   %-*s", cs, row_item[i]
524			}
525			printf "\n"
526		}
527		$0 ~ filter {
528			n++
529			cur_col = ((n - 1) % ncols) + 1
530			row_item[cur_col] = $0
531			if (cur_col == ncols) print_row()
532		}
533		END { if (cur_col < ncols) print_row() }
534	' # END-QUOTE
535
536	exit $SUCCESS
537}
538
539shell_escape()
540{
541	echo "$*" | awk 'gsub(/'\''/, "&\\\\&&")||1'
542}
543
544load_profile()
545{
546	local profile="$1"
547
548	[ "$profile" ] ||
549		die "missing profile argument (\`$pgm -Q' to list profiles)"
550
551	local oldIFS="$IFS"
552	local dir found=
553	local ARGV=
554
555	[ $COUNT -gt 0 ] &&			ARGV="$ARGV -N $COUNT"
556	[ "$DEBUG" ] &&				ARGV="$ARGV -d"
557	[ "$DESTRUCTIVE_ACTIONS" ] &&		ARGV="$ARGV -w"
558	[ "$EXIT_AFTER_COMPILE" ] &&		ARGV="$ARGV -e"
559	[ "$GROUP" ] &&				ARGV="$ARGV -g $GROUP"
560	[ "$JID" ] &&				ARGV="$ARGV -j $JID"
561	[ $MAX_ARGS -ne $_MAX_ARGS ] &&		ARGV="$ARGV -B $MAX_ARGS"
562	[ $MAX_DEPTH -ne $_MAX_DEPTH ] &&	ARGV="$ARGV -K $MAX_DEPTH"
563	[ "$ONELINE" ] &&			ARGV="$ARGV -1"
564	[ "$PID" ] &&				ARGV="$ARGV -p $PID"
565	[ "$PSTREE" ] &&			ARGV="$ARGV -R"
566	[ "$QUIET" ] &&				ARGV="$ARGV -q"
567	[ "$TIMEOUT" ] &&			ARGV="$ARGV -T $TIMEOUT"
568	[ "$TRACE" ] &&				ARGV="$ARGV -x"
569	[ "$USER" ] &&				ARGV="$ARGV -u $USER"
570	[ "$VERBOSE" ] &&			ARGV="$ARGV -v"
571
572	[ "$FILTER" ] &&
573		ARGV="$ARGV -r '$( shell_escape "$FILTER" )'"
574	[ "$EXECREGEX" ] &&
575		ARGV="$ARGV -z '$( shell_escape "$EXECREGEX" )'"
576	[ "$CUSTOM_DETAILS" ] &&
577		ARGV="$ARGV -E '$( shell_escape "$EVENT_DETAILS" )'"
578	[ "$CUSTOM_TEST" ] &&
579		ARGV="$ARGV -t '$( shell_escape "$CUSTOM_TEST" )'"
580	[ "$OUTPUT" ] &&
581		ARGV="$ARGV -o '$( shell_escape "$OUTPUT" )'"
582	[ "$OUTPUT_CMD" ] &&
583		ARGV="$ARGV -O '$( shell_escape "$OUTPUT_CMD" )'"
584
585	case "$PROBE_TYPE" in
586	provider) ARGV="$ARGV -P" ;;
587	  module) ARGV="$ARGV -m" ;;
588	function) ARGV="$ARGV -f" ;;
589	    name) ARGV="$ARGV -n" ;;
590	esac
591
592	IFS=:
593	for dir in $DWATCH_PROFILES_PATH; do
594		[ -d "$dir" ] || continue
595		[ -f "$dir/$profile" ] || continue
596		PROFILE="$profile" found=1
597		info "Sourcing $profile profile [found in %s]" "$dir"
598		. "$dir/$profile"
599		break
600	done
601	IFS="$oldIFS"
602
603	[ "$found" ] ||
604		die "no module named \`$profile' (\`$pgm -Q' to list profiles)"
605}
606
607pproc()
608{
609	local OPTIND=1 OPTARG flag
610	local P= N=0
611
612	while getopts P: flag; do
613		case "$flag" in
614		P) P="$OPTARG" ;;
615		esac
616	done
617	shift $(( OPTIND - 1 ))
618
619	local proc=$1
620	if [ ! "$proc" ]; then
621		if [ "$P" = "0" ]; then
622			proc="curthread->td_proc"
623		else
624			proc="this->proc ? this->proc->p_pptr : NULL"
625		fi
626	fi
627
628	awk 'NR > 1 && $0 { $0 = "\t" $0 }
629		gsub(/\\\t/, "\t") || 1
630	' <<-EOFPREAMBLE
631	this->proc = $proc;
632	this->uid$P = this->proc ? this->proc->p_ucred->cr_uid : -1;
633	this->gid$P = this->proc ? this->proc->p_ucred->cr_rgid : -1;
634	this->pid$P = this->proc ? this->proc->p_pid : -1;
635	this->jid$P = this->proc ? this->proc->p_ucred->cr_prison->pr_id : -1;
636
637	this->p_args = this->proc ? this->proc->p_args : 0;
638	this->ar_length = this->p_args ? this->p_args->ar_length : 0;
639	this->ar_args = (char *)(this->p_args ? this->p_args->ar_args : 0);
640
641	this->args$P = this->arg${P}_$N = this->ar_length > 0 ?
642	\	this->ar_args : stringof(this->proc->p_comm);
643	this->len = this->ar_length > 0 ? strlen(this->ar_args) + 1 : 0;
644	this->ar_args += this->len;
645	this->ar_length -= this->len;
646
647	EOFPREAMBLE
648
649	awk -v P=$P -v MAX_ARGS=$MAX_ARGS '
650		$0 { $0 = "\t" $0 }
651		buf = buf $0 "\n" { }
652		END {
653			while (++N <= MAX_ARGS) {
654				$0 = buf
655				gsub(/P/, P)
656				gsub(/N/, N)
657				gsub(/\\\t/, "\t")
658				sub(/\n$/, "")
659				print
660			}
661		}
662	' <<-EOFARGS
663	this->argP_N = this->ar_length > 0 ? this->ar_args : "";
664	this->argsP = strjoin(this->argsP,
665	\	strjoin(this->argP_N != "" ? " " : "", this->argP_N));
666	this->len = this->ar_length > 0 ? strlen(this->ar_args) + 1 : 0;
667	this->ar_args += this->len;
668	this->ar_length -= this->len;
669
670	EOFARGS
671
672	N=$(( $MAX_ARGS + 1 ))
673	awk 'sub(/^\\\t/, "\t") || 1, $0 = "\t" $0' <<-EOFPROC
674	this->arg${P}_$N = this->ar_length > 0 ? "..." : "";
675	this->args$P = strjoin(this->args$P,
676	\	strjoin(this->arg${P}_$N != "" ? " " : "", this->arg${P}_$N));
677	EOFPROC
678}
679
680pproc_dump()
681{
682	local OPTIND=1 OPTARG flag
683	local verbose=
684
685	while getopts v flag; do
686		case "$flag" in
687		v) verbose=1 ;;
688		esac
689	done
690	shift $(( $OPTIND - 1 ))
691
692	local P=$1
693	if [ "$verbose" ]; then
694		awk -v P=$P '
695			BEGIN { printf "\t" }
696			NR > 1 && $0 { $0 = "\t" $0 }
697			buf = buf $0 "\n" { }
698			END {
699				$0 = buf
700				if (P < 3) S = sprintf("%" 7-2*(P+1) "s", "")
701				gsub(/S/, S)
702				gsub(/B/, P < 3 ? "\\" : "")
703				gsub(/\\\t/, "\t")
704				sub(/\n$/, "")
705				print
706			}
707		' <<-EOFPREAMBLE
708		printf(" SB-+= %05d %d.%d %s\n",
709		\	this->pid$P, this->uid$P, this->gid$P, this->args$P);
710		EOFPREAMBLE
711	else
712		cat <<-EOFPREAMBLE
713		printf("%s", this->args$P);
714		EOFPREAMBLE
715	fi
716}
717
718############################################################ MAIN
719
720# If we're running as root, no need for sudo(8)
721[ "$( id -u )" != 0 ] && type sudo > /dev/null 2>&1 && SUDO=sudo
722
723#
724# Process command-line options
725#
726while getopts 1B:deE:fFg:j:k:K:lmnN:o:O:p:PqQr:Rt:T:u:vVwxX:yz: flag; do
727	case "$flag" in
728	1) ONELINE=1 PSTREE= ;;
729	B) MAX_ARGS="$OPTARG" ;;
730	d) DEBUG=1 ;;
731	e) EXIT_AFTER_COMPILE=1 ;;
732	E) CUSTOM_DETAILS=1
733	   EVENT_DETAILS="${EVENT_DETAILS%;}"
734	   [ "$EVENT_DETAILS" ] && EVENT_DETAILS="$EVENT_DETAILS;
735		printf(\" \");
736		" # END-QUOTE
737	   # Read event code from stdin if `-' is argument
738	   [ "$OPTARG" = "-" ] && OPTARG=$( cat )
739	   EVENT_DETAILS="$EVENT_DETAILS$OPTARG" ;;
740	f) PROBE_TYPE=function ;;
741	F) PROBE_COALESCE=1 ;;
742	g) GROUP="$OPTARG" ;;
743	j) JID="$OPTARG" ;;
744	k) EXECNAME="$EXECNAME${EXECNAME:+ }$OPTARG"
745	   case "$OPTARG" in
746	   \**\*) name="${OPTARG%\*}"
747		predicate="strstr(execname, \"${name#\*}\") != NULL" ;;
748	   \**) name="${OPTARG#\*}"
749		predicate="strstr(execname, \"$name\") == (execname +"
750		predicate="$predicate strlen(execname) - ${#name})" ;;
751	   *\*) predicate="strstr(execname, \"${OPTARG%\*}\") == execname" ;;
752	   *) predicate="execname == \"$OPTARG\""
753	   esac
754	   EVENT_TEST="$predicate${EVENT_TEST:+ ||
755		($EVENT_TEST)}" ;;
756	K) MAX_DEPTH="$OPTARG" ;;
757	l) LIST=1 ;;
758	m) PROBE_TYPE=module ;;
759	n) PROBE_TYPE=name ;;
760	N) COUNT="$OPTARG" ;;
761	o) OUTPUT="$OPTARG" ;;
762	O) OUTPUT_CMD="$OPTARG" ;;
763	p) PID="$OPTARG" ;;
764	P) PROBE_TYPE=provider ;;
765	q) QUIET=1 ;;
766	Q) LIST_PROFILES=1 ;;
767	r) FILTER="$OPTARG" ;;
768	R) PSTREE=1 ;;
769	t) CUSTOM_TEST="${CUSTOM_TEST:+($CUSTOM_TEST) && }$OPTARG" ;;
770	T) TIMEOUT="$OPTARG" ;;
771	u) USER="$OPTARG" ;;
772	v) VERBOSE=1 ;;
773	V) vers="${VERSION#\$*[:\$]}"
774	   vers="${vers% \$}"
775	   printf "%s: %s\n" "$pgm" "${vers# }"
776	   exit ;;
777	w) DESTRUCTIVE_ACTIONS=1 ;;
778	x) TRACE=1 ;;
779	X) USE_PROFILE=1 PROFILE="$OPTARG" ;;
780	y) CONSOLE=1 CONSOLE_FORCE=1 ;;
781	z) EXECREGEX="$OPTARG" ;;
782	*) usage
783	   # NOTREACHED
784	esac
785done
786shift $(( $OPTIND - 1 ))
787
788#
789# List probes if `-l' was given
790#
791[ "$LIST" ] &&
792	list_probes -f "$FILTER" ${QUIET:+-q} -t "$PROBE_TYPE" -- "$@"
793	# NOTREACHED
794
795#
796# List profiles if `-Q' was given
797#
798[ "$LIST_PROFILES" ] &&
799	list_profiles ${ONELINE:+-1} -f "$FILTER" ${QUIET:+-q}
800	# NOTREACHED
801
802#
803# Validate number of arguments
804#
805if [ ! "$PROFILE" ]; then
806	# If not given `-X profile' then a probe argument is required
807	[ $# -gt 0 ] || usage # NOTREACHED
808fi
809
810#
811# Validate `-N count' option argument
812#
813case "$COUNT" in
814"") usage "-N option requires a number argument" ;; # NOTREACHED
815*[!0-9]*) usage "-N argument must be a number" ;; # NOTREACHED
816esac
817
818#
819# Validate `-B num' option argument
820#
821case "$MAX_ARGS" in
822"") usage "-B option requires a number argument" ;; # NOTREACHED
823*[!0-9]*) usage "-B argument must be a number" ;; # NOTREACHED
824esac
825
826#
827# Validate `-K num' option argument
828#
829case "$MAX_DEPTH" in
830"") usage "-K option requires a number argument" ;; # NOTREACHED
831*[!0-9]*) usage "-K argument must be a number" ;; # NOTREACHED
832esac
833
834#
835# Validate `-j jail' option argument
836#
837case "$JID" in
838"") : fall through ;;
839*[!0-9]*) JID=$( jls -j "$JID" jid ) || exit ;;
840esac
841
842#
843# Validate `-u user' option argument
844#
845case "$USER" in
846"") : fall through ;;
847*[![:alnum:]_-]*) RUID="$USER" ;;
848*[!0-9]*) RUID=$( id -u "$USER" 2> /dev/null ) || die "No such user: $USER" ;;
849*) RUID=$USER
850esac
851
852#
853# Validate `-g group' option argument
854#
855case "$GROUP" in
856"") : fall-through ;;
857*[![:alnum:]_-]*) RGID="$GROUP" ;;
858*[!0-9]*)
859	RGID=$( getent group | awk -F: -v group="$GROUP" '
860		$1 == group { print $3; exit found=1 }
861		END { exit !found }
862	' ) || die "No such group: $GROUP" ;;
863*) RGID=$GROUP
864esac
865
866#
867# Expand probe argument into probe(s)
868#
869case "$1" in
870-*) : Assume dtrace options such as "-c cmd" or "-p pid" ;; # No probe(s) given
871*)
872	PROBE_ARG="$1"
873	shift
874esac
875if [ "$PROBE_ARG" ]; then
876	oldIFS="$IFS"
877	IFS="$IFS,"
878	for arg in $PROBE_ARG; do
879		arg=$( expand_probe -t "$PROBE_TYPE" -- "$arg" )
880		PROBE="$PROBE${PROBE:+, }$arg"
881	done
882	IFS="$oldIFS"
883fi
884
885#
886# Developer switch
887#
888[ "$DEBUG" -a "$EXIT_AFTER_COMPILE" -a "$VERBOSE" ] && DEVELOPER=1 DEBUG=
889
890#
891# Set default event details if `-E code' was not given
892#
893[ "$CUSTOM_DETAILS" ] || EVENT_DETAILS=$( pproc_dump 0 )
894
895#
896# Load profile if given `-X profile'
897#
898[ "$USE_PROFILE" ] && load_profile "$PROFILE"
899[ "$PROBE" ] || die "PROBE not defined by profile and none given as argument"
900
901#
902# Show the user what's being watched
903#
904[ "$DEBUG$EXIT_AFTER_COMPILE" ] || info "Watching '$PROBE' ..."
905
906#
907# Header for watched probe entry
908#
909case "$PROBE" in
910*,*) : fall-through ;;
911*:execve:entry|execve:entry)
912	ACTIONS=$( awk 'gsub(/\\\t/, "\t") || 1' <<-EOF
913		$PROBE /* probe ID $ID */
914		{${TRACE:+
915		\	printf("<$ID>");}
916		\	this->caller_execname = execname;
917		}
918		EOF
919	)
920	PROBE="${PROBE%entry}return"
921	ID=$(( $ID + 1 ))
922	EVENT_TEST="execname != this->caller_execname${EVENT_TEST:+ &&
923		($EVENT_TEST)}"
924	EVENT_TAG='printf("%d.%d %s[%d]: ",
925		this->uid1, this->gid1, this->caller_execname, this->pid1);'
926	;;
927esac
928
929#
930# Jail clause/predicate
931#
932if [ "$JID" ]; then
933	prison_id="curthread->td_proc->p_ucred->cr_prison->pr_id"
934	EVENT_TEST="$prison_id == $JID${EVENT_TEST:+ &&
935		($EVENT_TEST)}"
936fi
937
938#
939# Custom test clause/predicate
940#
941if [ "$CUSTOM_TEST" ]; then
942	case "$EVENT_TEST" in
943	"") EVENT_TEST="$CUSTOM_TEST" ;;
944	 *) EVENT_TEST="$EVENT_TEST &&
945		($CUSTOM_TEST)"
946	esac
947fi
948
949#
950# Make sure dynamic code has trailing semi-colons if non-NULL
951#
952EVENT_TAG="${EVENT_TAG%;}${EVENT_TAG:+;}"
953EVENT_DETAILS="${EVENT_DETAILS%;}${EVENT_DETAILS:+;}"
954
955#
956# DTrace script
957#
958# If `-d' is given, script is sent to stdout for debugging
959# If `-c count", `-g group', `-r regex', or `-u user' is given, run script with
960# dtrace and send output to awk(1) post-processor (making sure to preserve the
961# exit code returned by dtrace invocation). Otherwise, simply run script with
962# dtrace and then exit.
963#
964exec 9<<EOF
965$PROBE /* probe ID 2 */
966{${TRACE:+
967	printf("<2>");
968}
969	/*
970	 * Examine process, parent process, and grandparent process details
971	 */
972
973	/******************* CURPROC *******************/
974
975	$( pproc -P0 )
976
977	/******************* PPARENT *******************/
978
979	$( if [ "$PSTREE" ]; then pproc -P1; else echo -n \
980	"this->proc = this->proc ? this->proc->p_pptr : NULL;
981	this->pid1 = this->proc ? this->proc->p_pid : -1;
982	this->uid1 = this->proc ? this->proc->p_ucred->cr_uid : -1;
983	this->gid1 = this->proc ? this->proc->p_ucred->cr_rgid : -1;
984	this->jid1 = this->proc ? this->proc->p_ucred->cr_prison->pr_id : -1;"
985	fi )
986
987	/******************* GPARENT *******************/
988
989	$( [ "$PSTREE" ] && pproc -P2 )
990
991	/******************* APARENT *******************/
992
993	$( [ "$PSTREE" ] && pproc -P3 )
994}
995EOF
996PSARGS_ACTION=$( cat <&9 )
997[ "$OUTPUT" -a ! "$CONSOLE_FORCE" ] && CONSOLE=
998{
999	if [ "$DEBUG" ]; then
1000		# Send script to stdout
1001		cat
1002		exit
1003	fi
1004
1005	if [ "$CUSTOM_TEST$EXECNAME$JID$OUTPUT$TIMEOUT$TRACE$VERBOSE" -a \
1006	    ! "$QUIET" ]
1007	then
1008		msg=Setting
1009		[ "$CUSTOM_TEST" ] && msg="$msg test: $CUSTOM_TEST"
1010		[ "$EXECNAME" ] && msg="$msg execname: $EXECNAME"
1011		[ "$JID" ] && msg="$msg jid: $JID"
1012		[ "$OUTPUT" ] && msg="$msg output: $OUTPUT"
1013		[ "$TIMEOUT" ] && msg="$msg timeout: $TIMEOUT"
1014		[ "$TRACE" ] && msg="$msg trace: $TRACE"
1015		[ "$VERBOSE" ] && msg="$msg verbose: $VERBOSE"
1016		info "$msg"
1017	fi
1018
1019	exec 3>&1
1020	console_stdout=3
1021
1022	#
1023	# Developer debugging aide
1024	#
1025	if [ "$DEVELOPER" ]; then
1026		#
1027		# Run, capture the error line, and focus it
1028		#
1029		# Example error text to capture line number from:
1030		# 	dtrace: failed to compile script /dev/stdin: line 669: ...
1031		#
1032		errline=
1033		stdin_buf=$( cat )
1034		stderr_buf=$( echo "$stdin_buf" |
1035			dtrace_cmd -t -es /dev/stdin "$@" 2>&1 > /dev/null )
1036		status=$?
1037		if [ "$stderr_buf" ]; then
1038			errline=$( echo "$stderr_buf" | awk '
1039				BEGIN {
1040					ti = "\033[31m"
1041					te = "\033[39m"
1042				}
1043				{ line = $0 }
1044				sub(/.*: line /, "") && sub(/:.*/, "") {
1045					print # to errline
1046					sub("line " $0, ti "&" te, line)
1047				}
1048				{ print line > "/dev/stderr" }
1049			' 2>&3 )
1050		fi
1051		if  [ "$errline" ]; then
1052			echo "$stdin_buf" | awk -v line="${errline%%[^0-9]*}" '
1053				BEGIN {
1054					start = line < 10 ? 1 : line - 10
1055					end = line + 10
1056					slen = length(sprintf("%u", start))
1057					elen = length(sprintf("%u", end))
1058					N = elen > slen ? elen : slen
1059					ti[line] = "\033[31m"
1060					te[line] = "\033[39m"
1061					fmt = "%s%*u %s%s\n"
1062				}
1063				NR < start { next }
1064				NR == start, NR == end {
1065					printf(fmt, ti[NR], N, NR, $0, te[NR])
1066				}
1067				NR > end { exit }
1068			' # END-QUOTE
1069		fi
1070		exit $status
1071	fi
1072
1073	if [ $COUNT -eq 0 -a ! "$EXECREGEX$FILTER$GROUP$OUTPUT_CMD$PID$USER" ]
1074	then
1075		case "$OUTPUT" in
1076		-) output_path=/dev/stdout ;;
1077		*) output_path="$OUTPUT"
1078		esac
1079
1080		# Run script without pipe to awk post-processor
1081		dtrace_cmd -t \
1082			${DESTRUCTIVE_ACTIONS:+-w} \
1083			${EXIT_AFTER_COMPILE:+-e} \
1084			${OUTPUT:+-o "$output_path"} \
1085			-s /dev/stdin \
1086			"$@"
1087		exit
1088	fi
1089
1090	# Prevent backslashes from being lost
1091	FILTER=$( echo "$FILTER" | awk 'gsub(/\\/,"&&")||1' )
1092	EXECREGEX=$( echo "$EXECREGEX" | awk 'gsub(/\\/,"&&")||1' )
1093
1094	if [ ! "$QUIET" ]; then
1095		msg=Filtering
1096		[ "$EXECREGEX" ] && msg="$msg execregex: $EXECREGEX"
1097		[ "$FILTER" ] && msg="$msg filter: $FILTER"
1098		[ "$GROUP" ] && msg="$msg group: $GROUP"
1099		[ "$OUTPUT_CMD" ] && msg="$msg cmd: $OUTPUT_CMD"
1100		[ "$PID" ] && msg="$msg pid: $PID"
1101		[ "$USER" ] && msg="$msg user: $USER"
1102		[ $COUNT -gt 0 ] && msg="$msg count: $COUNT"
1103		info "$msg"
1104	fi
1105
1106	#
1107	# Send script output to post-processor for filtering
1108	#
1109	status=$(
1110		exec 4>&1
1111		to_status=4
1112		( exec 5>&1; to_dtrace_stderr_filter=5; (
1113			trap 'echo $? >&$to_status' EXIT
1114			eval $SUDO ${TIMEOUT:+timeout \"\$TIMEOUT\"} dtrace \
1115				${EXIT_AFTER_COMPILE:+-e} \
1116				${DESTRUCTIVE_ACTIONS:+-w} \
1117				-s /dev/stdin \
1118				\"\$@\" \
1119				2>&$to_dtrace_stderr_filter \
1120				${QUIET:+2> /dev/null}
1121		) | $SUDO awk \
1122			-v cmd="$OUTPUT_CMD" \
1123			-v console="$CONSOLE" \
1124			-v count=$COUNT \
1125			-v execregex="$EXECREGEX" \
1126			-v filter="$FILTER" \
1127			-v gid="$RGID" \
1128			-v output="$OUTPUT" \
1129			-v pid="$PID" \
1130			-v pstree=$PSTREE \
1131			-v quiet=$QUIET \
1132			-v tty=$( ps -o tty= -p $$ ) \
1133			-v uid="$RUID" \
1134		' # Start awk(1) post-processor
1135		############################################ BEGIN
1136		BEGIN {
1137			true = 1
1138			ansi = "(\\033\\[[[:digit:];]+m)?"
1139			num = year = day = "[[:digit:]]+"
1140			month = "[[:alpha:]]+"
1141			date = year " " month " +" day
1142			time = "[012][0-9]:[0-5][0-9]:[0-5][0-9]"
1143			date_time = ansi date " +" time ansi
1144			name1 = "[^\\[]*"
1145			name2 = "[^\\n]*"
1146			if (output == "-")
1147				output = "/dev/stdout"
1148
1149			#
1150			# Field definitions
1151			#
1152			nexecmatches = 2
1153			execstart[1] = sprintf( \
1154				"^(%s) (%s)\\.(%s) (%s)\\[(%s)\\]: ",
1155				date_time, num, num, name1, num)
1156			execstart[2] = sprintf( \
1157				"\\n +\\\\?-\\+= (%s) (%s)\\.(%s) ",
1158				num, num, num)
1159			npidmatches = 2
1160			pidstart[1] = sprintf("^(%s) (%s)\\.(%s) (%s)\\[",
1161				date_time, num, num, name1)
1162			pidstart[2] = "\\n +\\\\?-\\+= "
1163			pidpreen[2] = "^0*"
1164			piddeflt[2] = "0"
1165			ngidmatches = 2
1166			gidstart[1] = sprintf("^(%s) (%s)\\.", date_time, num)
1167			gidstart[2] = sprintf("\\n +\\\\?-\\+= (%s) (%s)\\.",
1168				ansi num ansi, num)
1169			nuidmatches = 2
1170			uidstart[1] = sprintf("^(%s) ", date_time)
1171			uidstart[2] = sprintf("\\n +\\\\?-\\+= (%s) ",
1172				ansi num ansi)
1173		}
1174		############################################ FUNCTIONS
1175		function strip(s) { gsub(/\033\[[0-9;]*m/, "", s); return s }
1176		function esc(str) { gsub(/'\''/, "&\\\\&&", str); return str }
1177		function arg(str) { return "'\''" esc(str) "'\''" }
1178		function env(var, str) { return var "=" arg(str) " " }
1179		function ans(seq) { return console ? "\033[" seq "m" : "" }
1180		function runcmd() {
1181			return system(sprintf("%s/bin/sh -c %s",
1182				env("TAG", strip(tag)) \
1183					env("DETAILS", strip(details)),
1184				arg(cmd)))
1185		}
1186		function filter_block() {
1187			if (length(lines) < 1) return 0
1188			block_match = 0
1189			newstr = ""
1190			start = 1
1191			if (match(lines, "^(" date_time ") ")) {
1192				newstr = newstr substr(lines, 1,
1193					RSTART + RLENGTH - 1)
1194				start = RSTART + RLENGTH
1195			}
1196			replace = ans("31;1") "&" ans("39;22")
1197			workstr = substr(lines, start)
1198			if (gsub(filter, replace, workstr)) block_match = 1
1199			lines = newstr workstr
1200			return block_match
1201		}
1202		function filter_field(startre, fieldre, matchre, isword,
1203			preenre, defaultstr)
1204		{
1205			if (length(lines) < 1) return 0
1206			field_match = 0
1207			newstr = ""
1208			start = 1
1209			while ((workstr = substr(lines, start)) &&
1210				(workstr ~ (startre fieldre)))
1211			{
1212				match(workstr, startre)
1213				start += end = RSTART + RLENGTH - 1
1214				newstr = newstr substr(workstr, 1, end)
1215				workstr = substr(workstr, end + 1)
1216				match(workstr, fieldre)
1217				start += end = RSTART + RLENGTH - 1
1218				field = matchstr = substr(workstr, 1, end)
1219				sub(preenre, "", matchstr)
1220				if (!matchstr) matchstr = defaultstr
1221				if (isword) {
1222					if (match(matchstr, matchre) &&
1223						RSTART == 1 &&
1224						RLENGTH == length(matchstr)) {
1225						field_match = 1
1226						field = ans(7) field ans(27)
1227					}
1228				} else {
1229					replace = ans(7) "&" ans(27)
1230					if (gsub(matchre, replace, matchstr)) {
1231						field_match = 1
1232						field = matchstr
1233					}
1234				}
1235				newstr = newstr field
1236			}
1237			lines = newstr workstr
1238			return field_match
1239		}
1240		function dump() {
1241			lines = block
1242			block = ""
1243			found = 0
1244			if (execregex != "") {
1245				for (n = 1; n <= nexecmatches; n++)
1246					if (filter_field(execstart[n], name2,
1247						execregex)) found = 1
1248				if (!found) return
1249			}
1250			if (pid != "") {
1251				for (n = 1; n <= npidmatches; n++)
1252					if (filter_field(pidstart[n], num, pid,
1253						true, pidpreen[n],
1254						piddeflt[n])) found = 1
1255				if (!found) return
1256			}
1257			if (gid != "") {
1258				for (n = 1; n <= ngidmatches; n++)
1259					if (filter_field(gidstart[n], num,
1260						gid, true)) found = 1
1261				if (!found) return
1262			}
1263			if (uid != "") {
1264				for (n = 1; n <= nuidmatches; n++)
1265					if (filter_field(uidstart[n], num,
1266						uid, true)) found = 1
1267				if (!found) return
1268			}
1269			if (filter != "" && !filter_block()) return
1270			if (lines) {
1271				stdout = 1
1272				if (output) {
1273					stdout = 0
1274					if (!console) lines = strip(lines)
1275					print lines > output
1276				} else if (cmd) {
1277					if (!quiet) print lines
1278					tag = details = lines
1279					sub(/: .*/, "", tag)
1280					sub(/.*: /, "", details)
1281					if (!console) tag = strip(tag)
1282					runcmd()
1283				} else print lines
1284			}
1285			fflush()
1286			++matches
1287		}
1288		############################################ MAIN
1289		{ block = (block ? block "\n" : block) $0 }
1290		!pstree { dump() }
1291		$0 ~ sprintf("^%6s\\\\-\\+= %s ", "", num) { dump() }
1292		count && matches >= count { exit }
1293		############################################ END
1294		END {
1295			dump()
1296			system(sprintf("pkill -t %s dtrace %s", tty,
1297				quiet ? "2> /dev/null" : ""))
1298		}
1299		' >&$console_stdout ) | dtrace_stderr_filter >&2
1300	) # status
1301	exit $status
1302
1303} <<EOF
1304#!/usr/sbin/dtrace -s
1305/* -
1306 * Copyright (c) 2014-2018 Devin Teske <dteske@FreeBSD.org>
1307 * All rights reserved.
1308 * Redistribution and use in source and binary forms, with or without
1309 * modification, are permitted provided that the following conditions
1310 * are met:
1311 * 1. Redistributions of source code must retain the above copyright
1312 *    notice, this list of conditions and the following disclaimer.
1313 * 2. Redistributions in binary form must reproduce the above copyright
1314 *    notice, this list of conditions and the following disclaimer in the
1315 *    documentation and/or other materials provided with the distribution.
1316 *
1317 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS \`\`AS IS'' AND
1318 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1319 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1320 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1321 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1322 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
1323 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
1324 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
1325 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
1326 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
1327 * SUCH DAMAGE.
1328 *
1329 * $TITLE dtrace(1) script to log process(es) triggering $PROBE $
1330 */
1331
1332$( echo "$DTRACE_PRAGMA" | awk '
1333	!/^[[:space:]]*(#|$)/, sub(/^[[:space:]]*/, "#pragma D ")||1
1334' )
1335
1336int console;
1337
1338dtrace:::BEGIN { console = ${CONSOLE:-0} } /* probe ID 1 */
1339
1340/*********************************************************/
1341
1342${PSARGS:+$PSARGS_ACTION}
1343${ACTIONS:+
1344/*********************************************************/
1345
1346$ACTIONS
1347}
1348/*********************************************************/
1349
1350$PROBE${EVENT_TEST:+ / $EVENT_TEST /} /* probe ID $ID */
1351{${TRACE:+
1352	printf("<$ID>");
1353}
1354	/***********************************************/
1355
1356	printf("%s%Y%s ",
1357		console ? "\033[32m" : "",
1358		walltimestamp,
1359		console ? "\033[39m" : "");
1360
1361	/****************** EVENT_TAG ******************/
1362
1363	${EVENT_TAG#[[:space:]]}
1364${PROBE_COALESCE:+
1365	/**************** PROBE_COALESCE ***************/
1366
1367	printf("%s%s:%s:%s:%s ", probename == "entry" ? "-> " :
1368			probename == "return" ? "<- " :
1369			probename == "start" ? "-> " :
1370			probename == "done" ? "<- " : " | ",
1371		probeprov, probemod, probefunc, probename);
1372}
1373	/**************** EVENT_DETAILS ****************/
1374
1375	${EVENT_DETAILS#[[:space:]]}
1376
1377	/***********************************************/
1378
1379	printf("\\n");
1380${PSTREE:+
1381	/*
1382	 * Print process, parent, grandparent, and ancestor details
1383	 */
1384$(	pproc_dump -v 3
1385	pproc_dump -v 2
1386	pproc_dump -v 1
1387	pproc_dump -v 0
1388)}
1389}
1390EOF
1391# NOTREACHED
1392
1393################################################################################
1394# END
1395################################################################################
1396