opensnoop: print flags, enable filtering (#2096)

* opensnoop: print flags, enable filtering

* Add docs, extended_fields option; filter flags in-kernel

* Homogenize documentation

* Add FLAGS to the FIELDS man page section
diff --git a/tools/opensnoop.py b/tools/opensnoop.py
index 818c602..2a00737 100755
--- a/tools/opensnoop.py
+++ b/tools/opensnoop.py
@@ -12,12 +12,14 @@
 # 17-Sep-2015   Brendan Gregg   Created this.
 # 29-Apr-2016   Allan McAleavy  Updated for BPF_PERF_OUTPUT.
 # 08-Oct-2016   Dina Goldshtein Support filtering by PID and TID.
+# 28-Dec-2018   Tim Douglas     Print flags argument, enable filtering
 
 from __future__ import print_function
 from bcc import ArgString, BPF
 import argparse
 import ctypes as ct
 from datetime import datetime, timedelta
+import os
 
 # arguments
 examples = """examples:
@@ -28,6 +30,8 @@
     ./opensnoop -t 123    # only trace TID 123
     ./opensnoop -d 10     # trace for 10 seconds only
     ./opensnoop -n main   # only print process names containing "main"
+    ./opensnoop -e        # show extended fields
+    ./opensnoop -f O_WRONLY -f O_RDWR  # only print calls for writing
 """
 parser = argparse.ArgumentParser(
     description="Trace open() syscalls",
@@ -48,10 +52,22 @@
     help="only print process names containing this name")
 parser.add_argument("--ebpf", action="store_true",
     help=argparse.SUPPRESS)
+parser.add_argument("-e", "--extended_fields", action="store_true",
+    help="show extended fields")
+parser.add_argument("-f", "--flag_filter", action="append",
+    help="filter on flags argument (e.g., O_WRONLY)")
 args = parser.parse_args()
 debug = 0
 if args.duration:
     args.duration = timedelta(seconds=int(args.duration))
+flag_filter_mask = 0
+for flag in args.flag_filter or []:
+    if not flag.startswith('O_'):
+        exit("Bad flag: %s" % flag)
+    try:
+        flag_filter_mask |= getattr(os, flag)
+    except AttributeError:
+        exit("Bad flag: %s" % flag)
 
 # define BPF program
 bpf_text = """
@@ -63,6 +79,7 @@
     u64 id;
     char comm[TASK_COMM_LEN];
     const char *fname;
+    int flags; // EXTENDED_STRUCT_MEMBER
 };
 
 struct data_t {
@@ -71,22 +88,25 @@
     int ret;
     char comm[TASK_COMM_LEN];
     char fname[NAME_MAX];
+    int flags; // EXTENDED_STRUCT_MEMBER
 };
 
 BPF_HASH(infotmp, u64, struct val_t);
 BPF_PERF_OUTPUT(events);
 
-int trace_entry(struct pt_regs *ctx, int dfd, const char __user *filename)
+int trace_entry(struct pt_regs *ctx, int dfd, const char __user *filename, int flags)
 {
     struct val_t val = {};
     u64 id = bpf_get_current_pid_tgid();
     u32 pid = id >> 32; // PID is higher part
     u32 tid = id;       // Cast and get the lower part
 
-    FILTER
+    PID_TID_FILTER
+    FLAGS_FILTER
     if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) == 0) {
         val.id = id;
         val.fname = filename;
+        val.flags = flags; // EXTENDED_STRUCT_MEMBER
         infotmp.update(&id, &val);
     }
 
@@ -110,6 +130,7 @@
     bpf_probe_read(&data.fname, sizeof(data.fname), (void *)valp->fname);
     data.id = valp->id;
     data.ts = tsp / 1000;
+    data.flags = valp->flags; // EXTENDED_STRUCT_MEMBER
     data.ret = PT_REGS_RC(ctx);
 
     events.perf_submit(ctx, &data, sizeof(data));
@@ -119,13 +140,21 @@
 }
 """
 if args.tid:  # TID trumps PID
-    bpf_text = bpf_text.replace('FILTER',
+    bpf_text = bpf_text.replace('PID_TID_FILTER',
         'if (tid != %s) { return 0; }' % args.tid)
 elif args.pid:
-    bpf_text = bpf_text.replace('FILTER',
+    bpf_text = bpf_text.replace('PID_TID_FILTER',
         'if (pid != %s) { return 0; }' % args.pid)
 else:
-    bpf_text = bpf_text.replace('FILTER', '')
+    bpf_text = bpf_text.replace('PID_TID_FILTER', '')
+if args.flag_filter:
+    bpf_text = bpf_text.replace('FLAGS_FILTER',
+        'if (!(flags & %d)) { return 0; }' % flag_filter_mask)
+else:
+    bpf_text = bpf_text.replace('FLAGS_FILTER', '')
+if not (args.extended_fields or args.flag_filter):
+    bpf_text = '\n'.join(x for x in bpf_text.split('\n')
+        if 'EXTENDED_STRUCT_MEMBER' not in x)
 if debug or args.ebpf:
     print(bpf_text)
     if args.ebpf:
@@ -145,7 +174,8 @@
         ("ts", ct.c_ulonglong),
         ("ret", ct.c_int),
         ("comm", ct.c_char * TASK_COMM_LEN),
-        ("fname", ct.c_char * NAME_MAX)
+        ("fname", ct.c_char * NAME_MAX),
+        ("flags", ct.c_int),
     ]
 
 initial_ts = 0
@@ -153,8 +183,11 @@
 # header
 if args.timestamp:
     print("%-14s" % ("TIME(s)"), end="")
-print("%-6s %-16s %4s %3s %s" %
-      ("TID" if args.tid else "PID", "COMM", "FD", "ERR", "PATH"))
+print("%-6s %-16s %4s %3s " %
+      ("TID" if args.tid else "PID", "COMM", "FD", "ERR"), end="")
+if args.extended_fields:
+    print("%-9s" % ("FLAGS"), end="")
+print("PATH")
 
 # process event
 def print_event(cpu, data, size):
@@ -182,10 +215,14 @@
         delta = event.ts - initial_ts
         print("%-14.9f" % (float(delta) / 1000000), end="")
 
-    print("%-6d %-16s %4d %3d %s" %
+    print("%-6d %-16s %4d %3d " %
           (event.id & 0xffffffff if args.tid else event.id >> 32,
-           event.comm.decode('utf-8', 'replace'), fd_s, err,
-           event.fname.decode('utf-8', 'replace')))
+           event.comm.decode('utf-8', 'replace'), fd_s, err), end="")
+
+    if args.extended_fields:
+        print("%08o " % event.flags, end="")
+
+    print(event.fname.decode('utf-8', 'replace'))
 
 # loop with callback to print_event
 b["events"].open_perf_buffer(print_event, page_cnt=64)