#!/usr/bin/env ruby

require 'optparse'
require 'etc'

# TODO: -p / cmd... (how?)

BPF = <<'EOF'
BEGIN {
  printf("SEP2\n");
}
tracepoint:syscalls:sys_enter_execve {
  printf("%ld +%d %d", elapsed, pid, uid);
  join(args->argv, "SEP1");
  printf("SEP2\n");
}
tracepoint:syscalls:sys_enter_exit_group {
  printf("%ld -%d %d\nSEP2\n", elapsed, pid, args->error_code);
}
EOF

SEP1 = "\xff\xff\xff".b
SEP2 = "\xff\xfe\xff".b

Procc = Struct.new(:pid, :uid, :name, :args, :start_time, :ppid, :depth)

PIDS = {}

def pid_depth(pid)
  procc = PIDS[pid]
  if procc && procc.depth
    procc.depth
  else
    procc = (PIDS[pid] ||= Procc.new(pid))
    stat = File.read("/proc/#{pid}/stat").b
    if stat =~ /.*\) . (\d+)/
      ppid = $1.to_i
      if ppid == 0
        return 0
      else
        procc.ppid = ppid
        procc.depth = pid_depth(ppid) + 1
      end
    end
  end
rescue Errno::ENOENT, Errno::ESRCH => e
  puts "failed to get pid #{pid} info: #{e}"
  0
end

def fmtcmd(c)
  if c.empty? || c =~ /[\001-\040`^#*\[\]|\\?${}()'\"<>&;\177]/
    "'" + c.gsub("'", "'\\''").gsub("\n", "'$'\\n''") + "'"
  else
    c
  end
end

def fmtcmds(cmds)
  cmds.map { |cmd| fmtcmd(cmd) }.join(' ')
end

params = ARGV.getopts("deflqtu")
dflag = params['d']
eflag = params['e']
fflag = params['f']
lflag = params['l']
qflag = params['q']
tflag = params['t']
uflag = params['u']

IO.popen(["bpftrace", "-e", BPF.gsub("SEP1", SEP1).gsub("SEP2", SEP2)],
         "r:BINARY") { |output|
  while line = output.gets("\n#{SEP2}\n")
    line.chomp!("\n#{SEP2}\n")
    case line
    when /\A(\d+) \+(\d+) (\d+)/
      elapsed = $1.to_i
      pid = $2.to_i
      uid = $3.to_i
      comm = $'.split(SEP1)
      
      next  unless comm.first

      PIDS[pid] = Procc.new(pid, uid, comm.first, comm[1..-1], elapsed)

      print "  " * pid_depth(pid)  unless fflag
      print pid
      print "+"  if tflag
      print " "
      print "<#{Etc.getpwuid(uid).name}> "  if uflag
      if dflag
        print (File.readlink("/proc/#{pid}/cwd") rescue "-")
        print " % "
      end
      comm[0] = (File.readlink("/proc/#{pid}/exe") rescue comm[0])  if lflag
      if qflag
        print fmtcmd(comm[0])
      else
        print fmtcmds(comm)
      end
      if eflag
        print "   "
        print (File.read("/proc/#{pid}/environ") rescue "-").
                b.split("\x00").map { |e|
          if e =~ /\A[a-zA-Z0-9_]+=/
            $& + fmtcmd($')
          else
            fmtcmd(e)
          end
        }.join(" ")
      end
      puts
    when /\A(\d+) -(\d+) (\d+)/
      elapsed = $1.to_i
      pid = $2.to_i
      status = $3.to_i

      if procc = PIDS[pid]
        PIDS.delete(pid)
        next  unless procc.name
        if tflag
          dur = (elapsed - procc.start_time) / 1e9
          print "  " * pid_depth(pid)  unless fflag
          puts "#{pid}- #{procc.name} exited status=#{status} time=#{"%.3f" % dur}s"
        end
      end
    when /^Attaching/
      # ignore
    else
      warn "can't parse #{line}"
    end
  end
}
