Windows: start on install. Add stop/start to all.
This commit is contained in:
parent
58cf28df8e
commit
4c44f70ec3
|
@ -1,3 +1,5 @@
|
||||||
|
*~
|
||||||
|
.*~
|
||||||
# ---> Go
|
# ---> Go
|
||||||
# Binaries for programs and plugins
|
# Binaries for programs and plugins
|
||||||
*.exe
|
*.exe
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -5,6 +5,7 @@ go 1.12
|
||||||
require (
|
require (
|
||||||
git.rootprojects.org/root/go-gitver v1.1.2
|
git.rootprojects.org/root/go-gitver v1.1.2
|
||||||
github.com/UnnoTed/fileb0x v1.1.3
|
github.com/UnnoTed/fileb0x v1.1.3
|
||||||
|
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936
|
||||||
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f
|
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f
|
||||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
|
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
|
||||||
)
|
)
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -26,6 +26,8 @@ github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
||||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936 h1:kw1v0NlnN+GZcU8Ma8CLF2Zzgjfx95gs3/GN3vYAPpo=
|
||||||
|
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
|
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||||
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e h1:fvw0uluMptljaRKSU8459cJ4bmi3qUYyMs5kzpic2fY=
|
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e h1:fvw0uluMptljaRKSU8459cJ4bmi3qUYyMs5kzpic2fY=
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.rootprojects.org/root/go-serviceman/service"
|
"git.rootprojects.org/root/go-serviceman/service"
|
||||||
)
|
)
|
||||||
|
@ -42,6 +43,14 @@ func Install(c *service.Service) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Start(conf *service.Service) error {
|
||||||
|
return start(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stop(conf *service.Service) error {
|
||||||
|
return stop(conf)
|
||||||
|
}
|
||||||
|
|
||||||
// IsPrivileged returns true if we suspect that the current user (or process) will be able
|
// IsPrivileged returns true if we suspect that the current user (or process) will be able
|
||||||
// to write to system folders, bind to privileged ports, and otherwise
|
// to write to system folders, bind to privileged ports, and otherwise
|
||||||
// successfully run a system service.
|
// successfully run a system service.
|
||||||
|
@ -57,3 +66,12 @@ func WhereIs(exe string) (string, error) {
|
||||||
}
|
}
|
||||||
return filepath.Abs(filepath.ToSlash(exepath))
|
return filepath.Abs(filepath.ToSlash(exepath))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrDaemonize struct {
|
||||||
|
DaemonArgs []string
|
||||||
|
error string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrDaemonize) Error() string {
|
||||||
|
return e.error + "\nYou need to switch on ErrDaemonize, and use .DaemonArgs, which would run this:" + strings.Join(e.DaemonArgs, " ")
|
||||||
|
}
|
||||||
|
|
|
@ -24,24 +24,15 @@ func init() {
|
||||||
srvLen = len(srvExt)
|
srvLen = len(srvExt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(system bool, home string, name string) error {
|
func start(conf *service.Service) error {
|
||||||
sys, user, err := getMatchingSrvs(home, name)
|
system := conf.System
|
||||||
if nil != err {
|
home := conf.Home
|
||||||
return err
|
rdns := conf.ReverseDNS
|
||||||
}
|
|
||||||
|
|
||||||
var service string
|
service, err := getService(system, home, rdns)
|
||||||
if system {
|
|
||||||
service, err = getOneSysSrv(sys, user, name)
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
service, err = getOneUserSrv(home, sys, user, name)
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds := []Runnable{
|
cmds := []Runnable{
|
||||||
Runnable{
|
Runnable{
|
||||||
|
@ -60,7 +51,51 @@ func start(system bool, home string, name string) error {
|
||||||
cmds = adjustPrivs(system, cmds)
|
cmds = adjustPrivs(system, cmds)
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Starting launchd service...")
|
typ := "USER"
|
||||||
|
if system {
|
||||||
|
typ = "SYSTEM"
|
||||||
|
}
|
||||||
|
fmt.Printf("Starting launchd %s service...\n", typ)
|
||||||
|
for i := range cmds {
|
||||||
|
exe := cmds[i]
|
||||||
|
fmt.Println("\t" + exe.String())
|
||||||
|
err := exe.Run()
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop(conf *service.Service) error {
|
||||||
|
system := conf.System
|
||||||
|
home := conf.Home
|
||||||
|
rdns := conf.ReverseDNS
|
||||||
|
|
||||||
|
service, err := getService(system, home, rdns)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds := []Runnable{
|
||||||
|
Runnable{
|
||||||
|
Exec: "launchctl",
|
||||||
|
Args: []string{"unload", service},
|
||||||
|
Must: false,
|
||||||
|
Badwords: []string{"No such file or directory", "Cound not find specified service"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds = adjustPrivs(system, cmds)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
typ := "USER"
|
||||||
|
if system {
|
||||||
|
typ = "SYSTEM"
|
||||||
|
}
|
||||||
|
fmt.Printf("Stopping launchd %s service...\n", typ)
|
||||||
for i := range cmds {
|
for i := range cmds {
|
||||||
exe := cmds[i]
|
exe := cmds[i]
|
||||||
fmt.Println("\t" + exe.String())
|
fmt.Println("\t" + exe.String())
|
||||||
|
@ -118,7 +153,7 @@ func install(c *service.Service) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO --no-start
|
// TODO --no-start
|
||||||
err = start(c.System, c.Home, c.ReverseDNS)
|
err = start(c)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
fmt.Printf("If things don't go well you should be able to get additional logging from launchctl:\n")
|
fmt.Printf("If things don't go well you should be able to get additional logging from launchctl:\n")
|
||||||
fmt.Printf("\tsudo launchctl log level debug\n")
|
fmt.Printf("\tsudo launchctl log level debug\n")
|
||||||
|
|
|
@ -29,24 +29,15 @@ func init() {
|
||||||
srvLen = len(srvExt)
|
srvLen = len(srvExt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(system bool, home string, name string) error {
|
func start(conf *service.Service) error {
|
||||||
sys, user, err := getMatchingSrvs(home, name)
|
system := conf.System
|
||||||
if nil != err {
|
home := conf.Home
|
||||||
return err
|
name := conf.ReverseDNS
|
||||||
}
|
|
||||||
|
|
||||||
// var service string
|
_, err := getService(system, home, name)
|
||||||
if system {
|
|
||||||
_, err = getOneSysSrv(sys, user, name)
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
_, err = getOneUserSrv(home, sys, user, name)
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmds []Runnable
|
var cmds []Runnable
|
||||||
if system {
|
if system {
|
||||||
|
@ -92,7 +83,64 @@ func start(system bool, home string, name string) error {
|
||||||
cmds = adjustPrivs(system, cmds)
|
cmds = adjustPrivs(system, cmds)
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Starting systemd service unit...")
|
typ := "USER MODE"
|
||||||
|
if system {
|
||||||
|
typ = "SYSTEM"
|
||||||
|
}
|
||||||
|
fmt.Printf("Starting systemd %s service unit...\n", typ)
|
||||||
|
for i := range cmds {
|
||||||
|
exe := cmds[i]
|
||||||
|
fmt.Println("\t" + exe.String())
|
||||||
|
err := exe.Run()
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop(conf *service.Service) error {
|
||||||
|
system := conf.System
|
||||||
|
home := conf.Home
|
||||||
|
name := conf.ReverseDNS
|
||||||
|
|
||||||
|
_, err := getService(system, home, name)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmds []Runnable
|
||||||
|
badwords := []string{"Failed to stop"}
|
||||||
|
if system {
|
||||||
|
cmds = []Runnable{
|
||||||
|
Runnable{
|
||||||
|
Exec: "systemctl",
|
||||||
|
Args: []string{"stop", name + ".service"},
|
||||||
|
Must: true,
|
||||||
|
Badwords: badwords,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmds = []Runnable{
|
||||||
|
Runnable{
|
||||||
|
Exec: "systemctl",
|
||||||
|
Args: []string{"stop", "--user", name + ".service"},
|
||||||
|
Must: true,
|
||||||
|
Badwords: badwords,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds = adjustPrivs(system, cmds)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
typ := "USER MODE"
|
||||||
|
if system {
|
||||||
|
typ = "SYSTEM"
|
||||||
|
}
|
||||||
|
fmt.Printf("Stopping systemd %s service...\n", typ)
|
||||||
for i := range cmds {
|
for i := range cmds {
|
||||||
exe := cmds[i]
|
exe := cmds[i]
|
||||||
fmt.Println("\t" + exe.String())
|
fmt.Println("\t" + exe.String())
|
||||||
|
@ -152,7 +200,7 @@ func install(c *service.Service) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO --no-start
|
// TODO --no-start
|
||||||
err = start(c.System, c.Home, c.Name)
|
err = start(c)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
sudo := ""
|
sudo := ""
|
||||||
// --user-unit rather than --user --unit for older systemd
|
// --user-unit rather than --user --unit for older systemd
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.rootprojects.org/root/go-serviceman/runner"
|
||||||
"git.rootprojects.org/root/go-serviceman/service"
|
"git.rootprojects.org/root/go-serviceman/service"
|
||||||
|
|
||||||
"golang.org/x/sys/windows/registry"
|
"golang.org/x/sys/windows/registry"
|
||||||
|
@ -67,6 +68,9 @@ func install(c *service.Service) error {
|
||||||
}
|
}
|
||||||
defer k.Close()
|
defer k.Close()
|
||||||
|
|
||||||
|
// Try to stop before trying to copy the file
|
||||||
|
_ = runner.Stop(c)
|
||||||
|
|
||||||
args, err := installServiceman(c)
|
args, err := installServiceman(c)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return err
|
return err
|
||||||
|
@ -104,22 +108,55 @@ func install(c *service.Service) error {
|
||||||
//fmt.Println(autorunKey, c.Title, regSZ)
|
//fmt.Println(autorunKey, c.Title, regSZ)
|
||||||
k.SetStringValue(c.Title, regSZ)
|
k.SetStringValue(c.Title, regSZ)
|
||||||
|
|
||||||
return nil
|
// to return ErrDaemonize
|
||||||
|
return start(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func start(conf *service.Service) error {
|
||||||
|
args := getRunnerArgs(conf)
|
||||||
|
return &ErrDaemonize{
|
||||||
|
DaemonArgs: append(args, "--daemon"),
|
||||||
|
error: "Not as much an error as a bad value...",
|
||||||
|
}
|
||||||
|
//return runner.Start(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop(conf *service.Service) error {
|
||||||
|
return runner.Stop(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRunnerArgs(c *service.Service) []string {
|
||||||
|
self := os.Args[0]
|
||||||
|
debug := ""
|
||||||
|
if strings.Contains(self, "debug.exe") {
|
||||||
|
debug = "debug."
|
||||||
|
}
|
||||||
|
|
||||||
|
smdir := `\opt\serviceman`
|
||||||
|
// TODO support service level services (which probably wouldn't need serviceman)
|
||||||
|
smdir = filepath.Join(c.Home, ".local", smdir)
|
||||||
|
// for now we'll scope the runner to the name of the application
|
||||||
|
smbin := filepath.Join(smdir, `bin\serviceman.`+debug+c.Name+`.exe`)
|
||||||
|
|
||||||
|
confpath := filepath.Join(smdir, `etc`)
|
||||||
|
conffile := filepath.Join(confpath, c.Name+`.json`)
|
||||||
|
|
||||||
|
return []string{
|
||||||
|
smbin,
|
||||||
|
"run",
|
||||||
|
"--config",
|
||||||
|
conffile,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// copies self to install path and returns config path
|
// copies self to install path and returns config path
|
||||||
func installServiceman(c *service.Service) ([]string, error) {
|
func installServiceman(c *service.Service) ([]string, error) {
|
||||||
// TODO check version and upgrade or dismiss
|
// TODO check version and upgrade or dismiss
|
||||||
self := os.Args[0]
|
self := os.Args[0]
|
||||||
debug := ""
|
|
||||||
if strings.Contains(self, "debug.exe") {
|
args := getRunnerArgs(c)
|
||||||
debug = "debug."
|
smbin := args[0]
|
||||||
}
|
conffile := args[len(args)-1]
|
||||||
smdir := `\opt\serviceman`
|
|
||||||
// TODO support service level services (which probably wouldn't need serviceman)
|
|
||||||
smdir = filepath.Join(c.Home, ".local", smdir)
|
|
||||||
// for now we'll scope the runner to the name of the application
|
|
||||||
smbin := filepath.Join(smdir, `bin\serviceman.`+debug+c.Name+`.exe`)
|
|
||||||
|
|
||||||
if smbin != self {
|
if smbin != self {
|
||||||
err := os.MkdirAll(filepath.Dir(smbin), 0755)
|
err := os.MkdirAll(filepath.Dir(smbin), 0755)
|
||||||
|
@ -141,21 +178,14 @@ func installServiceman(c *service.Service) ([]string, error) {
|
||||||
// this should be impossible, so we'll just panic
|
// this should be impossible, so we'll just panic
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
confpath := filepath.Join(smdir, `etc`)
|
err = os.MkdirAll(filepath.Dir(conffile), 0755)
|
||||||
err = os.MkdirAll(confpath, 0755)
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conffile := filepath.Join(confpath, c.Name+`.json`)
|
|
||||||
err = ioutil.WriteFile(conffile, b, 0640)
|
err = ioutil.WriteFile(conffile, b, 0640)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return []string{
|
return args, nil
|
||||||
smbin,
|
|
||||||
"run",
|
|
||||||
"--config",
|
|
||||||
conffile,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,28 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getService(system bool, home string, name string) (string, error) {
|
||||||
|
sys, user, err := getMatchingSrvs(home, name)
|
||||||
|
if nil != err {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var service string
|
||||||
|
if system {
|
||||||
|
service, err = getOneSysSrv(sys, user, name)
|
||||||
|
if nil != err {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
service, err = getOneUserSrv(home, sys, user, name)
|
||||||
|
if nil != err {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return service, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Runnable defines a command to run, along with its arguments,
|
// Runnable defines a command to run, along with its arguments,
|
||||||
// and whether or not failing to exit successfully matters.
|
// and whether or not failing to exit successfully matters.
|
||||||
// It also defines whether certains words must exist (or not exist)
|
// It also defines whether certains words must exist (or not exist)
|
||||||
|
|
142
runner/runner.go
142
runner/runner.go
|
@ -2,13 +2,17 @@ package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.rootprojects.org/root/go-serviceman/service"
|
"git.rootprojects.org/root/go-serviceman/service"
|
||||||
|
|
||||||
|
ps "github.com/mitchellh/go-ps"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Filled in on init by runner_windows.go
|
// Filled in on init by runner_windows.go
|
||||||
|
@ -17,7 +21,9 @@ var shellArgs = []string{}
|
||||||
// Notes on spawning a child process
|
// Notes on spawning a child process
|
||||||
// https://groups.google.com/forum/#!topic/golang-nuts/shST-SDqIp4
|
// https://groups.google.com/forum/#!topic/golang-nuts/shST-SDqIp4
|
||||||
|
|
||||||
func Run(conf *service.Service) {
|
// Start will execute the service, and write the PID and logs out to the log directory
|
||||||
|
func Start(conf *service.Service) error {
|
||||||
|
pid := os.Getpid()
|
||||||
originalBackoff := 1 * time.Second
|
originalBackoff := 1 * time.Second
|
||||||
maxBackoff := 1 * time.Minute
|
maxBackoff := 1 * time.Minute
|
||||||
threshold := 5 * time.Second
|
threshold := 5 * time.Second
|
||||||
|
@ -26,6 +32,17 @@ func Run(conf *service.Service) {
|
||||||
failures := 0
|
failures := 0
|
||||||
logfile := filepath.Join(conf.Logdir, conf.Name+".log")
|
logfile := filepath.Join(conf.Logdir, conf.Name+".log")
|
||||||
|
|
||||||
|
if oldPid, exename, err := getProcess(conf); nil == err {
|
||||||
|
return fmt.Errorf("%q may already be running as %q (pid %d)", conf.Name, exename, oldPid)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
maybeWritePidFile(pid, conf)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
binpath := conf.Exec
|
binpath := conf.Exec
|
||||||
args := []string{}
|
args := []string{}
|
||||||
if "" != conf.Interpreter {
|
if "" != conf.Interpreter {
|
||||||
|
@ -84,7 +101,7 @@ func Run(conf *service.Service) {
|
||||||
backoff = originalBackoff
|
backoff = originalBackoff
|
||||||
failures = 0
|
failures = 0
|
||||||
} else {
|
} else {
|
||||||
failures += 1
|
failures++
|
||||||
fmt.Fprintf(lf, "Waiting %s to restart %q (%d consequtive immediate exits)\n", backoff, conf.Name, failures)
|
fmt.Fprintf(lf, "Waiting %s to restart %q (%d consequtive immediate exits)\n", backoff, conf.Name, failures)
|
||||||
time.Sleep(backoff)
|
time.Sleep(backoff)
|
||||||
backoff *= 2
|
backoff *= 2
|
||||||
|
@ -93,4 +110,125 @@ func Run(conf *service.Service) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop will find and stop another serviceman runner instance by it's PID
|
||||||
|
func Stop(conf *service.Service) error {
|
||||||
|
i := 0
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
if i >= 3 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
oldPid, exename, err2 := getProcess(conf)
|
||||||
|
err = err2
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
fmt.Printf("killing old process %q with pid %d\n", exename, oldPid)
|
||||||
|
err := kill(oldPid)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return waitForProcessToDie(oldPid)
|
||||||
|
case ErrNoPidFile:
|
||||||
|
return err
|
||||||
|
case ErrNoProcess:
|
||||||
|
return err
|
||||||
|
case ErrInvalidPidFile:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
// waiting a little bit since the PID is written every second
|
||||||
|
time.Sleep(400 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart calls Stop, ignoring any failure, and then Start, returning any failure
|
||||||
|
func Restart(conf *service.Service) error {
|
||||||
|
_ = Stop(conf)
|
||||||
|
return Start(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrNoPidFile = fmt.Errorf("no pid file")
|
||||||
|
var ErrInvalidPidFile = fmt.Errorf("malformed pid file")
|
||||||
|
var ErrNoProcess = fmt.Errorf("process not found by pid")
|
||||||
|
|
||||||
|
func waitForProcessToDie(pid int) error {
|
||||||
|
exename := "unknown"
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
px, err := ps.FindProcess(pid)
|
||||||
|
if nil != err {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if nil == px {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
exename = px.Executable()
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("process %q (%d) just won't die", exename, pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProcess(conf *service.Service) (int, string, error) {
|
||||||
|
// TODO make Pidfile() a property of conf?
|
||||||
|
pidFile := filepath.Join(conf.Logdir, conf.Name+".pid")
|
||||||
|
b, err := ioutil.ReadFile(pidFile)
|
||||||
|
if nil != err {
|
||||||
|
return 0, "", ErrNoPidFile
|
||||||
|
}
|
||||||
|
|
||||||
|
s := strings.TrimSpace(string(b))
|
||||||
|
oldPid, err := strconv.Atoi(s)
|
||||||
|
if nil != err {
|
||||||
|
return 0, "", ErrInvalidPidFile
|
||||||
|
}
|
||||||
|
|
||||||
|
px, err := ps.FindProcess(oldPid)
|
||||||
|
if nil != err {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
if nil == px {
|
||||||
|
return 0, "", ErrNoProcess
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.FindProcess(oldPid)
|
||||||
|
if nil != err {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
exename := px.Executable()
|
||||||
|
return oldPid, exename, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO error out if can't write to PID or log
|
||||||
|
func maybeWritePidFile(pid int, conf *service.Service) bool {
|
||||||
|
newPid := []byte(strconv.Itoa(pid))
|
||||||
|
|
||||||
|
// TODO use a specific PID dir? meh...
|
||||||
|
pidFile := filepath.Join(conf.Logdir, conf.Name+".pid")
|
||||||
|
b, err := ioutil.ReadFile(pidFile)
|
||||||
|
if nil != err {
|
||||||
|
ioutil.WriteFile(pidFile, newPid, 0644)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
s := strings.TrimSpace(string(b))
|
||||||
|
oldPid, err := strconv.Atoi(s)
|
||||||
|
if nil != err {
|
||||||
|
ioutil.WriteFile(pidFile, newPid, 0644)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldPid != pid {
|
||||||
|
Stop(conf)
|
||||||
|
ioutil.WriteFile(pidFile, newPid, 0644)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,19 @@
|
||||||
|
|
||||||
package runner
|
package runner
|
||||||
|
|
||||||
import "os/exec"
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
func backgroundCmd(cmd *exec.Cmd) {
|
func backgroundCmd(cmd *exec.Cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func kill(pid int) error {
|
||||||
|
p, err := os.FindProcess(pid)
|
||||||
|
// already died
|
||||||
|
if nil != err {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return p.Kill()
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
package runner
|
package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func backgroundCmd(cmd *exec.Cmd) {
|
func backgroundCmd(cmd *exec.Cmd) {
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func kill(pid int) error {
|
||||||
|
// Kill the whole processes tree (all children and grandchildren)
|
||||||
|
cmd := exec.Command("taskkill", "/pid", strconv.Itoa(pid), "/T", "/F")
|
||||||
|
b, err := cmd.CombinedOutput()
|
||||||
|
if nil != err {
|
||||||
|
return fmt.Errorf("%s: %s", err.Error(), string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ type Service struct {
|
||||||
MultiuserProtection bool `json:"multiuser_protection,omitempty"`
|
MultiuserProtection bool `json:"multiuser_protection,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Normalize(force bool) {
|
func (s *Service) NormalizeWithoutPath() {
|
||||||
if "" == s.Name {
|
if "" == s.Name {
|
||||||
ext := filepath.Ext(s.Exec)
|
ext := filepath.Ext(s.Exec)
|
||||||
base := filepath.Base(s.Exec[:len(s.Exec)-len(ext)])
|
base := filepath.Base(s.Exec[:len(s.Exec)-len(ext)])
|
||||||
|
@ -93,11 +93,16 @@ func (s *Service) Normalize(force bool) {
|
||||||
os.Exit(4)
|
os.Exit(4)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
s.Home = home
|
||||||
s.Local = filepath.Join(home, ".local")
|
s.Local = filepath.Join(home, ".local")
|
||||||
s.Logdir = filepath.Join(home, ".local", "share", s.Name, "var", "log")
|
s.Logdir = filepath.Join(home, ".local", "share", s.Name, "var", "log")
|
||||||
} else {
|
} else {
|
||||||
s.Logdir = "/var/log/" + s.Name
|
s.Logdir = "/var/log/" + s.Name
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Normalize(force bool) {
|
||||||
|
s.NormalizeWithoutPath()
|
||||||
|
|
||||||
// Check to see if Exec exists
|
// Check to see if Exec exists
|
||||||
// /whatever => must exist exactly
|
// /whatever => must exist exactly
|
||||||
|
|
122
serviceman.go
122
serviceman.go
|
@ -22,8 +22,12 @@ var GitVersion = "v0.0.0"
|
||||||
var GitTimestamp = time.Now().Format(time.RFC3339)
|
var GitTimestamp = time.Now().Format(time.RFC3339)
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Println("Usage: serviceman add ./foo-app -- --foo-arg")
|
fmt.Println("Usage:")
|
||||||
fmt.Println("Usage: serviceman run --config ./foo-app.json")
|
fmt.Println("\tserviceman <command> --help")
|
||||||
|
fmt.Println("\tserviceman add ./foo-app -- --foo-arg")
|
||||||
|
fmt.Println("\tserviceman run --config ./foo-app.json")
|
||||||
|
fmt.Println("\tserviceman start <name>")
|
||||||
|
fmt.Println("\tserviceman stop <name>")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -38,10 +42,14 @@ func main() {
|
||||||
switch top {
|
switch top {
|
||||||
case "version":
|
case "version":
|
||||||
fmt.Println(GitVersion, GitTimestamp, GitRev)
|
fmt.Println(GitVersion, GitTimestamp, GitRev)
|
||||||
case "add":
|
|
||||||
add()
|
|
||||||
case "run":
|
case "run":
|
||||||
run()
|
run()
|
||||||
|
case "add":
|
||||||
|
add()
|
||||||
|
case "start":
|
||||||
|
start()
|
||||||
|
case "stop":
|
||||||
|
stop()
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "Unknown argument %s\n", top)
|
fmt.Fprintf(os.Stderr, "Unknown argument %s\n", top)
|
||||||
usage()
|
usage()
|
||||||
|
@ -106,7 +114,7 @@ func add() {
|
||||||
|
|
||||||
execpath, err := manager.WhereIs(args[0])
|
execpath, err := manager.WhereIs(args[0])
|
||||||
if nil != err {
|
if nil != err {
|
||||||
fmt.Fprintf(os.Stderr, "Error: '%s' could not be found.\n", args[0])
|
fmt.Fprintf(os.Stderr, "Error: '%s' could not be found in PATH or working directory.\n", args[0])
|
||||||
if !force {
|
if !force {
|
||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
return
|
return
|
||||||
|
@ -131,7 +139,12 @@ func add() {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = manager.Install(conf)
|
err = manager.Install(conf)
|
||||||
if nil != err {
|
switch e := err.(type) {
|
||||||
|
case nil:
|
||||||
|
// do nothing
|
||||||
|
case *manager.ErrDaemonize:
|
||||||
|
runAsDaemon(e.DaemonArgs[0], e.DaemonArgs[1:]...)
|
||||||
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,6 +152,88 @@ func add() {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
forUser := false
|
||||||
|
forSystem := false
|
||||||
|
flag.BoolVar(&forSystem, "system", false, "attempt to add system service as an unprivileged/unelevated user")
|
||||||
|
flag.BoolVar(&forUser, "user", false, "add user space / user mode service even when admin/root/sudo/elevated")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
args := flag.Args()
|
||||||
|
if 1 != len(args) {
|
||||||
|
fmt.Println("Usage: serviceman start <name>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if forUser && forSystem {
|
||||||
|
fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := &service.Service{
|
||||||
|
Name: args[0],
|
||||||
|
Restart: false,
|
||||||
|
}
|
||||||
|
if forUser {
|
||||||
|
conf.System = false
|
||||||
|
} else if forSystem {
|
||||||
|
conf.System = true
|
||||||
|
} else {
|
||||||
|
conf.System = manager.IsPrivileged()
|
||||||
|
}
|
||||||
|
conf.NormalizeWithoutPath()
|
||||||
|
|
||||||
|
err := manager.Start(conf)
|
||||||
|
switch e := err.(type) {
|
||||||
|
case nil:
|
||||||
|
// do nothing
|
||||||
|
case *manager.ErrDaemonize:
|
||||||
|
runAsDaemon(e.DaemonArgs[0], e.DaemonArgs[1:]...)
|
||||||
|
default:
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(127)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
forUser := false
|
||||||
|
forSystem := false
|
||||||
|
flag.BoolVar(&forSystem, "system", false, "attempt to add system service as an unprivileged/unelevated user")
|
||||||
|
flag.BoolVar(&forUser, "user", false, "add user space / user mode service even when admin/root/sudo/elevated")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
args := flag.Args()
|
||||||
|
if 1 != len(args) {
|
||||||
|
fmt.Println("Usage: serviceman stop <name>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if forUser && forSystem {
|
||||||
|
fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := &service.Service{
|
||||||
|
Name: args[0],
|
||||||
|
Restart: false,
|
||||||
|
}
|
||||||
|
if forUser {
|
||||||
|
conf.System = false
|
||||||
|
} else if forSystem {
|
||||||
|
conf.System = true
|
||||||
|
} else {
|
||||||
|
conf.System = manager.IsPrivileged()
|
||||||
|
}
|
||||||
|
conf.NormalizeWithoutPath()
|
||||||
|
|
||||||
|
if err := manager.Stop(conf); nil != err {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(127)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func run() {
|
func run() {
|
||||||
var confpath string
|
var confpath string
|
||||||
var daemonize bool
|
var daemonize bool
|
||||||
|
@ -183,7 +278,8 @@ func run() {
|
||||||
os.Exit(400)
|
os.Exit(400)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Normalize(false)
|
force := false
|
||||||
|
s.Normalize(force)
|
||||||
fmt.Printf("All output will be directed to the logs at:\n\t%s\n", s.Logdir)
|
fmt.Printf("All output will be directed to the logs at:\n\t%s\n", s.Logdir)
|
||||||
err = os.MkdirAll(s.Logdir, 0755)
|
err = os.MkdirAll(s.Logdir, 0755)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
|
@ -193,11 +289,17 @@ func run() {
|
||||||
|
|
||||||
if !daemonize {
|
if !daemonize {
|
||||||
//fmt.Fprintf(os.Stdout, "Running %s %s %s\n", s.Interpreter, s.Exec, strings.Join(s.Argv, " "))
|
//fmt.Fprintf(os.Stdout, "Running %s %s %s\n", s.Interpreter, s.Exec, strings.Join(s.Argv, " "))
|
||||||
runner.Run(s)
|
if err := runner.Start(s); nil != err {
|
||||||
|
fmt.Println("Error:", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(os.Args[0], "run", "--config", confpath)
|
runAsDaemon(os.Args[0], "run", "--config", confpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runAsDaemon(bin string, args ...string) {
|
||||||
|
cmd := exec.Command(bin, args...)
|
||||||
// for debugging
|
// for debugging
|
||||||
/*
|
/*
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
|
@ -207,7 +309,7 @@ func run() {
|
||||||
fmt.Println(string(out))
|
fmt.Println(string(out))
|
||||||
*/
|
*/
|
||||||
|
|
||||||
err = cmd.Start()
|
err := cmd.Start()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
os.Exit(500)
|
os.Exit(500)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
.vagrant/
|
|
@ -0,0 +1,4 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.2.1
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Mitchell Hashimoto
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Process List Library for Go
|
||||||
|
|
||||||
|
go-ps is a library for Go that implements OS-specific APIs to list and
|
||||||
|
manipulate processes in a platform-safe way. The library can find and
|
||||||
|
list processes on Linux, Mac OS X, Solaris, and Windows.
|
||||||
|
|
||||||
|
If you're new to Go, this library has a good amount of advanced Go educational
|
||||||
|
value as well. It uses some advanced features of Go: build tags, accessing
|
||||||
|
DLL methods for Windows, cgo for Darwin, etc.
|
||||||
|
|
||||||
|
How it works:
|
||||||
|
|
||||||
|
* **Darwin** uses the `sysctl` syscall to retrieve the process table.
|
||||||
|
* **Unix** uses the procfs at `/proc` to inspect the process tree.
|
||||||
|
* **Windows** uses the Windows API, and methods such as
|
||||||
|
`CreateToolhelp32Snapshot` to get a point-in-time snapshot of
|
||||||
|
the process table.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Install using standard `go get`:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go get github.com/mitchellh/go-ps
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
Want to contribute? Here is a short TODO list of things that aren't
|
||||||
|
implemented for this library that would be nice:
|
||||||
|
|
||||||
|
* FreeBSD support
|
||||||
|
* Plan9 support
|
|
@ -0,0 +1,43 @@
|
||||||
|
# -*- mode: ruby -*-
|
||||||
|
# vi: set ft=ruby :
|
||||||
|
|
||||||
|
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
|
||||||
|
VAGRANTFILE_API_VERSION = "2"
|
||||||
|
|
||||||
|
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
|
config.vm.box = "chef/ubuntu-12.04"
|
||||||
|
|
||||||
|
config.vm.provision "shell", inline: $script
|
||||||
|
|
||||||
|
["vmware_fusion", "vmware_workstation"].each do |p|
|
||||||
|
config.vm.provider "p" do |v|
|
||||||
|
v.vmx["memsize"] = "1024"
|
||||||
|
v.vmx["numvcpus"] = "2"
|
||||||
|
v.vmx["cpuid.coresPerSocket"] = "1"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
$script = <<SCRIPT
|
||||||
|
SRCROOT="/opt/go"
|
||||||
|
|
||||||
|
# Install Go
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential mercurial
|
||||||
|
sudo hg clone -u release https://code.google.com/p/go ${SRCROOT}
|
||||||
|
cd ${SRCROOT}/src
|
||||||
|
sudo ./all.bash
|
||||||
|
|
||||||
|
# Setup the GOPATH
|
||||||
|
sudo mkdir -p /opt/gopath
|
||||||
|
cat <<EOF >/tmp/gopath.sh
|
||||||
|
export GOPATH="/opt/gopath"
|
||||||
|
export PATH="/opt/go/bin:\$GOPATH/bin:\$PATH"
|
||||||
|
EOF
|
||||||
|
sudo mv /tmp/gopath.sh /etc/profile.d/gopath.sh
|
||||||
|
sudo chmod 0755 /etc/profile.d/gopath.sh
|
||||||
|
|
||||||
|
# Make sure the gopath is usable by bamboo
|
||||||
|
sudo chown -R vagrant:vagrant $SRCROOT
|
||||||
|
sudo chown -R vagrant:vagrant /opt/gopath
|
||||||
|
SCRIPT
|
|
@ -0,0 +1,40 @@
|
||||||
|
// ps provides an API for finding and listing processes in a platform-agnostic
|
||||||
|
// way.
|
||||||
|
//
|
||||||
|
// NOTE: If you're reading these docs online via GoDocs or some other system,
|
||||||
|
// you might only see the Unix docs. This project makes heavy use of
|
||||||
|
// platform-specific implementations. We recommend reading the source if you
|
||||||
|
// are interested.
|
||||||
|
package ps
|
||||||
|
|
||||||
|
// Process is the generic interface that is implemented on every platform
|
||||||
|
// and provides common operations for processes.
|
||||||
|
type Process interface {
|
||||||
|
// Pid is the process ID for this process.
|
||||||
|
Pid() int
|
||||||
|
|
||||||
|
// PPid is the parent process ID for this process.
|
||||||
|
PPid() int
|
||||||
|
|
||||||
|
// Executable name running this process. This is not a path to the
|
||||||
|
// executable.
|
||||||
|
Executable() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processes returns all processes.
|
||||||
|
//
|
||||||
|
// This of course will be a point-in-time snapshot of when this method was
|
||||||
|
// called. Some operating systems don't provide snapshot capability of the
|
||||||
|
// process table, in which case the process table returned might contain
|
||||||
|
// ephemeral entities that happened to be running when this was called.
|
||||||
|
func Processes() ([]Process, error) {
|
||||||
|
return processes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindProcess looks up a single process by pid.
|
||||||
|
//
|
||||||
|
// Process will be nil and error will be nil if a matching process is
|
||||||
|
// not found.
|
||||||
|
func FindProcess(pid int) (Process, error) {
|
||||||
|
return findProcess(pid)
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package ps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DarwinProcess struct {
|
||||||
|
pid int
|
||||||
|
ppid int
|
||||||
|
binary string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DarwinProcess) Pid() int {
|
||||||
|
return p.pid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DarwinProcess) PPid() int {
|
||||||
|
return p.ppid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *DarwinProcess) Executable() string {
|
||||||
|
return p.binary
|
||||||
|
}
|
||||||
|
|
||||||
|
func findProcess(pid int) (Process, error) {
|
||||||
|
ps, err := processes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range ps {
|
||||||
|
if p.Pid() == pid {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processes() ([]Process, error) {
|
||||||
|
buf, err := darwinSyscall()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
procs := make([]*kinfoProc, 0, 50)
|
||||||
|
k := 0
|
||||||
|
for i := _KINFO_STRUCT_SIZE; i < buf.Len(); i += _KINFO_STRUCT_SIZE {
|
||||||
|
proc := &kinfoProc{}
|
||||||
|
err = binary.Read(bytes.NewBuffer(buf.Bytes()[k:i]), binary.LittleEndian, proc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
k = i
|
||||||
|
procs = append(procs, proc)
|
||||||
|
}
|
||||||
|
|
||||||
|
darwinProcs := make([]Process, len(procs))
|
||||||
|
for i, p := range procs {
|
||||||
|
darwinProcs[i] = &DarwinProcess{
|
||||||
|
pid: int(p.Pid),
|
||||||
|
ppid: int(p.PPid),
|
||||||
|
binary: darwinCstring(p.Comm),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return darwinProcs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func darwinCstring(s [16]byte) string {
|
||||||
|
i := 0
|
||||||
|
for _, b := range s {
|
||||||
|
if b != 0 {
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(s[:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
func darwinSyscall() (*bytes.Buffer, error) {
|
||||||
|
mib := [4]int32{_CTRL_KERN, _KERN_PROC, _KERN_PROC_ALL, 0}
|
||||||
|
size := uintptr(0)
|
||||||
|
|
||||||
|
_, _, errno := syscall.Syscall6(
|
||||||
|
syscall.SYS___SYSCTL,
|
||||||
|
uintptr(unsafe.Pointer(&mib[0])),
|
||||||
|
4,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
|
||||||
|
if errno != 0 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := make([]byte, size)
|
||||||
|
_, _, errno = syscall.Syscall6(
|
||||||
|
syscall.SYS___SYSCTL,
|
||||||
|
uintptr(unsafe.Pointer(&mib[0])),
|
||||||
|
4,
|
||||||
|
uintptr(unsafe.Pointer(&bs[0])),
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
|
||||||
|
if errno != 0 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes.NewBuffer(bs[0:size]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
_CTRL_KERN = 1
|
||||||
|
_KERN_PROC = 14
|
||||||
|
_KERN_PROC_ALL = 0
|
||||||
|
_KINFO_STRUCT_SIZE = 648
|
||||||
|
)
|
||||||
|
|
||||||
|
type kinfoProc struct {
|
||||||
|
_ [40]byte
|
||||||
|
Pid int32
|
||||||
|
_ [199]byte
|
||||||
|
Comm [16]byte
|
||||||
|
_ [301]byte
|
||||||
|
PPid int32
|
||||||
|
_ [84]byte
|
||||||
|
}
|
|
@ -0,0 +1,260 @@
|
||||||
|
// +build freebsd,amd64
|
||||||
|
|
||||||
|
package ps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// copied from sys/sysctl.h
|
||||||
|
const (
|
||||||
|
CTL_KERN = 1 // "high kernel": proc, limits
|
||||||
|
KERN_PROC = 14 // struct: process entries
|
||||||
|
KERN_PROC_PID = 1 // by process id
|
||||||
|
KERN_PROC_PROC = 8 // only return procs
|
||||||
|
KERN_PROC_PATHNAME = 12 // path to executable
|
||||||
|
)
|
||||||
|
|
||||||
|
// copied from sys/user.h
|
||||||
|
type Kinfo_proc struct {
|
||||||
|
Ki_structsize int32
|
||||||
|
Ki_layout int32
|
||||||
|
Ki_args int64
|
||||||
|
Ki_paddr int64
|
||||||
|
Ki_addr int64
|
||||||
|
Ki_tracep int64
|
||||||
|
Ki_textvp int64
|
||||||
|
Ki_fd int64
|
||||||
|
Ki_vmspace int64
|
||||||
|
Ki_wchan int64
|
||||||
|
Ki_pid int32
|
||||||
|
Ki_ppid int32
|
||||||
|
Ki_pgid int32
|
||||||
|
Ki_tpgid int32
|
||||||
|
Ki_sid int32
|
||||||
|
Ki_tsid int32
|
||||||
|
Ki_jobc [2]byte
|
||||||
|
Ki_spare_short1 [2]byte
|
||||||
|
Ki_tdev int32
|
||||||
|
Ki_siglist [16]byte
|
||||||
|
Ki_sigmask [16]byte
|
||||||
|
Ki_sigignore [16]byte
|
||||||
|
Ki_sigcatch [16]byte
|
||||||
|
Ki_uid int32
|
||||||
|
Ki_ruid int32
|
||||||
|
Ki_svuid int32
|
||||||
|
Ki_rgid int32
|
||||||
|
Ki_svgid int32
|
||||||
|
Ki_ngroups [2]byte
|
||||||
|
Ki_spare_short2 [2]byte
|
||||||
|
Ki_groups [64]byte
|
||||||
|
Ki_size int64
|
||||||
|
Ki_rssize int64
|
||||||
|
Ki_swrss int64
|
||||||
|
Ki_tsize int64
|
||||||
|
Ki_dsize int64
|
||||||
|
Ki_ssize int64
|
||||||
|
Ki_xstat [2]byte
|
||||||
|
Ki_acflag [2]byte
|
||||||
|
Ki_pctcpu int32
|
||||||
|
Ki_estcpu int32
|
||||||
|
Ki_slptime int32
|
||||||
|
Ki_swtime int32
|
||||||
|
Ki_cow int32
|
||||||
|
Ki_runtime int64
|
||||||
|
Ki_start [16]byte
|
||||||
|
Ki_childtime [16]byte
|
||||||
|
Ki_flag int64
|
||||||
|
Ki_kiflag int64
|
||||||
|
Ki_traceflag int32
|
||||||
|
Ki_stat [1]byte
|
||||||
|
Ki_nice [1]byte
|
||||||
|
Ki_lock [1]byte
|
||||||
|
Ki_rqindex [1]byte
|
||||||
|
Ki_oncpu [1]byte
|
||||||
|
Ki_lastcpu [1]byte
|
||||||
|
Ki_ocomm [17]byte
|
||||||
|
Ki_wmesg [9]byte
|
||||||
|
Ki_login [18]byte
|
||||||
|
Ki_lockname [9]byte
|
||||||
|
Ki_comm [20]byte
|
||||||
|
Ki_emul [17]byte
|
||||||
|
Ki_sparestrings [68]byte
|
||||||
|
Ki_spareints [36]byte
|
||||||
|
Ki_cr_flags int32
|
||||||
|
Ki_jid int32
|
||||||
|
Ki_numthreads int32
|
||||||
|
Ki_tid int32
|
||||||
|
Ki_pri int32
|
||||||
|
Ki_rusage [144]byte
|
||||||
|
Ki_rusage_ch [144]byte
|
||||||
|
Ki_pcb int64
|
||||||
|
Ki_kstack int64
|
||||||
|
Ki_udata int64
|
||||||
|
Ki_tdaddr int64
|
||||||
|
Ki_spareptrs [48]byte
|
||||||
|
Ki_spareint64s [96]byte
|
||||||
|
Ki_sflag int64
|
||||||
|
Ki_tdflags int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnixProcess is an implementation of Process that contains Unix-specific
|
||||||
|
// fields and information.
|
||||||
|
type UnixProcess struct {
|
||||||
|
pid int
|
||||||
|
ppid int
|
||||||
|
state rune
|
||||||
|
pgrp int
|
||||||
|
sid int
|
||||||
|
|
||||||
|
binary string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UnixProcess) Pid() int {
|
||||||
|
return p.pid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UnixProcess) PPid() int {
|
||||||
|
return p.ppid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UnixProcess) Executable() string {
|
||||||
|
return p.binary
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh reloads all the data associated with this process.
|
||||||
|
func (p *UnixProcess) Refresh() error {
|
||||||
|
|
||||||
|
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PID, int32(p.pid)}
|
||||||
|
|
||||||
|
buf, length, err := call_syscall(mib)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
proc_k := Kinfo_proc{}
|
||||||
|
if length != uint64(unsafe.Sizeof(proc_k)) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := parse_kinfo_proc(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copy_params(k *Kinfo_proc) (int, int, int, string) {
|
||||||
|
n := -1
|
||||||
|
for i, b := range k.Ki_comm {
|
||||||
|
if b == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n = i + 1
|
||||||
|
}
|
||||||
|
comm := string(k.Ki_comm[:n])
|
||||||
|
|
||||||
|
return int(k.Ki_ppid), int(k.Ki_pgid), int(k.Ki_sid), comm
|
||||||
|
}
|
||||||
|
|
||||||
|
func findProcess(pid int) (Process, error) {
|
||||||
|
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, int32(pid)}
|
||||||
|
|
||||||
|
_, _, err := call_syscall(mib)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUnixProcess(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func processes() ([]Process, error) {
|
||||||
|
results := make([]Process, 0, 50)
|
||||||
|
|
||||||
|
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0}
|
||||||
|
buf, length, err := call_syscall(mib)
|
||||||
|
if err != nil {
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get kinfo_proc size
|
||||||
|
k := Kinfo_proc{}
|
||||||
|
procinfo_len := int(unsafe.Sizeof(k))
|
||||||
|
count := int(length / uint64(procinfo_len))
|
||||||
|
|
||||||
|
// parse buf to procs
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
b := buf[i*procinfo_len : i*procinfo_len+procinfo_len]
|
||||||
|
k, err := parse_kinfo_proc(b)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p, err := newUnixProcess(int(k.Ki_pid))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k)
|
||||||
|
|
||||||
|
results = append(results, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse_kinfo_proc(buf []byte) (Kinfo_proc, error) {
|
||||||
|
var k Kinfo_proc
|
||||||
|
br := bytes.NewReader(buf)
|
||||||
|
err := binary.Read(br, binary.LittleEndian, &k)
|
||||||
|
if err != nil {
|
||||||
|
return k, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func call_syscall(mib []int32) ([]byte, uint64, error) {
|
||||||
|
miblen := uint64(len(mib))
|
||||||
|
|
||||||
|
// get required buffer size
|
||||||
|
length := uint64(0)
|
||||||
|
_, _, err := syscall.RawSyscall6(
|
||||||
|
syscall.SYS___SYSCTL,
|
||||||
|
uintptr(unsafe.Pointer(&mib[0])),
|
||||||
|
uintptr(miblen),
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&length)),
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
if err != 0 {
|
||||||
|
b := make([]byte, 0)
|
||||||
|
return b, length, err
|
||||||
|
}
|
||||||
|
if length == 0 {
|
||||||
|
b := make([]byte, 0)
|
||||||
|
return b, length, err
|
||||||
|
}
|
||||||
|
// get proc info itself
|
||||||
|
buf := make([]byte, length)
|
||||||
|
_, _, err = syscall.RawSyscall6(
|
||||||
|
syscall.SYS___SYSCTL,
|
||||||
|
uintptr(unsafe.Pointer(&mib[0])),
|
||||||
|
uintptr(miblen),
|
||||||
|
uintptr(unsafe.Pointer(&buf[0])),
|
||||||
|
uintptr(unsafe.Pointer(&length)),
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
if err != 0 {
|
||||||
|
return buf, length, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnixProcess(pid int) (*UnixProcess, error) {
|
||||||
|
p := &UnixProcess{pid: pid}
|
||||||
|
return p, p.Refresh()
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package ps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Refresh reloads all the data associated with this process.
|
||||||
|
func (p *UnixProcess) Refresh() error {
|
||||||
|
statPath := fmt.Sprintf("/proc/%d/stat", p.pid)
|
||||||
|
dataBytes, err := ioutil.ReadFile(statPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, parse out the image name
|
||||||
|
data := string(dataBytes)
|
||||||
|
binStart := strings.IndexRune(data, '(') + 1
|
||||||
|
binEnd := strings.IndexRune(data[binStart:], ')')
|
||||||
|
p.binary = data[binStart : binStart+binEnd]
|
||||||
|
|
||||||
|
// Move past the image name and start parsing the rest
|
||||||
|
data = data[binStart+binEnd+2:]
|
||||||
|
_, err = fmt.Sscanf(data,
|
||||||
|
"%c %d %d %d",
|
||||||
|
&p.state,
|
||||||
|
&p.ppid,
|
||||||
|
&p.pgrp,
|
||||||
|
&p.sid)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
// +build solaris
|
||||||
|
|
||||||
|
package ps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ushort_t uint16
|
||||||
|
|
||||||
|
type id_t int32
|
||||||
|
type pid_t int32
|
||||||
|
type uid_t int32
|
||||||
|
type gid_t int32
|
||||||
|
|
||||||
|
type dev_t uint64
|
||||||
|
type size_t uint64
|
||||||
|
type uintptr_t uint64
|
||||||
|
|
||||||
|
type timestruc_t [16]byte
|
||||||
|
|
||||||
|
// This is copy from /usr/include/sys/procfs.h
|
||||||
|
type psinfo_t struct {
|
||||||
|
Pr_flag int32 /* process flags (DEPRECATED; do not use) */
|
||||||
|
Pr_nlwp int32 /* number of active lwps in the process */
|
||||||
|
Pr_pid pid_t /* unique process id */
|
||||||
|
Pr_ppid pid_t /* process id of parent */
|
||||||
|
Pr_pgid pid_t /* pid of process group leader */
|
||||||
|
Pr_sid pid_t /* session id */
|
||||||
|
Pr_uid uid_t /* real user id */
|
||||||
|
Pr_euid uid_t /* effective user id */
|
||||||
|
Pr_gid gid_t /* real group id */
|
||||||
|
Pr_egid gid_t /* effective group id */
|
||||||
|
Pr_addr uintptr_t /* address of process */
|
||||||
|
Pr_size size_t /* size of process image in Kbytes */
|
||||||
|
Pr_rssize size_t /* resident set size in Kbytes */
|
||||||
|
Pr_pad1 size_t
|
||||||
|
Pr_ttydev dev_t /* controlling tty device (or PRNODEV) */
|
||||||
|
|
||||||
|
// Guess this following 2 ushort_t values require a padding to properly
|
||||||
|
// align to the 64bit mark.
|
||||||
|
Pr_pctcpu ushort_t /* % of recent cpu time used by all lwps */
|
||||||
|
Pr_pctmem ushort_t /* % of system memory used by process */
|
||||||
|
Pr_pad64bit [4]byte
|
||||||
|
|
||||||
|
Pr_start timestruc_t /* process start time, from the epoch */
|
||||||
|
Pr_time timestruc_t /* usr+sys cpu time for this process */
|
||||||
|
Pr_ctime timestruc_t /* usr+sys cpu time for reaped children */
|
||||||
|
Pr_fname [16]byte /* name of execed file */
|
||||||
|
Pr_psargs [80]byte /* initial characters of arg list */
|
||||||
|
Pr_wstat int32 /* if zombie, the wait() status */
|
||||||
|
Pr_argc int32 /* initial argument count */
|
||||||
|
Pr_argv uintptr_t /* address of initial argument vector */
|
||||||
|
Pr_envp uintptr_t /* address of initial environment vector */
|
||||||
|
Pr_dmodel [1]byte /* data model of the process */
|
||||||
|
Pr_pad2 [3]byte
|
||||||
|
Pr_taskid id_t /* task id */
|
||||||
|
Pr_projid id_t /* project id */
|
||||||
|
Pr_nzomb int32 /* number of zombie lwps in the process */
|
||||||
|
Pr_poolid id_t /* pool id */
|
||||||
|
Pr_zoneid id_t /* zone id */
|
||||||
|
Pr_contract id_t /* process contract */
|
||||||
|
Pr_filler int32 /* reserved for future use */
|
||||||
|
Pr_lwp [128]byte /* information for representative lwp */
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UnixProcess) Refresh() error {
|
||||||
|
var psinfo psinfo_t
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/proc/%d/psinfo", p.pid)
|
||||||
|
fh, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
|
||||||
|
err = binary.Read(fh, binary.LittleEndian, &psinfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ppid = int(psinfo.Pr_ppid)
|
||||||
|
p.binary = toString(psinfo.Pr_fname[:], 16)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toString(array []byte, len int) string {
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
if array[i] == 0 {
|
||||||
|
return string(array[:i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(array[:])
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
// +build linux solaris
|
||||||
|
|
||||||
|
package ps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnixProcess is an implementation of Process that contains Unix-specific
|
||||||
|
// fields and information.
|
||||||
|
type UnixProcess struct {
|
||||||
|
pid int
|
||||||
|
ppid int
|
||||||
|
state rune
|
||||||
|
pgrp int
|
||||||
|
sid int
|
||||||
|
|
||||||
|
binary string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UnixProcess) Pid() int {
|
||||||
|
return p.pid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UnixProcess) PPid() int {
|
||||||
|
return p.ppid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UnixProcess) Executable() string {
|
||||||
|
return p.binary
|
||||||
|
}
|
||||||
|
|
||||||
|
func findProcess(pid int) (Process, error) {
|
||||||
|
dir := fmt.Sprintf("/proc/%d", pid)
|
||||||
|
_, err := os.Stat(dir)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUnixProcess(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func processes() ([]Process, error) {
|
||||||
|
d, err := os.Open("/proc")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer d.Close()
|
||||||
|
|
||||||
|
results := make([]Process, 0, 50)
|
||||||
|
for {
|
||||||
|
fis, err := d.Readdir(10)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fi := range fis {
|
||||||
|
// We only care about directories, since all pids are dirs
|
||||||
|
if !fi.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only care if the name starts with a numeric
|
||||||
|
name := fi.Name()
|
||||||
|
if name[0] < '0' || name[0] > '9' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// From this point forward, any errors we just ignore, because
|
||||||
|
// it might simply be that the process doesn't exist anymore.
|
||||||
|
pid, err := strconv.ParseInt(name, 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := newUnixProcess(int(pid))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnixProcess(pid int) (*UnixProcess, error) {
|
||||||
|
p := &UnixProcess{pid: pid}
|
||||||
|
return p, p.Refresh()
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package ps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows API functions
|
||||||
|
var (
|
||||||
|
modKernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
procCloseHandle = modKernel32.NewProc("CloseHandle")
|
||||||
|
procCreateToolhelp32Snapshot = modKernel32.NewProc("CreateToolhelp32Snapshot")
|
||||||
|
procProcess32First = modKernel32.NewProc("Process32FirstW")
|
||||||
|
procProcess32Next = modKernel32.NewProc("Process32NextW")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some constants from the Windows API
|
||||||
|
const (
|
||||||
|
ERROR_NO_MORE_FILES = 0x12
|
||||||
|
MAX_PATH = 260
|
||||||
|
)
|
||||||
|
|
||||||
|
// PROCESSENTRY32 is the Windows API structure that contains a process's
|
||||||
|
// information.
|
||||||
|
type PROCESSENTRY32 struct {
|
||||||
|
Size uint32
|
||||||
|
CntUsage uint32
|
||||||
|
ProcessID uint32
|
||||||
|
DefaultHeapID uintptr
|
||||||
|
ModuleID uint32
|
||||||
|
CntThreads uint32
|
||||||
|
ParentProcessID uint32
|
||||||
|
PriorityClassBase int32
|
||||||
|
Flags uint32
|
||||||
|
ExeFile [MAX_PATH]uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowsProcess is an implementation of Process for Windows.
|
||||||
|
type WindowsProcess struct {
|
||||||
|
pid int
|
||||||
|
ppid int
|
||||||
|
exe string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WindowsProcess) Pid() int {
|
||||||
|
return p.pid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WindowsProcess) PPid() int {
|
||||||
|
return p.ppid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WindowsProcess) Executable() string {
|
||||||
|
return p.exe
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWindowsProcess(e *PROCESSENTRY32) *WindowsProcess {
|
||||||
|
// Find when the string ends for decoding
|
||||||
|
end := 0
|
||||||
|
for {
|
||||||
|
if e.ExeFile[end] == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WindowsProcess{
|
||||||
|
pid: int(e.ProcessID),
|
||||||
|
ppid: int(e.ParentProcessID),
|
||||||
|
exe: syscall.UTF16ToString(e.ExeFile[:end]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findProcess(pid int) (Process, error) {
|
||||||
|
ps, err := processes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range ps {
|
||||||
|
if p.Pid() == pid {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processes() ([]Process, error) {
|
||||||
|
handle, _, _ := procCreateToolhelp32Snapshot.Call(
|
||||||
|
0x00000002,
|
||||||
|
0)
|
||||||
|
if handle < 0 {
|
||||||
|
return nil, syscall.GetLastError()
|
||||||
|
}
|
||||||
|
defer procCloseHandle.Call(handle)
|
||||||
|
|
||||||
|
var entry PROCESSENTRY32
|
||||||
|
entry.Size = uint32(unsafe.Sizeof(entry))
|
||||||
|
ret, _, _ := procProcess32First.Call(handle, uintptr(unsafe.Pointer(&entry)))
|
||||||
|
if ret == 0 {
|
||||||
|
return nil, fmt.Errorf("Error retrieving process info.")
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]Process, 0, 50)
|
||||||
|
for {
|
||||||
|
results = append(results, newWindowsProcess(&entry))
|
||||||
|
|
||||||
|
ret, _, _ := procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry)))
|
||||||
|
if ret == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
|
@ -32,6 +32,8 @@ github.com/mattn/go-colorable
|
||||||
github.com/mattn/go-isatty
|
github.com/mattn/go-isatty
|
||||||
# github.com/mattn/go-runewidth v0.0.3
|
# github.com/mattn/go-runewidth v0.0.3
|
||||||
github.com/mattn/go-runewidth
|
github.com/mattn/go-runewidth
|
||||||
|
# github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936
|
||||||
|
github.com/mitchellh/go-ps
|
||||||
# github.com/mitchellh/go-wordwrap v1.0.0
|
# github.com/mitchellh/go-wordwrap v1.0.0
|
||||||
github.com/mitchellh/go-wordwrap
|
github.com/mitchellh/go-wordwrap
|
||||||
# github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e
|
# github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e
|
||||||
|
|
Loading…
Reference in New Issue