diff --git a/.idea/runConfigurations/ServerWatchdog.xml b/.idea/runConfigurations/ServerWatchdog.xml index a0a36ed..2a5001c 100644 --- a/.idea/runConfigurations/ServerWatchdog.xml +++ b/.idea/runConfigurations/ServerWatchdog.xml @@ -1,5 +1,5 @@ - + diff --git a/README.md b/README.md index b276c4c..3fcae0c 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ Designates the target Slack WebHook channel for messaage delivery. The channel f ##### `channels.{channel-name}.slack.severity` -Sets the severity type of the notification: `error`, `warn` or `info`. +Sets the severity type of the notification: `error`, `warn`, `info` or `debug`. ##### `channels.{channel-name}.email` (optional) @@ -283,7 +283,7 @@ Establishes the channel to use when a notification must be sent because the chec ##### `webs[].severity` -Sets the severity type of the notification: `error`, `warn` or `info`. +Sets the severity type of the notification: `error`, `warn`, `info` or `debug`. #### `tcpPorts` (optional) @@ -311,7 +311,7 @@ Establishes the channel to use when a notification must be sent because the any ##### `tcpPorts[].severity` -Sets the severity type of the notification: `error`, `warn` or `info`. +Sets the severity type of the notification: `error`, `warn`, `info` or `debug`. #### `processes` (optional) @@ -335,7 +335,7 @@ Establishes the channel to use when a notification must be sent because the proc ##### `processes[].severity` -Sets the severity type of the notification: `error`, `warn` or `info`. +Sets the severity type of the notification: `error`, `warn`, `info` or `debug`. #### `freeDiskSpace` (optional) @@ -359,7 +359,7 @@ Establishes the channel to use when a notification must be sent because the chec ##### `freeDiskSpace[].severity` -Sets the severity type of the notification: `error`, `warn` or `info`. +Sets the severity type of the notification: `error`, `warn`, `info` or `debug`. # License diff --git a/src/console/console.go b/src/console/console.go index 79f4d8b..15783e1 100644 --- a/src/console/console.go +++ b/src/console/console.go @@ -14,9 +14,10 @@ import ( //------------------------------------------------------------------------------ const( - classInfo int = 0 + classError int = 0 classWarn = 1 - classError = 2 + classInfo = 2 + classDebug = 3 ) //------------------------------------------------------------------------------ @@ -34,8 +35,8 @@ func SetupService(s service.Service) error { return err } -func Info(format string, a ...interface{}) { - printCommon("", classInfo, getTimestamp(), fmt.Sprintf(format, a...)) +func Error(format string, a ...interface{}) { + printCommon("", classError, getTimestamp(), fmt.Sprintf(format, a...)) return } @@ -44,13 +45,18 @@ func Warn(format string, a ...interface{}) { return } -func Error(format string, a ...interface{}) { - printCommon("", classError, getTimestamp(), fmt.Sprintf(format, a...)) +func Info(format string, a ...interface{}) { + printCommon("", classInfo, getTimestamp(), fmt.Sprintf(format, a...)) return } -func LogInfo(title string, timestamp string, msg string) { - printCommon(title, classInfo, timestamp, msg) +func Debug(format string, a ...interface{}) { + printCommon("", classDebug, getTimestamp(), fmt.Sprintf(format, a...)) + return +} + +func LogError(title string, timestamp string, msg string) { + printCommon(title, classError, timestamp, msg) return } @@ -59,8 +65,13 @@ func LogWarn(title string, timestamp string, msg string) { return } -func LogError(title string, timestamp string, msg string) { - printCommon(title, classError, timestamp, msg) +func LogInfo(title string, timestamp string, msg string) { + printCommon(title, classInfo, timestamp, msg) + return +} + +func LogDebug(title string, timestamp string, msg string) { + printCommon(title, classDebug, timestamp, msg) return } @@ -71,7 +82,7 @@ func printCommon(title string, cls int, timestamp string, msg string) { m.Lock() defer m.Unlock() - if cls == classInfo { + if cls == classInfo || cls == classDebug { color.SetOutput(os.Stdout) } else { color.SetOutput(os.Stderr) @@ -80,12 +91,14 @@ func printCommon(title string, cls int, timestamp string, msg string) { color.Printf("%v ", timestamp) switch cls { - case classInfo: - color.Info.Print("[INFO]") - case classWarn: - color.Warn.Print("[WARN]") case classError: color.Error.Print("[ERROR]") + case classWarn: + color.Warn.Print("[WARN]") + case classInfo: + color.Info.Print("[INFO]") + case classDebug: + color.Debug.Print("[DEBUG]") } if len(title) > 0 { @@ -98,21 +111,25 @@ func printCommon(title string, cls int, timestamp string, msg string) { } else { if len(title) > 0 { switch cls { - case classInfo: - (*serviceLogger).Infof("[INFO] %v - %v", title, msg) - case classWarn: - (*serviceLogger).Warningf("[WARN] %v - %v", title, msg) case classError: (*serviceLogger).Errorf("[ERROR] %v - %v", title, msg) + case classWarn: + (*serviceLogger).Warningf("[WARN] %v - %v", title, msg) + case classInfo: + (*serviceLogger).Infof("[INFO] %v - %v", title, msg) + case classDebug: + (*serviceLogger).Infof("[DEBUG] %v - %v", title, msg) } } else { switch cls { - case classInfo: - (*serviceLogger).Infof("[INFO] - %v", msg) - case classWarn: - (*serviceLogger).Warningf("[WARN] - %v", msg) case classError: (*serviceLogger).Errorf("[ERROR] - %v", msg) + case classWarn: + (*serviceLogger).Warningf("[WARN] - %v", msg) + case classInfo: + (*serviceLogger).Infof("[INFO] - %v", msg) + case classDebug: + (*serviceLogger).Infof("[DEBUG] - %v", msg) } } } diff --git a/src/go.mod b/src/go.mod index d3f4936..7d44d33 100644 --- a/src/go.mod +++ b/src/go.mod @@ -19,3 +19,5 @@ require ( github.com/vmihailenco/msgpack/v4 v4.2.1 google.golang.org/appengine v1.6.5 // indirect ) + +replace github.com/shirou/gopsutil v2.19.12+incompatible => github.com/mxmauro/gopsutil v2.20.4+incompatible diff --git a/src/go.sum b/src/go.sum index d0b6f67..dc7a9f6 100644 --- a/src/go.sum +++ b/src/go.sum @@ -47,6 +47,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/djherbis/atime v1.0.0/go.mod h1:5W+KBIuTwVGcqjIfaTwt+KSYX1o6uep8dtevevQP/f8= @@ -209,6 +210,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxmauro/gopsutil v2.20.4+incompatible h1:PQMCZFzCD9A6hlXu3fLB5eCb/N8NqmH97S6iTPUB8SM= +github.com/mxmauro/gopsutil v2.20.4+incompatible/go.mod h1:aGKuDiZnqaSzhEZAyzPR5gbtmTYuI7Iwcz1mb+qhkDY= github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ= github.com/nats-io/go-nats v1.7.2/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0= github.com/nats-io/go-nats-streaming v0.4.4/go.mod h1:gfq4R3c9sKAINOpelo0gn/b9QDMBZnmrttcsNF+lqyo= @@ -286,6 +289,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tidwall/gjson v1.3.5/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= @@ -431,6 +435,7 @@ gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHO gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/src/modules/backend/handlers/handlers.go b/src/modules/backend/handlers/handlers.go index f19f9bc..4916256 100644 --- a/src/modules/backend/handlers/handlers.go +++ b/src/modules/backend/handlers/handlers.go @@ -93,7 +93,7 @@ func onPostWatchProcess(ctx *server.RequestCtx) { } //add to watch list - err = processwatcher.AddProcess(r.Pid, r.Name, r.Severity, r.Channel) + err = processwatcher.AddProcess(r.Pid, r.Name, r.MaxMemUsage, r.Severity, r.Channel) if err != nil { server.SendBadRequest(ctx, err.Error()) return diff --git a/src/modules/backend/handlers/models.go b/src/modules/backend/handlers/models.go index b6fc6e8..2872e64 100644 --- a/src/modules/backend/handlers/models.go +++ b/src/modules/backend/handlers/models.go @@ -9,10 +9,11 @@ type NotifyRequest struct { } type WatchProcessRequest struct { - Channel string `json:"channel"` - Pid int `json:"pid"` - Name string `json:"name,omitempty"` - Severity string `json:"severity,omitempty"` + Channel string `json:"channel"` + Pid int `json:"pid"` + MaxMemUsage string `json:"maxMem,omitempty"` + Name string `json:"name,omitempty"` + Severity string `json:"severity,omitempty"` } type UnwatchProcessRequest struct { diff --git a/src/modules/logger/email/email.go b/src/modules/logger/email/email.go index d73b526..82abb64 100644 --- a/src/modules/logger/email/email.go +++ b/src/modules/logger/email/email.go @@ -66,8 +66,8 @@ func Run(wg sync.WaitGroup) { return } -func Info(channel string, timestamp string, msg string) { - emailModule.sendEmailNotification(channel, "[INFO]", timestamp, msg) +func Error(channel string, timestamp string, msg string) { + emailModule.sendEmailNotification(channel, "[ERROR]", timestamp, msg) return } @@ -76,8 +76,13 @@ func Warn(channel string, timestamp string, msg string) { return } -func Error(channel string, timestamp string, msg string) { - emailModule.sendEmailNotification(channel, "[ERROR]", timestamp, msg) +func Info(channel string, timestamp string, msg string) { + emailModule.sendEmailNotification(channel, "[INFO]", timestamp, msg) + return +} + +func Debug(channel string, timestamp string, msg string) { + emailModule.sendEmailNotification(channel, "[DEBUG]", timestamp, msg) return } diff --git a/src/modules/logger/file/file.go b/src/modules/logger/file/file.go index 0d6592f..1cdb9f0 100644 --- a/src/modules/logger/file/file.go +++ b/src/modules/logger/file/file.go @@ -136,8 +136,8 @@ func Run(wg sync.WaitGroup) { return } -func Info(channel string, timestamp string, msg string) { - fileModule.writeFileLog(channel, "[INFO]", timestamp, msg) +func Error(channel string, timestamp string, msg string) { + fileModule.writeFileLog(channel, "[ERROR]", timestamp, msg) return } @@ -146,8 +146,13 @@ func Warn(channel string, timestamp string, msg string) { return } -func Error(channel string, timestamp string, msg string) { - fileModule.writeFileLog(channel, "[ERROR]", timestamp, msg) +func Info(channel string, timestamp string, msg string) { + fileModule.writeFileLog(channel, "[INFO]", timestamp, msg) + return +} + +func Debug(channel string, timestamp string, msg string) { + fileModule.writeFileLog(channel, "[DEBUG]", timestamp, msg) return } diff --git a/src/modules/logger/log.go b/src/modules/logger/log.go index bd14c5f..58bf1dc 100644 --- a/src/modules/logger/log.go +++ b/src/modules/logger/log.go @@ -54,6 +54,9 @@ func Log(severity string, channel string, format string, a ...interface{}) error case "info": LogInfo(channel, format, a...) + case "debug": + LogDebug(channel, format, a...) + default: return errors.New("Invalid severity") } @@ -94,6 +97,17 @@ func LogInfo(channel string, format string, a ...interface{}) { return } +func LogDebug(channel string, format string, a ...interface{}) { + timestamp := getTimestamp() + msg := fmt.Sprintf(format, a...) + + console.LogDebug(channel, timestamp, msg) + file.Debug(channel, timestamp, msg) + slack.Debug(channel, timestamp, msg) + email.Debug(channel, timestamp, msg) + return +} + func getTimestamp() string { now := time.Now() if !settings.Config.Log.UseLocalTime { diff --git a/src/modules/logger/slack/slack.go b/src/modules/logger/slack/slack.go index 87aeb6f..ac45fa5 100644 --- a/src/modules/logger/slack/slack.go +++ b/src/modules/logger/slack/slack.go @@ -70,8 +70,8 @@ func Run(wg sync.WaitGroup) { return } -func Info(channel string, timestamp string, msg string) { - slackModule.sendSlackNotification(channel, "[INFO]", timestamp, msg) +func Error(channel string, timestamp string, msg string) { + slackModule.sendSlackNotification(channel, "[ERROR]", timestamp, msg) return } @@ -80,8 +80,13 @@ func Warn(channel string, timestamp string, msg string) { return } -func Error(channel string, timestamp string, msg string) { - slackModule.sendSlackNotification(channel, "[ERROR]", timestamp, msg) +func Info(channel string, timestamp string, msg string) { + slackModule.sendSlackNotification(channel, "[INFO]", timestamp, msg) + return +} + +func Debug(channel string, timestamp string, msg string) { + slackModule.sendSlackNotification(channel, "[DEBUG", timestamp, msg) return } diff --git a/src/modules/processwatcher/processwatcher.go b/src/modules/processwatcher/processwatcher.go index 138857b..e6e91b6 100644 --- a/src/modules/processwatcher/processwatcher.go +++ b/src/modules/processwatcher/processwatcher.go @@ -32,6 +32,7 @@ type ProcessItem struct { Name string Channel string Severity string + MaxMemUsage string } //------------------------------------------------------------------------------ @@ -115,7 +116,7 @@ func Run(wg sync.WaitGroup) { return } -func AddProcess(pid int, name string, severity string, channel string) error { +func AddProcess(pid int, name string, maxMemUsage string, severity string, channel string) error { lock.RLock() localModule := module lock.RUnlock() @@ -126,7 +127,7 @@ func AddProcess(pid int, name string, severity string, channel string) error { var err error = nil if localModule.r.Acquire() { - err = localModule.addProcessInternal(pid, name, severity, channel) + err = localModule.addProcessInternal(pid, name, maxMemUsage, severity, channel) if err == nil { localModule.runSaveState() } else { @@ -182,7 +183,7 @@ func RemoveProcess(pid int, channel string) error { //------------------------------------------------------------------------------ -func (m *Module) addProcessInternal(pid int, name string, severity string, channel string) error { +func (m *Module) addProcessInternal(pid int, name string, maxMemUsage string, severity string, channel string) error { var i int var err error = nil @@ -364,7 +365,7 @@ func (m *Module) checkForNewProcesses() { name = filepath.Base(exeName) } - err = m.addProcessInternal(int(proc.Pid), name, cfgProc.Severity, cfgProc.Channel) + err = m.addProcessInternal(int(proc.Pid), name, "", cfgProc.Severity, cfgProc.Channel) if err != nil { console.Error("Unable to watch process #%v (%v) [%v]", int(proc.Pid), name, err.Error()) } diff --git a/src/modules/processwatcher/storage.go b/src/modules/processwatcher/storage.go index 4a90716..9846e3f 100644 --- a/src/modules/processwatcher/storage.go +++ b/src/modules/processwatcher/storage.go @@ -11,6 +11,7 @@ import ( type ProcessWatcherStateItem struct { Pid int Name string + MaxMemUsage string Channel string Severity string } @@ -37,7 +38,7 @@ func (m *Module) loadState() error { var stateModified = false for _, v := range loadedItems { - err = m.addProcessInternal(v.Pid, v.Name, v.Severity, v.Channel) + err = m.addProcessInternal(v.Pid, v.Name, v.MaxMemUsage, v.Severity, v.Channel) if err != nil { stateModified = true @@ -65,6 +66,7 @@ func (m *Module) saveState() error { toSave[idx] = ProcessWatcherStateItem{ Pid : v.Pid, Name : v.Name, + MaxMemUsage : v.MaxMemUsage, Channel : v.Channel, Severity : v.Severity, } diff --git a/src/modules/webchecker/webchecker.go b/src/modules/webchecker/webchecker.go index 6d6fc8c..40dd9af 100644 --- a/src/modules/webchecker/webchecker.go +++ b/src/modules/webchecker/webchecker.go @@ -1,6 +1,7 @@ package webchecker import ( + "github.com/randlabs/server-watchdog/modules/logger" "hash/fnv" "io/ioutil" "net/http" @@ -11,7 +12,6 @@ import ( rp "github.com/randlabs/rundown-protection" "github.com/randlabs/server-watchdog/console" - "github.com/randlabs/server-watchdog/modules/logger" "github.com/randlabs/server-watchdog/settings" ) @@ -230,49 +230,52 @@ func (m *Module) checkWebs(elapsedTime time.Duration) { resp, err := client.Do(req) if err == nil { if resp.StatusCode == http.StatusOK { + bodyString := "" if web.Content != nil { bodyBytes, err := ioutil.ReadAll(resp.Body) if err == nil { - allMatches := true - - bodyString := string(bodyBytes) - - for contentIdx := range web.Content { - wc := &web.Content[contentIdx] + bodyString = string(bodyBytes) + } + } - matches := wc.SearchRegex.FindStringSubmatch(bodyString) - matchesCount := uint(len(matches)) - if matchesCount > 0 { - matches = matches[1:] - matchesCount -= 1 - } + if err == nil { + allMatches := true - for idx := range wc.CheckChanges { - matchIndex := wc.CheckChanges[idx] - 1 - if matchIndex >= matchesCount { - allMatches = false - break - } + for contentIdx := range web.Content { + wc := &web.Content[contentIdx] - if web.Content[contentIdx].LastContent[idx] == matches[matchIndex] { - break - } + matches := wc.SearchRegex.FindStringSubmatch(bodyString) + matchesCount := uint(len(matches)) + if matchesCount > 0 { + matches = matches[1:] + matchesCount -= 1 + } - web.Content[contentIdx].LastContent[idx] = matches[matchIndex] + for idx := range wc.CheckChanges { + matchIndex := wc.CheckChanges[idx] - 1 + if matchIndex >= matchesCount { allMatches = false + break } - if !allMatches { + if web.Content[contentIdx].LastContent[idx] == matches[matchIndex] { break } + + web.Content[contentIdx].LastContent[idx] = matches[matchIndex] + allMatches = false } if !allMatches { - newStatus = 1 + break } } - } else { - newStatus = 1 + + if !allMatches { + newStatus = 1 + } else { + newStatus = -1 + } } } @@ -281,25 +284,22 @@ func (m *Module) checkWebs(elapsedTime time.Duration) { oldStatus := atomic.SwapInt32(&web.LastCheckStatus, newStatus) if oldStatus != newStatus { - if newStatus == 0 { - for contentIdx := range web.Content { - for idx := range web.Content[contentIdx].LastContent { - web.Content[contentIdx].LastContent[idx] = "" - } - } - } - m.runSaveState() } //notify only if status changed from true to false - if oldStatus == 1 && newStatus == 0 { + if oldStatus == 1 && newStatus <= 0 { if m.r.Acquire() { - go func(web *WebItem) { - _ = logger.Log(web.Severity, web.Channel, "Site '%s' is down.", web.Url) + go func(web *WebItem, newStatus int32) { + switch newStatus { + case 0: + _ = logger.Log(web.Severity, web.Channel, "Site '%s' is down.", web.Url) + case -1: + _ = logger.Log(web.Severity, web.Channel, "Site '%s' is stalled.", web.Url) + } m.r.Release() - }(web) + }(web, newStatus) } } diff --git a/src/settings/models.go b/src/settings/models.go index bf64cab..0802cb4 100644 --- a/src/settings/models.go +++ b/src/settings/models.go @@ -60,12 +60,13 @@ type SettingsJSON_EMail_SmtpServer struct { } type SettingsJSON_Processes struct { - FriendlyName string `json:"name,omitempty"` - ExecutableName string `json:"executableName"` + FriendlyName string `json:"name,omitempty"` + ExecutableName string `json:"executableName"` CommandLineParams string `json:"args,omitempty"` - IncludeChilds bool `json:"includeChilds,omitempty"` - Channel string `json:"channel"` - Severity string `json:"severity,omitempty"` + IncludeChilds bool `json:"includeChilds,omitempty"` + MaxMemUsage string `json:"maxMem,omitempty"` + Channel string `json:"channel"` + Severity string `json:"severity,omitempty"` } type SettingsJSON_Webs struct { diff --git a/src/settings/settings.go b/src/settings/settings.go index e19b868..acba1ce 100644 --- a/src/settings/settings.go +++ b/src/settings/settings.go @@ -16,6 +16,7 @@ import ( valid "github.com/asaskevich/govalidator" "github.com/randlabs/server-watchdog/utils/process" "github.com/randlabs/server-watchdog/utils/stringparser" + "github.com/ricochet2200/go-disk-usage/du" ) //------------------------------------------------------------------------------ @@ -76,7 +77,7 @@ func Load() error { //---- if len(Config.Log.MaxAge) > 0 { - Config.Log.MaxAgeX, ok = parseDuration(Config.Log.MaxAge) + Config.Log.MaxAgeX, ok = ValidateTimeSpan(Config.Log.MaxAge) if !ok { return errors.New("Invalid log files max age value.") } @@ -187,7 +188,7 @@ func Load() error { } if len(web.CheckPeriod) > 0 { - web.CheckPeriodX, ok = parseDuration(web.CheckPeriod) + web.CheckPeriodX, ok = ValidateTimeSpan(web.CheckPeriod) if !ok { return errors.New(fmt.Sprintf("Invalid web check period value for web \"%v\".", web.Url)) } @@ -218,7 +219,7 @@ func Load() error { } if len(web.Timeout) > 0 { - web.TimeoutX, ok = parseDuration(web.Timeout) + web.TimeoutX, ok = ValidateTimeSpan(web.Timeout) if !ok { return errors.New(fmt.Sprintf("Invalid web check timeout value for web \"%v\".", web.Url)) } @@ -259,7 +260,7 @@ func Load() error { } if len(port.CheckPeriod) > 0 { - port.CheckPeriodX, ok = parseDuration(port.CheckPeriod) + port.CheckPeriodX, ok = ValidateTimeSpan(port.CheckPeriod) if !ok { return errors.New(fmt.Sprintf("Invalid check period value for TCP port group \"%v\".", port.Name)) } @@ -271,7 +272,7 @@ func Load() error { } if len(port.Timeout) > 0 { - port.TimeoutX, ok = parseDuration(port.Timeout) + port.TimeoutX, ok = ValidateTimeSpan(port.Timeout) if !ok { return errors.New(fmt.Sprintf("Invalid check timeout value for TCP port group \"%v\".", port.Name)) } @@ -309,14 +310,20 @@ func Load() error { } } - fds.MinimumSpaceX, ok = parseMinimumRequiredSpace(fds.MinimumSpace) + diskUsage := du.NewDiskUsage(fds.Device) + if diskUsage.Size() == 0 { + return errors.New(fmt.Sprintf("Invalid or missing free disk space check device \"%v\".", fds.Device)) + } + diskSize := diskUsage.Size() + + fds.MinimumSpaceX, ok = ValidateMemoryAmount(fds.MinimumSpace, &diskSize) if !ok { return errors.New(fmt.Sprintf("Invalid free disk space check minimum value for device \"%v\".", - fds.Device)) + fds.Device)) } if len(fds.CheckPeriod) > 0 { - fds.CheckPeriodX, ok = parseDuration(fds.CheckPeriod) + fds.CheckPeriodX, ok = ValidateTimeSpan(fds.CheckPeriod) if !ok { return errors.New(fmt.Sprintf("Invalid free disk space check period value for device \"%v\".", fds.Device)) @@ -362,6 +369,8 @@ func ValidateSeverity(severity string) string { case "warn": fallthrough case "info": + fallthrough + case "debug": return severity case "warning": return "warn" @@ -378,9 +387,12 @@ func ValidateChannel(channel string) bool { return ok } -//------------------------------------------------------------------------------ +func ValidateMaxMemoryUsage(channel string) bool { + _, ok := Config.Channels[channel] + return ok +} -func parseDuration(t string) (time.Duration, bool) { +func ValidateTimeSpan(t string) (time.Duration, bool) { var d time.Duration width := stringparser.SkipSpaces(t) @@ -470,7 +482,7 @@ func parseDuration(t string) (time.Duration, bool) { return d, true } -func parseMinimumRequiredSpace(t string) (uint64, bool) { +func ValidateMemoryAmount(t string, totalAvailable *uint64) (uint64, bool) { var siz uint64 var w int @@ -549,6 +561,15 @@ func parseMinimumRequiredSpace(t string) (uint64, bool) { } siz = uint64(value * 1073741824.0) + case "%": + if totalAvailable == nil { + return 0, false + } + if value < 0 || value > 100.0 { + return 0, false + } + siz = uint64(value / 100.0 * float64(*totalAvailable)) + default: return 0, false } @@ -556,6 +577,8 @@ func parseMinimumRequiredSpace(t string) (uint64, bool) { return siz, true } +//------------------------------------------------------------------------------ + func parsePortsList(p string) (*roaring.Bitmap, bool) { var s string var port int diff --git a/src/vendor/github.com/shirou/gopsutil/internal/common/common.go b/src/vendor/github.com/shirou/gopsutil/internal/common/common.go index 4ca8bc9..d46aaeb 100644 --- a/src/vendor/github.com/shirou/gopsutil/internal/common/common.go +++ b/src/vendor/github.com/shirou/gopsutil/internal/common/common.go @@ -338,6 +338,10 @@ func HostRun(combineWith ...string) string { return GetEnv("HOST_RUN", "/run", combineWith...) } +func HostDev(combineWith ...string) string { + return GetEnv("HOST_DEV", "/dev", combineWith...) +} + // getSysctrlEnv sets LC_ALL=C in a list of env vars for use when running // sysctl commands (see DoSysctrl). func getSysctrlEnv(env []string) []string { diff --git a/src/vendor/github.com/shirou/gopsutil/internal/common/common_windows.go b/src/vendor/github.com/shirou/gopsutil/internal/common/common_windows.go index dbc8b67..9bc05de 100644 --- a/src/vendor/github.com/shirou/gopsutil/internal/common/common_windows.go +++ b/src/vendor/github.com/shirou/gopsutil/internal/common/common_windows.go @@ -49,21 +49,33 @@ const ( PDH_NO_DATA = 0x800007d5 ) +const ( + ProcessBasicInformation = 0 + ProcessWow64Information = 26 +) + var ( Modkernel32 = windows.NewLazySystemDLL("kernel32.dll") ModNt = windows.NewLazySystemDLL("ntdll.dll") ModPdh = windows.NewLazySystemDLL("pdh.dll") ModPsapi = windows.NewLazySystemDLL("psapi.dll") - ProcGetSystemTimes = Modkernel32.NewProc("GetSystemTimes") - ProcNtQuerySystemInformation = ModNt.NewProc("NtQuerySystemInformation") - PdhOpenQuery = ModPdh.NewProc("PdhOpenQuery") - PdhAddCounter = ModPdh.NewProc("PdhAddCounterW") - PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData") - PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue") - PdhCloseQuery = ModPdh.NewProc("PdhCloseQuery") - - procQueryDosDeviceW = Modkernel32.NewProc("QueryDosDeviceW") + ProcGetSystemTimes = Modkernel32.NewProc("GetSystemTimes") + ProcNtQuerySystemInformation = ModNt.NewProc("NtQuerySystemInformation") + ProcRtlGetNativeSystemInformation = ModNt.NewProc("RtlGetNativeSystemInformation") + ProcRtlNtStatusToDosError = ModNt.NewProc("RtlNtStatusToDosError") + ProcNtQueryInformationProcess = ModNt.NewProc("NtQueryInformationProcess") + ProcNtReadVirtualMemory = ModNt.NewProc("NtReadVirtualMemory") + ProcNtWow64QueryInformationProcess64 = ModNt.NewProc("NtWow64QueryInformationProcess64") + ProcNtWow64ReadVirtualMemory64 = ModNt.NewProc("NtWow64ReadVirtualMemory64") + + PdhOpenQuery = ModPdh.NewProc("PdhOpenQuery") + PdhAddCounter = ModPdh.NewProc("PdhAddCounterW") + PdhCollectQueryData = ModPdh.NewProc("PdhCollectQueryData") + PdhGetFormattedCounterValue = ModPdh.NewProc("PdhGetFormattedCounterValue") + PdhCloseQuery = ModPdh.NewProc("PdhCloseQuery") + + procQueryDosDeviceW = Modkernel32.NewProc("QueryDosDeviceW") ) type FILETIME struct { diff --git a/src/vendor/github.com/shirou/gopsutil/mem/mem_linux.go b/src/vendor/github.com/shirou/gopsutil/mem/mem_linux.go index 41355ba..66ccca9 100644 --- a/src/vendor/github.com/shirou/gopsutil/mem/mem_linux.go +++ b/src/vendor/github.com/shirou/gopsutil/mem/mem_linux.go @@ -4,6 +4,7 @@ package mem import ( "context" + "encoding/json" "math" "os" "strconv" @@ -16,6 +17,14 @@ import ( type VirtualMemoryExStat struct { ActiveFile uint64 `json:"activefile"` InactiveFile uint64 `json:"inactivefile"` + ActiveAnon uint64 `json:"activeanon"` + InactiveAnon uint64 `json:"inactiveanon"` + Unevictable uint64 `json:"unevictable"` +} + +func (v VirtualMemoryExStat) String() string { + s, _ := json.Marshal(v) + return string(s) } func VirtualMemory() (*VirtualMemoryStat, error) { @@ -23,6 +32,26 @@ func VirtualMemory() (*VirtualMemoryStat, error) { } func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { + vm, _, err := fillFromMeminfoWithContext(ctx) + if err != nil { + return nil, err + } + return vm, nil +} + +func VirtualMemoryEx() (*VirtualMemoryExStat, error) { + return VirtualMemoryExWithContext(context.Background()) +} + +func VirtualMemoryExWithContext(ctx context.Context) (*VirtualMemoryExStat, error) { + _, vmEx, err := fillFromMeminfoWithContext(ctx) + if err != nil { + return nil, err + } + return vmEx, nil +} + +func fillFromMeminfoWithContext(ctx context.Context) (*VirtualMemoryStat, *VirtualMemoryExStat, error) { filename := common.HostProc("meminfo") lines, _ := common.ReadLines(filename) @@ -46,7 +75,7 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { t, err := strconv.ParseUint(value, 10, 64) if err != nil { - return ret, err + return ret, retEx,err } switch key { case "MemTotal": @@ -64,12 +93,18 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { ret.Active = t * 1024 case "Inactive": ret.Inactive = t * 1024 + case "Active(anon)": + retEx.ActiveAnon = t * 1024 + case "Inactive(anon)": + retEx.InactiveAnon = t * 1024 case "Active(file)": activeFile = true retEx.ActiveFile = t * 1024 case "Inactive(file)": inactiveFile = true retEx.InactiveFile = t * 1024 + case "Unevictable": + retEx.Unevictable = t * 1024 case "Writeback": ret.Writeback = t * 1024 case "WritebackTmp": @@ -135,7 +170,7 @@ func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) { ret.Used = ret.Total - ret.Free - ret.Buffers - ret.Cached ret.UsedPercent = float64(ret.Used) / float64(ret.Total) * 100.0 - return ret, nil + return ret, retEx, nil } func SwapMemory() (*SwapMemoryStat, error) { diff --git a/src/vendor/github.com/shirou/gopsutil/net/net.go b/src/vendor/github.com/shirou/gopsutil/net/net.go index c9a4baf..f1f99dc 100644 --- a/src/vendor/github.com/shirou/gopsutil/net/net.go +++ b/src/vendor/github.com/shirou/gopsutil/net/net.go @@ -3,11 +3,7 @@ package net import ( "context" "encoding/json" - "fmt" "net" - "strconv" - "strings" - "syscall" "github.com/shirou/gopsutil/internal/common" ) @@ -161,14 +157,6 @@ func (l *ConntrackStatList) Summary() []ConntrackStat { return []ConntrackStat{*summary} } -var constMap = map[string]int{ - "unix": syscall.AF_UNIX, - "TCP": syscall.SOCK_STREAM, - "UDP": syscall.SOCK_DGRAM, - "IPv4": syscall.AF_INET, - "IPv6": syscall.AF_INET6, -} - func (n IOCountersStat) String() string { s, _ := json.Marshal(n) return string(s) @@ -273,84 +261,3 @@ func getIOCountersAll(n []IOCountersStat) ([]IOCountersStat, error) { return []IOCountersStat{r}, nil } - -func parseNetLine(line string) (ConnectionStat, error) { - f := strings.Fields(line) - if len(f) < 8 { - return ConnectionStat{}, fmt.Errorf("wrong line,%s", line) - } - - if len(f) == 8 { - f = append(f, f[7]) - f[7] = "unix" - } - - pid, err := strconv.Atoi(f[1]) - if err != nil { - return ConnectionStat{}, err - } - fd, err := strconv.Atoi(strings.Trim(f[3], "u")) - if err != nil { - return ConnectionStat{}, fmt.Errorf("unknown fd, %s", f[3]) - } - netFamily, ok := constMap[f[4]] - if !ok { - return ConnectionStat{}, fmt.Errorf("unknown family, %s", f[4]) - } - netType, ok := constMap[f[7]] - if !ok { - return ConnectionStat{}, fmt.Errorf("unknown type, %s", f[7]) - } - - var laddr, raddr Addr - if f[7] == "unix" { - laddr.IP = f[8] - } else { - laddr, raddr, err = parseNetAddr(f[8]) - if err != nil { - return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s", f[8]) - } - } - - n := ConnectionStat{ - Fd: uint32(fd), - Family: uint32(netFamily), - Type: uint32(netType), - Laddr: laddr, - Raddr: raddr, - Pid: int32(pid), - } - if len(f) == 10 { - n.Status = strings.Trim(f[9], "()") - } - - return n, nil -} - -func parseNetAddr(line string) (laddr Addr, raddr Addr, err error) { - parse := func(l string) (Addr, error) { - host, port, err := net.SplitHostPort(l) - if err != nil { - return Addr{}, fmt.Errorf("wrong addr, %s", l) - } - lport, err := strconv.Atoi(port) - if err != nil { - return Addr{}, err - } - return Addr{IP: host, Port: uint32(lport)}, nil - } - - addrs := strings.Split(line, "->") - if len(addrs) == 0 { - return laddr, raddr, fmt.Errorf("wrong netaddr, %s", line) - } - laddr, err = parse(addrs[0]) - if len(addrs) == 2 { // remote addr exists - raddr, err = parse(addrs[1]) - if err != nil { - return laddr, raddr, err - } - } - - return laddr, raddr, err -} diff --git a/src/vendor/github.com/shirou/gopsutil/net/net_aix.go b/src/vendor/github.com/shirou/gopsutil/net/net_aix.go new file mode 100644 index 0000000..4ac8497 --- /dev/null +++ b/src/vendor/github.com/shirou/gopsutil/net/net_aix.go @@ -0,0 +1,425 @@ +// +build aix + +package net + +import ( + "context" + "fmt" + "os/exec" + "regexp" + "strconv" + "strings" + "syscall" + + "github.com/shirou/gopsutil/internal/common" +) + +func parseNetstatI(output string) ([]IOCountersStat, error) { + lines := strings.Split(string(output), "\n") + ret := make([]IOCountersStat, 0, len(lines)-1) + exists := make([]string, 0, len(ret)) + + // Check first line is header + if len(lines) > 0 && strings.Fields(lines[0])[0] != "Name" { + return nil, fmt.Errorf("not a 'netstat -i' output") + } + + for _, line := range lines[1:] { + values := strings.Fields(line) + if len(values) < 1 || values[0] == "Name" { + continue + } + if common.StringsHas(exists, values[0]) { + // skip if already get + continue + } + exists = append(exists, values[0]) + + if len(values) < 9 { + continue + } + + base := 1 + // sometimes Address is omitted + if len(values) < 10 { + base = 0 + } + + parsed := make([]uint64, 0, 5) + vv := []string{ + values[base+3], // Ipkts == PacketsRecv + values[base+4], // Ierrs == Errin + values[base+5], // Opkts == PacketsSent + values[base+6], // Oerrs == Errout + values[base+8], // Drops == Dropout + } + + for _, target := range vv { + if target == "-" { + parsed = append(parsed, 0) + continue + } + + t, err := strconv.ParseUint(target, 10, 64) + if err != nil { + return nil, err + } + parsed = append(parsed, t) + } + + n := IOCountersStat{ + Name: values[0], + PacketsRecv: parsed[0], + Errin: parsed[1], + PacketsSent: parsed[2], + Errout: parsed[3], + Dropout: parsed[4], + } + ret = append(ret, n) + } + return ret, nil +} + +func IOCounters(pernic bool) ([]IOCountersStat, error) { + return IOCountersWithContext(context.Background(), pernic) +} + +func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) { + netstat, err := exec.LookPath("netstat") + if err != nil { + return nil, err + } + out, err := invoke.CommandWithContext(ctx, netstat, "-idn") + if err != nil { + return nil, err + } + + iocounters, err := parseNetstatI(string(out)) + if err != nil { + return nil, err + } + if pernic == false { + return getIOCountersAll(iocounters) + } + return iocounters, nil + +} + +// NetIOCountersByFile is an method which is added just a compatibility for linux. +func IOCountersByFile(pernic bool, filename string) ([]IOCountersStat, error) { + return IOCountersByFileWithContext(context.Background(), pernic, filename) +} + +func IOCountersByFileWithContext(ctx context.Context, pernic bool, filename string) ([]IOCountersStat, error) { + return IOCounters(pernic) +} + +func FilterCounters() ([]FilterStat, error) { + return FilterCountersWithContext(context.Background()) +} + +func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) { + return nil, common.ErrNotImplementedError +} + +func ConntrackStats(percpu bool) ([]ConntrackStat, error) { + return ConntrackStatsWithContext(context.Background(), percpu) +} + +func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) { + return nil, common.ErrNotImplementedError +} + +func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) { + return ProtoCountersWithContext(context.Background(), protocols) +} + +func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoCountersStat, error) { + return nil, common.ErrNotImplementedError +} + +func parseNetstatNetLine(line string) (ConnectionStat, error) { + f := strings.Fields(line) + if len(f) < 5 { + return ConnectionStat{}, fmt.Errorf("wrong line,%s", line) + } + + var netType, netFamily uint32 + switch f[0] { + case "tcp", "tcp4": + netType = syscall.SOCK_STREAM + netFamily = syscall.AF_INET + case "udp", "udp4": + netType = syscall.SOCK_DGRAM + netFamily = syscall.AF_INET + case "tcp6": + netType = syscall.SOCK_STREAM + netFamily = syscall.AF_INET6 + case "udp6": + netType = syscall.SOCK_DGRAM + netFamily = syscall.AF_INET6 + default: + return ConnectionStat{}, fmt.Errorf("unknown type, %s", f[0]) + } + + laddr, raddr, err := parseNetstatAddr(f[3], f[4], netFamily) + if err != nil { + return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s %s", f[3], f[4]) + } + + n := ConnectionStat{ + Fd: uint32(0), // not supported + Family: uint32(netFamily), + Type: uint32(netType), + Laddr: laddr, + Raddr: raddr, + Pid: int32(0), // not supported + } + if len(f) == 6 { + n.Status = f[5] + } + + return n, nil +} + +var portMatch = regexp.MustCompile(`(.*)\.(\d+)$`) + +// This function only works for netstat returning addresses with a "." +// before the port (0.0.0.0.22 instead of 0.0.0.0:22). +func parseNetstatAddr(local string, remote string, family uint32) (laddr Addr, raddr Addr, err error) { + parse := func(l string) (Addr, error) { + matches := portMatch.FindStringSubmatch(l) + if matches == nil { + return Addr{}, fmt.Errorf("wrong addr, %s", l) + } + host := matches[1] + port := matches[2] + if host == "*" { + switch family { + case syscall.AF_INET: + host = "0.0.0.0" + case syscall.AF_INET6: + host = "::" + default: + return Addr{}, fmt.Errorf("unknown family, %d", family) + } + } + lport, err := strconv.Atoi(port) + if err != nil { + return Addr{}, err + } + return Addr{IP: host, Port: uint32(lport)}, nil + } + + laddr, err = parse(local) + if remote != "*.*" { // remote addr exists + raddr, err = parse(remote) + if err != nil { + return laddr, raddr, err + } + } + + return laddr, raddr, err +} + +func parseNetstatUnixLine(f []string) (ConnectionStat, error) { + if len(f) < 8 { + return ConnectionStat{}, fmt.Errorf("wrong number of fields: expected >=8 got %d", len(f)) + } + + var netType uint32 + + switch f[1] { + case "dgram": + netType = syscall.SOCK_DGRAM + case "stream": + netType = syscall.SOCK_STREAM + default: + return ConnectionStat{}, fmt.Errorf("unknown type: %s", f[1]) + } + + // Some Unix Socket don't have any address associated + addr := "" + if len(f) == 9 { + addr = f[8] + } + + c := ConnectionStat{ + Fd: uint32(0), // not supported + Family: uint32(syscall.AF_UNIX), + Type: uint32(netType), + Laddr: Addr{ + IP: addr, + }, + Status: "NONE", + Pid: int32(0), // not supported + } + + return c, nil +} + +// Return true if proto is the corresponding to the kind parameter +// Only for Inet lines +func hasCorrectInetProto(kind, proto string) bool { + switch kind { + case "all", "inet": + return true + case "unix": + return false + case "inet4": + return !strings.HasSuffix(proto, "6") + case "inet6": + return strings.HasSuffix(proto, "6") + case "tcp": + return proto == "tcp" || proto == "tcp4" || proto == "tcp6" + case "tcp4": + return proto == "tcp" || proto == "tcp4" + case "tcp6": + return proto == "tcp6" + case "udp": + return proto == "udp" || proto == "udp4" || proto == "udp6" + case "udp4": + return proto == "udp" || proto == "udp4" + case "udp6": + return proto == "udp6" + } + return false +} + +func parseNetstatA(output string, kind string) ([]ConnectionStat, error) { + var ret []ConnectionStat + lines := strings.Split(string(output), "\n") + + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) < 1 { + continue + } + + if strings.HasPrefix(fields[0], "f1") { + // Unix lines + if len(fields) < 2 { + // every unix connections have two lines + continue + } + + c, err := parseNetstatUnixLine(fields) + if err != nil { + return nil, fmt.Errorf("failed to parse Unix Address (%s): %s", line, err) + } + + ret = append(ret, c) + + } else if strings.HasPrefix(fields[0], "tcp") || strings.HasPrefix(fields[0], "udp") { + // Inet lines + if !hasCorrectInetProto(kind, fields[0]) { + continue + } + + // On AIX, netstat display some connections with "*.*" as local addresses + // Skip them as they aren't real connections. + if fields[3] == "*.*" { + continue + } + + c, err := parseNetstatNetLine(line) + if err != nil { + return nil, fmt.Errorf("failed to parse Inet Address (%s): %s", line, err) + } + + ret = append(ret, c) + } else { + // Header lines + continue + } + } + + return ret, nil + +} + +func Connections(kind string) ([]ConnectionStat, error) { + return ConnectionsWithContext(context.Background(), kind) +} + +func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) { + + args := []string{"-na"} + switch strings.ToLower(kind) { + default: + fallthrough + case "": + kind = "all" + case "all": + // nothing to add + case "inet", "inet4", "inet6": + args = append(args, "-finet") + case "tcp", "tcp4", "tcp6": + args = append(args, "-finet") + case "udp", "udp4", "udp6": + args = append(args, "-finet") + case "unix": + args = append(args, "-funix") + } + + netstat, err := exec.LookPath("netstat") + if err != nil { + return nil, err + } + out, err := invoke.CommandWithContext(ctx, netstat, args...) + + if err != nil { + return nil, err + } + + ret, err := parseNetstatA(string(out), kind) + if err != nil { + return nil, err + } + + return ret, nil + +} + +func ConnectionsMax(kind string, max int) ([]ConnectionStat, error) { + return ConnectionsMaxWithContext(context.Background(), kind, max) +} + +func ConnectionsMaxWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) { + return []ConnectionStat{}, common.ErrNotImplementedError +} + +// Return a list of network connections opened, omitting `Uids`. +// WithoutUids functions are reliant on implementation details. They may be altered to be an alias for Connections or be +// removed from the API in the future. +func ConnectionsWithoutUids(kind string) ([]ConnectionStat, error) { + return ConnectionsWithoutUidsWithContext(context.Background(), kind) +} + +func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) { + return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0) +} + +func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) { + return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, max) +} + +func ConnectionsPidWithoutUids(kind string, pid int32) ([]ConnectionStat, error) { + return ConnectionsPidWithoutUidsWithContext(context.Background(), kind, pid) +} + +func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) { + return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0) +} + +func ConnectionsPidMaxWithoutUids(kind string, pid int32, max int) ([]ConnectionStat, error) { + return ConnectionsPidMaxWithoutUidsWithContext(context.Background(), kind, pid, max) +} + +func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) { + return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, max) +} + +func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) { + return []ConnectionStat{}, common.ErrNotImplementedError +} diff --git a/src/vendor/github.com/shirou/gopsutil/net/net_fallback.go b/src/vendor/github.com/shirou/gopsutil/net/net_fallback.go index 707b80f..7d9a265 100644 --- a/src/vendor/github.com/shirou/gopsutil/net/net_fallback.go +++ b/src/vendor/github.com/shirou/gopsutil/net/net_fallback.go @@ -1,4 +1,4 @@ -// +build !darwin,!linux,!freebsd,!openbsd,!windows +// +build !aix,!darwin,!linux,!freebsd,!openbsd,!windows package net diff --git a/src/vendor/github.com/shirou/gopsutil/net/net_unix.go b/src/vendor/github.com/shirou/gopsutil/net/net_unix.go index d11fceb..d6e4303 100644 --- a/src/vendor/github.com/shirou/gopsutil/net/net_unix.go +++ b/src/vendor/github.com/shirou/gopsutil/net/net_unix.go @@ -4,7 +4,11 @@ package net import ( "context" + "fmt" + "net" + "strconv" "strings" + "syscall" "github.com/shirou/gopsutil/internal/common" ) @@ -86,6 +90,95 @@ func ConnectionsPidWithContext(ctx context.Context, kind string, pid int32) ([]C return ret, nil } +var constMap = map[string]int{ + "unix": syscall.AF_UNIX, + "TCP": syscall.SOCK_STREAM, + "UDP": syscall.SOCK_DGRAM, + "IPv4": syscall.AF_INET, + "IPv6": syscall.AF_INET6, +} + +func parseNetLine(line string) (ConnectionStat, error) { + f := strings.Fields(line) + if len(f) < 8 { + return ConnectionStat{}, fmt.Errorf("wrong line,%s", line) + } + + if len(f) == 8 { + f = append(f, f[7]) + f[7] = "unix" + } + + pid, err := strconv.Atoi(f[1]) + if err != nil { + return ConnectionStat{}, err + } + fd, err := strconv.Atoi(strings.Trim(f[3], "u")) + if err != nil { + return ConnectionStat{}, fmt.Errorf("unknown fd, %s", f[3]) + } + netFamily, ok := constMap[f[4]] + if !ok { + return ConnectionStat{}, fmt.Errorf("unknown family, %s", f[4]) + } + netType, ok := constMap[f[7]] + if !ok { + return ConnectionStat{}, fmt.Errorf("unknown type, %s", f[7]) + } + + var laddr, raddr Addr + if f[7] == "unix" { + laddr.IP = f[8] + } else { + laddr, raddr, err = parseNetAddr(f[8]) + if err != nil { + return ConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s", f[8]) + } + } + + n := ConnectionStat{ + Fd: uint32(fd), + Family: uint32(netFamily), + Type: uint32(netType), + Laddr: laddr, + Raddr: raddr, + Pid: int32(pid), + } + if len(f) == 10 { + n.Status = strings.Trim(f[9], "()") + } + + return n, nil +} + +func parseNetAddr(line string) (laddr Addr, raddr Addr, err error) { + parse := func(l string) (Addr, error) { + host, port, err := net.SplitHostPort(l) + if err != nil { + return Addr{}, fmt.Errorf("wrong addr, %s", l) + } + lport, err := strconv.Atoi(port) + if err != nil { + return Addr{}, err + } + return Addr{IP: host, Port: uint32(lport)}, nil + } + + addrs := strings.Split(line, "->") + if len(addrs) == 0 { + return laddr, raddr, fmt.Errorf("wrong netaddr, %s", line) + } + laddr, err = parse(addrs[0]) + if len(addrs) == 2 { // remote addr exists + raddr, err = parse(addrs[1]) + if err != nil { + return laddr, raddr, err + } + } + + return laddr, raddr, err +} + // Return up to `max` network connections opened by a process. func ConnectionsPidMax(kind string, pid int32, max int) ([]ConnectionStat, error) { return ConnectionsPidMaxWithContext(context.Background(), kind, pid, max) diff --git a/src/vendor/github.com/shirou/gopsutil/process/process_linux.go b/src/vendor/github.com/shirou/gopsutil/process/process_linux.go index fab7b55..afd5e28 100644 --- a/src/vendor/github.com/shirou/gopsutil/process/process_linux.go +++ b/src/vendor/github.com/shirou/gopsutil/process/process_linux.go @@ -1157,10 +1157,19 @@ func (p *Process) fillFromTIDStatWithContext(ctx context.Context, tid int32) (ui return 0, 0, nil, 0, 0, 0, nil, err } + // There is no such thing as iotime in stat file. As an approximation, we + // will use delayacct_blkio_ticks (aggregated block I/O delays, as per Linux + // docs). Note: I am assuming at least Linux 2.6.18 + iotime, err := strconv.ParseFloat(fields[i+40], 64) + if err != nil { + iotime = 0 // Ancient linux version, most likely + } + cpuTimes := &cpu.TimesStat{ CPU: "cpu", User: float64(utime / ClockTicks), System: float64(stime / ClockTicks), + Iowait: float64(iotime / ClockTicks), } bootTime, _ := common.BootTimeWithContext(ctx) diff --git a/src/vendor/github.com/shirou/gopsutil/process/process_posix.go b/src/vendor/github.com/shirou/gopsutil/process/process_posix.go index 13f0308..109239a 100644 --- a/src/vendor/github.com/shirou/gopsutil/process/process_posix.go +++ b/src/vendor/github.com/shirou/gopsutil/process/process_posix.go @@ -12,6 +12,7 @@ import ( "strings" "syscall" + "github.com/shirou/gopsutil/internal/common" "golang.org/x/sys/unix" ) @@ -78,6 +79,20 @@ func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) { if err != nil { return false, err } + + if _, err := os.Stat(common.HostProc()); err == nil { //Means that proc filesystem exist + // Checking PID existence based on existence of //proc/ folder + // This covers the case when running inside container with a different process namespace (by default) + + _, err := os.Stat(common.HostProc(strconv.Itoa(int(pid)))) + if os.IsNotExist(err) { + return false, nil + } + return err == nil, err + } + + //'/proc' filesystem is not exist, checking of PID existence is done via signalling the process + //Make sense only if we run in the same process namespace err = proc.Signal(syscall.Signal(0)) if err == nil { return true, nil @@ -95,6 +110,7 @@ func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) { case syscall.EPERM: return true, nil } + return false, err } diff --git a/src/vendor/github.com/shirou/gopsutil/process/process_windows.go b/src/vendor/github.com/shirou/gopsutil/process/process_windows.go index 56e535f..cdce609 100644 --- a/src/vendor/github.com/shirou/gopsutil/process/process_windows.go +++ b/src/vendor/github.com/shirou/gopsutil/process/process_windows.go @@ -4,14 +4,13 @@ package process import ( "context" + "errors" "fmt" "os" "strings" "syscall" - "time" "unsafe" - "github.com/StackExchange/wmi" cpu "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/internal/common" net "github.com/shirou/gopsutil/net" @@ -29,6 +28,10 @@ var ( procQueryFullProcessImageNameW = common.Modkernel32.NewProc("QueryFullProcessImageNameW") procGetPriorityClass = common.Modkernel32.NewProc("GetPriorityClass") + procGetProcessIoCounters = common.Modkernel32.NewProc("GetProcessIoCounters") + procGetNativeSystemInfo = common.Modkernel32.NewProc("GetNativeSystemInfo") + + processorArchitecture uint ) type SystemProcessInformation struct { @@ -46,6 +49,28 @@ type SystemProcessInformation struct { Reserved6 [6]uint64 } +type systemProcessorInformation struct { + ProcessorArchitecture uint16 + ProcessorLevel uint16 + ProcessorRevision uint16 + Reserved uint16 + ProcessorFeatureBits uint16 +} + +type systemInfo struct { + wProcessorArchitecture uint16 + wReserved uint16 + dwPageSize uint32 + lpMinimumApplicationAddress uintptr + lpMaximumApplicationAddress uintptr + dwActiveProcessorMask uintptr + dwNumberOfProcessors uint32 + dwProcessorType uint32 + dwAllocationGranularity uint32 + wProcessorLevel uint16 + wProcessorRevision uint16 +} + // Memory_info_ex is different between OSes type MemoryInfoExStat struct { } @@ -53,43 +78,33 @@ type MemoryInfoExStat struct { type MemoryMapsStat struct { } -type Win32_Process struct { - Name string - ExecutablePath *string - CommandLine *string - Priority uint32 - CreationDate *time.Time - ProcessID uint32 - ThreadCount uint32 - Status *string - ReadOperationCount uint64 - ReadTransferCount uint64 - WriteOperationCount uint64 - WriteTransferCount uint64 - CSCreationClassName string - CSName string - Caption *string - CreationClassName string - Description *string - ExecutionState *uint16 - HandleCount uint32 - KernelModeTime uint64 - MaximumWorkingSetSize *uint32 - MinimumWorkingSetSize *uint32 - OSCreationClassName string - OSName string - OtherOperationCount uint64 - OtherTransferCount uint64 - PageFaults uint32 - PageFileUsage uint32 - ParentProcessID uint32 - PeakPageFileUsage uint32 - PeakVirtualSize uint64 - PeakWorkingSetSize uint32 - PrivatePageCount uint64 - TerminationDate *time.Time - UserModeTime uint64 - WorkingSetSize uint64 +// ioCounters is an equivalent representation of IO_COUNTERS in the Windows API. +// https://docs.microsoft.com/windows/win32/api/winnt/ns-winnt-io_counters +type ioCounters struct { + ReadOperationCount uint64 + WriteOperationCount uint64 + OtherOperationCount uint64 + ReadTransferCount uint64 + WriteTransferCount uint64 + OtherTransferCount uint64 +} + +type processBasicInformation32 struct { + Reserved1 uint32 + PebBaseAddress uint32 + Reserved2 uint32 + Reserved3 uint32 + UniqueProcessId uint32 + Reserved4 uint32 +} + +type processBasicInformation64 struct { + Reserved1 uint64 + PebBaseAddress uint64 + Reserved2 uint64 + Reserved3 uint64 + UniqueProcessId uint64 + Reserved4 uint64 } type winLUID struct { @@ -113,7 +128,10 @@ type winLong int32 type winDWord uint32 func init() { - wmi.DefaultClient.AllowMissingFields = true + var systemInfo systemInfo + + procGetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&systemInfo))) + processorArchitecture = uint(systemInfo.wProcessorArchitecture) // enable SeDebugPrivilege https://github.com/midstar/proci/blob/6ec79f57b90ba3d9efa2a7b16ef9c9369d4be875/proci_windows.go#L80-L119 handle, err := syscall.GetCurrentProcess() @@ -225,26 +243,6 @@ func (p *Process) PpidWithContext(ctx context.Context) (int32, error) { return ppid, nil } -func GetWin32Proc(pid int32) ([]Win32_Process, error) { - return GetWin32ProcWithContext(context.Background(), pid) -} - -func GetWin32ProcWithContext(ctx context.Context, pid int32) ([]Win32_Process, error) { - var dst []Win32_Process - query := fmt.Sprintf("WHERE ProcessId = %d", pid) - q := wmi.CreateQuery(&dst, query) - err := common.WMIQueryWithContext(ctx, q, &dst) - if err != nil { - return []Win32_Process{}, fmt.Errorf("could not get win32Proc: %s", err) - } - - if len(dst) == 0 { - return []Win32_Process{}, fmt.Errorf("could not get win32Proc: empty") - } - - return dst, nil -} - func (p *Process) Name() (string, error) { return p.NameWithContext(context.Background()) } @@ -296,12 +294,12 @@ func (p *Process) Cmdline() (string, error) { return p.CmdlineWithContext(context.Background()) } -func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { - dst, err := GetWin32ProcWithContext(ctx, p.Pid) +func (p *Process) CmdlineWithContext(_ context.Context) (string, error) { + cmdline, err := getProcessCommandLine(p.Pid) if err != nil { return "", fmt.Errorf("could not get CommandLine: %s", err) } - return *dst[0].CommandLine, nil + return cmdline, nil } // CmdlineSlice returns the command line arguments of the process as a slice with each @@ -479,18 +477,24 @@ func (p *Process) IOCounters() (*IOCountersStat, error) { } func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) { - dst, err := GetWin32ProcWithContext(ctx, p.Pid) - if err != nil || len(dst) == 0 { - return nil, fmt.Errorf("could not get Win32Proc: %s", err) + c, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(p.Pid)) + if err != nil { + return nil, err + } + defer windows.CloseHandle(c) + var ioCounters ioCounters + ret, _, err := procGetProcessIoCounters.Call(uintptr(c), uintptr(unsafe.Pointer(&ioCounters))) + if ret == 0 { + return nil, err } - ret := &IOCountersStat{ - ReadCount: uint64(dst[0].ReadOperationCount), - ReadBytes: uint64(dst[0].ReadTransferCount), - WriteCount: uint64(dst[0].WriteOperationCount), - WriteBytes: uint64(dst[0].WriteTransferCount), + stats := &IOCountersStat{ + ReadCount: ioCounters.ReadOperationCount, + ReadBytes: ioCounters.ReadTransferCount, + WriteCount: ioCounters.WriteOperationCount, + WriteBytes: ioCounters.WriteTransferCount, } - return ret, nil + return stats, nil } func (p *Process) NumCtxSwitches() (*NumCtxSwitchesStat, error) { return p.NumCtxSwitchesWithContext(context.Background()) @@ -756,24 +760,6 @@ func ProcessesWithContext(ctx context.Context) ([]*Process, error) { return out, nil } -func getProcInfo(pid int32) (*SystemProcessInformation, error) { - initialBufferSize := uint64(0x4000) - bufferSize := initialBufferSize - buffer := make([]byte, bufferSize) - - var sysProcInfo SystemProcessInformation - ret, _, _ := common.ProcNtQuerySystemInformation.Call( - uintptr(unsafe.Pointer(&sysProcInfo)), - uintptr(unsafe.Pointer(&buffer[0])), - uintptr(unsafe.Pointer(&bufferSize)), - uintptr(unsafe.Pointer(&bufferSize))) - if ret != 0 { - return nil, windows.GetLastError() - } - - return &sysProcInfo, nil -} - func getRusage(pid int32) (*windows.Rusage, error) { var CPU windows.Rusage @@ -842,3 +828,148 @@ func getProcessCPUTimes(pid int32) (SYSTEM_TIMES, error) { return times, err } + +func is32BitProcess(procHandle syscall.Handle) bool { + var wow64 uint + + ret, _, _ := common.ProcNtQueryInformationProcess.Call( + uintptr(procHandle), + uintptr(common.ProcessWow64Information), + uintptr(unsafe.Pointer(&wow64)), + uintptr(unsafe.Sizeof(wow64)), + uintptr(0), + ) + if int(ret) >= 0 { + if wow64 != 0 { + return true + } + } else { + //if the OS does not support the call, we fallback into the bitness of the app + if unsafe.Sizeof(wow64) == 4 { + return true + } + } + return false +} + +func getProcessCommandLine(pid int32) (string, error) { + h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION | windows.PROCESS_VM_READ, false, uint32(pid)) + if err == windows.ERROR_ACCESS_DENIED || err == windows.ERROR_INVALID_PARAMETER { + return "", nil + } + if err != nil { + return "", err + } + defer syscall.CloseHandle(syscall.Handle(h)) + + const ( + PROCESSOR_ARCHITECTURE_INTEL = 0 + PROCESSOR_ARCHITECTURE_ARM = 5 + PROCESSOR_ARCHITECTURE_ARM64 = 12 + PROCESSOR_ARCHITECTURE_IA64 = 6 + PROCESSOR_ARCHITECTURE_AMD64 = 9 + ) + + procIs32Bits := true + switch processorArchitecture { + case PROCESSOR_ARCHITECTURE_INTEL: + fallthrough + case PROCESSOR_ARCHITECTURE_ARM: + procIs32Bits = true + + case PROCESSOR_ARCHITECTURE_ARM64: + fallthrough + case PROCESSOR_ARCHITECTURE_IA64: + fallthrough + case PROCESSOR_ARCHITECTURE_AMD64: + procIs32Bits = is32BitProcess(syscall.Handle(h)) + + default: + //for other unknown platforms, we rely on process platform + if unsafe.Sizeof(processorArchitecture) == 8 { + procIs32Bits = false + } + } + + pebAddress := queryPebAddress(syscall.Handle(h), procIs32Bits) + if pebAddress == 0 { + return "", errors.New("cannot locate process PEB") + } + + if procIs32Bits { + buf := readProcessMemory(syscall.Handle(h), procIs32Bits, pebAddress + uint64(16), 4) + if len(buf) != 4 { + return "", errors.New("cannot locate process user parameters") + } + userProcParams := uint64(buf[0]) | (uint64(buf[1]) << 8) | (uint64(buf[2]) << 16) | (uint64(buf[3]) << 24) + + //read CommandLine field from PRTL_USER_PROCESS_PARAMETERS + remoteCmdLine := readProcessMemory(syscall.Handle(h), procIs32Bits, userProcParams + uint64(64), 8) + if len(remoteCmdLine) != 8 { + return "", errors.New("cannot read cmdline field") + } + + //remoteCmdLine is actually a UNICODE_STRING32 + //the first two bytes has the length + cmdLineLength := uint(remoteCmdLine[0]) | (uint(remoteCmdLine[1]) << 8) + if cmdLineLength > 0 { + //and, at offset 4, is the pointer to the buffer + bufferAddress := uint32(remoteCmdLine[4]) | (uint32(remoteCmdLine[5]) << 8) | + (uint32(remoteCmdLine[6]) << 16) | (uint32(remoteCmdLine[7]) << 24) + + cmdLine := readProcessMemory(syscall.Handle(h), procIs32Bits, uint64(bufferAddress), cmdLineLength) + if len(cmdLine) != int(cmdLineLength) { + return "", errors.New("cannot read cmdline") + } + + return convertUTF16ToString(cmdLine), nil + } + } else { + buf := readProcessMemory(syscall.Handle(h), procIs32Bits, pebAddress + uint64(32), 8) + if len(buf) != 8 { + return "", errors.New("cannot locate process user parameters") + } + userProcParams := uint64(buf[0]) | (uint64(buf[1]) << 8) | (uint64(buf[2]) << 16) | (uint64(buf[3]) << 24) | + (uint64(buf[4]) << 32) | (uint64(buf[5]) << 40) | (uint64(buf[6]) << 48) | (uint64(buf[7]) << 56) + + //read CommandLine field from PRTL_USER_PROCESS_PARAMETERS + remoteCmdLine := readProcessMemory(syscall.Handle(h), procIs32Bits, userProcParams + uint64(112), 16) + if len(remoteCmdLine) != 16 { + return "", errors.New("cannot read cmdline field") + } + + //remoteCmdLine is actually a UNICODE_STRING64 + //the first two bytes has the length + cmdLineLength := uint(remoteCmdLine[0]) | (uint(remoteCmdLine[1]) << 8) + if cmdLineLength > 0 { + //and, at offset 8, is the pointer to the buffer + bufferAddress := uint64(remoteCmdLine[8]) | (uint64(remoteCmdLine[9]) << 8) | + (uint64(remoteCmdLine[10]) << 16) | (uint64(remoteCmdLine[11]) << 24) | + (uint64(remoteCmdLine[12]) << 32) | (uint64(remoteCmdLine[13]) << 40) | + (uint64(remoteCmdLine[14]) << 48) | (uint64(remoteCmdLine[15]) << 56) + + cmdLine := readProcessMemory(syscall.Handle(h), procIs32Bits, bufferAddress, cmdLineLength) + if len(cmdLine) != int(cmdLineLength) { + return "", errors.New("cannot read cmdline") + } + + return convertUTF16ToString(cmdLine), nil + } + } + + //if we reach here, we have no command line + return "", nil +} + +func convertUTF16ToString(src []byte) string { + srcLen := len(src) / 2 + + codePoints := make([]uint16, srcLen) + + srcIdx := 0 + for i := 0; i < srcLen; i++ { + codePoints[i] = uint16(src[srcIdx]) | uint16(src[srcIdx + 1] << 8) + srcIdx += 2 + } + return syscall.UTF16ToString(codePoints) +} diff --git a/src/vendor/github.com/shirou/gopsutil/process/process_windows_386.go b/src/vendor/github.com/shirou/gopsutil/process/process_windows_386.go index 68f3153..cd88496 100644 --- a/src/vendor/github.com/shirou/gopsutil/process/process_windows_386.go +++ b/src/vendor/github.com/shirou/gopsutil/process/process_windows_386.go @@ -2,6 +2,13 @@ package process +import ( + "syscall" + "unsafe" + + "github.com/shirou/gopsutil/internal/common" +) + type PROCESS_MEMORY_COUNTERS struct { CB uint32 PageFaultCount uint32 @@ -14,3 +21,82 @@ type PROCESS_MEMORY_COUNTERS struct { PagefileUsage uint32 PeakPagefileUsage uint32 } + +func queryPebAddress(procHandle syscall.Handle, is32BitProcess bool) uint64 { + if is32BitProcess { + //we are on a 32-bit process reading an external 32-bit process + var info processBasicInformation32 + + ret, _, _ := common.ProcNtQueryInformationProcess.Call( + uintptr(procHandle), + uintptr(common.ProcessBasicInformation), + uintptr(unsafe.Pointer(&info)), + uintptr(unsafe.Sizeof(info)), + uintptr(0), + ) + if int(ret) >= 0 { + return uint64(info.PebBaseAddress) + } + } else { + //we are on a 32-bit process reading an external 64-bit process + if common.ProcNtWow64QueryInformationProcess64.Find() == nil { //avoid panic + var info processBasicInformation64 + + ret, _, _ := common.ProcNtWow64QueryInformationProcess64.Call( + uintptr(procHandle), + uintptr(common.ProcessBasicInformation), + uintptr(unsafe.Pointer(&info)), + uintptr(unsafe.Sizeof(info)), + uintptr(0), + ) + if int(ret) >= 0 { + return info.PebBaseAddress + } + } + } + + //return 0 on error + return 0 +} + +func readProcessMemory(h syscall.Handle, is32BitProcess bool, address uint64, size uint) []byte { + if is32BitProcess { + var read uint + + buffer := make([]byte, size) + + ret, _, _ := common.ProcNtReadVirtualMemory.Call( + uintptr(h), + uintptr(address), + uintptr(unsafe.Pointer(&buffer[0])), + uintptr(size), + uintptr(unsafe.Pointer(&read)), + ) + if int(ret) >= 0 && read > 0 { + return buffer[:read] + } + } else { + //reading a 64-bit process from a 32-bit one + if common.ProcNtWow64ReadVirtualMemory64.Find() == nil { //avoid panic + var read uint64 + + buffer := make([]byte, size) + + ret, _, _ := common.ProcNtWow64ReadVirtualMemory64.Call( + uintptr(h), + uintptr(address & 0xFFFFFFFF), //the call expects a 64-bit value + uintptr(address >> 32), + uintptr(unsafe.Pointer(&buffer[0])), + uintptr(size), //the call expects a 64-bit value + uintptr(0), //but size is 32-bit so pass zero as the high dword + uintptr(unsafe.Pointer(&read)), + ) + if int(ret) >= 0 && read > 0 { + return buffer[:uint(read)] + } + } + } + + //if we reach here, an error happened + return nil +} diff --git a/src/vendor/github.com/shirou/gopsutil/process/process_windows_amd64.go b/src/vendor/github.com/shirou/gopsutil/process/process_windows_amd64.go index df286df..3ee5be4 100644 --- a/src/vendor/github.com/shirou/gopsutil/process/process_windows_amd64.go +++ b/src/vendor/github.com/shirou/gopsutil/process/process_windows_amd64.go @@ -2,6 +2,13 @@ package process +import ( + "syscall" + "unsafe" + + "github.com/shirou/gopsutil/internal/common" +) + type PROCESS_MEMORY_COUNTERS struct { CB uint32 PageFaultCount uint32 @@ -14,3 +21,56 @@ type PROCESS_MEMORY_COUNTERS struct { PagefileUsage uint64 PeakPagefileUsage uint64 } + +func queryPebAddress(procHandle syscall.Handle, is32BitProcess bool) uint64 { + if is32BitProcess { + //we are on a 64-bit process reading an external 32-bit process + var wow64 uint + + ret, _, _ := common.ProcNtQueryInformationProcess.Call( + uintptr(procHandle), + uintptr(common.ProcessWow64Information), + uintptr(unsafe.Pointer(&wow64)), + uintptr(unsafe.Sizeof(wow64)), + uintptr(0), + ) + if int(ret) >= 0 { + return uint64(wow64) + } + } else { + //we are on a 64-bit process reading an external 64-bit process + var info processBasicInformation64 + + ret, _, _ := common.ProcNtQueryInformationProcess.Call( + uintptr(procHandle), + uintptr(common.ProcessBasicInformation), + uintptr(unsafe.Pointer(&info)), + uintptr(unsafe.Sizeof(info)), + uintptr(0), + ) + if int(ret) >= 0 { + return info.PebBaseAddress + } + } + + //return 0 on error + return 0 +} + +func readProcessMemory(procHandle syscall.Handle, _ bool, address uint64, size uint) []byte { + var read uint + + buffer := make([]byte, size) + + ret, _, _ := common.ProcNtReadVirtualMemory.Call( + uintptr(procHandle), + uintptr(address), + uintptr(unsafe.Pointer(&buffer[0])), + uintptr(size), + uintptr(unsafe.Pointer(&read)), + ) + if int(ret) >= 0 && read > 0 { + return buffer[:read] + } + return nil +} diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index f86ee9b..4bc8e25 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -37,7 +37,7 @@ github.com/randlabs/directories github.com/randlabs/rundown-protection # github.com/ricochet2200/go-disk-usage v0.0.0-20150921141558-f0d1b743428f github.com/ricochet2200/go-disk-usage/du -# github.com/shirou/gopsutil v2.19.12+incompatible +# github.com/shirou/gopsutil v2.19.12+incompatible => github.com/mxmauro/gopsutil v2.20.4+incompatible github.com/shirou/gopsutil/cpu github.com/shirou/gopsutil/internal/common github.com/shirou/gopsutil/mem