78 Commits

Author SHA1 Message Date
Dominic Breuker
4c0edc540c Merge pull request #7 from DominicBreuker/fix-read-syscall
Fix errno 22 errors
2019-08-11 23:12:40 +02:00
Dominic Breuker
0e4d11951b split off image version for docker image check 2019-08-11 23:09:44 +02:00
Dominic Breuker
8264b725de improve read syscall reliability by inreasing buffer size and handling old kernel errors 2019-08-11 23:01:53 +02:00
Dominic Breuker
0c973089dd Merge pull request #6 from socketz/patch-1
Fixed Makefile
2019-08-03 23:29:50 +02:00
socketz
d2b84b9415 Fixed Makefile
Removed line was triggering an error
2019-07-31 23:07:52 +02:00
Dominic Breuker
af4e8ff857 Merge pull request #4 from DominicBreuker/small-improvements
Small improvements
2019-04-25 08:46:42 +02:00
Dominic Breuker
21a0666ff1 enable coloring by UIDs 2019-04-24 22:30:14 +02:00
Dominic Breuker
d1b6518db5 turn process monitoring events into structured objects 2019-04-24 22:08:59 +02:00
Dominic Breuker
8a1838faee introduce --debug flag to hide excessive error messages by default but allow displaying them 2019-04-24 21:42:18 +02:00
Dominic Breuker
7d9d32412b bump go version to 1.12 2019-04-24 21:35:26 +02:00
Dominic Breuker
59960f9f37 integrate docker image builds into make targets 2019-04-24 21:31:43 +02:00
Dominic Breuker
f8b2492730 solve merge conflicts 2019-04-24 15:44:51 +02:00
Dominic Breuker
d2c8362729 add download links to readme 2018-06-09 22:51:00 +02:00
Dominic Breuker
aba533f86e switch to debian for build image to link against libc 2018-04-05 09:01:27 +02:00
Dominic Breuker
986cafff6f add build command for small binaries 2018-04-05 08:47:43 +02:00
Dominic Breuker
ab8dfc2252 delete the binaries - why on earth did I put them here in the first place... 2018-04-05 08:45:28 +02:00
Dominic Breuker
d4e20c3629 bundle chans to struct 2018-03-29 08:51:03 +02:00
Dominic Breuker
84db6dd806 add config to enable/disable colored output 2018-03-29 08:43:13 +02:00
Dominic Breuker
093b9ec69c update readme to include new flag -i 2018-03-28 09:39:50 +02:00
Dominic Breuker
8e61b2bd9d adjust main test to account for new config format 2018-03-28 09:06:21 +02:00
Dominic Breuker
056c91801d add flag to configure scanning interval 2018-03-28 09:01:25 +02:00
Dominic Breuker
1e67dc332b use factory method in walker test 2018-03-16 09:40:13 +01:00
Dominic Breuker
72bbfac3e0 add tests for main method 2018-03-16 09:33:22 +01:00
Dominic Breuker
3eed2c29aa add tests for main pspy methods 2018-03-15 09:22:22 +01:00
Dominic Breuker
fe0300a67c further extend inotify test 2018-03-14 09:21:14 +01:00
Dominic Breuker
f357059f36 extend inotify test 2018-03-14 09:12:45 +01:00
Dominic Breuker
1f52ae340c add tests and remove unused method in inotify 2018-03-14 08:49:54 +01:00
Dominic Breuker
fb7dff2d13 refactor start method 2018-03-12 08:58:01 +01:00
Dominic Breuker
8440e46afc refactor event parsing 2018-03-12 08:58:01 +01:00
Dominic Breuker
723fb79e06 refactor walker 2018-03-12 08:58:01 +01:00
Dominic Breuker
53bb0c2566 refactor again 2018-03-12 08:58:01 +01:00
Dominic Breuker
b7c5e0b984 refactor complex code into smaller pieces 2018-03-12 08:58:01 +01:00
Dominic Breuker
d56eca305e add badges 2018-03-12 08:58:01 +01:00
Dominic Breuker
8fea22e762 copy .git folder explicity 2018-03-12 08:58:01 +01:00
Dominic Breuker
5a2d99fafb fix circle again 2018-03-12 08:58:01 +01:00
Dominic Breuker
96fad366d0 fix circle 2018-03-12 08:58:01 +01:00
Dominic Breuker
87639a4cd2 try machine build 2018-03-12 08:58:01 +01:00
Dominic Breuker
5ac0573e41 add env for test reporter 2018-03-12 08:58:01 +01:00
Dominic Breuker
00e2783e2c put volume mount into correct docker statement 2018-03-12 08:58:01 +01:00
Dominic Breuker
42c4561c22 try code coverage reporting on circle 2018-03-12 08:58:01 +01:00
Dominic Breuker
67590d8216 fix stupid mistake 2018-03-12 08:58:01 +01:00
Dominic Breuker
0e5d2ed39e move unit tests from image build to contrainer run 2018-03-12 08:58:01 +01:00
Dominic Breuker
f4b2fdf6b6 skip tests just to see if e2e test would work 2018-03-12 08:58:01 +01:00
Dominic Breuker
8b7ed9c586 fix circle config 2018-03-12 08:58:01 +01:00
Dominic Breuker
56cfc0555b try docker based build 2018-03-12 08:58:01 +01:00
Dominic Breuker
2d2b523bb6 try if circleci works with inotify 2018-03-12 08:58:01 +01:00
Dominic Breuker
5cdc39457a try native test run 2018-03-12 08:58:01 +01:00
Dominic Breuker
3c755caea7 try travis with more recent docker version 2018-03-12 08:58:01 +01:00
Dominic Breuker
d2996b5229 make error more clear 2018-03-12 08:58:01 +01:00
Dominic Breuker
ad8d51f4db trigger travis 2018-03-12 08:58:01 +01:00
Dominic Breuker
eb2fe0c668 testing travis 2018-03-12 08:58:01 +01:00
Dominic Breuker
a6d948819f refactor process refresh 2018-03-12 08:58:01 +01:00
Dominic Breuker
65ec5b1202 refactor get pid 2018-03-12 08:58:01 +01:00
Dominic Breuker
d3c7681096 add specs for logging and proc scanner 2018-03-12 08:58:01 +01:00
Dominic Breuker
572ce2ef3e add specs for process scanner 2018-03-12 08:58:01 +01:00
Dominic Breuker
644d65be7b refactors psscanner 2018-03-12 08:58:01 +01:00
Dominic Breuker
9670b85f43 add tests for fswatcher 2018-03-12 08:58:01 +01:00
Dominic Breuker
1deb4838a5 add tests for fswatcher package 2018-03-12 08:58:01 +01:00
Dominic Breuker
94a12cf031 refactor inotify implementation 2018-03-12 08:58:01 +01:00
Dominic Breuker
2750defb63 try some more tsting 2018-03-12 08:58:01 +01:00
Dominic Breuker
d1c18d901a restructure inotify package and add some tests 2018-03-12 08:58:01 +01:00
Dominic Breuker
f5ca2dad75 experiment with some tests 2018-03-12 08:58:01 +01:00
Dominic Breuker
cb48cc1b37 start big refactoring 2018-03-12 08:58:01 +01:00
Dominic Breuker
95150e5e12 Create LICENSE 2018-02-19 08:16:37 +01:00
Dominic Breuker
628e20cd56 add badges for code quality 2018-02-18 22:42:22 +01:00
Dominic Breuker
01521a19cc update readme 2018-02-18 22:30:33 +01:00
Dominic Breuker
693bc1ef17 refactor readme 2018-02-18 22:29:07 +01:00
Dominic Breuker
773f370c35 build proper dockerized example 2018-02-18 14:03:59 +01:00
Dominic Breuker
6b1f75f4d8 add link to wikimedia 2018-02-13 22:55:50 +01:00
Dominic Breuker
c469335009 update readme 2018-02-13 22:54:25 +01:00
Dominic Breuker
c191b4b7f4 rename image to demo 2018-02-13 22:45:35 +01:00
Dominic Breuker
167782b25f use full gif 2018-02-13 22:42:40 +01:00
Dominic Breuker
f7d0089b1e add a readme and update binaries 2018-02-13 22:39:45 +01:00
Dominic Breuker
4495ae7c96 pass through arguments from command line 2018-02-13 21:41:41 +01:00
Dominic Breuker
c92dd9992f switch to dep and integrate cobra 2018-02-13 10:00:38 +01:00
Dominic Breuker
56f706b7e2 improve console output 2018-02-12 23:39:04 +01:00
Dominic Breuker
9bc66835a6 add some hacky experiments for inotify event parsing 2018-02-11 22:15:10 +01:00
Dominic Breuker
38c5d42bb4 integrate inotify syscalls 2018-02-09 09:50:31 +01:00
15 changed files with 171 additions and 103 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)' | cut -d ':' -f1; 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)' | cut -d ':' -f1; then echo 'building build image'; docker build -f $(BUILD_DOCKERFILE) -t $(BUILD_IMAGE) .; fi"
mkdir -p $(PROJECT_DIR)/bin mkdir -p $(PROJECT_DIR)/bin
docker run -it \ docker run -it \
--rm \ --rm \

