Merge pull request #4 from DominicBreuker/small-improvements

Small improvements
This commit is contained in:
Dominic Breuker
2019-04-25 08:46:42 +02:00
committed by GitHub
14 changed files with 159 additions and 95 deletions

View File

@@ -14,14 +14,10 @@ test:
docker build -f $(TEST_DOCKERFILE) -t $(TEST_IMAGE) . docker build -f $(TEST_DOCKERFILE) -t $(TEST_IMAGE) .
docker run -it --rm $(TEST_IMAGE) docker run -it --rm $(TEST_IMAGE)
# Build Docker image for development
# pspy has to run on Linux - use this if you develop on another OS
dev-build:
docker build -f $(DEV_DOCKERFILE) -t $(DEV_IMAGE) .
# Drops you into a shell in the development container and mounts the source code # Drops you into a shell in the development container and mounts the source code
# You can edit to source on your host, then run go commans (e.g., `go test ./...`) inside the container # You can edit to source on your host, then run go commans (e.g., `go test ./...`) inside the container
dev: dev:
sh -c "if ! docker image ls | grep '$(DEV_IMAGE)'; then echo 'building dev image'; docker build -f $(DEV_DOCKERFILE) -t $(DEV_IMAGE) .; fi"
docker run -it \ docker run -it \
--rm \ --rm \
-v $(PROJECT_DIR):/go/src/github.com/dominicbreuker/pspy \ -v $(PROJECT_DIR):/go/src/github.com/dominicbreuker/pspy \
@@ -37,14 +33,13 @@ example:
docker run -it --rm $(EXAMPLE_IMAGE) docker run -it --rm $(EXAMPLE_IMAGE)
build-build-image:
docker build -f $(BUILD_DOCKERFILE) -t $(BUILD_IMAGE) .
# Build different binaries # Build different binaries
# builds binaries for both 32bit and 64bit systems # builds binaries for both 32bit and 64bit systems
# builds one set of static binaries that should work on any system without dependencies, but are huge # builds one set of static binaries that should work on any system without dependencies, but are huge
# builds another set of binaries that are as small as possible, but may not work # builds another set of binaries that are as small as possible, but may not work
build: build:
sh -c "if ! docker image ls | grep '$(BUILD_IMAGE)'; then echo 'building build image'; docker build -f $(BUILD_DOCKERFILE) -t $(BUILD_IMAGE) .; fi"
docker run -it \
mkdir -p $(PROJECT_DIR)/bin mkdir -p $(PROJECT_DIR)/bin
docker run -it \ docker run -it \
--rm \ --rm \

View File

