diff --git a/docker/Dockerfile b/docker/Dockerfile index 709c734..8c30c67 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,9 @@ FROM golang:1.9-stretch +RUN useradd -ms /bin/bash myuser + +USER myuser + WORKDIR /go/src/github.com/dominicbreuker diff --git a/internal/dev/plist.go b/internal/dev/plist.go index 75f3446..c97a925 100644 --- a/internal/dev/plist.go +++ b/internal/dev/plist.go @@ -1,12 +1,11 @@ package dev import ( - "fmt" - "io/ioutil" "log" - "strconv" + "time" - "github.com/fsnotify/fsnotify" + "github.com/dominicbreuker/pspy/internal/inotify" + "github.com/dominicbreuker/pspy/internal/process" ) type Process struct { @@ -30,78 +29,47 @@ func Monitor() { } func watch() { - watcher, err := fsnotify.NewWatcher() + ping := make(chan struct{}) + in, err := inotify.NewInotify(ping) if err != nil { - log.Fatalf("Can't create file system watcher: %v", err) + log.Fatalf("Can't init inotify: %v", err) } - defer watcher.Close() - done := make(chan bool) + dirs := []string{ + "/proc", + "/var/log", + "/home", + "/tmp", + } - go func() { - for { - select { - case event := <-watcher.Events: - log.Println("event:", event) - if event.Op&fsnotify.Write == fsnotify.Write { - log.Println("modified file:", event.Name) - } - case err := <-watcher.Errors: - log.Println("error:", err) - } + for _, dir := range dirs { + if err := in.Watch(dir); err != nil { + log.Fatalf("Can't create watcher: %v", err) + } + } + + log.Printf("Inotify set up: %s\n", in) + + procList := process.NewProcList() + + ticker := time.NewTicker(50 * time.Millisecond).C + + for { + select { + case <-ticker: + refresh(in, procList) + case <-ping: + log.Printf("PING") + refresh(in, procList) } - }() - - err = watcher.Add("/tmp") - if err != nil { - log.Fatal(err) } - <-done } -func refresh(procList map[int]string) error { - proc, err := ioutil.ReadDir("/proc") - if err != nil { - return fmt.Errorf("opening proc dir: %v", err) +func refresh(in *inotify.Inotify, pl *process.ProcList) { + in.Pause() + if err := pl.Refresh(); err != nil { + log.Printf("ERROR refreshing process list: %v", err) } - - pids := make([]int, 0) - - for _, f := range proc { - if f.IsDir() { - name := f.Name() - pid, err := strconv.Atoi(name) - if err != nil { - continue // not a pid - } - pids = append(pids, pid) - } - } - - for _, pid := range pids { - _, ok := procList[pid] - if !ok { - cmd, err := getCmd(pid) - if err != nil { - cmd = "UNKNOWN" // process probably terminated - } - log.Printf("New process: %5d: %s\n", pid, cmd) - procList[pid] = cmd - } - } - return nil -} - -func getCmd(pid int) (string, error) { - cmdPath := fmt.Sprintf("/proc/%d/cmdline", pid) - cmd, err := ioutil.ReadFile(cmdPath) - if err != nil { - return "", err - } - for i := 0; i < len(cmd); i++ { - if cmd[i] == 0 { - cmd[i] = 32 - } - } - return string(cmd), nil + time.Sleep(50 * time.Millisecond) + in.UnPause() } diff --git a/internal/inotify/inotify.go b/internal/inotify/inotify.go new file mode 100644 index 0000000..c3c1a5e --- /dev/null +++ b/internal/inotify/inotify.go @@ -0,0 +1,65 @@ +package inotify + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +type Inotify struct { + fd int + watchers []*watcher + ping chan struct{} + paused bool +} + +func NewInotify(ping chan struct{}) (*Inotify, error) { + fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC) + if fd == -1 { + return nil, fmt.Errorf("Can't init inotify: %d", errno) + } + + i := &Inotify{ + fd: fd, + ping: ping, + paused: false, + } + go watch(i) + + return i, nil +} + +func (i *Inotify) Watch(dir string) error { + w, err := newWatcher(i.fd, dir, i.ping) + if err != nil { + return fmt.Errorf("creating watcher: %v", err) + } + i.watchers = append(i.watchers, w) + return nil +} + +func (i *Inotify) Pause() { + i.paused = true +} + +func (i *Inotify) UnPause() { + i.paused = false +} + +func (i *Inotify) String() string { + dirs := make([]string, 0) + for _, w := range i.watchers { + dirs = append(dirs, w.dir) + } + return fmt.Sprintf("Watching: %v", dirs) +} + +func watch(i *Inotify) { + buf := make([]byte, 1024) + for { + _, _ = unix.Read(i.fd, buf) + if !i.paused { + i.ping <- struct{}{} + } + } +} diff --git a/internal/inotify/watcher.go b/internal/inotify/watcher.go new file mode 100644 index 0000000..64fb81d --- /dev/null +++ b/internal/inotify/watcher.go @@ -0,0 +1,25 @@ +package inotify + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +const events = unix.IN_ALL_EVENTS + +type watcher struct { + wd int + dir string +} + +func newWatcher(fd int, dir string, ping chan struct{}) (*watcher, error) { + wd, errno := unix.InotifyAddWatch(fd, dir, events) + if wd == -1 { + return nil, fmt.Errorf("adding watcher on %s: %d", dir, errno) + } + return &watcher{ + wd: wd, + dir: dir, + }, nil +} diff --git a/internal/process/process.go b/internal/process/process.go new file mode 100644 index 0000000..50cd097 --- /dev/null +++ b/internal/process/process.go @@ -0,0 +1,68 @@ +package process + +import ( + "fmt" + "io/ioutil" + "log" + "strconv" +) + +type ProcList map[int]string + +// type Proc struct { +// Cmd string +// User string +// } + +func NewProcList() *ProcList { + pl := make(ProcList) + return &pl +} + +func (pl ProcList) Refresh() error { + proc, err := ioutil.ReadDir("/proc") + if err != nil { + return fmt.Errorf("opening proc dir: %v", err) + } + + pids := make([]int, 0) + + for _, f := range proc { + if f.IsDir() { + name := f.Name() + pid, err := strconv.Atoi(name) + if err != nil { + continue // not a pid + } + pids = append(pids, pid) + } + } + + for i := len(pids) - 1; i >= 0; i-- { + pid := pids[i] + _, ok := pl[pid] + if !ok { + cmd, err := getCmd(pid) + if err != nil { + cmd = "UNKNOWN" // process probably terminated + } + log.Printf("New process: %5d: %s\n", pid, cmd) + pl[pid] = cmd + } + } + return nil +} + +func getCmd(pid int) (string, error) { + cmdPath := fmt.Sprintf("/proc/%d/cmdline", pid) + cmd, err := ioutil.ReadFile(cmdPath) + if err != nil { + return "", err + } + for i := 0; i < len(cmd); i++ { + if cmd[i] == 0 { + cmd[i] = 32 + } + } + return string(cmd), nil +}