diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000000..353d23f7bee3 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,60 @@ +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in BCC, please report it responsibly: + +1. **Do not** open a public GitHub issue for security vulnerabilities. +2. Email the maintainers at the [IOVisor mailing list](https://lists.iovisor.org/g/iovisor-dev) with a description of the issue. +3. Include steps to reproduce, affected versions, and any potential mitigations. +4. Allow reasonable time for a fix before public disclosure. + +## Security Advisories + +### BCC-2026-001: BPF C Code Injection via CLI Arguments + +**Severity:** High +**Affected versions:** All versions prior to this fix +**CVSSv3 estimate:** 7.8 (Local / High) + +**Description:** +43 Python-based BCC tools accepted command-line arguments (PIDs, TIDs, UIDs, signal numbers) as unvalidated strings and interpolated them directly into BPF C source code via `bpf_text.replace()`. A local attacker with the ability to influence tool invocation (e.g., through wrapper scripts, cron jobs, or shared automation) could inject arbitrary C code into kernel-loaded BPF programs. + +**Example:** +``` +# Before fix: this would inject C code into the BPF program +sudo tcptop.py -p '1234; } malicious(); if (0' +``` + +**Fix:** +All vulnerable `argparse` arguments now use `type=int` (or a custom `positive_int_list` validator for comma-separated signal lists), ensuring non-numeric input is rejected at argument parsing time before it reaches string interpolation. + +**Affected tools:** tcptop, tcpconnlat, tcplife, tcpaccept, capable, cpudist, statsnoop, filelife, filegone, compactsnoop, vfsstat, ext4dist, shmsnoop, sofdsnoop, numasched, klockstat, opensnoop, drsnoop, tcpconnect, bindsnoop, nfsslower, xfsslower, zfsslower, ext4slower, btrfsslower, f2fsslower, execsnoop, killsnoop, ttysnoop, and 14 tools in `tools/old/`. + +--- + +### BCC-2026-002: World-Writable Directory Permissions + +**Severity:** Medium +**Affected versions:** All versions prior to this fix +**CVSSv3 estimate:** 5.5 (Local / Medium) + +**Description:** +`src/cc/bpf_module.cc` created the BPF program tag cache directory (`/var/tmp/bcc/`) and its subdirectories with mode `0777` (world-writable). A local attacker could: + +1. Create symlinks in the world-writable directory pointing to sensitive files. +2. When BCC writes cached BPF program source files, it would follow symlinks and overwrite arbitrary files owned by root. + +**Fix:** +- Directory creation now uses mode `0700` (owner-only access). +- All `open()` calls include `O_NOFOLLOW` to refuse to follow symlinks. +- All `write()` return values are now checked for errors. + +--- + +## Secure Usage Guidelines + +1. **Always run BCC tools directly** — avoid passing user-controlled input to BCC tool arguments without validation. +2. **Restrict access** — BCC tools require root or `CAP_BPF`/`CAP_SYS_ADMIN`. Limit who can execute them. +3. **Keep BCC updated** — apply security patches promptly. +4. **Audit wrapper scripts** — if you wrap BCC tools in scripts that accept external input, validate all numeric arguments before passing them through. diff --git a/src/cc/bpf_module.cc b/src/cc/bpf_module.cc index 128aa295eda9..34f0ad2f0288 100644 --- a/src/cc/bpf_module.cc +++ b/src/cc/bpf_module.cc @@ -693,7 +693,7 @@ int BPFModule::annotate_prog_tag(const string &name, int prog_fd, return -1; } - err = mkdir(BCC_PROG_TAG_DIR, 0777); + err = mkdir(BCC_PROG_TAG_DIR, 0700); if (err && errno != EEXIST) { fprintf(stderr, "cannot create " BCC_PROG_TAG_DIR "\n"); return -1; @@ -701,7 +701,7 @@ int BPFModule::annotate_prog_tag(const string &name, int prog_fd, char buf[128]; ::snprintf(buf, sizeof(buf), BCC_PROG_TAG_DIR "/bpf_prog_%llx", tag1); - err = mkdir(buf, 0777); + err = mkdir(buf, 0700); if (err && errno != EEXIST) { fprintf(stderr, "cannot create %s\n", buf); return -1; @@ -709,37 +709,46 @@ int BPFModule::annotate_prog_tag(const string &name, int prog_fd, ::snprintf(buf, sizeof(buf), BCC_PROG_TAG_DIR "/bpf_prog_%llx/%s.c", tag1, name.data()); - FileDesc fd(open(buf, O_CREAT | O_WRONLY | O_TRUNC, 0644)); + FileDesc fd(open(buf, O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW, 0644)); if (fd < 0) { fprintf(stderr, "cannot create %s\n", buf); return -1; } const char *src = function_source(name); - write(fd, src, strlen(src)); + if (write(fd, src, strlen(src)) < 0) { + fprintf(stderr, "cannot write to %s\n", buf); + return -1; + } ::snprintf(buf, sizeof(buf), BCC_PROG_TAG_DIR "/bpf_prog_%llx/%s.rewritten.c", tag1, name.data()); - fd = open(buf, O_CREAT | O_WRONLY | O_TRUNC, 0644); + fd = open(buf, O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW, 0644); if (fd < 0) { fprintf(stderr, "cannot create %s\n", buf); return -1; } src = function_source_rewritten(name); - write(fd, src, strlen(src)); + if (write(fd, src, strlen(src)) < 0) { + fprintf(stderr, "cannot write to %s\n", buf); + return -1; + } if (!src_dbg_fmap_[name].empty()) { ::snprintf(buf, sizeof(buf), BCC_PROG_TAG_DIR "/bpf_prog_%llx/%s.dis.txt", tag1, name.data()); - fd = open(buf, O_CREAT | O_WRONLY | O_TRUNC, 0644); + fd = open(buf, O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW, 0644); if (fd < 0) { fprintf(stderr, "cannot create %s\n", buf); return -1; } const char *src = src_dbg_fmap_[name].c_str(); - write(fd, src, strlen(src)); + if (write(fd, src, strlen(src)) < 0) { + fprintf(stderr, "cannot write to %s\n", buf); + return -1; + } } return 0; diff --git a/src/python/bcc/utils.py b/src/python/bcc/utils.py index 782b27bbca39..f6dce842af08 100644 --- a/src/python/bcc/utils.py +++ b/src/python/bcc/utils.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import argparse import ctypes as ct import sys import traceback @@ -19,6 +20,34 @@ from .libbcc import lib +def positive_int(value): + """argparse type: accept integers >= 0, reject non-numeric strings.""" + ival = int(value) + if ival < 0: + raise argparse.ArgumentTypeError( + "%s is not a non-negative integer" % value) + return ival + +def positive_nonzero_int(value): + """argparse type: accept integers >= 1, reject zero and non-numeric strings.""" + ival = int(value) + if ival <= 0: + raise argparse.ArgumentTypeError( + "%s is not a positive integer" % value) + return ival + +def positive_int_list(value): + """argparse type: accept comma-separated list of non-negative integers.""" + result = [] + for item in value.split(','): + item = item.strip() + ival = int(item) + if ival < 0: + raise argparse.ArgumentTypeError( + "%s is not a non-negative integer" % item) + result.append(ival) + return result + def _read_cpu_range(path): cpus = [] with open(path, 'r') as f: diff --git a/tests/python/test_tool_args_validation.py b/tests/python/test_tool_args_validation.py new file mode 100644 index 000000000000..b1e2a387a17e --- /dev/null +++ b/tests/python/test_tool_args_validation.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026, BCC Contributors +# Licensed under the Apache License, Version 2.0 (the "License") + +""" +Tests that BCC Python tools reject malicious non-integer input for +arguments that get interpolated into BPF C source code. + +These tests do NOT require root — they only exercise argparse validation, +not BPF program loading. +""" + +import os +import subprocess +import unittest + +TOOLS_DIR = os.path.join(os.path.dirname(__file__), '..', '..', 'tools') +OLD_TOOLS_DIR = os.path.join(TOOLS_DIR, 'old') + +# Injection payloads that must be rejected +PAYLOADS = [ + '1234; } malicious(); if (0', + '1 || 1', + '$(whoami)', + 'abc', + '1.5', +] + + +class TestToolArgsValidation(unittest.TestCase): + """Test that tools reject non-integer values for numeric arguments.""" + + def _assert_tool_rejects(self, tool_dir, tool, flag, payload): + """Run a tool with a malicious payload and assert it fails.""" + tool_path = os.path.join(tool_dir, tool) + if not os.path.exists(tool_path): + self.skipTest("Tool not found: %s" % tool_path) + + result = subprocess.run( + ['python3', tool_path, flag, payload], + capture_output=True, text=True, timeout=10 + ) + self.assertNotEqual(result.returncode, 0, + "Tool %s accepted malicious input %r for %s" % (tool, payload, flag)) + self.assertTrue( + 'error' in result.stderr.lower() or 'invalid' in result.stderr.lower(), + "Tool %s stderr did not contain expected error for %s=%r:\n%s" % ( + tool, flag, payload, result.stderr)) + + # --- Simple PID-only tools (tools/) --- + + def test_tcptop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'tcptop.py', '-p', p) + + def test_tcpconnlat_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'tcpconnlat.py', '-p', p) + + def test_tcplife_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'tcplife.py', '-p', p) + + def test_tcpaccept_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'tcpaccept.py', '-p', p) + + def test_capable_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'capable.py', '-p', p) + + def test_cpudist_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'cpudist.py', '-p', p) + + def test_statsnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'statsnoop.py', '-p', p) + + def test_filelife_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'filelife.py', '-p', p) + + def test_filegone_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'filegone.py', '-p', p) + + def test_compactsnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'compactsnoop.py', '-p', p) + + def test_vfsstat_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'vfsstat.py', '-p', p) + + def test_ext4dist_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'ext4dist.py', '-p', p) + + # --- PID + TID tools --- + + def test_shmsnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'shmsnoop.py', '-p', p) + + def test_shmsnoop_tid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'shmsnoop.py', '-t', p) + + def test_sofdsnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'sofdsnoop.py', '-p', p) + + def test_sofdsnoop_tid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'sofdsnoop.py', '-t', p) + + def test_numasched_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'numasched.py', '-p', p) + + def test_numasched_tid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'numasched.py', '-t', p) + + def test_klockstat_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'klockstat.py', '-p', p) + + def test_klockstat_tid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'klockstat.py', '-t', p) + + def test_opensnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'opensnoop.py', '-p', p) + + def test_opensnoop_tid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'opensnoop.py', '-t', p) + + def test_drsnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'drsnoop.py', '-p', p) + + def test_drsnoop_tid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'drsnoop.py', '-t', p) + + # --- PID + UID tools --- + + def test_tcpconnect_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'tcpconnect.py', '-p', p) + + def test_tcpconnect_uid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'tcpconnect.py', '-u', p) + + def test_bindsnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'bindsnoop.py', '-p', p) + + def test_bindsnoop_uid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'bindsnoop.py', '-u', p) + + # --- Filesystem slower tools --- + + def test_nfsslower_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'nfsslower.py', '-p', p) + + def test_xfsslower_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'xfsslower.py', '-p', p) + + def test_zfsslower_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'zfsslower.py', '-p', p) + + def test_ext4slower_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'ext4slower.py', '-p', p) + + def test_btrfsslower_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'btrfsslower.py', '-p', p) + + def test_f2fsslower_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'f2fsslower.py', '-p', p) + + # --- Special handling tools --- + + def test_execsnoop_max_args(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'execsnoop.py', '--max-args', p) + + def test_execsnoop_ppid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'execsnoop.py', '-P', p) + + def test_killsnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'killsnoop.py', '-p', p) + + def test_killsnoop_tpid(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'killsnoop.py', '-T', p) + + def test_killsnoop_signal(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'killsnoop.py', '-s', p) + + def test_ttysnoop_datasize(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'ttysnoop.py', '-s', p) + + def test_ttysnoop_datacount(self): + for p in PAYLOADS: + self._assert_tool_rejects(TOOLS_DIR, 'ttysnoop.py', '-c', p) + + # --- Old tools --- + + def test_old_tcptop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'tcptop.py', '-p', p) + + def test_old_stacksnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'stacksnoop.py', '-p', p) + + def test_old_tcpconnect_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'tcpconnect.py', '-p', p) + + def test_old_statsnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'statsnoop.py', '-p', p) + + def test_old_tcpaccept_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'tcpaccept.py', '-p', p) + + def test_old_filelife_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'filelife.py', '-p', p) + + def test_old_killsnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'killsnoop.py', '-p', p) + + def test_old_opensnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'opensnoop.py', '-p', p) + + def test_old_stackcount_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'stackcount.py', '-p', p) + + def test_old_compactsnoop_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'compactsnoop.py', '-p', p) + + def test_old_filegone_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'filegone.py', '-p', p) + + def test_old_wakeuptime_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'wakeuptime.py', '-p', p) + + def test_old_offcputime_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'offcputime.py', '-p', p) + + def test_old_offwaketime_pid(self): + for p in PAYLOADS: + self._assert_tool_rejects(OLD_TOOLS_DIR, 'offwaketime.py', '-p', p) + + # --- Regression: valid integers should be accepted by argparse --- + # (These will fail at BPF load without root, but argparse should not reject them) + + def test_valid_integer_accepted(self): + """Verify argparse accepts valid integers (tool may fail later at BPF load).""" + tool_path = os.path.join(TOOLS_DIR, 'tcptop.py') + if not os.path.exists(tool_path): + self.skipTest("Tool not found") + result = subprocess.run( + ['python3', tool_path, '-p', '1234'], + capture_output=True, text=True, timeout=10 + ) + # Should NOT fail with argparse error (may fail with BPF error, that's ok) + if result.returncode != 0: + self.assertNotIn('invalid int value', result.stderr, + "Valid integer '1234' was rejected by argparse") + + def test_killsnoop_valid_signal_list(self): + """Verify killsnoop accepts valid comma-separated signal list.""" + tool_path = os.path.join(TOOLS_DIR, 'killsnoop.py') + if not os.path.exists(tool_path): + self.skipTest("Tool not found") + result = subprocess.run( + ['python3', tool_path, '-s', '9,15'], + capture_output=True, text=True, timeout=10 + ) + if result.returncode != 0: + self.assertNotIn('invalid positive_int_list value', result.stderr, + "Valid signal list '9,15' was rejected by argparse") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index 9a3b7b7a5e41..9764b0084454 100755 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -2,7 +2,9 @@ # Copyright (c) Catalysts GmbH # Licensed under the Apache License, Version 2.0 (the "License") +import argparse from bcc.utils import get_online_cpus, detect_language +from bcc.utils import positive_int, positive_nonzero_int, positive_int_list import multiprocessing import unittest import os @@ -19,5 +21,82 @@ def test_detect_language(self): language = detect_language(candidates, os.getpid()) self.assertEqual(language, "python") +class TestPositiveInt(unittest.TestCase): + def test_accepts_zero(self): + self.assertEqual(positive_int("0"), 0) + + def test_accepts_positive(self): + self.assertEqual(positive_int("42"), 42) + + def test_accepts_large(self): + self.assertEqual(positive_int("1000000"), 1000000) + + def test_rejects_negative(self): + with self.assertRaises(argparse.ArgumentTypeError): + positive_int("-1") + + def test_rejects_non_numeric(self): + with self.assertRaises(ValueError): + positive_int("abc") + + def test_rejects_float_string(self): + with self.assertRaises(ValueError): + positive_int("1.5") + + def test_rejects_injection(self): + with self.assertRaises(ValueError): + positive_int("1; } malicious(); if (0") + + def test_rejects_shell_expansion(self): + with self.assertRaises(ValueError): + positive_int("$(whoami)") + + +class TestPositiveNonzeroInt(unittest.TestCase): + def test_accepts_one(self): + self.assertEqual(positive_nonzero_int("1"), 1) + + def test_accepts_large(self): + self.assertEqual(positive_nonzero_int("999"), 999) + + def test_rejects_zero(self): + with self.assertRaises(argparse.ArgumentTypeError): + positive_nonzero_int("0") + + def test_rejects_negative(self): + with self.assertRaises(argparse.ArgumentTypeError): + positive_nonzero_int("-1") + + def test_rejects_non_numeric(self): + with self.assertRaises(ValueError): + positive_nonzero_int("abc") + + +class TestPositiveIntList(unittest.TestCase): + def test_accepts_single(self): + self.assertEqual(positive_int_list("9"), [9]) + + def test_accepts_multiple(self): + self.assertEqual(positive_int_list("1,2,3"), [1, 2, 3]) + + def test_accepts_with_spaces(self): + self.assertEqual(positive_int_list("9, 15"), [9, 15]) + + def test_accepts_zero(self): + self.assertEqual(positive_int_list("0,1"), [0, 1]) + + def test_rejects_non_numeric_in_list(self): + with self.assertRaises(ValueError): + positive_int_list("1,malicious,3") + + def test_rejects_injection_in_list(self): + with self.assertRaises(ValueError): + positive_int_list("1; } evil()") + + def test_rejects_negative_in_list(self): + with self.assertRaises(argparse.ArgumentTypeError): + positive_int_list("1,-1") + + if __name__ == "__main__": unittest.main() diff --git a/tools/bindsnoop.py b/tools/bindsnoop.py index acae0ad055fe..b072c1458a77 100755 --- a/tools/bindsnoop.py +++ b/tools/bindsnoop.py @@ -72,7 +72,7 @@ help="include timestamp on output") parser.add_argument("-w", "--wide", action="store_true", help="wide column output (fits IPv6 addresses)") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-P", "--port", help="comma-separated list of ports to trace.") @@ -80,7 +80,7 @@ help="include errors in the output.") parser.add_argument("-U", "--print-uid", action="store_true", help="include UID on output") -parser.add_argument("-u", "--uid", +parser.add_argument("-u", "--uid", type=int, help="trace this UID only") parser.add_argument("--count", action="store_true", help="count binds per src ip and port") diff --git a/tools/btrfsslower.py b/tools/btrfsslower.py index 7badd4b00d9d..f1bc09a17cc3 100755 --- a/tools/btrfsslower.py +++ b/tools/btrfsslower.py @@ -48,7 +48,7 @@ epilog=examples) parser.add_argument("-j", "--csv", action="store_true", help="just print fields: comma-separated values") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("min_ms", nargs="?", default='10', help="minimum I/O duration to trace, in ms (default 10)") diff --git a/tools/capable.py b/tools/capable.py index db78de39e562..6bf73ff562ac 100755 --- a/tools/capable.py +++ b/tools/capable.py @@ -38,7 +38,7 @@ epilog=examples) parser.add_argument("-v", "--verbose", action="store_true", help="include non-audit checks") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-K", "--kernel-stack", action="store_true", help="output kernel stack trace") diff --git a/tools/compactsnoop.py b/tools/compactsnoop.py index 4c4c981665f1..07468be0ed8a 100755 --- a/tools/compactsnoop.py +++ b/tools/compactsnoop.py @@ -36,7 +36,7 @@ ) parser.add_argument("-T", "--timestamp", action="store_true", help="include timestamp on output") -parser.add_argument("-p", "--pid", help="trace this PID only") +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-d", "--duration", help="total duration of trace in seconds") parser.add_argument("-K", "--kernel-stack", action="store_true", diff --git a/tools/cpudist.py b/tools/cpudist.py index e5e71b6f5790..295e98619b60 100755 --- a/tools/cpudist.py +++ b/tools/cpudist.py @@ -46,7 +46,7 @@ help="print a histogram per process ID") parser.add_argument("-L", "--tids", action="store_true", help="print a histogram per thread ID") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-I", "--include-idle", action="store_true", help="include CPU idle time") diff --git a/tools/drsnoop.py b/tools/drsnoop.py index 55a7bbf97c70..6c3565692b59 100755 --- a/tools/drsnoop.py +++ b/tools/drsnoop.py @@ -44,9 +44,9 @@ help="include timestamp on output") parser.add_argument("-U", "--print-uid", action="store_true", help="print UID column") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") -parser.add_argument("-t", "--tid", +parser.add_argument("-t", "--tid", type=int, help="trace this TID only") parser.add_argument("-u", "--uid", help="trace this UID only") diff --git a/tools/execsnoop.py b/tools/execsnoop.py index b2a3ce29cc28..81b0b1e1577b 100755 --- a/tools/execsnoop.py +++ b/tools/execsnoop.py @@ -96,9 +96,9 @@ def parse_uid(user): help="print CPU column") parser.add_argument("-M", "--print-pcomm", action="store_true", help="print parent command") -parser.add_argument("--max-args", default="20", +parser.add_argument("--max-args", default=20, type=int, help="maximum number of arguments parsed and displayed, defaults to 20") -parser.add_argument("-P", "--ppid", +parser.add_argument("-P", "--ppid", type=int, help="trace this parent PID only") parser.add_argument("--ebpf", action="store_true", help=argparse.SUPPRESS) @@ -241,7 +241,7 @@ def check_cpu_filed(): } """ -bpf_text = bpf_text.replace("MAXARG", args.max_args) +bpf_text = bpf_text.replace("MAXARG", str(args.max_args)) if args.uid: bpf_text = bpf_text.replace('UID_FILTER', diff --git a/tools/ext4dist.py b/tools/ext4dist.py index 22f07e4195ea..67c16691776e 100755 --- a/tools/ext4dist.py +++ b/tools/ext4dist.py @@ -34,7 +34,7 @@ help="don't include timestamp on interval output") parser.add_argument("-m", "--milliseconds", action="store_true", help="output in milliseconds") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("interval", nargs="?", help="output interval, in seconds") diff --git a/tools/ext4slower.py b/tools/ext4slower.py index 0e5170e35bc5..ac612714a2af 100755 --- a/tools/ext4slower.py +++ b/tools/ext4slower.py @@ -47,7 +47,7 @@ epilog=examples) parser.add_argument("-j", "--csv", action="store_true", help="just print fields: comma-separated values") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("min_ms", nargs="?", default='10', help="minimum I/O duration to trace, in ms (default 10)") diff --git a/tools/f2fsslower.py b/tools/f2fsslower.py index dc4018890ac9..b3d57987ba24 100755 --- a/tools/f2fsslower.py +++ b/tools/f2fsslower.py @@ -48,7 +48,7 @@ epilog=examples) parser.add_argument("-s", "--csv", action="store_true", help="just print fields: comma-separated values") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("min_ms", nargs="?", default='10', help="minimum I/O duration to trace, in ms (default 10)") diff --git a/tools/filegone.py b/tools/filegone.py index 9b8c01684a19..31f862449db3 100755 --- a/tools/filegone.py +++ b/tools/filegone.py @@ -26,7 +26,7 @@ description="Trace why file gone (deleted or renamed)", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("--ebpf", action="store_true", help=argparse.SUPPRESS) diff --git a/tools/filelife.py b/tools/filelife.py index 1f00dbdcd7e9..36dca3d1049e 100755 --- a/tools/filelife.py +++ b/tools/filelife.py @@ -33,7 +33,7 @@ description="Trace lifecycle of file", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("--ebpf", action="store_true", help=argparse.SUPPRESS) diff --git a/tools/killsnoop.py b/tools/killsnoop.py index 96b9bdef5db1..e718045ccb5c 100755 --- a/tools/killsnoop.py +++ b/tools/killsnoop.py @@ -14,7 +14,7 @@ from __future__ import print_function from bcc import BPF -from bcc.utils import ArgString, printb +from bcc.utils import ArgString, printb, positive_int_list import argparse from time import strftime @@ -33,11 +33,11 @@ epilog=examples) parser.add_argument("-x", "--failed", action="store_true", help="only show failed kill syscalls") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only which is the sender of signal") -parser.add_argument("-T", "--tpid", +parser.add_argument("-T", "--tpid", type=int, help="trace this target PID only which is the receiver of signal") -parser.add_argument("-s", "--signal", +parser.add_argument("-s", "--signal", type=positive_int_list, help="trace a signal or a signal list") parser.add_argument("--ebpf", action="store_true", help=argparse.SUPPRESS) @@ -127,8 +127,7 @@ bpf_text = bpf_text.replace('PID_FILTER', '') if args.signal: - signals = args.signal.split(',') - signal_filter = ' && '.join(['sig != %s' % signal for signal in signals]) + signal_filter = ' && '.join(['sig != %d' % signal for signal in args.signal]) bpf_text = bpf_text.replace('SIGNAL_FILTER', 'if (%s) { return 0; }' % signal_filter) else: diff --git a/tools/klockstat.py b/tools/klockstat.py index 53b6a10075e8..7f2218741f79 100755 --- a/tools/klockstat.py +++ b/tools/klockstat.py @@ -69,9 +69,9 @@ def stack_id_err(stack_id): help="print locks taken by given caller") parser.add_argument("-S", "--sort", help="sort data on , fields: acq_[max|total|count] hld_[max|total|count]") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") -parser.add_argument("-t", "--tid", +parser.add_argument("-t", "--tid", type=int, help="trace this TID only") parser.add_argument("--stack-storage-size", default=16384, type=positive_nonzero_int, diff --git a/tools/nfsslower.py b/tools/nfsslower.py index 0e20d756cfcd..44c4955fe8ce 100755 --- a/tools/nfsslower.py +++ b/tools/nfsslower.py @@ -53,7 +53,7 @@ parser.add_argument("-j", "--csv", action="store_true", help="just print fields: comma-separated values") -parser.add_argument("-p", "--pid", help="Trace this pid only") +parser.add_argument("-p", "--pid", type=int, help="Trace this pid only") parser.add_argument("min_ms", nargs="?", default='10', help="Minimum IO duration to trace in ms (default=10ms)") parser.add_argument("--ebpf", action="store_true", diff --git a/tools/numasched.py b/tools/numasched.py index 74fbd0771ae5..59e3c07d0719 100755 --- a/tools/numasched.py +++ b/tools/numasched.py @@ -32,9 +32,9 @@ description="Trace task NUMA switch", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") -parser.add_argument("-t", "--tid", +parser.add_argument("-t", "--tid", type=int, help="trace this TID only") parser.add_argument("-c", "--comm", help="trace this COMM only") diff --git a/tools/old/compactsnoop.py b/tools/old/compactsnoop.py index c544041763f4..b8c9939aef86 100755 --- a/tools/old/compactsnoop.py +++ b/tools/old/compactsnoop.py @@ -35,7 +35,7 @@ ) parser.add_argument("-T", "--timestamp", action="store_true", help="include timestamp on output") -parser.add_argument("-p", "--pid", help="trace this PID only") +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-d", "--duration", help="total duration of trace in seconds") parser.add_argument("-K", "--kernel-stack", action="store_true", diff --git a/tools/old/filegone.py b/tools/old/filegone.py index 37b22839c82c..d021279b42ba 100755 --- a/tools/old/filegone.py +++ b/tools/old/filegone.py @@ -24,7 +24,7 @@ description="Trace why file gone (deleted or renamed)", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("--ebpf", action="store_true", help=argparse.SUPPRESS) diff --git a/tools/old/filelife.py b/tools/old/filelife.py index 075be087d37d..b271dc517f5d 100755 --- a/tools/old/filelife.py +++ b/tools/old/filelife.py @@ -30,7 +30,7 @@ description="Trace stat() syscalls", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") args = parser.parse_args() debug = 0 diff --git a/tools/old/killsnoop.py b/tools/old/killsnoop.py index ddf9d5af0fc7..224dda3eca25 100755 --- a/tools/old/killsnoop.py +++ b/tools/old/killsnoop.py @@ -30,7 +30,7 @@ help="include timestamp on output") parser.add_argument("-x", "--failed", action="store_true", help="only show failed opens") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") args = parser.parse_args() debug = 0 diff --git a/tools/old/offcputime.py b/tools/old/offcputime.py index 27a2c7d17f63..bfb53817e97f 100755 --- a/tools/old/offcputime.py +++ b/tools/old/offcputime.py @@ -36,7 +36,7 @@ epilog=examples) parser.add_argument("-u", "--useronly", action="store_true", help="user threads only (no kernel threads)") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-v", "--verbose", action="store_true", help="show raw addresses") diff --git a/tools/old/offwaketime.py b/tools/old/offwaketime.py index 68e87403bdea..f3c6343d3a30 100755 --- a/tools/old/offwaketime.py +++ b/tools/old/offwaketime.py @@ -39,7 +39,7 @@ epilog=examples) parser.add_argument("-u", "--useronly", action="store_true", help="user threads only (no kernel threads)") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-v", "--verbose", action="store_true", help="show raw addresses") diff --git a/tools/old/opensnoop.py b/tools/old/opensnoop.py index 5df3b417894c..3e5db4ed43d4 100755 --- a/tools/old/opensnoop.py +++ b/tools/old/opensnoop.py @@ -30,7 +30,7 @@ help="include timestamp on output") parser.add_argument("-x", "--failed", action="store_true", help="only show failed opens") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") args = parser.parse_args() debug = 0 diff --git a/tools/old/stackcount.py b/tools/old/stackcount.py index 7efb5e8d7f2d..59326c617a20 100755 --- a/tools/old/stackcount.py +++ b/tools/old/stackcount.py @@ -40,7 +40,7 @@ description="Count kernel function calls and their stack traces", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-i", "--interval", default=99999999, help="summary interval, seconds") diff --git a/tools/old/stacksnoop.py b/tools/old/stacksnoop.py index 9fcc12b015c2..4152fbdebaf2 100755 --- a/tools/old/stacksnoop.py +++ b/tools/old/stacksnoop.py @@ -32,7 +32,7 @@ description="Trace and print kernel stack traces for a kernel function", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-s", "--offset", action="store_true", help="show address offsets") diff --git a/tools/old/statsnoop.py b/tools/old/statsnoop.py index ad54ac78c20b..ec1d6e3c0f25 100755 --- a/tools/old/statsnoop.py +++ b/tools/old/statsnoop.py @@ -30,7 +30,7 @@ help="include timestamp on output") parser.add_argument("-x", "--failed", action="store_true", help="only show failed stats") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") args = parser.parse_args() debug = 0 diff --git a/tools/old/tcpaccept.py b/tools/old/tcpaccept.py index 8125eaa3579c..3d7dc6e80191 100755 --- a/tools/old/tcpaccept.py +++ b/tools/old/tcpaccept.py @@ -34,7 +34,7 @@ epilog=examples) parser.add_argument("-t", "--timestamp", action="store_true", help="include timestamp on output") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") args = parser.parse_args() debug = 0 diff --git a/tools/old/tcpconnect.py b/tools/old/tcpconnect.py index 579a85f917e5..5413c1645c6d 100755 --- a/tools/old/tcpconnect.py +++ b/tools/old/tcpconnect.py @@ -29,7 +29,7 @@ epilog=examples) parser.add_argument("-t", "--timestamp", action="store_true", help="include timestamp on output") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") args = parser.parse_args() debug = 0 diff --git a/tools/old/tcptop.py b/tools/old/tcptop.py index 072d6dc72d03..a97e1d461a1d 100755 --- a/tools/old/tcptop.py +++ b/tools/old/tcptop.py @@ -59,7 +59,7 @@ def range_check(string): help="don't clear the screen") parser.add_argument("-S", "--nosummary", action="store_true", help="skip system summary line") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("interval", nargs="?", default=1, type=range_check, help="output interval, in seconds (default 1)") diff --git a/tools/old/wakeuptime.py b/tools/old/wakeuptime.py index 882af691eff5..4c0f0cd3dfda 100755 --- a/tools/old/wakeuptime.py +++ b/tools/old/wakeuptime.py @@ -36,7 +36,7 @@ epilog=examples) parser.add_argument("-u", "--useronly", action="store_true", help="user threads only (no kernel threads)") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-v", "--verbose", action="store_true", help="show raw addresses") diff --git a/tools/opensnoop.py b/tools/opensnoop.py index 57c5d5260f65..2c2f122f65b2 100755 --- a/tools/opensnoop.py +++ b/tools/opensnoop.py @@ -58,9 +58,9 @@ help="print UID column") parser.add_argument("-x", "--failed", action="store_true", help="only show failed opens") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") -parser.add_argument("-t", "--tid", +parser.add_argument("-t", "--tid", type=int, help="trace this TID only") parser.add_argument("--cgroupmap", help="trace cgroups in this BPF map only") diff --git a/tools/shmsnoop.py b/tools/shmsnoop.py index 85ee559c5de2..ba20570bd799 100755 --- a/tools/shmsnoop.py +++ b/tools/shmsnoop.py @@ -31,9 +31,9 @@ epilog=examples) parser.add_argument("-T", "--timestamp", action="store_true", help="include timestamp on output") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") -parser.add_argument("-t", "--tid", +parser.add_argument("-t", "--tid", type=int, help="trace this TID only") parser.add_argument("-d", "--duration", help="total duration of trace in seconds") diff --git a/tools/sofdsnoop.py b/tools/sofdsnoop.py index 5c593d432e24..d46e3deab18f 100755 --- a/tools/sofdsnoop.py +++ b/tools/sofdsnoop.py @@ -34,9 +34,9 @@ epilog=examples) parser.add_argument("-T", "--timestamp", action="store_true", help="include timestamp on output") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") -parser.add_argument("-t", "--tid", +parser.add_argument("-t", "--tid", type=int, help="trace this TID only") parser.add_argument("-n", "--name", type=ArgString, diff --git a/tools/statsnoop.py b/tools/statsnoop.py index 560374e36338..dca5f2562118 100755 --- a/tools/statsnoop.py +++ b/tools/statsnoop.py @@ -35,7 +35,7 @@ help="include syscall name on output") parser.add_argument("-x", "--failed", action="store_true", help="only show failed stats") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("--ebpf", action="store_true", help=argparse.SUPPRESS) diff --git a/tools/tcpaccept.py b/tools/tcpaccept.py index c4af2a5eea3e..41d0ae08a609 100755 --- a/tools/tcpaccept.py +++ b/tools/tcpaccept.py @@ -43,7 +43,7 @@ help="include time column on output (HH:MM:SS)") parser.add_argument("-t", "--timestamp", action="store_true", help="include timestamp on output") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-P", "--port", help="comma-separated list of local ports to trace") diff --git a/tools/tcpconnect.py b/tools/tcpconnect.py index 0ea3629febc6..faf2d818a9b4 100755 --- a/tools/tcpconnect.py +++ b/tools/tcpconnect.py @@ -54,7 +54,7 @@ epilog=examples) parser.add_argument("-t", "--timestamp", action="store_true", help="include timestamp on output") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-P", "--port", help="comma-separated list of destination ports to trace.") @@ -67,7 +67,7 @@ help="include LPORT on output") parser.add_argument("-U", "--print-uid", action="store_true", help="include UID on output") -parser.add_argument("-u", "--uid", +parser.add_argument("-u", "--uid", type=int, help="trace this UID only") parser.add_argument("-c", "--count", action="store_true", help="count connects per src ip and dest ip/port") diff --git a/tools/tcpconnlat.py b/tools/tcpconnlat.py index 85ffaff84684..c89012ae6f58 100755 --- a/tools/tcpconnlat.py +++ b/tools/tcpconnlat.py @@ -49,7 +49,7 @@ def positive_float(val): epilog=examples) parser.add_argument("-t", "--timestamp", action="store_true", help="include timestamp on output") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-L", "--lport", action="store_true", help="include LPORT on output") diff --git a/tools/tcplife.py b/tools/tcplife.py index 125f0feae87f..5569093a4efb 100755 --- a/tools/tcplife.py +++ b/tools/tcplife.py @@ -54,7 +54,7 @@ help="wide column output (fits IPv6 addresses)") parser.add_argument("-s", "--csv", action="store_true", help="comma separated values output") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("-L", "--localport", help="comma-separated list of local ports to trace.") diff --git a/tools/tcptop.py b/tools/tcptop.py index e83bb80bd790..6613c11d0cbf 100755 --- a/tools/tcptop.py +++ b/tools/tcptop.py @@ -60,7 +60,7 @@ def range_check(string): help="don't clear the screen") parser.add_argument("-S", "--nosummary", action="store_true", help="skip system summary line") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("interval", nargs="?", default=1, type=range_check, help="output interval, in seconds (default 1)") diff --git a/tools/ttysnoop.py b/tools/ttysnoop.py index ed537e8d6711..879704399387 100755 --- a/tools/ttysnoop.py +++ b/tools/ttysnoop.py @@ -46,9 +46,9 @@ def usage(): help="don't clear the screen") parser.add_argument("device", default="-1", help="path to a tty device (eg, /dev/tty0) or pts number") -parser.add_argument("-s", "--datasize", default="256", +parser.add_argument("-s", "--datasize", default=256, type=int, help="size of the transmitting buffer (default 256)") -parser.add_argument("-c", "--datacount", default="16", +parser.add_argument("-c", "--datacount", default=16, type=int, help="number of times we check for 'data-size' data (default 16)") parser.add_argument("--ebpf", action="store_true", help=argparse.SUPPRESS) @@ -229,8 +229,8 @@ def usage(): if args.ebpf: exit() -bpf_text = bpf_text.replace('USER_DATASIZE', '%s' % args.datasize) -bpf_text = bpf_text.replace('USER_DATACOUNT', '%s' % args.datacount) +bpf_text = bpf_text.replace('USER_DATASIZE', str(args.datasize)) +bpf_text = bpf_text.replace('USER_DATACOUNT', str(args.datacount)) # initialize BPF b = BPF(text=bpf_text) diff --git a/tools/vfsstat.py b/tools/vfsstat.py index 168551b137f6..9290f947bd00 100755 --- a/tools/vfsstat.py +++ b/tools/vfsstat.py @@ -32,7 +32,7 @@ description="Count some VFS calls.", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("interval", nargs="?", default=1, help="output interval, in seconds") diff --git a/tools/xfsslower.py b/tools/xfsslower.py index bc622382e8ea..6c4f2d8fce68 100755 --- a/tools/xfsslower.py +++ b/tools/xfsslower.py @@ -43,7 +43,7 @@ epilog=examples) parser.add_argument("-j", "--csv", action="store_true", help="just print fields: comma-separated values") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("min_ms", nargs="?", default='10', help="minimum I/O duration to trace, in ms (default 10)") diff --git a/tools/zfsslower.py b/tools/zfsslower.py index a8645e72700b..8a26163c1c68 100755 --- a/tools/zfsslower.py +++ b/tools/zfsslower.py @@ -46,7 +46,7 @@ epilog=examples) parser.add_argument("-j", "--csv", action="store_true", help="just print fields: comma-separated values") -parser.add_argument("-p", "--pid", +parser.add_argument("-p", "--pid", type=int, help="trace this PID only") parser.add_argument("min_ms", nargs="?", default='10', help="minimum I/O duration to trace, in ms (default 10)")