@@ -17,6 +17,8 @@ Inotify watchers placed on selected parts of the file system trigger these scans
## Getting started ## Getting started
### Download
Get the tool onto the Linux machine you want to inspect. Get the tool onto the Linux machine you want to inspect.
First get the binaries. Download the released binaries here: First get the binaries. Download the released binaries here:
- 32 bit big, static version: `pspy32` [download](https://github.com/DominicBreuker/pspy/releases/download/v1.0.0/pspy32) - 32 bit big, static version: `pspy32` [download](https://github.com/DominicBreuker/pspy/releases/download/v1.0.0/pspy32)
@@ -27,7 +29,8 @@ First get the binaries. Download the released binaries here:
The statically compiled files should work on any Linux system but are quite huge (~4MB). The statically compiled files should work on any Linux system but are quite huge (~4MB).
If size is an issue, try the smaller versions which depend on libc and are compressed with UPX (<1MB). If size is an issue, try the smaller versions which depend on libc and are compressed with UPX (<1MB).
Alternatively, build the binaries yourself. ### Build
Either use Go installed on your system or run the Docker-based build process which ran to create the release. Either use Go installed on your system or run the Docker-based build process which ran to create the release.
For the latter, ensure Docker is installed, and then run `make build-build-image` to build a Docker image, followed by `make build` to build the binaries with it. For the latter, ensure Docker is installed, and then run `make build-build-image` to build a Docker image, followed by `make build` to build the binaries with it.

View File

@@ -58,6 +58,7 @@ var defaultRDirs = []string{
var defaultDirs = []string{} var defaultDirs = []string{}
var triggerInterval int var triggerInterval int
var colored bool var colored bool
var debug bool
func init() { func init() {
rootCmd.PersistentFlags().BoolVarP(&logPS, "procevents", "p", true, "print new processes to stdout") rootCmd.PersistentFlags().BoolVarP(&logPS, "procevents", "p", true, "print new processes to stdout")
@@ -66,12 +67,13 @@ func init() {
rootCmd.PersistentFlags().StringArrayVarP(&dirs, "dirs", "d", defaultDirs, "watch these dirs") rootCmd.PersistentFlags().StringArrayVarP(&dirs, "dirs", "d", defaultDirs, "watch these dirs")
rootCmd.PersistentFlags().IntVarP(&triggerInterval, "interval", "i", 100, "scan every 'interval' milliseconds for new processes") rootCmd.PersistentFlags().IntVarP(&triggerInterval, "interval", "i", 100, "scan every 'interval' milliseconds for new processes")
rootCmd.PersistentFlags().BoolVarP(&colored, "color", "c", true, "color the printed events") rootCmd.PersistentFlags().BoolVarP(&colored, "color", "c", true, "color the printed events")
rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "", false, "print detailed error messages")
log.SetOutput(os.Stdout) log.SetOutput(os.Stdout)
} }
func root(cmd *cobra.Command, args []string) { func root(cmd *cobra.Command, args []string) {
logger := logging.NewLogger() logger := logging.NewLogger(debug)
cfg := &config.Config{ cfg := &config.Config{
RDirs: rDirs, RDirs: rDirs,

View File

@@ -1,3 +1,3 @@
FROM golang:1.10-stretch FROM golang:1.12-stretch
RUN apt-get update && apt-get install -y upx && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y upx && rm -rf /var/lib/apt/lists/*

View File

@@ -1,4 +1,4 @@
FROM golang:1.10-stretch FROM golang:1.12-stretch
RUN apt-get update && apt-get -y install cron python3 sudo procps RUN apt-get update && apt-get -y install cron python3 sudo procps

View File

@@ -1,4 +1,4 @@
FROM golang:1.10-stretch FROM golang:1.12-stretch
RUN apt-get update && apt-get -y install cron python3 sudo procps RUN apt-get update && apt-get -y install cron python3 sudo procps

View File

@@ -2,27 +2,35 @@ package logging
import ( import (
"fmt" "fmt"
"hash/fnv"
"log" "log"
"os" "os"
"strconv"
) )
const ( const (
ColorNone = iota ColorNone = iota
ColorRed ColorRed
ColorGreen ColorGreen
ColorYellow
ColorBlue
ColorPurple
ColorTeal
) )
type Logger struct { type Logger struct {
infoLogger *log.Logger infoLogger *log.Logger
errorLogger *log.Logger errorLogger *log.Logger
eventLogger *log.Logger eventLogger *log.Logger
debug bool
} }
func NewLogger() *Logger { func NewLogger(debug bool) *Logger {
return &Logger{ return &Logger{
infoLogger: log.New(os.Stdout, "", 0), infoLogger: log.New(os.Stdout, "", 0),
errorLogger: log.New(os.Stderr, "", 0), errorLogger: log.New(os.Stderr, "", 0),
eventLogger: log.New(os.Stdout, "", log.Ldate|log.Ltime), eventLogger: log.New(os.Stdout, "", log.Ldate|log.Ltime),
debug: debug,
} }
} }
@@ -32,21 +40,24 @@ func (l *Logger) Infof(format string, v ...interface{}) {
} }
// Errorf writes an error message to stderr // Errorf writes an error message to stderr
func (l *Logger) Errorf(format string, v ...interface{}) { func (l *Logger) Errorf(debug bool, format string, v ...interface{}) {
l.errorLogger.Printf(format, v...) if l.debug == debug {
l.errorLogger.Printf(format, v...)
}
} }
// Eventf writes an event with timestamp to stdout // Eventf writes an event with timestamp to stdout
func (l *Logger) Eventf(color int, format string, v ...interface{}) { func (l *Logger) Eventf(color int, format string, v ...interface{}) {
msg := fmt.Sprintf(format, v...) msg := fmt.Sprintf(format, v...)
if color != ColorNone {
switch color { msg = fmt.Sprintf("\x1b[%d;1m%s\x1b[0m", 30+color, msg)
case ColorRed:
msg = fmt.Sprintf("\x1b[31;1m%s\x1b[0m", msg)
case ColorGreen:
msg = fmt.Sprintf("\x1b[32;1m%s\x1b[0m", msg)
default:
} }
l.eventLogger.Printf("%s", msg) l.eventLogger.Printf("%s", msg)
} }
func GetColorByUID(uid int) int {
h := fnv.New32a()
h.Write([]byte(strconv.Itoa(uid)))
return (int(h.Sum32()) % (ColorTeal)) + 1
}

View File

@@ -13,7 +13,7 @@ const ansiPattern = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\
var ansiMatcher = regexp.MustCompile(ansiPattern) var ansiMatcher = regexp.MustCompile(ansiPattern)
var l = NewLogger() var l = NewLogger(true)
var logTests = []struct { var logTests = []struct {
logger *log.Logger logger *log.Logger
@@ -23,8 +23,8 @@ var logTests = []struct {
}{ }{
{l.infoLogger, func() { l.Infof("Info message no. %d", 1) }, "Info message no. 1\n", nil}, {l.infoLogger, func() { l.Infof("Info message no. %d", 1) }, "Info message no. 1\n", nil},
{l.infoLogger, func() { l.Infof("Info message no. %d with a string %s\n", 2, "appended to it") }, "Info message no. 2 with a string appended to it\n", nil}, {l.infoLogger, func() { l.Infof("Info message no. %d with a string %s\n", 2, "appended to it") }, "Info message no. 2 with a string appended to it\n", nil},
{l.errorLogger, func() { l.Errorf("Error message") }, "Error message\n", nil}, {l.errorLogger, func() { l.Errorf(true, "Error message") }, "Error message\n", nil},
{l.errorLogger, func() { l.Errorf("Error message\n") }, "Error message\n", nil}, {l.errorLogger, func() { l.Errorf(true, "Error message\n") }, "Error message\n", nil},
{l.eventLogger, func() { l.Eventf(ColorNone, "Event message") }, dateFormatPattern + " Event message\n", nil}, {l.eventLogger, func() { l.Eventf(ColorNone, "Event message") }, dateFormatPattern + " Event message\n", nil},
{l.eventLogger, func() { l.Eventf(ColorRed, "Event message") }, dateFormatPattern + " Event message\n", [][]byte{[]byte("\x1b[31;1m"), []byte("\x1b[0m")}}, {l.eventLogger, func() { l.Eventf(ColorRed, "Event message") }, dateFormatPattern + " Event message\n", [][]byte{[]byte("\x1b[31;1m"), []byte("\x1b[0m")}},
{l.eventLogger, func() { l.Eventf(ColorGreen, "Event message") }, dateFormatPattern + " Event message\n", [][]byte{[]byte("\x1b[32;1m"), []byte("\x1b[0m")}}, {l.eventLogger, func() { l.Eventf(ColorGreen, "Event message") }, dateFormatPattern + " Event message\n", [][]byte{[]byte("\x1b[32;1m"), []byte("\x1b[0m")}},
@@ -33,6 +33,7 @@ var logTests = []struct {
func TestLogging(t *testing.T) { func TestLogging(t *testing.T) {
for i, tt := range logTests { for i, tt := range logTests {
actual := captureOutput(tt.logger, tt.test) actual := captureOutput(tt.logger, tt.test)
log.Printf("OUT: %s", actual)
// check colors and remove afterwards // check colors and remove afterwards
colors := ansiMatcher.FindAll(actual, 2) colors := ansiMatcher.FindAll(actual, 2)
@@ -55,3 +56,46 @@ func captureOutput(logger *log.Logger, f func()) []byte {
f() f()
return buf.Bytes() return buf.Bytes()
} }
func TestGetColorByUID(t *testing.T) {
tests := []struct {
uid int
color int
}{
{uid: 0, color: 4},
{uid: 1, color: 5},
{uid: 2, color: 2},
{uid: 3, color: 3},
{uid: 99999999999, color: 5},
}
for _, tt := range tests {
color := GetColorByUID(tt.uid)
if color != tt.color {
t.Errorf("GetColorByUID(%d)=%d but want %d", tt.uid, color, tt.color)
}
}
minColor := 9999999
maxColor := -9999999
for i := 0; i < 1000; i++ {
color := GetColorByUID(i)
if color < 1 || color > ColorTeal {
t.Fatalf("GetColorByUID(%d)=%d but this is out of range [%d, %d]", i, color, ColorRed, ColorTeal)
}
if color < minColor {
minColor = color
}
if color > maxColor {
maxColor = color
}
}
if minColor != 1 {
t.Errorf("GetColorByUID returned minimum color %d, not %d, on 1000 trials, which is extremely unlikely", minColor, 1)
}
if maxColor != ColorTeal {
t.Errorf("GetColorByUID returned maximum color %d, not %d, on 1000 trials, which is extremely unlikely", maxColor, ColorTeal)
}
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/dominicbreuker/pspy/internal/config" "github.com/dominicbreuker/pspy/internal/config"
"github.com/dominicbreuker/pspy/internal/logging" "github.com/dominicbreuker/pspy/internal/logging"
"github.com/dominicbreuker/pspy/internal/psscanner"
) )
type Bindings struct { type Bindings struct {
@@ -16,7 +17,7 @@ type Bindings struct {
type Logger interface { type Logger interface {
Infof(format string, v ...interface{}) Infof(format string, v ...interface{})
Errorf(format string, v ...interface{}) Errorf(debug bool, format string, v ...interface{})
Eventf(color int, format string, v ...interface{}) Eventf(color int, format string, v ...interface{})
} }
@@ -26,13 +27,13 @@ type FSWatcher interface {
} }
type PSScanner interface { type PSScanner interface {
Run(triggerCh chan struct{}) (chan string, chan error) Run(triggerCh chan struct{}) (chan psscanner.PSEvent, chan error)
} }
type chans struct { type chans struct {
sigCh chan os.Signal sigCh chan os.Signal
fsEventCh chan string fsEventCh chan string
psEventCh chan string psEventCh chan psscanner.PSEvent
} }
func Start(cfg *config.Config, b *Bindings, sigCh chan os.Signal) chan struct{} { func Start(cfg *config.Config, b *Bindings, sigCh chan os.Signal) chan struct{} {
@@ -56,7 +57,7 @@ func Start(cfg *config.Config, b *Bindings, sigCh chan os.Signal) chan struct{}
func printOutput(cfg *config.Config, b *Bindings, chans *chans) chan struct{} { func printOutput(cfg *config.Config, b *Bindings, chans *chans) chan struct{} {
exit := make(chan struct{}) exit := make(chan struct{})
fsEventColor, psEventColor := getColors(cfg.Colored) // fsEventColor, psEventColor := getColors(cfg.Colored)
go func() { go func() {
for { for {
@@ -66,11 +67,15 @@ func printOutput(cfg *config.Config, b *Bindings, chans *chans) chan struct{} {
exit <- struct{}{} exit <- struct{}{}
case fe := <-chans.fsEventCh: case fe := <-chans.fsEventCh:
if cfg.LogFS { if cfg.LogFS {
b.Logger.Eventf(fsEventColor, "FS: %+v", fe) b.Logger.Eventf(logging.ColorNone, "FS: %+v", fe)
} }
case pe := <-chans.psEventCh: case pe := <-chans.psEventCh:
if cfg.LogPS { if cfg.LogPS {
b.Logger.Eventf(psEventColor, "CMD: %+v", pe) color := logging.ColorNone
if cfg.Colored {
color = logging.GetColorByUID(pe.UID)
}
b.Logger.Eventf(color, "CMD: %+v", pe)
} }
} }
} }
@@ -78,17 +83,6 @@ func printOutput(cfg *config.Config, b *Bindings, chans *chans) chan struct{} {
return exit return exit
} }
func getColors(colored bool) (fsEventColor int, psEventColor int) {
if colored {
fsEventColor = logging.ColorGreen
psEventColor = logging.ColorRed
} else {
fsEventColor = logging.ColorNone
psEventColor = logging.ColorNone
}
return
}
func initFSW(fsw FSWatcher, rdirs, dirs []string, logger Logger) { func initFSW(fsw FSWatcher, rdirs, dirs []string, logger Logger) {
errCh, doneCh := fsw.Init(rdirs, dirs) errCh, doneCh := fsw.Init(rdirs, dirs)
for { for {
@@ -96,7 +90,7 @@ func initFSW(fsw FSWatcher, rdirs, dirs []string, logger Logger) {
case <-doneCh: case <-doneCh:
return return
case err := <-errCh: case err := <-errCh:
logger.Errorf("initializing fs watcher: %v", err) logger.Errorf(true, "initializing fs watcher: %v", err)
} }
} }
} }
@@ -112,7 +106,7 @@ func startFSW(fsw FSWatcher, logger Logger, drainFor time.Duration) (triggerCh c
return triggerCh, fsEventCh return triggerCh, fsEventCh
} }
func startPSS(pss PSScanner, logger Logger, triggerCh chan struct{}) (psEventCh chan string) { func startPSS(pss PSScanner, logger Logger, triggerCh chan struct{}) (psEventCh chan psscanner.PSEvent) {
psEventCh, errCh := pss.Run(triggerCh) psEventCh, errCh := pss.Run(triggerCh)
go logErrors(errCh, logger) go logErrors(errCh, logger)
return psEventCh return psEventCh
@@ -130,7 +124,7 @@ func triggerEvery(d time.Duration, triggerCh chan struct{}) {
func logErrors(errCh chan error, logger Logger) { func logErrors(errCh chan error, logger Logger) {
for { for {
err := <-errCh err := <-errCh
logger.Errorf("ERROR: %v", err) logger.Errorf(true, "ERROR: %v", err)
} }
} }

View File

@@ -9,6 +9,7 @@ import (
"github.com/dominicbreuker/pspy/internal/config" "github.com/dominicbreuker/pspy/internal/config"
"github.com/dominicbreuker/pspy/internal/logging" "github.com/dominicbreuker/pspy/internal/logging"
"github.com/dominicbreuker/pspy/internal/psscanner"
) )
func TestInitFSW(t *testing.T) { func TestInitFSW(t *testing.T) {
@@ -67,24 +68,6 @@ func TestStartPSS(t *testing.T) {
expectMessage(t, l.Error, "ERROR: error during refresh") expectMessage(t, l.Error, "ERROR: error during refresh")
} }
func TestGetColors(t *testing.T) {
tests := []struct {
colored bool
fsEventColor int
psEventColor int
}{
{colored: true, fsEventColor: logging.ColorGreen, psEventColor: logging.ColorRed},
{colored: false, fsEventColor: logging.ColorNone, psEventColor: logging.ColorNone},
}
for _, tt := range tests {
c1, c2 := getColors(tt.colored)
if c1 != tt.fsEventColor || c2 != tt.psEventColor {
t.Errorf("Got wrong colors when colored=%t: expected %d, %d but got %d, %d", tt.colored, tt.fsEventColor, tt.psEventColor, c1, c2)
}
}
}
func TestStart(t *testing.T) { func TestStart(t *testing.T) {
drainFor := 10 * time.Millisecond drainFor := 10 * time.Millisecond
triggerEvery := 999 * time.Second triggerEvery := 999 * time.Second
@@ -112,7 +95,7 @@ func TestStart(t *testing.T) {
close(fsw.initDoneCh) close(fsw.initDoneCh)
<-time.After(2 * drainFor) <-time.After(2 * drainFor)
fsw.runTriggerCh <- struct{}{} fsw.runTriggerCh <- struct{}{}
pss.runEventCh <- "pss event" pss.runEventCh <- psscanner.PSEvent{UID: 1000, PID: 12345, CMD: "pss event"}
pss.runErrCh <- errors.New("pss error") pss.runErrCh <- errors.New("pss error")
fsw.runEventCh <- "fsw event" fsw.runEventCh <- "fsw event"
fsw.runErrCh <- errors.New("fsw error") fsw.runErrCh <- errors.New("fsw error")
@@ -125,9 +108,9 @@ func TestStart(t *testing.T) {
<-time.After(2 * drainFor) <-time.After(2 * drainFor)
expectMessage(t, l.Info, "done") expectMessage(t, l.Info, "done")
expectTrigger(t, pss.runTriggerCh) // pss receives triggers from fsw expectTrigger(t, pss.runTriggerCh) // pss receives triggers from fsw
expectMessage(t, l.Event, fmt.Sprintf("%d CMD: pss event", logging.ColorRed)) expectMessage(t, l.Event, fmt.Sprintf("%d CMD: UID=1000 PID=12345 | pss event", logging.ColorPurple))
expectMessage(t, l.Error, "ERROR: pss error") expectMessage(t, l.Error, "ERROR: pss error")
expectMessage(t, l.Event, fmt.Sprintf("%d FS: fsw event", logging.ColorGreen)) expectMessage(t, l.Event, fmt.Sprintf("%d FS: fsw event", logging.ColorNone))
expectMessage(t, l.Error, "ERROR: fsw error") expectMessage(t, l.Error, "ERROR: fsw error")
expectMessage(t, l.Info, "Exiting program... (interrupt)") expectMessage(t, l.Info, "Exiting program... (interrupt)")
@@ -190,6 +173,7 @@ type mockLogger struct {
Info chan string Info chan string
Error chan string Error chan string
Event chan string Event chan string
Debug bool
} }
func newMockLogger() *mockLogger { func newMockLogger() *mockLogger {
@@ -197,6 +181,7 @@ func newMockLogger() *mockLogger {
Info: make(chan string, 10), Info: make(chan string, 10),
Error: make(chan string, 10), Error: make(chan string, 10),
Event: make(chan string, 10), Event: make(chan string, 10),
Debug: true,
} }
} }
@@ -204,8 +189,10 @@ func (l *mockLogger) Infof(format string, v ...interface{}) {
l.Info <- fmt.Sprintf(format, v...) l.Info <- fmt.Sprintf(format, v...)
} }
func (l *mockLogger) Errorf(format string, v ...interface{}) { func (l *mockLogger) Errorf(debug bool, format string, v ...interface{}) {
l.Error <- fmt.Sprintf(format, v...) if l.Debug == debug {
l.Error <- fmt.Sprintf(format, v...)
}
} }
func (l *mockLogger) Eventf(color int, format string, v ...interface{}) { func (l *mockLogger) Eventf(color int, format string, v ...interface{}) {
@@ -251,7 +238,7 @@ func (fsw *mockFSWatcher) Run() (chan struct{}, chan string, chan error) {
type mockPSScanner struct { type mockPSScanner struct {
runTriggerCh chan struct{} runTriggerCh chan struct{}
runEventCh chan string runEventCh chan psscanner.PSEvent
runErrCh chan error runErrCh chan error
numRefreshes int numRefreshes int
} }
@@ -260,9 +247,9 @@ func newMockPSScanner() *mockPSScanner {
return &mockPSScanner{} return &mockPSScanner{}
} }
func (pss *mockPSScanner) Run(triggerCh chan struct{}) (chan string, chan error) { func (pss *mockPSScanner) Run(triggerCh chan struct{}) (chan psscanner.PSEvent, chan error) {
pss.runTriggerCh = triggerCh pss.runTriggerCh = triggerCh
pss.runEventCh = make(chan string) pss.runEventCh = make(chan psscanner.PSEvent)
pss.runErrCh = make(chan error) pss.runErrCh = make(chan error)
go func() { go func() {

View File

@@ -25,7 +25,7 @@ var cmdLineReader = func(pid int) ([]byte, error) {
type procList map[int]string type procList map[int]string
func (pl procList) refresh(eventCh chan string) error { func (pl procList) refresh(eventCh chan PSEvent) error {
pids, err := getPIDs() pids, err := getPIDs()
if err != nil { if err != nil {
return err return err
@@ -74,16 +74,16 @@ func file2Pid(f os.FileInfo) (int, error) {
return pid, nil return pid, nil
} }
func (pl procList) addPid(pid int, eventCh chan string) { func (pl procList) addPid(pid int, eventCh chan PSEvent) {
cmd, err := getCmd(pid) cmd, err := getCmd(pid)
if err != nil { if err != nil {
cmd = "???" // process probably terminated cmd = "???" // process probably terminated
} }
uid, err := getUID(pid) uid, err := getUID(pid)
if err != nil { if err != nil {
uid = "???" uid = -1
} }
eventCh <- fmt.Sprintf("UID=%-4s PID=%-6d | %s", uid, pid, cmd) eventCh <- PSEvent{UID: uid, PID: pid, CMD: cmd}
pl[pid] = cmd pl[pid] = cmd
} }
@@ -100,18 +100,26 @@ func getCmd(pid int) (string, error) {
return string(cmd), nil return string(cmd), nil
} }
func getUID(pid int) (string, error) { func getUID(pid int) (int, error) {
stat, err := procStatusReader(pid) stat, err := procStatusReader(pid)
if err != nil { if err != nil {
return "", err return -1, err
} }
lines := strings.Split(string(stat), "\n") lines := strings.Split(string(stat), "\n")
if len(lines) < 9 { if len(lines) < 9 {
return "", fmt.Errorf("no uid information") return -1, fmt.Errorf("no uid information")
} }
uidL := strings.Split(lines[8], "\t") uidL := strings.Split(lines[8], "\t")
if len(uidL) < 2 { if len(uidL) < 2 {
return "", fmt.Errorf("uid line read incomplete") return -1, fmt.Errorf("uid line read incomplete")
} }
return uidL[1], nil
uid, err := strconv.Atoi(uidL[1])
if err != nil {
return -1, fmt.Errorf("converting %s to int: %v", uidL[1], err)
}
return uid, nil
} }

View File

@@ -136,14 +136,14 @@ func TestGetUID(t *testing.T) {
pid int pid int
stat []byte stat []byte
statErr error statErr error
uid string uid int
err string err string
}{ }{
{pid: 7, stat: completeStatus, statErr: nil, uid: "0", err: ""}, // can read normal stat {pid: 7, stat: completeStatus, statErr: nil, uid: 0, err: ""}, // can read normal stat
{pid: 7, stat: uidLineBroken, statErr: nil, uid: "", err: "uid line read incomplete"}, // errors on incomplete Uid line {pid: 7, stat: uidLineBroken, statErr: nil, uid: -1, err: "uid line read incomplete"}, // errors on incomplete Uid line
{pid: 7, stat: notEnoughLines, statErr: nil, uid: "", err: "no uid information"}, // errors for insufficient lines {pid: 7, stat: notEnoughLines, statErr: nil, uid: -1, err: "no uid information"}, // errors for insufficient lines
{pid: 7, stat: []byte(""), statErr: nil, uid: "", err: "no uid information"}, // errors for insufficient lines {pid: 7, stat: []byte(""), statErr: nil, uid: -1, err: "no uid information"}, // errors for insufficient lines
{pid: 7, stat: []byte(""), statErr: errors.New("file-system-error"), uid: "", err: "file-system-error"}, // returns file system errors {pid: 7, stat: []byte(""), statErr: errors.New("file-system-error"), uid: -1, err: "file-system-error"}, // returns file system errors
} }
for _, tt := range tests { for _, tt := range tests {
@@ -151,7 +151,7 @@ func TestGetUID(t *testing.T) {
uid, err := getUID(tt.pid) uid, err := getUID(tt.pid)
if uid != tt.uid { if uid != tt.uid {
fmt.Printf("STAT: %s", tt.stat) fmt.Printf("STAT: %s", tt.stat)
t.Errorf("Wrong uid returned: got %s but want %s", uid, tt.uid) t.Errorf("Wrong uid returned: got %d but want %d", uid, tt.uid)
} }
if (err != nil || tt.err != "") && fmt.Sprintf("%v", err) != tt.err { if (err != nil || tt.err != "") && fmt.Sprintf("%v", err) != tt.err {
t.Errorf("Wrong error returned: got %v but want %s", err, tt.err) t.Errorf("Wrong error returned: got %v but want %s", err, tt.err)
@@ -174,18 +174,18 @@ func mockProcStatusReader(stat []byte, err error) (restore func()) {
func TestRefresh(t *testing.T) { func TestRefresh(t *testing.T) {
tests := []struct { tests := []struct {
eventCh chan string eventCh chan PSEvent
pl procList pl procList
newPids []int newPids []int
pidsAfter []int pidsAfter []int
events []string events []string
}{ }{
{eventCh: make(chan string), pl: procList{}, newPids: []int{1, 2, 3}, pidsAfter: []int{3, 2, 1}, events: []string{ {eventCh: make(chan PSEvent), pl: procList{}, newPids: []int{1, 2, 3}, pidsAfter: []int{3, 2, 1}, events: []string{
"UID=??? PID=3 | the-command", "UID=??? PID=3 | the-command",
"UID=??? PID=2 | the-command", "UID=??? PID=2 | the-command",
"UID=??? PID=1 | the-command", "UID=??? PID=1 | the-command",
}}, }},
{eventCh: make(chan string), pl: procList{1: "pid-found-before"}, newPids: []int{1, 2, 3}, pidsAfter: []int{1, 3, 2}, events: []string{ {eventCh: make(chan PSEvent), pl: procList{1: "pid-found-before"}, newPids: []int{1, 2, 3}, pidsAfter: []int{1, 3, 2}, events: []string{
"UID=??? PID=3 | the-command", "UID=??? PID=3 | the-command",
"UID=??? PID=2 | the-command", "UID=??? PID=2 | the-command",
}}, // no events emitted for PIDs already known }}, // no events emitted for PIDs already known
@@ -200,7 +200,7 @@ func TestRefresh(t *testing.T) {
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
for e := range tt.eventCh { for e := range tt.eventCh {
events = append(events, e) events = append(events, e.String())
} }
done <- struct{}{} done <- struct{}{}
}() }()

View File

@@ -1,13 +1,33 @@
package psscanner package psscanner
import (
"fmt"
"strconv"
)
type PSScanner struct{} type PSScanner struct{}
type PSEvent struct {
UID int
PID int
CMD string
}
func (evt PSEvent) String() string {
uid := strconv.Itoa(evt.UID)
if evt.UID == -1 {
uid = "???"
}
return fmt.Sprintf("UID=%-4s PID=%-6d | %s", uid, evt.PID, evt.CMD)
}
func NewPSScanner() *PSScanner { func NewPSScanner() *PSScanner {
return &PSScanner{} return &PSScanner{}
} }
func (p *PSScanner) Run(triggerCh chan struct{}) (chan string, chan error) { func (p *PSScanner) Run(triggerCh chan struct{}) (chan PSEvent, chan error) {
eventCh := make(chan string, 100) eventCh := make(chan PSEvent, 100)
errCh := make(chan error) errCh := make(chan error)
pl := make(procList) pl := make(procList)

View File

@@ -48,7 +48,7 @@ func TestRun(t *testing.T) {
case <-time.After(timeout): case <-time.After(timeout):
t.Errorf("did not receive event in time") t.Errorf("did not receive event in time")
case e := <-eventCh: case e := <-eventCh:
if e != tt.events[i] { if e.String() != tt.events[i] {
t.Errorf("Wrong event received: got '%s' but wanted '%s'", e, tt.events[i]) t.Errorf("Wrong event received: got '%s' but wanted '%s'", e, tt.events[i])
} }
case err := <-errCh: case err := <-errCh: