From owner-svn-src-head@freebsd.org Tue Mar 6 23:44:20 2018 Return-Path: Delivered-To: svn-src-head@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id 7C38AF3A93B; Tue, 6 Mar 2018 23:44:20 +0000 (UTC) (envelope-from dteske@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 1537072092; Tue, 6 Mar 2018 23:44:20 +0000 (UTC) (envelope-from dteske@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 0A17619B6A; Tue, 6 Mar 2018 23:44:20 +0000 (UTC) (envelope-from dteske@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id w26NiKCl003912; Tue, 6 Mar 2018 23:44:20 GMT (envelope-from dteske@FreeBSD.org) Received: (from dteske@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id w26NiJv0003907; Tue, 6 Mar 2018 23:44:19 GMT (envelope-from dteske@FreeBSD.org) Message-Id: <201803062344.w26NiJv0003907@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: dteske set sender to dteske@FreeBSD.org using -f From: Devin Teske Date: Tue, 6 Mar 2018 23:44:19 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r330559 - in head: . cddl/usr.sbin cddl/usr.sbin/dwatch cddl/usr.sbin/dwatch/examples cddl/usr.sbin/dwatch/libexec etc/mtree share/dtrace X-SVN-Group: head X-SVN-Commit-Author: dteske X-SVN-Commit-Paths: in head: . cddl/usr.sbin cddl/usr.sbin/dwatch cddl/usr.sbin/dwatch/examples cddl/usr.sbin/dwatch/libexec etc/mtree share/dtrace X-SVN-Commit-Revision: 330559 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.25 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 06 Mar 2018 23:44:20 -0000 Author: dteske Date: Tue Mar 6 23:44:19 2018 New Revision: 330559 URL: https://svnweb.freebsd.org/changeset/base/330559 Log: Introduce dwatch(1) as a tool for making DTrace more useful Reviewed by: markj, gnn, bdrewery (earlier version) Relnotes: yes Sponsored by: Smule, Inc. Differential Revision: https://reviews.freebsd.org/D10006 Added: head/cddl/usr.sbin/dwatch/ head/cddl/usr.sbin/dwatch/Makefile (contents, props changed) head/cddl/usr.sbin/dwatch/dwatch (contents, props changed) head/cddl/usr.sbin/dwatch/dwatch.1 (contents, props changed) head/cddl/usr.sbin/dwatch/examples/ head/cddl/usr.sbin/dwatch/examples/Makefile (contents, props changed) head/cddl/usr.sbin/dwatch/examples/profile_template (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/ head/cddl/usr.sbin/dwatch/libexec/Makefile (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/chmod (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/errno (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/io (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/ip (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/kill (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/nanosleep (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/open (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/proc (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/rw (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/sched (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/tcp (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/udp (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/vop_create (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/vop_readdir (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/vop_rename (contents, props changed) head/cddl/usr.sbin/dwatch/libexec/vop_symlink (contents, props changed) Deleted: head/share/dtrace/watch_execve head/share/dtrace/watch_kill head/share/dtrace/watch_vop_remove Modified: head/ObsoleteFiles.inc head/cddl/usr.sbin/Makefile head/etc/mtree/BSD.usr.dist head/share/dtrace/Makefile Modified: head/ObsoleteFiles.inc ============================================================================== --- head/ObsoleteFiles.inc Tue Mar 6 23:39:43 2018 (r330558) +++ head/ObsoleteFiles.inc Tue Mar 6 23:44:19 2018 (r330559) @@ -38,6 +38,10 @@ # xargs -n1 | sort | uniq -d; # done +# 20180306: remove DTrace scripts made obsolete by dwatch(1) +OLD_FILES+=usr/share/dtrace/watch_execve +OLD_FILES+=usr/share/dtrace/watch_kill +OLD_FILES+=usr/share/dtrace/watch_vop_remove # 20180212: move devmatch OLD_FILES+=usr/sbin/devmatch # 20180211: remove usb.conf Modified: head/cddl/usr.sbin/Makefile ============================================================================== --- head/cddl/usr.sbin/Makefile Tue Mar 6 23:39:43 2018 (r330558) +++ head/cddl/usr.sbin/Makefile Tue Mar 6 23:44:19 2018 (r330559) @@ -3,6 +3,7 @@ .include SUBDIR= ${_dtrace} \ + ${_dwatch} \ ${_lockstat} \ ${_plockstat} \ ${_zdb} \ @@ -23,6 +24,7 @@ _zfsd= zfsd .if ${MACHINE_ARCH} == "amd64" || ${MACHINE_ARCH} == "i386" _dtrace= dtrace +_dwatch= dwatch _lockstat= lockstat _plockstat= plockstat .endif @@ -30,15 +32,18 @@ _plockstat= plockstat .if ${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "arm" || \ ${MACHINE_CPUARCH} == "riscv" _dtrace= dtrace +_dwatch= dwatch _lockstat= lockstat .endif .if ${MACHINE_CPUARCH} == "mips" _dtrace= dtrace +_dwatch= dwatch .endif .if ${MACHINE_CPUARCH} == "powerpc" _dtrace= dtrace +_dwatch= dwatch _lockstat= lockstat .endif Added: head/cddl/usr.sbin/dwatch/Makefile ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/cddl/usr.sbin/dwatch/Makefile Tue Mar 6 23:44:19 2018 (r330559) @@ -0,0 +1,15 @@ +# $FreeBSD$ + +.include + +SUBDIR= libexec + +.if ${MK_EXAMPLES} != "no" +SUBDIR+= examples +.endif + +SCRIPTS= dwatch + +MAN= dwatch.1 + +.include Added: head/cddl/usr.sbin/dwatch/dwatch ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/cddl/usr.sbin/dwatch/dwatch Tue Mar 6 23:44:19 2018 (r330559) @@ -0,0 +1,1291 @@ +#!/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 \`' 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 : No such file or directory/ { next } + /failed to write to : 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$/, "") + print + } + } + ' <<-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$/, "") + print + } + ' <<-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<"); +} + /* + * 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; *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***