Windows: start on install. Add stop/start to all.
This commit is contained in:
parent
58cf28df8e
commit
4c44f70ec3
|
@ -1,3 +1,5 @@
|
|||
*~
|
||||
.*~
|
||||
# ---> Go
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
|
|
1
go.mod
1
go.mod
|
@ -5,6 +5,7 @@ go 1.12
|
|||
require (
|
||||
git.rootprojects.org/root/go-gitver v1.1.2
|
||||
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/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-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
||||
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/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e h1:fvw0uluMptljaRKSU8459cJ4bmi3qUYyMs5kzpic2fY=
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.rootprojects.org/root/go-serviceman/service"
|
||||
)
|
||||
|
@ -42,6 +43,14 @@ func Install(c *service.Service) error {
|
|||
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
|
||||
// to write to system folders, bind to privileged ports, and otherwise
|
||||
// successfully run a system service.
|
||||
|
@ -57,3 +66,12 @@ func WhereIs(exe string) (string, error) {
|
|||
}
|
||||
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,25 +24,16 @@ func init() {
|
|||
srvLen = len(srvExt)
|
||||
}
|
||||
|
||||
func start(system bool, home string, name string) error {
|
||||
sys, user, err := getMatchingSrvs(home, name)
|
||||
func start(conf *service.Service) error {
|
||||
system := conf.System
|
||||
home := conf.Home
|
||||
rdns := conf.ReverseDNS
|
||||
|
||||
service, err := getService(system, home, rdns)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
cmds := []Runnable{
|
||||
Runnable{
|
||||
Exec: "launchctl",
|
||||
|
@ -60,7 +51,51 @@ func start(system bool, home string, name string) error {
|
|||
cmds = adjustPrivs(system, cmds)
|
||||
|
||||
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 {
|
||||
exe := cmds[i]
|
||||
fmt.Println("\t" + exe.String())
|
||||
|
@ -118,7 +153,7 @@ func install(c *service.Service) error {
|
|||
}
|
||||
|
||||
// TODO --no-start
|
||||
err = start(c.System, c.Home, c.ReverseDNS)
|
||||
err = start(c)
|
||||
if nil != err {
|
||||
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")
|
||||
|
|
|
@ -29,25 +29,16 @@ func init() {
|
|||
srvLen = len(srvExt)
|
||||
}
|
||||
|
||||
func start(system bool, home string, name string) error {
|
||||
sys, user, err := getMatchingSrvs(home, name)
|
||||
func start(conf *service.Service) error {
|
||||
system := conf.System
|
||||
home := conf.Home
|
||||
name := conf.ReverseDNS
|
||||
|
||||
_, err := getService(system, home, name)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
|
||||
// var service string
|
||||
if system {
|
||||
_, err = getOneSysSrv(sys, user, name)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, err = getOneUserSrv(home, sys, user, name)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var cmds []Runnable
|
||||
if system {
|
||||
cmds = []Runnable{
|
||||
|
@ -92,7 +83,64 @@ func start(system bool, home string, name string) error {
|
|||
cmds = adjustPrivs(system, cmds)
|
||||
|
||||
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 {
|
||||
exe := cmds[i]
|
||||
fmt.Println("\t" + exe.String())
|
||||
|
@ -152,7 +200,7 @@ func install(c *service.Service) error {
|
|||
}
|
||||
|
||||
// TODO --no-start
|
||||
err = start(c.System, c.Home, c.Name)
|
||||
err = start(c)
|
||||
if nil != err {
|
||||
sudo := ""
|
||||
// --user-unit rather than --user --unit for older systemd
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.rootprojects.org/root/go-serviceman/runner"
|
||||
"git.rootprojects.org/root/go-serviceman/service"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
|
@ -67,6 +68,9 @@ func install(c *service.Service) error {
|
|||
}
|
||||
defer k.Close()
|
||||
|
||||
// Try to stop before trying to copy the file
|
||||
_ = runner.Stop(c)
|
||||
|
||||
args, err := installServiceman(c)
|
||||
if nil != err {
|
||||
return err
|
||||
|
@ -104,22 +108,55 @@ func install(c *service.Service) error {
|
|||
//fmt.Println(autorunKey, 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
|
||||
func installServiceman(c *service.Service) ([]string, error) {
|
||||
// TODO check version and upgrade or dismiss
|
||||
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`)
|
||||
|
||||
args := getRunnerArgs(c)
|
||||
smbin := args[0]
|
||||
conffile := args[len(args)-1]
|
||||
|
||||
if smbin != self {
|
||||
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
|
||||
panic(err)
|
||||
}
|
||||
confpath := filepath.Join(smdir, `etc`)
|
||||
err = os.MkdirAll(confpath, 0755)
|
||||
err = os.MkdirAll(filepath.Dir(conffile), 0755)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
conffile := filepath.Join(confpath, c.Name+`.json`)
|
||||
err = ioutil.WriteFile(conffile, b, 0640)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []string{
|
||||
smbin,
|
||||
"run",
|
||||
"--config",
|
||||
conffile,
|
||||
}, nil
|
||||
return args, nil
|
||||
}
|
||||
|
|
|
@ -8,6 +8,28 @@ import (
|
|||
"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,
|
||||
// and whether or not failing to exit successfully matters.
|
||||
// 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 (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.rootprojects.org/root/go-serviceman/service"
|
||||
|
||||
ps "github.com/mitchellh/go-ps"
|
||||
)
|
||||
|
||||
// Filled in on init by runner_windows.go
|
||||
|
@ -17,7 +21,9 @@ var shellArgs = []string{}
|
|||
// Notes on spawning a child process
|
||||
// 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
|
||||
maxBackoff := 1 * time.Minute
|
||||
threshold := 5 * time.Second
|
||||
|
@ -26,6 +32,17 @@ func Run(conf *service.Service) {
|
|||
failures := 0
|
||||
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
|
||||
args := []string{}
|
||||
if "" != conf.Interpreter {
|
||||
|
@ -84,7 +101,7 @@ func Run(conf *service.Service) {
|
|||
backoff = originalBackoff
|
||||
failures = 0
|
||||
} else {
|
||||
failures += 1
|
||||
failures++
|
||||
fmt.Fprintf(lf, "Waiting %s to restart %q (%d consequtive immediate exits)\n", backoff, conf.Name, failures)
|
||||
time.Sleep(backoff)
|
||||
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
|
||||
|
||||
import "os/exec"
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func backgroundCmd(cmd *exec.Cmd) {
|
||||
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"`
|
||||
}
|
||||
|
||||
func (s *Service) Normalize(force bool) {
|
||||
func (s *Service) NormalizeWithoutPath() {
|
||||
if "" == s.Name {
|
||||
ext := filepath.Ext(s.Exec)
|
||||
base := filepath.Base(s.Exec[:len(s.Exec)-len(ext)])
|
||||
|
@ -93,11 +93,16 @@ func (s *Service) Normalize(force bool) {
|
|||
os.Exit(4)
|
||||
return
|
||||
}
|
||||
s.Home = home
|
||||
s.Local = filepath.Join(home, ".local")
|
||||
s.Logdir = filepath.Join(home, ".local", "share", s.Name, "var", "log")
|
||||
} else {
|
||||
s.Logdir = "/var/log/" + s.Name
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Normalize(force bool) {
|
||||
s.NormalizeWithoutPath()
|
||||
|
||||
// Check to see if Exec exists
|
||||
// /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)
|
||||
|
||||
func usage() {
|
||||
fmt.Println("Usage: serviceman add ./foo-app -- --foo-arg")
|
||||
fmt.Println("Usage: serviceman run --config ./foo-app.json")
|
||||
fmt.Println("Usage:")
|
||||
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() {
|
||||
|
@ -38,10 +42,14 @@ func main() {
|
|||
switch top {
|
||||
case "version":
|
||||
fmt.Println(GitVersion, GitTimestamp, GitRev)
|
||||
case "add":
|
||||
add()
|
||||
case "run":
|
||||
run()
|
||||
case "add":
|
||||
add()
|
||||
case "start":
|
||||
start()
|
||||
case "stop":
|
||||
stop()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown argument %s\n", top)
|
||||
usage()
|
||||
|
@ -106,7 +114,7 @@ func add() {
|
|||
|
||||
execpath, err := manager.WhereIs(args[0])
|
||||
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 {
|
||||
os.Exit(3)
|
||||
return
|
||||
|
@ -131,7 +139,12 @@ func add() {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -139,6 +152,88 @@ func add() {
|
|||
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() {
|
||||
var confpath string
|
||||
var daemonize bool
|
||||
|
@ -183,7 +278,8 @@ func run() {
|
|||
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)
|
||||
err = os.MkdirAll(s.Logdir, 0755)
|
||||
if nil != err {
|
||||
|
@ -193,11 +289,17 @@ func run() {
|
|||
|
||||
if !daemonize {
|
||||
//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
|
||||
}
|
||||
|
||||
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
|
||||
/*
|
||||
out, err := cmd.CombinedOutput()
|
||||
|
@ -207,7 +309,7 @@ func run() {
|
|||
fmt.Println(string(out))
|
||||
*/
|
||||
|
||||
err = cmd.Start()
|
||||
err := cmd.Start()
|
||||
if nil != err {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
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-runewidth v0.0.3
|
||||
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
|
||||
# github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e
|
||||
|
|
Loading…
Reference in New Issue