mirror of
https://github.com/DominicBreuker/pspy.git
synced 2025-12-21 11:44:51 +00:00
672 lines
15 KiB
Go
672 lines
15 KiB
Go
package psscanner
|
|
|
|
import (
|
|
//"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const timeout = 100 * time.Millisecond
|
|
|
|
func TestRun(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pids []int
|
|
events []string
|
|
}{
|
|
{
|
|
name: "nominal",
|
|
pids: []int{1, 2, 3},
|
|
events: []string{
|
|
"UID=??? PID=3 | the-command",
|
|
"UID=??? PID=2 | the-command",
|
|
"UID=??? PID=1 | the-command",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
defer mockPidList(tt.pids, t)()
|
|
for _, pid := range tt.pids {
|
|
defer mockPidCmdLine(pid, []byte("the-command"), nil, nil, t)()
|
|
defer mockPidUid(pid, 0, errors.New("file not found"), t)()
|
|
}
|
|
|
|
pss := NewPSScanner(false, 2048)
|
|
triggerCh := make(chan struct{})
|
|
eventCh, errCh := pss.Run(triggerCh)
|
|
|
|
// does nothing without triggering
|
|
select {
|
|
case e := <-eventCh:
|
|
t.Errorf("Received event before trigger: %s", e)
|
|
case err := <-errCh:
|
|
t.Errorf("Received error before trigger: %v", err)
|
|
case <-time.After(timeout):
|
|
// ok
|
|
}
|
|
|
|
triggerCh <- struct{}{}
|
|
|
|
// received event after the trigger
|
|
for i := 0; i < 3; i++ {
|
|
select {
|
|
case <-time.After(timeout):
|
|
t.Errorf("did not receive event in time")
|
|
case e := <-eventCh:
|
|
if e.String() != tt.events[i] {
|
|
t.Errorf("Wrong event received: got '%s' but wanted '%s'", e, tt.events[i])
|
|
}
|
|
case err := <-errCh:
|
|
t.Errorf("Received unexpected error: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
var (
|
|
completeStat = []byte("1314 (some proc with) odd chars)) in name) R 5560 1314 5560 34821 1314 4194304 82 0 0 0 0 0 0 0 20 0 1 0 15047943 7790592 196 18446744073709551615 94260770430976 94260770462160 140725974097504 0 0 0 0 0 0 0 0 0 17 1 0 0 0 0 0 94260772559472 94260772561088 94260783992832 140725974106274 140725974106294 140725974106294 140725974110191 0\n")
|
|
partialStat = []byte("1314 (ps) ")
|
|
invalidPpid = []byte("1314 (ps) R XYZ 1314 5560 34821 1314 4194304 82 0 0 0 0 0 0 0 20 0 1 0 15047943 7790592 196 18446744073709551615 94260770430976 94260770462160 140725974097504 0 0 0 0 0 0 0 0 0 17 1 0 0 0 0 0 94260772559472 94260772561088 94260783992832 140725974106274 140725974106294 140725974106294 140725974110191 0\n")
|
|
)
|
|
|
|
func TestProcessNewPid(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
enablePpid bool
|
|
truncate int
|
|
pid int
|
|
cmdLine []byte
|
|
cmdLineErrRead error
|
|
cmdLineErrOpen error
|
|
stat []byte
|
|
statErrRead error
|
|
statErrOpen error
|
|
lstatUid uint32
|
|
lstatErr error
|
|
expected PSEvent
|
|
}{
|
|
{
|
|
name: "nominal-no-ppid",
|
|
enablePpid: false,
|
|
truncate: 100,
|
|
pid: 1,
|
|
cmdLine: []byte("abc\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: completeStat,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 0,
|
|
PID: 1,
|
|
PPID: -1,
|
|
CMD: "abc 123",
|
|
},
|
|
},
|
|
{
|
|
name: "nominal-ppid",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 1,
|
|
cmdLine: []byte("abc\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: completeStat,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 999,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 999,
|
|
PID: 1,
|
|
PPID: 5560,
|
|
CMD: "abc 123",
|
|
},
|
|
},
|
|
{
|
|
name: "empty-cmd-ok",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 1,
|
|
cmdLine: []byte{},
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: completeStat,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 0,
|
|
PID: 1,
|
|
PPID: 5560,
|
|
CMD: "",
|
|
},
|
|
},
|
|
{
|
|
name: "cmd-truncate",
|
|
enablePpid: false,
|
|
truncate: 10,
|
|
pid: 1,
|
|
cmdLine: []byte("abc\x00123\x00alpha"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: completeStat,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 0,
|
|
PID: 1,
|
|
PPID: -1,
|
|
CMD: "abc 123 al",
|
|
},
|
|
},
|
|
{
|
|
name: "cmd-io-error",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 2,
|
|
cmdLine: nil,
|
|
cmdLineErrRead: errors.New("file-system-error"),
|
|
cmdLineErrOpen: nil,
|
|
stat: completeStat,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 0,
|
|
PID: 2,
|
|
PPID: 5560,
|
|
CMD: "???",
|
|
},
|
|
},
|
|
{
|
|
name: "cmd-io-error2",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 2,
|
|
cmdLine: nil,
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: errors.New("file-system-error"),
|
|
stat: completeStat,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 0,
|
|
PID: 2,
|
|
PPID: 5560,
|
|
CMD: "???",
|
|
},
|
|
},
|
|
{
|
|
name: "stat-io-error",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 2,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: nil,
|
|
statErrRead: errors.New("file-system-error"),
|
|
statErrOpen: nil,
|
|
lstatUid: 321,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 321,
|
|
PID: 2,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
{
|
|
name: "stat-io-error2",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 2,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: nil,
|
|
statErrRead: nil,
|
|
statErrOpen: errors.New("file-system-error"),
|
|
lstatUid: 4454,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 4454,
|
|
PID: 2,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
{
|
|
name: "lstat-fail",
|
|
enablePpid: false,
|
|
truncate: 100,
|
|
pid: 3,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: completeStat,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: errors.New("file not found"),
|
|
expected: PSEvent{
|
|
UID: -1,
|
|
PID: 3,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
{
|
|
name: "lstat-with-ppid",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 3,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: completeStat,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: errors.New("file not found"),
|
|
expected: PSEvent{
|
|
UID: -1,
|
|
PID: 3,
|
|
PPID: 5560,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
{
|
|
name: "stat-too-short",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 3,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: partialStat,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 66,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 66,
|
|
PID: 3,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
{
|
|
name: "stat-bad-ppid",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 3,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: invalidPpid,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 66,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 66,
|
|
PID: 3,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
{
|
|
name: "stat-empty",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 3,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: []byte{},
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 88,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 88,
|
|
PID: 3,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
/*{
|
|
name: "uid-line-too-short",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 3,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: uidLineBroken,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: -1,
|
|
PID: 3,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
{
|
|
name: "uid-parse-error",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 3,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: uidNaN,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: -1,
|
|
PID: 3,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
{
|
|
name: "ppid-line-too-short",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 3,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: ppidLineShort,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: -1,
|
|
PID: 3,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
{
|
|
name: "ppid-parse-error",
|
|
enablePpid: true,
|
|
truncate: 100,
|
|
pid: 3,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: ppidNaN,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: -1,
|
|
PID: 3,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
{
|
|
name: "no-ppid-line-too-short",
|
|
enablePpid: false,
|
|
truncate: 100,
|
|
pid: 3,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: ppidLineShort,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 0,
|
|
PID: 3,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},
|
|
{
|
|
name: "no-ppid-parse-error",
|
|
enablePpid: false,
|
|
truncate: 100,
|
|
pid: 3,
|
|
cmdLine: []byte("some\x00cmd\x00123"),
|
|
cmdLineErrRead: nil,
|
|
cmdLineErrOpen: nil,
|
|
stat: ppidNaN,
|
|
statErrRead: nil,
|
|
statErrOpen: nil,
|
|
lstatUid: 0,
|
|
lstatErr: nil,
|
|
expected: PSEvent{
|
|
UID: 0,
|
|
PID: 3,
|
|
PPID: -1,
|
|
CMD: "some cmd 123",
|
|
},
|
|
},*/
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
defer mockPidCmdLine(tt.pid, tt.cmdLine, tt.cmdLineErrRead, tt.cmdLineErrOpen, t)()
|
|
defer mockPidStat(tt.pid, tt.stat, tt.statErrRead, tt.statErrOpen, t)()
|
|
defer mockPidUid(tt.pid, tt.lstatUid, tt.lstatErr, t)()
|
|
|
|
results := make(chan PSEvent, 1)
|
|
|
|
scanner := &PSScanner{
|
|
enablePpid: tt.enablePpid,
|
|
eventCh: results,
|
|
maxCmdLength: tt.truncate,
|
|
}
|
|
|
|
go func() {
|
|
scanner.processNewPid(tt.pid)
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(timeout):
|
|
t.Error("Timeout waiting for event")
|
|
case event := <-results:
|
|
close(results)
|
|
if testing.Verbose() {
|
|
t.Logf("received event: %#v", event)
|
|
}
|
|
if !reflect.DeepEqual(event, tt.expected) {
|
|
t.Errorf("Event received but format is has unexpected values: got %#v but want %#v", event, tt.expected)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func mockPidStat(pid int, stat []byte, errRead error, errOpen error, t *testing.T) func() {
|
|
return mockFile(fmt.Sprintf("/proc/%d/stat", pid), stat, errRead, errOpen, t)
|
|
}
|
|
|
|
func mockPidCmdLine(pid int, cmdline []byte, errRead error, errOpen error, t *testing.T) func() {
|
|
return mockFile(fmt.Sprintf("/proc/%d/cmdline", pid), cmdline, errRead, errOpen, t)
|
|
}
|
|
|
|
type MockFile struct {
|
|
content []byte
|
|
err error
|
|
}
|
|
|
|
func (f *MockFile) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (f *MockFile) Read(p []byte) (int, error) {
|
|
return copy(p, f.content), f.err
|
|
}
|
|
|
|
// Hook/chain a mocked file into the "open" variable
|
|
func mockFile(name string, content []byte, errRead error, errOpen error, t *testing.T) func() {
|
|
oldopen := open
|
|
open = func(n string) (io.ReadCloser, error) {
|
|
if name == n {
|
|
if testing.Verbose() {
|
|
t.Logf("opening mocked file: %s", n)
|
|
}
|
|
return &MockFile{
|
|
content: content,
|
|
err: errRead,
|
|
}, errOpen
|
|
}
|
|
return oldopen(n)
|
|
}
|
|
return func() {
|
|
open = oldopen
|
|
}
|
|
}
|
|
|
|
func mockPidUid(pid int, uid uint32, err error, t *testing.T) func() {
|
|
return mockLStat(fmt.Sprintf("/proc/%d", pid), uid, err, t)
|
|
}
|
|
|
|
func mockLStat(name string, uid uint32, err error, t *testing.T) func() {
|
|
oldlstat := lstat
|
|
lstat = func(path string, stat *syscall.Stat_t) error {
|
|
if path == name {
|
|
if testing.Verbose() {
|
|
t.Logf("mocking lstat for %s", name)
|
|
}
|
|
stat.Uid = uid
|
|
return err
|
|
}
|
|
return oldlstat(path, stat)
|
|
}
|
|
return func() {
|
|
lstat = oldlstat
|
|
}
|
|
}
|
|
|
|
func TestNewPSScanner(t *testing.T) {
|
|
for _, tt := range []struct {
|
|
name string
|
|
ppid bool
|
|
cmdlen int
|
|
}{
|
|
{
|
|
name: "without-ppid",
|
|
ppid: false,
|
|
cmdlen: 30,
|
|
},
|
|
{
|
|
name: "with-ppid",
|
|
ppid: true,
|
|
cmdlen: 5000,
|
|
},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
expected := &PSScanner{
|
|
enablePpid: tt.ppid,
|
|
eventCh: nil,
|
|
maxCmdLength: tt.cmdlen,
|
|
}
|
|
new := NewPSScanner(tt.ppid, tt.cmdlen)
|
|
|
|
if !reflect.DeepEqual(new, expected) {
|
|
t.Errorf("Unexpected scanner initialisation state: got %#v but want %#v", new, expected)
|
|
}
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPSEvent(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
uid int
|
|
pid int
|
|
ppid int
|
|
cmd string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "nominal-with-ppid",
|
|
uid: 999,
|
|
pid: 123,
|
|
ppid: 321,
|
|
cmd: "some cmd",
|
|
expected: "UID=999 PID=123 PPID=321 | some cmd",
|
|
},
|
|
{
|
|
name: "nominal-without-ppid",
|
|
uid: 999,
|
|
pid: 123,
|
|
ppid: -1,
|
|
cmd: "some cmd",
|
|
expected: "UID=999 PID=123 | some cmd",
|
|
},
|
|
{
|
|
name: "nocmd-without-ppid",
|
|
uid: 999,
|
|
pid: 123,
|
|
ppid: -1,
|
|
cmd: "",
|
|
expected: "UID=999 PID=123 | ",
|
|
},
|
|
{
|
|
name: "nocmd-with-ppid",
|
|
uid: 999,
|
|
pid: 123,
|
|
ppid: 321,
|
|
cmd: "",
|
|
expected: "UID=999 PID=123 PPID=321 | ",
|
|
},
|
|
{
|
|
name: "nouid",
|
|
uid: -1,
|
|
pid: 123,
|
|
ppid: 321,
|
|
cmd: "some cmd",
|
|
expected: "UID=??? PID=123 PPID=321 | some cmd",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ps := PSEvent{
|
|
UID: tt.uid,
|
|
PID: tt.pid,
|
|
PPID: tt.ppid,
|
|
CMD: tt.cmd,
|
|
}
|
|
if ps.String() != tt.expected {
|
|
t.Errorf("Expecting \"%s\", got \"%s\"", tt.expected, ps.String())
|
|
}
|
|
})
|
|
}
|
|
}
|