From 4c44f70ec3875be897c078d14346365c7cd2cb12 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 10 Jul 2019 01:16:45 -0600 Subject: [PATCH] Windows: start on install. Add stop/start to all. --- .gitignore | 2 + go.mod | 1 + go.sum | 2 + manager/install.go | 18 ++ manager/install_darwin.go | 69 +++-- manager/install_linux.go | 82 ++++-- manager/install_windows.go | 56 +++- manager/start.go | 22 ++ runner/runner.go | 142 +++++++++- runner/runner_notwindows.go | 14 +- runner/runner_windows.go | 13 + service/service.go | 7 +- serviceman.go | 122 +++++++- vendor/github.com/mitchellh/go-ps/.gitignore | 1 + vendor/github.com/mitchellh/go-ps/.travis.yml | 4 + vendor/github.com/mitchellh/go-ps/LICENSE.md | 21 ++ vendor/github.com/mitchellh/go-ps/README.md | 34 +++ vendor/github.com/mitchellh/go-ps/Vagrantfile | 43 +++ vendor/github.com/mitchellh/go-ps/process.go | 40 +++ .../mitchellh/go-ps/process_darwin.go | 138 ++++++++++ .../mitchellh/go-ps/process_freebsd.go | 260 ++++++++++++++++++ .../mitchellh/go-ps/process_linux.go | 35 +++ .../mitchellh/go-ps/process_solaris.go | 96 +++++++ .../mitchellh/go-ps/process_unix.go | 101 +++++++ .../mitchellh/go-ps/process_windows.go | 119 ++++++++ vendor/modules.txt | 2 + 26 files changed, 1383 insertions(+), 61 deletions(-) create mode 100644 vendor/github.com/mitchellh/go-ps/.gitignore create mode 100644 vendor/github.com/mitchellh/go-ps/.travis.yml create mode 100644 vendor/github.com/mitchellh/go-ps/LICENSE.md create mode 100644 vendor/github.com/mitchellh/go-ps/README.md create mode 100644 vendor/github.com/mitchellh/go-ps/Vagrantfile create mode 100644 vendor/github.com/mitchellh/go-ps/process.go create mode 100644 vendor/github.com/mitchellh/go-ps/process_darwin.go create mode 100644 vendor/github.com/mitchellh/go-ps/process_freebsd.go create mode 100644 vendor/github.com/mitchellh/go-ps/process_linux.go create mode 100644 vendor/github.com/mitchellh/go-ps/process_solaris.go create mode 100644 vendor/github.com/mitchellh/go-ps/process_unix.go create mode 100644 vendor/github.com/mitchellh/go-ps/process_windows.go diff --git a/.gitignore b/.gitignore index 9a3a8d8..24c45f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +*~ +.*~ # ---> Go # Binaries for programs and plugins *.exe diff --git a/go.mod b/go.mod index 4e07ad5..67da1a4 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index a70e9af..0d7441a 100644 --- a/go.sum +++ b/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= diff --git a/manager/install.go b/manager/install.go index 70e0c25..1dcd504 100644 --- a/manager/install.go +++ b/manager/install.go @@ -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, " ") +} diff --git a/manager/install_darwin.go b/manager/install_darwin.go index 0903bb9..efde7be 100644 --- a/manager/install_darwin.go +++ b/manager/install_darwin.go @@ -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") diff --git a/manager/install_linux.go b/manager/install_linux.go index 21ffeab..c9529e8 100644 --- a/manager/install_linux.go +++ b/manager/install_linux.go @@ -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 diff --git a/manager/install_windows.go b/manager/install_windows.go index 2bc3bbb..a9f2a72 100644 --- a/manager/install_windows.go +++ b/manager/install_windows.go @@ -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,23 +108,56 @@ 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) } -// copies self to install path and returns config path -func installServiceman(c *service.Service) ([]string, error) { - // TODO check version and upgrade or dismiss +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] + + args := getRunnerArgs(c) + smbin := args[0] + conffile := args[len(args)-1] + if smbin != self { err := os.MkdirAll(filepath.Dir(smbin), 0755) if nil != err { @@ -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 } diff --git a/manager/start.go b/manager/start.go index 03f0e5b..9c222ba 100644 --- a/manager/start.go +++ b/manager/start.go @@ -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) diff --git a/runner/runner.go b/runner/runner.go index 8c80e6b..ccd3883 100644 --- a/runner/runner.go +++ b/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 } diff --git a/runner/runner_notwindows.go b/runner/runner_notwindows.go index 71b636c..0ba8b5c 100644 --- a/runner/runner_notwindows.go +++ b/runner/runner_notwindows.go @@ -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() +} diff --git a/runner/runner_windows.go b/runner/runner_windows.go index df358f4..6a2387d 100644 --- a/runner/runner_windows.go +++ b/runner/runner_windows.go @@ -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 +} diff --git a/service/service.go b/service/service.go index eb033f0..e59e17a 100644 --- a/service/service.go +++ b/service/service.go @@ -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 diff --git a/serviceman.go b/serviceman.go index f54cce9..a565e76 100644 --- a/serviceman.go +++ b/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 --help") + fmt.Println("\tserviceman add ./foo-app -- --foo-arg") + fmt.Println("\tserviceman run --config ./foo-app.json") + fmt.Println("\tserviceman start ") + fmt.Println("\tserviceman stop ") } 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 ") + 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 ") + 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) diff --git a/vendor/github.com/mitchellh/go-ps/.gitignore b/vendor/github.com/mitchellh/go-ps/.gitignore new file mode 100644 index 0000000..a977916 --- /dev/null +++ b/vendor/github.com/mitchellh/go-ps/.gitignore @@ -0,0 +1 @@ +.vagrant/ diff --git a/vendor/github.com/mitchellh/go-ps/.travis.yml b/vendor/github.com/mitchellh/go-ps/.travis.yml new file mode 100644 index 0000000..8f794f7 --- /dev/null +++ b/vendor/github.com/mitchellh/go-ps/.travis.yml @@ -0,0 +1,4 @@ +language: go + +go: + - 1.2.1 diff --git a/vendor/github.com/mitchellh/go-ps/LICENSE.md b/vendor/github.com/mitchellh/go-ps/LICENSE.md new file mode 100644 index 0000000..2298515 --- /dev/null +++ b/vendor/github.com/mitchellh/go-ps/LICENSE.md @@ -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. diff --git a/vendor/github.com/mitchellh/go-ps/README.md b/vendor/github.com/mitchellh/go-ps/README.md new file mode 100644 index 0000000..8e8baf9 --- /dev/null +++ b/vendor/github.com/mitchellh/go-ps/README.md @@ -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 diff --git a/vendor/github.com/mitchellh/go-ps/Vagrantfile b/vendor/github.com/mitchellh/go-ps/Vagrantfile new file mode 100644 index 0000000..61662ab --- /dev/null +++ b/vendor/github.com/mitchellh/go-ps/Vagrantfile @@ -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 = <