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