Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 6 Mar 2018 23:44:19 +0000 (UTC)
From:      Devin Teske <dteske@FreeBSD.org>
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
Message-ID:  <201803062344.w26NiJv0003907@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
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 <src.opts.mk>
 
 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 <src.opts.mk>
+
+SUBDIR= libexec
+
+.if ${MK_EXAMPLES} != "no"
+SUBDIR+= examples
+.endif
+
+SCRIPTS= dwatch
+
+MAN= dwatch.1
+
+.include <bsd.prog.mk>

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 \`<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$/, "")
+				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<<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;

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201803062344.w26NiJv0003907>