]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - cddl/usr.sbin/dwatch/dwatch
Merge llvm, clang, compiler-rt, libc++, libunwind, lld, lldb and openmp
[FreeBSD/FreeBSD.git] / cddl / usr.sbin / dwatch / dwatch
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 #
37 DTRACE_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
50 VERSION='$Version: 1.4 $' # -V
51
52 pgm="${0##*/}" # Program basename
53
54 #
55 # Command-line arguments
56 #
57 PROBE_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 #
68 CONSOLE=                # -y
69 CONSOLE_FORCE=          # -y
70 [ -t 1 ] && CONSOLE=1   # -y
71 COUNT=0                 # -N count
72 CUSTOM_DETAILS=         # -E code
73 CUSTOM_TEST=            # -t test
74 DEBUG=                  # -d
75 DESTRUCTIVE_ACTIONS=    # -w
76 DEVELOPER=              # -dev
77 EXECNAME=               # -k name
78 EXECREGEX=              # -z regex
79 EXIT_AFTER_COMPILE=     # -e
80 FILTER=                 # -r regex
81 PROBE_COALESCE=         # -F
82 GROUP=                  # -g group
83 JID=                    # -j jail
84 LIST=                   # -l
85 LIST_PROFILES=          # -Q
86 MAX_ARGS=$_MAX_ARGS     # -B num
87 MAX_DEPTH=$_MAX_DEPTH   # -K num
88 ONELINE=                # -1
89 OUTPUT=                 # -o file
90 OUTPUT_CMD=             # -O cmd
91 PID=                    # -p pid
92 PROBE_TYPE=             # -f -m -n -P
93 PROFILE=                # -X profile
94 PSTREE=                 # -R
95 QUIET=                  # -q
96 TIMEOUT=                # -T time
97 TRACE=                  # -x
98 USER=                   # -u user
99 USE_PROFILE=            # -X profile
100 VERBOSE=                # -v
101
102 #
103 # Global exit status
104 #
105 SUCCESS=0
106 FAILURE=1
107
108 #
109 # Miscellaneous
110 #
111 ACTIONS=
112 EVENT_DETAILS=
113 EVENT_TAG='printf("%d.%d %s[%d]: ",
114                 this->uid0, this->gid0, execname, this->pid0);'
115 EVENT_TEST=
116 FILE=
117 ID=3
118 MODULE_CHECKED=
119 PROBE=
120 PSARGS=1
121 RGID=
122 RUID=
123 SUDO=
124 export SUDO_PROMPT="[sudo] Password:"
125 TITLE=\$Title:
126
127 ############################################################ FUNCTIONS
128
129 ansi() { local fmt="$2 $4"; [ "$CONSOLE" ] && fmt="\\033[$1m$2\\033[$3m $4";
130         shift 4; printf "$fmt\n" "$@"; }
131 die() { exec >&2; [ "$*" ] && echo "$pgm:" "$@"; exit $FAILURE; }
132 info() { [ "$QUIET" ] || ansi 35 "INFO" 39 "$@" >&2; }
133
134 usage()
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
219 dtrace_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
247 dtrace_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
267 expand_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
388 list_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
442 list_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
540 shell_escape()
541 {
542         echo "$*" | awk 'gsub(/'\''/, "&\\\\&&")||1'
543 }
544
545 load_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
608 pproc()
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
681 pproc_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 #
727 while 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
786 done
787 shift $(( $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 #
806 if [ ! "$PROFILE" ]; then
807         # If not given `-X profile' then a probe argument is required
808         [ $# -gt 0 ] || usage # NOTREACHED
809 fi
810
811 #
812 # Validate `-N count' option argument
813 #
814 case "$COUNT" in
815 "") usage "-N option requires a number argument" ;; # NOTREACHED
816 *[!0-9]*) usage "-N argument must be a number" ;; # NOTREACHED
817 esac
818
819 #
820 # Validate `-B num' option argument
821 #
822 case "$MAX_ARGS" in
823 "") usage "-B option requires a number argument" ;; # NOTREACHED
824 *[!0-9]*) usage "-B argument must be a number" ;; # NOTREACHED
825 esac
826
827 #
828 # Validate `-K num' option argument
829 #
830 case "$MAX_DEPTH" in
831 "") usage "-K option requires a number argument" ;; # NOTREACHED
832 *[!0-9]*) usage "-K argument must be a number" ;; # NOTREACHED
833 esac
834
835 #
836 # Validate `-j jail' option argument
837 #
838 case "$JID" in
839 "") : fall through ;;
840 *[!0-9]*) JID=$( jls -j "$JID" jid ) || exit ;;
841 esac
842
843 #
844 # Validate `-u user' option argument
845 #
846 case "$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
851 esac
852
853 #
854 # Validate `-g group' option argument
855 #
856 case "$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
865 esac
866
867 #
868 # Expand probe argument into probe(s)
869 #
870 case "$1" in
871 -*) : Assume dtrace options such as "-c cmd" or "-p pid" ;; # No probe(s) given
872 *)
873         PROBE_ARG="$1"
874         shift
875 esac
876 if [ "$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"
884 fi
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 #
910 case "$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         ;;
928 esac
929
930 #
931 # Jail clause/predicate
932 #
933 if [ "$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)}"
937 fi
938
939 #
940 # Custom test clause/predicate
941 #
942 if [ "$CUSTOM_TEST" ]; then
943         case "$EVENT_TEST" in
944         "") EVENT_TEST="$CUSTOM_TEST" ;;
945          *) EVENT_TEST="$EVENT_TEST &&
946                 ($CUSTOM_TEST)"
947         esac
948 fi
949
950 #
951 # Make sure dynamic code has trailing semi-colons if non-NULL
952 #
953 EVENT_TAG="${EVENT_TAG%;}${EVENT_TAG:+;}"
954 EVENT_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 #
965 exec 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 }
996 EOF
997 PSARGS_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
1338 int console;
1339
1340 dtrace:::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 }
1392 EOF
1393 # NOTREACHED
1394
1395 ################################################################################
1396 # END
1397 ################################################################################