Skip to content

Instantly share code, notes, and snippets.

@bored-engineer
Created January 17, 2020 23:42
Show Gist options
  • Select an option

  • Save bored-engineer/ff428f9e41a8ff4743435e8ba30bc7fb to your computer and use it in GitHub Desktop.

Select an option

Save bored-engineer/ff428f9e41a8ff4743435e8ba30bc7fb to your computer and use it in GitHub Desktop.

Revisions

  1. bored-engineer created this gist Jan 17, 2020.
    87 changes: 87 additions & 0 deletions watch.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,87 @@
    package main

    import (
    "bufio"
    "bytes"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "strconv"
    "strings"
    "syscall"
    "time"
    )

    // Entry point
    func main() {
    // Read in /etc/passwd so we know which uid is which (os/user cannot x-compile easily)
    passwdBytes, err := ioutil.ReadFile("/etc/passwd")
    if err != nil {
    log.Fatalf("failed to read /etc/passwd: %v", err)
    }
    scanner := bufio.NewScanner(bytes.NewReader(passwdBytes))
    users := make(map[uint32]string)
    for scanner.Scan() {
    parts := strings.SplitN(scanner.Text(), ":", 4)
    if uid, err := strconv.Atoi(parts[2]); err == nil {
    users[uint32(uid)] = parts[0]
    }
    }
    if err := scanner.Err(); err != nil {
    log.Fatalf("scanner failed: %v", err)
    }
    // Determine the max PID that can occur
    maxPIDBytes, err := ioutil.ReadFile("/proc/sys/kernel/pid_max")
    if err != nil {
    log.Fatalf("failed to determine max PID: %v", err)
    }
    maxPID, err := strconv.Atoi(strings.TrimSpace(string(maxPIDBytes)))
    if err != nil {
    log.Fatalf("failed to parse max PID: %v", err)
    }
    // Slices are _fast_ compared to map[int]time.Time
    processes := make([]time.Time, maxPID)
    // Using time.Tick will skip ticks if we're still processing the last tick
    for tick := range time.Tick(time.Millisecond * 100) {
    // List out /proc to get a list of every running process
    files, err := ioutil.ReadDir("/proc")
    if err != nil {
    log.Fatalf("failed to list proc: %v", err)
    }
    for _, file := range files {
    // Skip anything that's not a PID/number
    id, err := strconv.Atoi(file.Name())
    if err != nil {
    continue
    }
    // If this is the first time we've seen the process, emit it
    if processes[id].IsZero() {
    cmdline, err := ioutil.ReadFile(filepath.Join("/proc", strconv.Itoa(id), "cmdline"))
    if err != nil {
    if os.IsNotExist(err) {
    log.Printf("Process %d went away while we were reading it", id)
    continue
    }
    log.Fatalf("failed to read PID: %v", err)
    }
    args := strings.Split(strings.TrimSpace(string(cmdline)), "\000")
    username := "unknown"
    if stat, ok := file.Sys().(*syscall.Stat_t); ok {
    if name, ok := users[stat.Uid]; ok {
    username = name
    }
    }
    log.Printf("Process %d spawned by %s with arguments %v", id, username, args)
    }
    processes[id] = tick
    }
    // Expire any process we haven't seen in a second
    for id, lastSeen := range processes {
    if !lastSeen.IsZero() && lastSeen.Before(tick) {
    log.Printf("Process %d seems to have stopped", id)
    processes[id] = time.Time{}
    }
    }
    }
    }