View File

@@ -17,17 +17,23 @@ Inotify watchers placed on selected parts of the file system trigger these scans
## Getting started ## Getting started
Get the tool onto the Linux machine you want to inspect. ### Download
First get the binaries.
Get the tool onto the Linux machine you want to inspect.
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)
- 64 bit big, static version: `pspy64` [download](https://github.com/DominicBreuker/pspy/releases/download/v1.0.0/pspy64)
- 32 bit small version: `pspy32s` [download](https://github.com/DominicBreuker/pspy/releases/download/v1.0.0/pspy32s)
- 64 bit small version: `pspy64s` [download](https://github.com/DominicBreuker/pspy/releases/download/v1.0.0/pspy64s)
You can build them yourself by running `make build-build-image` to build a docker image used in `make build` to build four binaries:
- 32 bit big, static version: `pspy32`
- 64 bit big, static version: `pspy64`
- 32 bit small version: `pspy32s`
- 64 bit small version: `pspy64s`
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).
### Build
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.
You can run `pspy --help` to learn about the flags and their meaning. You can run `pspy --help` to learn about the flags and their meaning.
The summary is as follows: The summary is as follows:
- -p: enables printing commands to stdout (enabled by default) - -p: enables printing commands to stdout (enabled by default)

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

@@ -17,7 +17,8 @@ const maximumWatchersFile = "/proc/sys/fs/inotify/max_user_watches"
// set to -1 if the number cannot be determined // set to -1 if the number cannot be determined
var MaxWatchers int = -1 var MaxWatchers int = -1
const EventSize int = unix.SizeofInotifyEvent // sizeof(struct inotify_event) + NAME_MAX + 1
const EventSize int = unix.SizeofInotifyEvent + 255 + 1
func init() { func init() {
mw, err := getMaxWatchers() mw, err := getMaxWatchers()
@@ -71,7 +72,7 @@ func (i *Inotify) Watch(dir string) error {
func (i *Inotify) Read(buf []byte) (int, error) { func (i *Inotify) Read(buf []byte) (int, error) {
n, errno := unix.Read(i.FD, buf) n, errno := unix.Read(i.FD, buf)
if n < 0 { if n < 1 {
return n, fmt.Errorf("reading from inotify fd %d: errno: %d", i.FD, errno) return n, fmt.Errorf("reading from inotify fd %d: errno: %d", i.FD, errno)
} }
return n, nil return n, nil

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: