Changeset View
Changeset View
Standalone View
Standalone View
head/cddl/usr.sbin/dwatch/dwatch
Property | Old Value | New Value |
---|---|---|
svn:executable | null | * \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
#!/bin/sh | |||||
#- | |||||
# Copyright (c) 2014-2018 Devin Teske | |||||
# All rights reserved. | |||||
# | |||||
# Redistribution and use in source and binary forms, with or without | |||||
# modification, are permitted provided that the following conditions | |||||
# are met: | |||||
# 1. Redistributions of source code must retain the above copyright | |||||
# notice, this list of conditions and the following disclaimer. | |||||
# 2. Redistributions in binary form must reproduce the above copyright | |||||
# notice, this list of conditions and the following disclaimer in the | |||||
# documentation and/or other materials provided with the distribution. | |||||
# | |||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
# SUCH DAMAGE. | |||||
# | |||||
############################################################ IDENT(1) | |||||
# | |||||
# $Title: Watch processes as they trigger a particular DTrace probe $ | |||||
# $FreeBSD$ | |||||
# | |||||
############################################################ CONFIGURATION | |||||
# | |||||
# DTrace pragma settings | |||||
# | |||||
DTRACE_PRAGMA=" | |||||
option quiet | |||||
option dynvarsize=16m | |||||
option switchrate=10hz | |||||
" # END-QUOTE | |||||
# | |||||
# Profiles | |||||
# | |||||
: ${DWATCH_PROFILES_PATH="/usr/libexec/dwatch:/usr/local/libexec/dwatch"} | |||||
############################################################ GLOBALS | |||||
VERSION='$Version: 1.0-beta-91 $' # -V | |||||
pgm="${0##*/}" # Program basename | |||||
# | |||||
# Command-line arguments | |||||
# | |||||
PROBE_ARG= | |||||
# | |||||
# Command-line options | |||||
# | |||||
CONSOLE= # -y | |||||
CONSOLE_FORCE= # -y | |||||
[ -t 1 ] && CONSOLE=1 # -y | |||||
COUNT=0 # -N count | |||||
CUSTOM_DETAILS= # -E code | |||||
CUSTOM_TEST= # -t test | |||||
DEBUG= # -d | |||||
DESTRUCTIVE_ACTIONS= # -w | |||||
EXECNAME= # -k name | |||||
EXECREGEX= # -z regex | |||||
EXIT_AFTER_COMPILE= # -e | |||||
FILTER= # -r regex | |||||
PROBE_COALESCE= # -F | |||||
GROUP= # -g group | |||||
JID= # -j jail | |||||
LIST= # -l | |||||
LIST_PROFILES= # -Q | |||||
MAX_ARGS=64 # -B num | |||||
MAX_DEPTH=64 # -K num | |||||
ONELINE= # -1 | |||||
OUTPUT= # -o file | |||||
OUTPUT_CMD= # -O cmd | |||||
PID= # -p pid | |||||
PROBE_TYPE= # -f -m -n -P | |||||
PROFILE= # -X profile | |||||
PSTREE= # -R | |||||
QUIET= # -q | |||||
TIMEOUT= # -T time | |||||
TRACE= # -x | |||||
USER= # -u user | |||||
USE_PROFILE= # -X profile | |||||
VERBOSE= # -v | |||||
# | |||||
# Global exit status | |||||
# | |||||
SUCCESS=0 | |||||
FAILURE=1 | |||||
# | |||||
# Miscellaneous | |||||
# | |||||
ACTIONS= | |||||
EVENT_DETAILS= | |||||
EVENT_TAG='printf("%d.%d %s[%d]: ", | |||||
this->uid0, this->gid0, execname, this->pid0);' | |||||
EVENT_TEST= | |||||
FILE= | |||||
ID=3 | |||||
MODULE_CHECKED= | |||||
PROBE= | |||||
PSARGS=1 | |||||
RGID= | |||||
RUID= | |||||
SUDO= | |||||
export SUDO_PROMPT="[sudo] Password:" | |||||
TITLE=\$Title: | |||||
############################################################ FUNCTIONS | |||||
ansi() { local fmt="$2 $4"; [ "$CONSOLE" ] && fmt="\\033[$1m$2\\033[$3m $4"; | |||||
shift 4; printf "$fmt\n" "$@"; } | |||||
die() { exec >&2; [ "$*" ] && echo "$pgm:" "$@"; exit $FAILURE; } | |||||
info() { [ "$QUIET" ] || ansi 35 "INFO" 39 "$@" >&2; } | |||||
usage() | |||||
{ | |||||
local optfmt="\t%-10s %s\n" | |||||
exec >&2 | |||||
[ "$*" ] && printf "%s: %s\n" "$pgm" "$*" | |||||
printf "Usage: %s [-1defFmnPqRvVwxy] [%s] [%s] [%s] [%s]\n" "$pgm" \ | |||||
"-B num" "-E code" "-g group" "-j jail" | |||||
printf "\t [%s] [%s] [%s] [%s] [%s] [%s]\n" \ | |||||
"-k name" "-K num" "-N count" "-o file" "-O cmd" "-p pid" | |||||
printf "\t [%s] [%s] [%s] [%s] [%s] [%s]\n" \ | |||||
"-r regex" "-t test" "-T time" "-u user" "-X profile" \ | |||||
"-z regex" | |||||
printf "\t probe[,...] [args ...]\n" | |||||
printf " %s -l [-fmnPqy] [-r regex] [probe ...]\n" "$pgm" | |||||
printf " %s -Q [-1qy] [-r regex]\n" "$pgm" | |||||
printf "\n" | |||||
printf "$optfmt" "-1" \ | |||||
"Print one line per process/profile (Default; disables \`-R')." | |||||
printf "$optfmt" "-B num" \ | |||||
"Maximum process arguments to display (Default $MAX_ARGS)." | |||||
printf "$optfmt" "-d" \ | |||||
"Debug. Send dtrace(1) script to stdout instead of executing." | |||||
printf "$optfmt" "-e" \ | |||||
"Exit after compiling request but prior to enabling probes." | |||||
printf "$optfmt" "-E code" \ | |||||
"DTrace code for event details. If \`-', read from stdin." | |||||
printf "$optfmt" "-f" \ | |||||
"Enable probe matching the specified function name." | |||||
printf "$optfmt" "-F" \ | |||||
"Coalesce trace output by function." | |||||
printf "$optfmt" "-g group" \ | |||||
"Group filter. Only show processes matching group name/gid." | |||||
printf "$optfmt" "-j jail" \ | |||||
"Jail filter. Only show processes matching jail name/jid." | |||||
printf "$optfmt" "-k name" \ | |||||
"Only show processes matching name." | |||||
printf "$optfmt" "-K num" \ | |||||
"Maximum directory depth to display (Default $MAX_DEPTH)." | |||||
printf "$optfmt" "-l" \ | |||||
"List available probes on standard output and exit." | |||||
printf "$optfmt" "-m" \ | |||||
"Enable probe matching the specified module name." | |||||
printf "$optfmt" "-n" \ | |||||
"Enable probe matching the specified probe name." | |||||
printf "$optfmt" "-N count" \ | |||||
"Exit after count matching entries (Default 0 for disabled)." | |||||
printf "$optfmt" "-o file" \ | |||||
"Set output file. If \`-', the path \`/dev/stdout' is used." | |||||
printf "$optfmt" "-O cmd" \ | |||||
"Execute cmd for each event." | |||||
printf "$optfmt" "-p pid" \ | |||||
"Process id filter. Only show processes with matching pid." | |||||
printf "$optfmt" "-P" \ | |||||
"Enable probe matching the specified provider name." | |||||
printf "$optfmt" "-q" \ | |||||
"Quiet. Hide informational messages and all dtrace(1) errors." | |||||
printf "$optfmt" "-Q" \ | |||||
"List available profiles in DWATCH_PROFILES_PATH and exit." | |||||
printf "$optfmt" "-r regex" \ | |||||
"Filter. Only show blocks matching awk(1) regular expression." | |||||
printf "$optfmt" "-R" \ | |||||
"Show parent, grandparent, and ancestor of process." | |||||
printf "$optfmt" "-t test" \ | |||||
"Test clause (predicate) to limit events (Default none)." | |||||
printf "$optfmt" "-T time" \ | |||||
"Timeout. Format is \`\#[smhd]' or simply \`\#' for seconds." | |||||
printf "$optfmt" "-u user" \ | |||||
"User filter. Only show processes matching user name/uid." | |||||
printf "$optfmt" "-v" \ | |||||
"Verbose. Show all errors from dtrace(1)." | |||||
printf "$optfmt" "-V" \ | |||||
"Report dwatch version on standard output and exit." | |||||
printf "$optfmt" "-w" \ | |||||
"Permit destructive actions (copyout*, stop, panic, etc.)." | |||||
printf "$optfmt" "-x" \ | |||||
"Trace. Print \`<probe-id>' when a probe is triggered." | |||||
printf "$optfmt" "-X profile" \ | |||||
"Load profile name from DWATCH_PROFILES_PATH." | |||||
printf "$optfmt" "-y" \ | |||||
"Always treat stdout as console (enable colors/columns/etc.)." | |||||
printf "$optfmt" "-z regex" \ | |||||
"Only show processes matching awk(1) regular expression." | |||||
die | |||||
} | |||||
dtrace_cmd() | |||||
{ | |||||
local status stdout | |||||
local timeout= | |||||
if [ "$1" = "-t" ]; then | |||||
shift | |||||
[ "$TIMEOUT" ] && timeout=1 | |||||
fi | |||||
exec 3>&1 | |||||
stdout=3 | |||||
# | |||||
# Filter dtrace(1) stderr while preserving exit status | |||||
# | |||||
status=$( | |||||
exec 4>&1 | |||||
to_status=4 | |||||
( trap 'echo $? >&$to_status' EXIT | |||||
eval $SUDO ${timeout:+timeout \"\$TIMEOUT\"} dtrace \ | |||||
\"\$@\" 2>&1 ${QUIET:+2> /dev/null} >&$stdout | |||||
) | dtrace_stderr_filter >&2 | |||||
) | |||||
return $status | |||||
} | |||||
dtrace_stderr_filter() | |||||
{ | |||||
if [ "$VERBOSE" ]; then | |||||
cat | |||||
return | |||||
# NOTREACHED | |||||
fi | |||||
awk ' # Start awk(1) stderr-filter | |||||
/[[:digit:]]+ drops? on CPU [[:digit:]]+/ { next } | |||||
/failed to write to <stdout>: No such file or directory/ { next } | |||||
/failed to write to <stdout>: Broken pipe/ { next } | |||||
/processing aborted: Broken pipe/ { next } | |||||
/invalid address \(0x[[:xdigit:]]+\) in action #[[:digit:]]+/ { next } | |||||
/out of scratch space in action #[[:digit:]]+/ { next } | |||||
/^Bus error$/ { next } | |||||
{ print; fflush() } | |||||
' # END-QUOTE | |||||
} | |||||
expand_probe() | |||||
{ | |||||
local OPTIND=1 OPTARG flag | |||||
local type= | |||||
while getopts t: flag; do | |||||
case "$flag" in | |||||
t) type="$OPTARG" ;; | |||||
esac | |||||
done | |||||
shift $(( $OPTIND - 1 )) | |||||
local probe="$1" | |||||
case "$probe" in | |||||
*:*) | |||||
echo "$probe" | |||||
return $SUCCESS | |||||
;; | |||||
esac | |||||
dtrace_cmd -l | awk -v probe="$probe" -v type="$type" ' | |||||
# Start awk(1) processor | |||||
#################################################### BEGIN | |||||
BEGIN { getline dtrace_header } | |||||
#################################################### FUNCTIONS | |||||
function dump(unused1,unused2) { | |||||
if (n) { | |||||
if (NcF[n] == 1) f = N2F[n] | |||||
if (NcM[n] == 1) m = N2M[n] | |||||
if (NcP[n] == 1) p = N2P[n] | |||||
} else if (f) { | |||||
if (FcM[f] == 1) m = F2M[f] | |||||
if (FcP[f] == 1) p = F2P[f] | |||||
if (FcN[f] == 0 && found) n = "entry" | |||||
} else if (m) { | |||||
if (McP[m] == 1) p = M2P[m] | |||||
} | |||||
printf "%s:%s:%s:%s\n", p, m, f, n | |||||
exit !found | |||||
} | |||||
function inFMP() { return probe in F || probe in M || probe in P } | |||||
function inNMP() { return probe in N || probe in M || probe in P } | |||||
function inNFP() { return probe in N || probe in F || probe in P } | |||||
function inNFM() { return probe in N || probe in F || probe in M } | |||||
function diva(value, peerA, peerB, peerC) { | |||||
return value >= peerA && value >= peerB && value >= peerC | |||||
} | |||||
#################################################### MAIN | |||||
type == "name" && $NF != probe { next } | |||||
type == "function" && NF >=4 && $(NF-1) != probe { next } | |||||
type == "module" && NF == 5 && $(NF-2) != probe { next } | |||||
type == "provider" && $2 != probe { next } | |||||
type || $2 == probe || $3 == probe || $4 == probe || $5 == probe { | |||||
P[_p = $2]++ | |||||
M[_m = (NF >= 5 ? $(NF-2) : "")]++ | |||||
F[_f = (NF >= 4 ? $(NF-1) : "")]++ | |||||
N[_n = $NF]++ | |||||
if (N2F[_n] != _f) NcF[_n]++; N2F[_n] = _f | |||||
if (N2M[_n] != _m) NcM[_n]++; N2M[_n] = _m | |||||
if (N2P[_n] != _p) NcP[_n]++; N2P[_n] = _p | |||||
if (_n !~ /entry|return/) { | |||||
if (F2N[_f] != _n) FcN[_f]++ | |||||
F2N[_f] = _n | |||||
} | |||||
if (F2M[_f] != _m) FcM[_f]++; F2M[_f] = _m | |||||
if (F2P[_f] != _p) FcP[_f]++; F2P[_f] = _p | |||||
if (M2P[_m] != _p) McP[_m]++; M2P[_m] = _p | |||||
} | |||||
#################################################### END | |||||
END { | |||||
if (type == "name") dump(n = probe, found = probe in N) | |||||
if (type == "function") dump(f = probe, found = probe in F) | |||||
if (type == "module") dump(m = probe, found = probe in M) | |||||
if (type == "provider") dump(p = probe, found = probe in P) | |||||
if (probe in N) { | |||||
found = 1 | |||||
if (!inFMP()) dump(n = probe) | |||||
if (diva(F[probe], N[probe], M[probe], P[probe])) | |||||
dump(f = probe) | |||||
if (diva(M[probe], N[probe], F[probe], P[probe])) | |||||
dump(m = probe) | |||||
if (diva(P[probe], N[probe], F[probe], M[probe])) | |||||
dump(p = probe) | |||||
dump(n = probe) # N is the diva | |||||
} else if (probe in F) { | |||||
found = 1 | |||||
if (!inNMP()) dump(f = probe) | |||||
if (diva(N[probe], F[probe], M[probe], P[probe])) | |||||
dump(n = probe) | |||||
if (diva(M[probe], F[probe], N[probe], P[probe])) | |||||
dump(m = probe) | |||||
if (diva(P[probe], F[probe], N[probe], M[probe])) | |||||
dump(p = probe) | |||||
dump(f = probe) # F is the diva | |||||
} else if (probe in M) { | |||||
found = 1 | |||||
if (!inNFP()) dump(m = probe) | |||||
if (diva(N[probe], M[probe], F[probe], P[probe])) | |||||
dump(n = probe) | |||||
if (diva(F[probe], M[probe], N[probe], P[probe])) | |||||
dump(f = probe) | |||||
if (diva(P[probe], M[probe], N[probe], F[probe])) | |||||
dump(p = probe) | |||||
dump(m = probe) # M is the diva | |||||
} else if (probe in P) { | |||||
found = 1 | |||||
if (!inNFM()) dump(p = probe) | |||||
if (diva(N[probe], P[probe], F[probe], M[probe])) | |||||
dump(n = probe) | |||||
if (diva(F[probe], P[probe], N[probe], M[probe])) | |||||
dump(f = probe) | |||||
if (diva(M[probe], P[probe], N[probe], F[probe])) | |||||
dump(m = probe) | |||||
dump(p = probe) # P is the diva | |||||
} | |||||
if (!found) print probe | |||||
exit !found | |||||
} | |||||
' # END-QUOTE | |||||
} | |||||
list_probes() | |||||
{ | |||||
local OPTIND=1 OPTARG flag | |||||
local column=0 header="PROVIDER:MODULE:FUNCTION:NAME" | |||||
local filter= quiet= type= | |||||
while getopts f:qt: flag; do | |||||
case "$flag" in | |||||
f) filter="$OPTARG" ;; | |||||
q) quiet=1 ;; | |||||
t) type="$OPTARG" ;; | |||||
esac | |||||
done | |||||
shift $(( $OPTIND - 1 )) | |||||
if [ $# -eq 0 ]; then | |||||
case "$type" in | |||||
provider) column=1 header="PROVIDER" ;; | |||||
module) column=2 header="MODULE" ;; | |||||
function) column=3 header="FUNCTION" ;; | |||||
name) column=4 header="NAME" ;; | |||||
esac | |||||
fi | |||||
[ "$quiet" ] || echo "$header" | |||||
local arg probe= | |||||
for arg in "$@"; do | |||||
arg=$( expand_probe -t "$type" -- "$arg" ) | |||||
probe="$probe${probe:+, }$arg" | |||||
done | |||||
dtrace_cmd -l${probe:+n "$probe"} | awk -v pattern="$( | |||||
# Prevent backslashes from being lost | |||||
echo "$filter" | awk 'gsub(/\\/,"&&")||1' | |||||
)" -v want="$column" -v console="$CONSOLE" ' | |||||
BEGIN { getline dtrace_header } | |||||
function ans(seq) { return console ? "\033[" seq "m" : "" } | |||||
NF > 3 && $(NF-1) ~ /^#/ { next } | |||||
!_[$0 = column[0] = sprintf("%s:%s:%s:%s", | |||||
column[1] = $2, | |||||
column[2] = (NF >= 5 ? $(NF-2) : ""), | |||||
column[3] = (NF >= 4 ? $(NF-1) : ""), | |||||
column[4] = $NF)]++ && | |||||
!__[$0 = column[want]]++ && | |||||
gsub(pattern, ans("31;1") "&" ans("39;22")) { | |||||
print | "sort" | |||||
} | |||||
END { close("sort") } | |||||
' # END-QUOTE | |||||
exit $SUCCESS | |||||
} | |||||
list_profiles() | |||||
{ | |||||
local OPTIND=1 OPTARG flag | |||||
local filter= oneline= quiet= | |||||
while getopts 1f:q flag; do | |||||
case "$flag" in | |||||
1) oneline=1 ;; | |||||
f) filter="$OPTARG" ;; | |||||
q) quiet=1 ;; | |||||
esac | |||||
done | |||||
shift $(( $OPTIND - 1 )) | |||||
# Prevent backslashes from being lost | |||||
filter=$( echo "$filter" | awk 'gsub(/\\/,"&&")||1' ) | |||||
# Build a list of profiles available | |||||
local profiles | |||||
profiles=$( { IFS=: | |||||
for dir in $DWATCH_PROFILES_PATH; do | |||||
[ -d "$dir" ] || continue | |||||
for path in $dir/*; do | |||||
[ -f "$path" ] || continue | |||||
name="${path##*/}" | |||||
[ "$name" = "${name%%[!0-9A-Za-z_-]*}" ] || | |||||
continue | |||||
echo $name | |||||
done | |||||
done | |||||
} | sort -u ) | |||||
# Get the longest profile name | |||||
local longest_profile_name | |||||
longest_profile_name=$( echo "$profiles" | | |||||
awk -v N=0 '(L = length($0)) > N { N = L } END { print N }' ) | |||||
# Get the width of the terminal | |||||
local max_size="$( stty size 2> /dev/null )" | |||||
: ${max_size:=24 80} | |||||
local max_width="${max_size#*[$IFS]}" | |||||
# Determine how many columns we can display | |||||
local x=$longest_profile_name ncols=1 | |||||
[ "$QUIET" ] || x=$(( $x + 8 )) # Accommodate leading tab character | |||||
x=$(( $x + 3 + $longest_profile_name )) # Preload end of next column | |||||
while [ $x -lt $max_width ]; do | |||||
ncols=$(( $ncols + 1 )) | |||||
x=$(( $x + 3 + $longest_profile_name )) | |||||
done | |||||
# Output single lines if sent to a pipe | |||||
if [ "$oneline" ]; then | |||||
echo "$profiles" | awk -v filter="$filter" -v cons="$CONSOLE" ' | |||||
function ans(s) { return cons ? "\033[" s "m" : "" } | |||||
gsub(filter, ans("31;1") "&" ans("39;22")) | |||||
' # END-QUOTE | |||||
return $SUCCESS | |||||
# NOTREACHED | |||||
fi | |||||
[ "$quiet" ] || echo PROFILES: | |||||
echo "$profiles" | awk \ | |||||
-v colsize=$longest_profile_name \ | |||||
-v console="$CONSOLE" \ | |||||
-v ncols=$ncols \ | |||||
-v quiet="$quiet" \ | |||||
-v filter="$filter" \ | |||||
' # Begin awk(1) processor | |||||
function ans(seq) { return console ? "\033[" seq "m" : "" } | |||||
BEGIN { | |||||
row_item[1] = "" | |||||
replace = ans("31;1") "&" ans("39;22") | |||||
ansi_offset = length(replace) - 1 | |||||
} | |||||
function print_row() | |||||
{ | |||||
cs = colsize + ansi_offset * \ | |||||
gsub(filter, replace, row_item[1]) | |||||
printf "%s%-*s", quiet ? "" : "\t", cs, row_item[1] | |||||
for (i = 2; i <= cur_col; i++) { | |||||
cs = colsize + ansi_offset * \ | |||||
gsub(filter, replace, row_item[i]) | |||||
printf " %-*s", cs, row_item[i] | |||||
} | |||||
printf "\n" | |||||
} | |||||
$0 ~ filter { | |||||
n++ | |||||
cur_col = ((n - 1) % ncols) + 1 | |||||
row_item[cur_col] = $0 | |||||
if (cur_col == ncols) print_row() | |||||
} | |||||
END { if (cur_col < ncols) print_row() } | |||||
' # END-QUOTE | |||||
exit $SUCCESS | |||||
} | |||||
load_profile() | |||||
{ | |||||
local profile="$1" | |||||
[ "$profile" ] || | |||||
die "missing profile argument (\`$pgm -Q' to list profiles)" | |||||
local oldIFS="$IFS" | |||||
local dir found= | |||||
IFS=: | |||||
for dir in $DWATCH_PROFILES_PATH; do | |||||
[ -d "$dir" ] || continue | |||||
[ -f "$dir/$profile" ] || continue | |||||
PROFILE="$profile" found=1 | |||||
info "Sourcing $profile profile [found in %s]" "$dir" | |||||
. "$dir/$profile" | |||||
break | |||||
done | |||||
IFS="$oldIFS" | |||||
[ "$found" ] || | |||||
die "no module named \`$profile' (\`$pgm -Q' to list profiles)" | |||||
} | |||||
pproc() | |||||
{ | |||||
local OPTIND=1 OPTARG flag | |||||
local P= N=0 | |||||
while getopts P: flag; do | |||||
case "$flag" in | |||||
P) P="$OPTARG" ;; | |||||
esac | |||||
done | |||||
shift $(( OPTIND - 1 )) | |||||
local proc=$1 | |||||
if [ ! "$proc" ]; then | |||||
if [ "$P" = "0" ]; then | |||||
proc="curthread->td_proc" | |||||
else | |||||
proc="this->proc ? this->proc->p_pptr : NULL" | |||||
fi | |||||
fi | |||||
awk 'NR > 1 && $0 { $0 = "\t" $0 } | |||||
gsub(/\\\t/, "\t") || 1 | |||||
' <<-EOFPREAMBLE | |||||
this->proc = $proc; | |||||
this->uid$P = this->proc ? this->proc->p_ucred->cr_uid : -1; | |||||
this->gid$P = this->proc ? this->proc->p_ucred->cr_rgid : -1; | |||||
this->pid$P = this->proc ? this->proc->p_pid : -1; | |||||
this->jid$P = this->proc ? this->proc->p_ucred->cr_prison->pr_id : -1; | |||||
this->p_args = this->proc ? this->proc->p_args : 0; | |||||
this->ar_length = this->p_args ? this->p_args->ar_length : 0; | |||||
this->ar_args = (char *)(this->p_args ? this->p_args->ar_args : 0); | |||||
this->args$P = this->arg${P}_$N = this->ar_length > 0 ? | |||||
\ this->ar_args : stringof(this->proc->p_comm); | |||||
this->len = this->ar_length > 0 ? strlen(this->ar_args) + 1 : 0; | |||||
this->ar_args += this->len; | |||||
this->ar_length -= this->len; | |||||
EOFPREAMBLE | |||||
awk -v P=$P -v MAX_ARGS=$MAX_ARGS ' | |||||
$0 { $0 = "\t" $0 } | |||||
buf = buf $0 "\n" { } | |||||
END { | |||||
while (++N <= MAX_ARGS) { | |||||
$0 = buf | |||||
gsub(/P/, P) | |||||
gsub(/N/, N) | |||||
gsub(/\\\t/, "\t") | |||||
sub(/\n$/, "") | |||||
} | |||||
} | |||||
' <<-EOFARGS | |||||
this->argP_N = this->ar_length > 0 ? this->ar_args : ""; | |||||
this->argsP = strjoin(this->argsP, | |||||
\ strjoin(this->argP_N != "" ? " " : "", this->argP_N)); | |||||
this->len = this->ar_length > 0 ? strlen(this->ar_args) + 1 : 0; | |||||
this->ar_args += this->len; | |||||
this->ar_length -= this->len; | |||||
EOFARGS | |||||
N=$(( $MAX_ARGS + 1 )) | |||||
awk 'sub(/^\\\t/, "\t") || 1, $0 = "\t" $0' <<-EOFPROC | |||||
this->arg${P}_$N = this->ar_length > 0 ? "..." : ""; | |||||
this->args$P = strjoin(this->args$P, | |||||
\ strjoin(this->arg${P}_$N != "" ? " " : "", this->arg${P}_$N)); | |||||
EOFPROC | |||||
} | |||||
pproc_dump() | |||||
{ | |||||
local OPTIND=1 OPTARG flag | |||||
local verbose= | |||||
while getopts v flag; do | |||||
case "$flag" in | |||||
v) verbose=1 ;; | |||||
esac | |||||
done | |||||
shift $(( $OPTIND - 1 )) | |||||
local P=$1 | |||||
if [ "$verbose" ]; then | |||||
awk -v P=$P ' | |||||
BEGIN { printf "\t" } | |||||
NR > 1 && $0 { $0 = "\t" $0 } | |||||
buf = buf $0 "\n" { } | |||||
END { | |||||
$0 = buf | |||||
if (P < 3) S = sprintf("%" 7-2*(P+1) "s", "") | |||||
gsub(/S/, S) | |||||
gsub(/B/, P < 3 ? "\\" : "") | |||||
gsub(/\\\t/, "\t") | |||||
sub(/\n$/, "") | |||||
} | |||||
' <<-EOFPREAMBLE | |||||
printf(" SB-+= %05d %d.%d %s\n", | |||||
\ this->pid$P, this->uid$P, this->gid$P, this->args$P); | |||||
EOFPREAMBLE | |||||
else | |||||
cat <<-EOFPREAMBLE | |||||
printf("%s", this->args$P); | |||||
EOFPREAMBLE | |||||
fi | |||||
} | |||||
############################################################ MAIN | |||||
# If we're running as root, no need for sudo(8) | |||||
[ "$( id -u )" != 0 ] && type sudo > /dev/null 2>&1 && SUDO=sudo | |||||
# | |||||
# Process command-line options | |||||
# | |||||
while getopts 1B:deE:fFg:j:k:K:lmnN:o:O:p:PqQr:Rt:T:u:vVwxX:yz: flag; do | |||||
case "$flag" in | |||||
1) ONELINE=1 PSTREE= ;; | |||||
B) MAX_ARGS="$OPTARG" ;; | |||||
d) DEBUG=1 ;; | |||||
e) EXIT_AFTER_COMPILE=1 ;; | |||||
E) CUSTOM_DETAILS=1 | |||||
EVENT_DETAILS="${EVENT_DETAILS%;}" | |||||
[ "$EVENT_DETAILS" ] && EVENT_DETAILS="$EVENT_DETAILS; | |||||
printf(\" \"); | |||||
" # END-QUOTE | |||||
# Read event code from stdin if `-' is argument | |||||
[ "$OPTARG" = "-" ] && OPTARG=$( cat ) | |||||
EVENT_DETAILS="$EVENT_DETAILS$OPTARG" ;; | |||||
f) PROBE_TYPE=function ;; | |||||
F) PROBE_COALESCE=1 ;; | |||||
g) GROUP="$OPTARG" ;; | |||||
j) JID="$OPTARG" ;; | |||||
k) EXECNAME="$EXECNAME${EXECNAME:+ }$OPTARG" | |||||
case "$OPTARG" in | |||||
\**\*) name="${OPTARG%\*}" | |||||
predicate="strstr(execname, \"${name#\*}\") != NULL" ;; | |||||
\**) name="${OPTARG#\*}" | |||||
predicate="strstr(execname, \"$name\") == (execname +" | |||||
predicate="$predicate strlen(execname) - ${#name})" ;; | |||||
*\*) predicate="strstr(execname, \"${OPTARG%\*}\") == execname" ;; | |||||
*) predicate="execname == \"$OPTARG\"" | |||||
esac | |||||
EVENT_TEST="$predicate${EVENT_TEST:+ || | |||||
($EVENT_TEST)}" ;; | |||||
K) MAX_DEPTH="$OPTARG" ;; | |||||
l) LIST=1 ;; | |||||
m) PROBE_TYPE=module ;; | |||||
n) PROBE_TYPE=name ;; | |||||
N) COUNT="$OPTARG" ;; | |||||
o) OUTPUT="$OPTARG" ;; | |||||
O) OUTPUT_CMD="$OPTARG" ;; | |||||
p) PID="$OPTARG" ;; | |||||
P) PROBE_TYPE=provider ;; | |||||
q) QUIET=1 ;; | |||||
Q) LIST_PROFILES=1 ;; | |||||
r) FILTER="$OPTARG" ;; | |||||
R) PSTREE=1 ;; | |||||
t) CUSTOM_TEST="${CUSTOM_TEST:+($CUSTOM_TEST) && }$OPTARG" ;; | |||||
T) TIMEOUT="$OPTARG" ;; | |||||
u) USER="$OPTARG" ;; | |||||
v) VERBOSE=1 ;; | |||||
V) vers="${VERSION#\$*[:\$]}" | |||||
vers="${vers% \$}" | |||||
printf "%s: %s\n" "$pgm" "${vers# }" | |||||
exit ;; | |||||
w) DESTRUCTIVE_ACTIONS=1 ;; | |||||
x) TRACE=1 ;; | |||||
X) USE_PROFILE=1 PROFILE="$OPTARG" ;; | |||||
y) CONSOLE=1 CONSOLE_FORCE=1 ;; | |||||
z) EXECREGEX="$OPTARG" ;; | |||||
*) usage | |||||
# NOTREACHED | |||||
esac | |||||
done | |||||
shift $(( $OPTIND - 1 )) | |||||
# | |||||
# List probes if `-l' was given | |||||
# | |||||
[ "$LIST" ] && | |||||
list_probes -f "$FILTER" ${QUIET:+-q} -t "$PROBE_TYPE" -- "$@" | |||||
# NOTREACHED | |||||
# | |||||
# List profiles if `-Q' was given | |||||
# | |||||
[ "$LIST_PROFILES" ] && | |||||
list_profiles ${ONELINE:+-1} -f "$FILTER" ${QUIET:+-q} | |||||
# NOTREACHED | |||||
# | |||||
# Validate number of arguments | |||||
# | |||||
if [ ! "$PROFILE" ]; then | |||||
# If not given `-X profile' then a probe argument is required | |||||
[ $# -gt 0 ] || usage # NOTREACHED | |||||
fi | |||||
# | |||||
# Validate `-N count' option argument | |||||
# | |||||
case "$COUNT" in | |||||
"") usage "-N option requires a number argument" ;; # NOTREACHED | |||||
*[!0-9]*) usage "-N argument must be a number" ;; # NOTREACHED | |||||
esac | |||||
# | |||||
# Validate `-B num' option argument | |||||
# | |||||
case "$MAX_ARGS" in | |||||
"") usage "-B option requires a number argument" ;; # NOTREACHED | |||||
*[!0-9]*) usage "-B argument must be a number" ;; # NOTREACHED | |||||
esac | |||||
# | |||||
# Validate `-K num' option argument | |||||
# | |||||
case "$MAX_DEPTH" in | |||||
"") usage "-K option requires a number argument" ;; # NOTREACHED | |||||
*[!0-9]*) usage "-K argument must be a number" ;; # NOTREACHED | |||||
esac | |||||
# | |||||
# Validate `-j jail' option argument | |||||
# | |||||
case "$JID" in | |||||
"") : fall through ;; | |||||
*[!0-9]*) JID=$( jls -j "$JID" jid ) || exit ;; | |||||
esac | |||||
# | |||||
# Validate `-u user' option argument | |||||
# | |||||
case "$USER" in | |||||
"") : fall through ;; | |||||
*[![:alnum:]_-]*) RUID="$USER" ;; | |||||
*[!0-9]*) RUID=$( id -u "$USER" 2> /dev/null ) || die "No such user: $USER" ;; | |||||
*) RUID=$USER | |||||
esac | |||||
# | |||||
# Validate `-g group' option argument | |||||
# | |||||
case "$GROUP" in | |||||
"") : fall-through ;; | |||||
*[![:alnum:]_-]*) RGID="$GROUP" ;; | |||||
*[!0-9]*) | |||||
RGID=$( getent group | awk -F: -v group="$GROUP" ' | |||||
$1 == group { print $3; exit found=1 } | |||||
END { exit !found } | |||||
' ) || die "No such group: $GROUP" ;; | |||||
*) RGID=$GROUP | |||||
esac | |||||
# | |||||
# Expand probe argument into probe(s) | |||||
# | |||||
case "$1" in | |||||
-*) : Assume dtrace options such as "-c cmd" or "-p pid" ;; # No probe(s) given | |||||
*) | |||||
PROBE_ARG="$1" | |||||
shift | |||||
esac | |||||
if [ "$PROBE_ARG" ]; then | |||||
oldIFS="$IFS" | |||||
IFS="$IFS," | |||||
for arg in $PROBE_ARG; do | |||||
arg=$( expand_probe -t "$PROBE_TYPE" -- "$arg" ) | |||||
PROBE="$PROBE${PROBE:+, }$arg" | |||||
done | |||||
IFS="$oldIFS" | |||||
fi | |||||
# | |||||
# Set default event details if `-E code' was not given | |||||
# | |||||
[ "$CUSTOM_DETAILS" ] || EVENT_DETAILS=$( pproc_dump 0 ) | |||||
# | |||||
# Load profile if given `-X profile' | |||||
# | |||||
[ "$USE_PROFILE" ] && load_profile "$PROFILE" | |||||
[ "$PROBE" ] || die "PROBE not defined by profile and none given as argument" | |||||
# | |||||
# Show the user what's being watched | |||||
# | |||||
[ "$DEBUG$QUIET$EXIT_AFTER_COMPILE" ] || info "Watching '$PROBE' ..." | |||||
# | |||||
# Header for watched probe entry | |||||
# | |||||
case "$PROBE" in | |||||
*,*) : fall-through ;; | |||||
*:execve:entry|execve:entry) | |||||
ACTIONS=$( awk 'gsub(/\\\t/, "\t") || 1' <<-EOF | |||||
$PROBE /* probe ID $ID */ | |||||
{${TRACE:+ | |||||
\ printf("<$ID>");} | |||||
\ this->caller_execname = execname; | |||||
} | |||||
EOF | |||||
) | |||||
PROBE="${PROBE%entry}return" | |||||
ID=$(( $ID + 1 )) | |||||
EVENT_TEST="execname != this->caller_execname${EVENT_TEST:+ && | |||||
($EVENT_TEST)}" | |||||
EVENT_TAG='printf("%d.%d %s[%d]: ", | |||||
this->uid1, this->gid1, this->caller_execname, this->pid1);' | |||||
;; | |||||
esac | |||||
# | |||||
# Jail clause/predicate | |||||
# | |||||
if [ "$JID" ]; then | |||||
prison_id="curthread->td_proc->p_ucred->cr_prison->pr_id" | |||||
EVENT_TEST="$prison_id == $JID${EVENT_TEST:+ && | |||||
($EVENT_TEST)}" | |||||
fi | |||||
# | |||||
# Custom test clause/predicate | |||||
# | |||||
if [ "$CUSTOM_TEST" ]; then | |||||
case "$EVENT_TEST" in | |||||
"") EVENT_TEST="$CUSTOM_TEST" ;; | |||||
*) EVENT_TEST="$EVENT_TEST && | |||||
($CUSTOM_TEST)" | |||||
esac | |||||
fi | |||||
# | |||||
# Make sure dynamic code has trailing semi-colons if non-NULL | |||||
# | |||||
EVENT_TAG="${EVENT_TAG%;}${EVENT_TAG:+;}" | |||||
EVENT_DETAILS="${EVENT_DETAILS%;}${EVENT_DETAILS:+;}" | |||||
# | |||||
# DTrace script | |||||
# | |||||
# If `-d' is given, script is sent to stdout for debugging | |||||
# If `-c count", `-g group', `-r regex', or `-u user' is given, run script with | |||||
# dtrace and send output to awk(1) post-processor (making sure to preserve the | |||||
# exit code returned by dtrace invocation). Otherwise, simply run script with | |||||
# dtrace and then exit. | |||||
# | |||||
exec 9<<EOF | |||||
$PROBE /* probe ID 2 */ | |||||
{${TRACE:+ | |||||
printf("<2>"); | |||||
} | |||||
/* | |||||
* Examine process, parent process, and grandparent process details | |||||
*/ | |||||
/******************* CURPROC *******************/ | |||||
$( pproc -P0 ) | |||||
/******************* PPARENT *******************/ | |||||
$( if [ "$PSTREE" ]; then pproc -P1; else echo -n \ | |||||
"this->proc = this->proc ? this->proc->p_pptr : NULL; | |||||
this->pid1 = this->proc ? this->proc->p_pid : -1; | |||||
this->uid1 = this->proc ? this->proc->p_ucred->cr_uid : -1; | |||||
this->gid1 = this->proc ? this->proc->p_ucred->cr_rgid : -1; | |||||
this->jid1 = this->proc ? this->proc->p_ucred->cr_prison->pr_id : -1;" | |||||
fi ) | |||||
/******************* GPARENT *******************/ | |||||
$( [ "$PSTREE" ] && pproc -P2 ) | |||||
/******************* APARENT *******************/ | |||||
$( [ "$PSTREE" ] && pproc -P3 ) | |||||
} | |||||
EOF | |||||
PSARGS_ACTION=$( cat <&9 ) | |||||
[ "$OUTPUT" -a ! "$CONSOLE_FORCE" ] && CONSOLE= | |||||
{ | |||||
if [ "$DEBUG" ]; then | |||||
# Send script to stdout | |||||
cat | |||||
exit | |||||
fi | |||||
if [ "$CUSTOM_TEST$EXECNAME$JID$OUTPUT$TIMEOUT$TRACE$VERBOSE" -a \ | |||||
! "$QUIET" ] | |||||
then | |||||
msg=Setting | |||||
[ "$CUSTOM_TEST" ] && msg="$msg test: $CUSTOM_TEST" | |||||
[ "$EXECNAME" ] && msg="$msg execname: $EXECNAME" | |||||
[ "$JID" ] && msg="$msg jid: $JID" | |||||
[ "$OUTPUT" ] && msg="$msg output: $OUTPUT" | |||||
[ "$TIMEOUT" ] && msg="$msg timeout: $TIMEOUT" | |||||
[ "$TRACE" ] && msg="$msg trace: $TRACE" | |||||
[ "$VERBOSE" ] && msg="$msg verbose: $VERBOSE" | |||||
info "$msg" | |||||
fi | |||||
exec 3>&1 | |||||
console_stdout=3 | |||||
if [ $COUNT -eq 0 -a ! "$EXECREGEX$FILTER$GROUP$OUTPUT_CMD$PID$USER" ] | |||||
then | |||||
case "$OUTPUT" in | |||||
-) output_path=/dev/stdout ;; | |||||
*) output_path="$OUTPUT" | |||||
esac | |||||
# Run script without pipe to awk post-processor | |||||
dtrace_cmd -t \ | |||||
${DESTRUCTIVE_ACTIONS:+-w} \ | |||||
${EXIT_AFTER_COMPILE:+-e} \ | |||||
${OUTPUT:+-o "$output_path"} \ | |||||
-s /dev/stdin \ | |||||
"$@" | |||||
exit | |||||
fi | |||||
# Prevent backslashes from being lost | |||||
FILTER=$( echo "$FILTER" | awk 'gsub(/\\/,"&&")||1' ) | |||||
EXECREGEX=$( echo "$EXECREGEX" | awk 'gsub(/\\/,"&&")||1' ) | |||||
if [ ! "$QUIET" ]; then | |||||
msg=Filtering | |||||
[ "$EXECREGEX" ] && msg="$msg execregex: $EXECREGEX" | |||||
[ "$FILTER" ] && msg="$msg filter: $FILTER" | |||||
[ "$GROUP" ] && msg="$msg group: $GROUP" | |||||
[ "$OUTPUT_CMD" ] && msg="$msg cmd: $OUTPUT_CMD" | |||||
[ "$PID" ] && msg="$msg pid: $PID" | |||||
[ "$USER" ] && msg="$msg user: $USER" | |||||
[ $COUNT -gt 0 ] && msg="$msg count: $COUNT" | |||||
info "$msg" | |||||
fi | |||||
# | |||||
# Send script output to post-processor for filtering | |||||
# | |||||
status=$( | |||||
exec 4>&1 | |||||
to_status=4 | |||||
( exec 5>&1; to_dtrace_stderr_filter=5; ( | |||||
trap 'echo $? >&$to_status' EXIT | |||||
eval $SUDO ${TIMEOUT:+timeout \"\$TIMEOUT\"} dtrace \ | |||||
${EXIT_AFTER_COMPILE:+-e} \ | |||||
${DESTRUCTIVE_ACTIONS:+-w} \ | |||||
-s /dev/stdin \ | |||||
\"\$@\" \ | |||||
2>&$to_dtrace_stderr_filter \ | |||||
${QUIET:+2> /dev/null} | |||||
) | $SUDO awk \ | |||||
-v cmd="$OUTPUT_CMD" \ | |||||
-v console="$CONSOLE" \ | |||||
-v count=$COUNT \ | |||||
-v execregex="$EXECREGEX" \ | |||||
-v filter="$FILTER" \ | |||||
-v gid="$RGID" \ | |||||
-v output="$OUTPUT" \ | |||||
-v pid="$PID" \ | |||||
-v pstree=$PSTREE \ | |||||
-v quiet=$QUIET \ | |||||
-v tty=$( ps -o tty= -p $$ ) \ | |||||
-v uid="$RUID" \ | |||||
' # Start awk(1) post-processor | |||||
############################################ BEGIN | |||||
BEGIN { | |||||
true = 1 | |||||
ansi = "(\\033\\[[[:digit:];]+m)?" | |||||
num = year = day = "[[:digit:]]+" | |||||
month = "[[:alpha:]]+" | |||||
date = year " " month " +" day | |||||
time = "[012][0-9]:[0-5][0-9]:[0-5][0-9]" | |||||
date_time = ansi date " +" time ansi | |||||
name1 = "[^\\[]*" | |||||
name2 = "[^\\n]*" | |||||
if (output == "-") | |||||
output = "/dev/stdout" | |||||
# | |||||
# Field definitions | |||||
# | |||||
nexecmatches = 2 | |||||
execstart[1] = sprintf( \ | |||||
"^(%s) (%s)\\.(%s) (%s)\\[(%s)\\]: ", | |||||
date_time, num, num, name1, num) | |||||
execstart[2] = sprintf( \ | |||||
"\\n +\\\\?-\\+= (%s) (%s)\\.(%s) ", | |||||
num, num, num) | |||||
npidmatches = 2 | |||||
pidstart[1] = sprintf("^(%s) (%s)\\.(%s) (%s)\\[", | |||||
date_time, num, num, name1) | |||||
pidstart[2] = "\\n +\\\\?-\\+= " | |||||
pidpreen[2] = "^0*" | |||||
piddeflt[2] = "0" | |||||
ngidmatches = 2 | |||||
gidstart[1] = sprintf("^(%s) (%s)\\.", date_time, num) | |||||
gidstart[2] = sprintf("\\n +\\\\?-\\+= (%s) (%s)\\.", | |||||
ansi num ansi, num) | |||||
nuidmatches = 2 | |||||
uidstart[1] = sprintf("^(%s) ", date_time) | |||||
uidstart[2] = sprintf("\\n +\\\\?-\\+= (%s) ", | |||||
ansi num ansi) | |||||
} | |||||
############################################ FUNCTIONS | |||||
function strip(s) { gsub(/\033\[[0-9;]*m/, "", s); return s } | |||||
function esc(str) { gsub(/'\''/, "&\\\\&&", str); return str } | |||||
function arg(str) { return "'\''" esc(str) "'\''" } | |||||
function env(var, str) { return var "=" arg(str) " " } | |||||
function ans(seq) { return console ? "\033[" seq "m" : "" } | |||||
function runcmd() { | |||||
return system(sprintf("%s/bin/sh -c %s", | |||||
env("TAG", strip(tag)) \ | |||||
env("DETAILS", strip(details)), | |||||
arg(cmd))) | |||||
} | |||||
function filter_block() { | |||||
if (length(lines) < 1) return 0 | |||||
block_match = 0 | |||||
newstr = "" | |||||
start = 1 | |||||
if (match(lines, "^(" date_time ") ")) { | |||||
newstr = newstr substr(lines, 1, | |||||
RSTART + RLENGTH - 1) | |||||
start = RSTART + RLENGTH | |||||
} | |||||
replace = ans("31;1") "&" ans("39;22") | |||||
workstr = substr(lines, start) | |||||
if (gsub(filter, replace, workstr)) block_match = 1 | |||||
lines = newstr workstr | |||||
return block_match | |||||
} | |||||
function filter_field(startre, fieldre, matchre, isword, | |||||
preenre, defaultstr) | |||||
{ | |||||
if (length(lines) < 1) return 0 | |||||
field_match = 0 | |||||
newstr = "" | |||||
start = 1 | |||||
while ((workstr = substr(lines, start)) && | |||||
(workstr ~ (startre fieldre))) | |||||
{ | |||||
match(workstr, startre) | |||||
start += end = RSTART + RLENGTH - 1 | |||||
newstr = newstr substr(workstr, 1, end) | |||||
workstr = substr(workstr, end + 1) | |||||
match(workstr, fieldre) | |||||
start += end = RSTART + RLENGTH - 1 | |||||
field = matchstr = substr(workstr, 1, end) | |||||
sub(preenre, "", matchstr) | |||||
if (!matchstr) matchstr = defaultstr | |||||
if (isword) { | |||||
if (match(matchstr, matchre) && | |||||
RSTART == 1 && | |||||
RLENGTH == length(matchstr)) { | |||||
field_match = 1 | |||||
field = ans(7) field ans(27) | |||||
} | |||||
} else { | |||||
replace = ans(7) "&" ans(27) | |||||
if (gsub(matchre, replace, matchstr)) { | |||||
field_match = 1 | |||||
field = matchstr | |||||
} | |||||
} | |||||
newstr = newstr field | |||||
} | |||||
lines = newstr workstr | |||||
return field_match | |||||
} | |||||
function dump() { | |||||
lines = block | |||||
block = "" | |||||
found = 0 | |||||
if (execregex != "") { | |||||
for (n = 1; n <= nexecmatches; n++) | |||||
if (filter_field(execstart[n], name2, | |||||
execregex)) found = 1 | |||||
if (!found) return | |||||
} | |||||
if (pid != "") { | |||||
for (n = 1; n <= npidmatches; n++) | |||||
if (filter_field(pidstart[n], num, pid, | |||||
true, pidpreen[n], | |||||
piddeflt[n])) found = 1 | |||||
if (!found) return | |||||
} | |||||
if (gid != "") { | |||||
for (n = 1; n <= ngidmatches; n++) | |||||
if (filter_field(gidstart[n], num, | |||||
gid, true)) found = 1 | |||||
if (!found) return | |||||
} | |||||
if (uid != "") { | |||||
for (n = 1; n <= nuidmatches; n++) | |||||
if (filter_field(uidstart[n], num, | |||||
uid, true)) found = 1 | |||||
if (!found) return | |||||
} | |||||
if (filter != "" && !filter_block()) return | |||||
if (lines) { | |||||
stdout = 1 | |||||
if (output) { | |||||
stdout = 0 | |||||
if (!console) lines = strip(lines) | |||||
print lines > output | |||||
} else if (cmd) { | |||||
if (!quiet) print lines | |||||
tag = details = lines | |||||
sub(/: .*/, "", tag) | |||||
sub(/.*: /, "", details) | |||||
if (!console) tag = strip(tag) | |||||
runcmd() | |||||
} else print lines | |||||
} | |||||
fflush() | |||||
++matches | |||||
} | |||||
############################################ MAIN | |||||
{ block = (block ? block "\n" : block) $0 } | |||||
!pstree { dump() } | |||||
$0 ~ sprintf("^%6s\\\\-\\+= %s ", "", num) { dump() } | |||||
count && matches >= count { exit } | |||||
############################################ END | |||||
END { | |||||
dump() | |||||
system(sprintf("pkill -t %s dtrace %s", tty, | |||||
quiet ? "2> /dev/null" : "")) | |||||
} | |||||
' >&$console_stdout ) | dtrace_stderr_filter >&2 | |||||
) # status | |||||
exit $status | |||||
} <<EOF | |||||
#!/usr/sbin/dtrace -s | |||||
/* - | |||||
* Copyright (c) 2014-2018 Devin Teske <dteske@FreeBSD.org> | |||||
* All rights reserved. | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS \`\`AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
* | |||||
* $TITLE dtrace(1) script to log process(es) triggering $PROBE $ | |||||
* \$FreeBSD$ | |||||
*/ | |||||
$( echo "$DTRACE_PRAGMA" | awk ' | |||||
!/^[[:space:]]*(#|$)/, sub(/^[[:space:]]*/, "#pragma D ")||1 | |||||
' ) | |||||
int console; | |||||
dtrace:::BEGIN { console = ${CONSOLE:-0} } /* probe ID 1 */ | |||||
/*********************************************************/ | |||||
${PSARGS:+$PSARGS_ACTION} | |||||
${ACTIONS:+ | |||||
/*********************************************************/ | |||||
$ACTIONS | |||||
} | |||||
/*********************************************************/ | |||||
$PROBE${EVENT_TEST:+ /$EVENT_TEST/} /* probe ID $ID */ | |||||
{${TRACE:+ | |||||
printf("<$ID>"); | |||||
} | |||||
/***********************************************/ | |||||
printf("%s%Y%s ", | |||||
console ? "\033[32m" : "", | |||||
walltimestamp, | |||||
console ? "\033[39m" : ""); | |||||
/****************** EVENT_TAG ******************/ | |||||
${EVENT_TAG#[[:space:]]} | |||||
${PROBE_COALESCE:+ | |||||
/**************** PROBE_COALESCE ***************/ | |||||
printf("%s%s:%s:%s:%s ", probename == "entry" ? "-> " : | |||||
probename == "return" ? "<- " : | |||||
probename == "start" ? "-> " : | |||||
probename == "done" ? "<- " : " | ", | |||||
probeprov, probemod, probefunc, probename); | |||||
} | |||||
/**************** EVENT_DETAILS ****************/ | |||||
${EVENT_DETAILS#[[:space:]]} | |||||
/***********************************************/ | |||||
printf("\\n"); | |||||
${PSTREE:+ | |||||
/* | |||||
* Print process, parent, grandparent, and ancestor details | |||||
*/ | |||||
$( pproc_dump -v 3 | |||||
pproc_dump -v 2 | |||||
pproc_dump -v 1 | |||||
pproc_dump -v 0 | |||||
)} | |||||
} | |||||
EOF | |||||
################################################################################ | |||||
# END | |||||
################################################################################ |