Browse Source

Windows: start on install. Add stop/start to all.

tags/v0.3.0
AJ ONeal 4 months ago
parent
commit
4c44f70ec3

+ 2
- 0
.gitignore View File

@@ -1,3 +1,5 @@
1
+*~
2
+.*~
1 3
 # ---> Go
2 4
 # Binaries for programs and plugins
3 5
 *.exe

+ 1
- 0
go.mod View File

@@ -5,6 +5,7 @@ go 1.12
5 5
 require (
6 6
 	git.rootprojects.org/root/go-gitver v1.1.2
7 7
 	github.com/UnnoTed/fileb0x v1.1.3
8
+	github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936
8 9
 	golang.org/x/net v0.0.0-20180921000356-2f5d2388922f
9 10
 	golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
10 11
 )

+ 2
- 0
go.sum View File

@@ -26,6 +26,8 @@ github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs
26 26
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
27 27
 github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
28 28
 github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
29
+github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936 h1:kw1v0NlnN+GZcU8Ma8CLF2Zzgjfx95gs3/GN3vYAPpo=
30
+github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
29 31
 github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
30 32
 github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
31 33
 github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e h1:fvw0uluMptljaRKSU8459cJ4bmi3qUYyMs5kzpic2fY=

+ 18
- 0
manager/install.go View File

@@ -7,6 +7,7 @@ import (
7 7
 	"os"
8 8
 	"os/exec"
9 9
 	"path/filepath"
10
+	"strings"
10 11
 
11 12
 	"git.rootprojects.org/root/go-serviceman/service"
12 13
 )
@@ -42,6 +43,14 @@ func Install(c *service.Service) error {
42 43
 	return nil
43 44
 }
44 45
 
46
+func Start(conf *service.Service) error {
47
+	return start(conf)
48
+}
49
+
50
+func Stop(conf *service.Service) error {
51
+	return stop(conf)
52
+}
53
+
45 54
 // IsPrivileged returns true if we suspect that the current user (or process) will be able
46 55
 // to write to system folders, bind to privileged ports, and otherwise
47 56
 // successfully run a system service.
@@ -57,3 +66,12 @@ func WhereIs(exe string) (string, error) {
57 66
 	}
58 67
 	return filepath.Abs(filepath.ToSlash(exepath))
59 68
 }
69
+
70
+type ErrDaemonize struct {
71
+	DaemonArgs []string
72
+	error      string
73
+}
74
+
75
+func (e *ErrDaemonize) Error() string {
76
+	return e.error + "\nYou need to switch on ErrDaemonize, and use .DaemonArgs, which would run this:" + strings.Join(e.DaemonArgs, " ")
77
+}

+ 52
- 17
manager/install_darwin.go View File

@@ -24,25 +24,16 @@ func init() {
24 24
 	srvLen = len(srvExt)
25 25
 }
26 26
 
27
-func start(system bool, home string, name string) error {
28
-	sys, user, err := getMatchingSrvs(home, name)
27
+func start(conf *service.Service) error {
28
+	system := conf.System
29
+	home := conf.Home
30
+	rdns := conf.ReverseDNS
31
+
32
+	service, err := getService(system, home, rdns)
29 33
 	if nil != err {
30 34
 		return err
31 35
 	}
32 36
 
33
-	var service string
34
-	if system {
35
-		service, err = getOneSysSrv(sys, user, name)
36
-		if nil != err {
37
-			return err
38
-		}
39
-	} else {
40
-		service, err = getOneUserSrv(home, sys, user, name)
41
-		if nil != err {
42
-			return err
43
-		}
44
-	}
45
-
46 37
 	cmds := []Runnable{
47 38
 		Runnable{
48 39
 			Exec: "launchctl",
@@ -60,7 +51,51 @@ func start(system bool, home string, name string) error {
60 51
 	cmds = adjustPrivs(system, cmds)
61 52
 
62 53
 	fmt.Println()
63
-	fmt.Println("Starting launchd service...")
54
+	typ := "USER"
55
+	if system {
56
+		typ = "SYSTEM"
57
+	}
58
+	fmt.Printf("Starting launchd %s service...\n", typ)
59
+	for i := range cmds {
60
+		exe := cmds[i]
61
+		fmt.Println("\t" + exe.String())
62
+		err := exe.Run()
63
+		if nil != err {
64
+			return err
65
+		}
66
+	}
67
+	fmt.Println()
68
+
69
+	return nil
70
+}
71
+
72
+func stop(conf *service.Service) error {
73
+	system := conf.System
74
+	home := conf.Home
75
+	rdns := conf.ReverseDNS
76
+
77
+	service, err := getService(system, home, rdns)
78
+	if nil != err {
79
+		return err
80
+	}
81
+
82
+	cmds := []Runnable{
83
+		Runnable{
84
+			Exec:     "launchctl",
85
+			Args:     []string{"unload", service},
86
+			Must:     false,
87
+			Badwords: []string{"No such file or directory", "Cound not find specified service"},
88
+		},
89
+	}
90
+
91
+	cmds = adjustPrivs(system, cmds)
92
+
93
+	fmt.Println()
94
+	typ := "USER"
95
+	if system {
96
+		typ = "SYSTEM"
97
+	}
98
+	fmt.Printf("Stopping launchd %s service...\n", typ)
64 99
 	for i := range cmds {
65 100
 		exe := cmds[i]
66 101
 		fmt.Println("\t" + exe.String())
@@ -118,7 +153,7 @@ func install(c *service.Service) error {
118 153
 	}
119 154
 
120 155
 	// TODO --no-start
121
-	err = start(c.System, c.Home, c.ReverseDNS)
156
+	err = start(c)
122 157
 	if nil != err {
123 158
 		fmt.Printf("If things don't go well you should be able to get additional logging from launchctl:\n")
124 159
 		fmt.Printf("\tsudo launchctl log level debug\n")

+ 65
- 17
manager/install_linux.go View File

@@ -29,25 +29,16 @@ func init() {
29 29
 	srvLen = len(srvExt)
30 30
 }
31 31
 
32
-func start(system bool, home string, name string) error {
33
-	sys, user, err := getMatchingSrvs(home, name)
32
+func start(conf *service.Service) error {
33
+	system := conf.System
34
+	home := conf.Home
35
+	name := conf.ReverseDNS
36
+
37
+	_, err := getService(system, home, name)
34 38
 	if nil != err {
35 39
 		return err
36 40
 	}
37 41
 
38
-	// var service string
39
-	if system {
40
-		_, err = getOneSysSrv(sys, user, name)
41
-		if nil != err {
42
-			return err
43
-		}
44
-	} else {
45
-		_, err = getOneUserSrv(home, sys, user, name)
46
-		if nil != err {
47
-			return err
48
-		}
49
-	}
50
-
51 42
 	var cmds []Runnable
52 43
 	if system {
53 44
 		cmds = []Runnable{
@@ -92,7 +83,64 @@ func start(system bool, home string, name string) error {
92 83
 	cmds = adjustPrivs(system, cmds)
93 84
 
94 85
 	fmt.Println()
95
-	fmt.Println("Starting systemd service unit...")
86
+	typ := "USER MODE"
87
+	if system {
88
+		typ = "SYSTEM"
89
+	}
90
+	fmt.Printf("Starting systemd %s service unit...\n", typ)
91
+	for i := range cmds {
92
+		exe := cmds[i]
93
+		fmt.Println("\t" + exe.String())
94
+		err := exe.Run()
95
+		if nil != err {
96
+			return err
97
+		}
98
+	}
99
+	fmt.Println()
100
+
101
+	return nil
102
+}
103
+
104
+func stop(conf *service.Service) error {
105
+	system := conf.System
106
+	home := conf.Home
107
+	name := conf.ReverseDNS
108
+
109
+	_, err := getService(system, home, name)
110
+	if nil != err {
111
+		return err
112
+	}
113
+
114
+	var cmds []Runnable
115
+	badwords := []string{"Failed to stop"}
116
+	if system {
117
+		cmds = []Runnable{
118
+			Runnable{
119
+				Exec:     "systemctl",
120
+				Args:     []string{"stop", name + ".service"},
121
+				Must:     true,
122
+				Badwords: badwords,
123
+			},
124
+		}
125
+	} else {
126
+		cmds = []Runnable{
127
+			Runnable{
128
+				Exec:     "systemctl",
129
+				Args:     []string{"stop", "--user", name + ".service"},
130
+				Must:     true,
131
+				Badwords: badwords,
132
+			},
133
+		}
134
+	}
135
+
136
+	cmds = adjustPrivs(system, cmds)
137
+
138
+	fmt.Println()
139
+	typ := "USER MODE"
140
+	if system {
141
+		typ = "SYSTEM"
142
+	}
143
+	fmt.Printf("Stopping systemd %s service...\n", typ)
96 144
 	for i := range cmds {
97 145
 		exe := cmds[i]
98 146
 		fmt.Println("\t" + exe.String())
@@ -152,7 +200,7 @@ func install(c *service.Service) error {
152 200
 	}
153 201
 
154 202
 	// TODO --no-start
155
-	err = start(c.System, c.Home, c.Name)
203
+	err = start(c)
156 204
 	if nil != err {
157 205
 		sudo := ""
158 206
 		// --user-unit rather than --user --unit for older systemd

+ 43
- 13
manager/install_windows.go View File

@@ -9,6 +9,7 @@ import (
9 9
 	"path/filepath"
10 10
 	"strings"
11 11
 
12
+	"git.rootprojects.org/root/go-serviceman/runner"
12 13
 	"git.rootprojects.org/root/go-serviceman/service"
13 14
 
14 15
 	"golang.org/x/sys/windows/registry"
@@ -67,6 +68,9 @@ func install(c *service.Service) error {
67 68
 	}
68 69
 	defer k.Close()
69 70
 
71
+	// Try to stop before trying to copy the file
72
+	_ = runner.Stop(c)
73
+
70 74
 	args, err := installServiceman(c)
71 75
 	if nil != err {
72 76
 		return err
@@ -104,23 +108,56 @@ func install(c *service.Service) error {
104 108
 	//fmt.Println(autorunKey, c.Title, regSZ)
105 109
 	k.SetStringValue(c.Title, regSZ)
106 110
 
107
-	return nil
111
+	// to return ErrDaemonize
112
+	return start(c)
108 113
 }
109 114
 
110
-// copies self to install path and returns config path
111
-func installServiceman(c *service.Service) ([]string, error) {
112
-	// TODO check version and upgrade or dismiss
115
+func start(conf *service.Service) error {
116
+	args := getRunnerArgs(conf)
117
+	return &ErrDaemonize{
118
+		DaemonArgs: append(args, "--daemon"),
119
+		error:      "Not as much an error as a bad value...",
120
+	}
121
+	//return runner.Start(conf)
122
+}
123
+
124
+func stop(conf *service.Service) error {
125
+	return runner.Stop(conf)
126
+}
127
+
128
+func getRunnerArgs(c *service.Service) []string {
113 129
 	self := os.Args[0]
114 130
 	debug := ""
115 131
 	if strings.Contains(self, "debug.exe") {
116 132
 		debug = "debug."
117 133
 	}
134
+
118 135
 	smdir := `\opt\serviceman`
119 136
 	// TODO support service level services (which probably wouldn't need serviceman)
120 137
 	smdir = filepath.Join(c.Home, ".local", smdir)
121 138
 	// for now we'll scope the runner to the name of the application
122 139
 	smbin := filepath.Join(smdir, `bin\serviceman.`+debug+c.Name+`.exe`)
123 140
 
141
+	confpath := filepath.Join(smdir, `etc`)
142
+	conffile := filepath.Join(confpath, c.Name+`.json`)
143
+
144
+	return []string{
145
+		smbin,
146
+		"run",
147
+		"--config",
148
+		conffile,
149
+	}
150
+}
151
+
152
+// copies self to install path and returns config path
153
+func installServiceman(c *service.Service) ([]string, error) {
154
+	// TODO check version and upgrade or dismiss
155
+	self := os.Args[0]
156
+
157
+	args := getRunnerArgs(c)
158
+	smbin := args[0]
159
+	conffile := args[len(args)-1]
160
+
124 161
 	if smbin != self {
125 162
 		err := os.MkdirAll(filepath.Dir(smbin), 0755)
126 163
 		if nil != err {
@@ -141,21 +178,14 @@ func installServiceman(c *service.Service) ([]string, error) {
141 178
 		// this should be impossible, so we'll just panic
142 179
 		panic(err)
143 180
 	}
144
-	confpath := filepath.Join(smdir, `etc`)
145
-	err = os.MkdirAll(confpath, 0755)
181
+	err = os.MkdirAll(filepath.Dir(conffile), 0755)
146 182
 	if nil != err {
147 183
 		return nil, err
148 184
 	}
149
-	conffile := filepath.Join(confpath, c.Name+`.json`)
150 185
 	err = ioutil.WriteFile(conffile, b, 0640)
151 186
 	if nil != err {
152 187
 		return nil, err
153 188
 	}
154 189
 
155
-	return []string{
156
-		smbin,
157
-		"run",
158
-		"--config",
159
-		conffile,
160
-	}, nil
190
+	return args, nil
161 191
 }

+ 22
- 0
manager/start.go View File

@@ -8,6 +8,28 @@ import (
8 8
 	"strings"
9 9
 )
10 10
 
11
+func getService(system bool, home string, name string) (string, error) {
12
+	sys, user, err := getMatchingSrvs(home, name)
13
+	if nil != err {
14
+		return "", err
15
+	}
16
+
17
+	var service string
18
+	if system {
19
+		service, err = getOneSysSrv(sys, user, name)
20
+		if nil != err {
21
+			return "", err
22
+		}
23
+	} else {
24
+		service, err = getOneUserSrv(home, sys, user, name)
25
+		if nil != err {
26
+			return "", err
27
+		}
28
+	}
29
+
30
+	return service, nil
31
+}
32
+
11 33
 // Runnable defines a command to run, along with its arguments,
12 34
 // and whether or not failing to exit successfully matters.
13 35
 // It also defines whether certains words must exist (or not exist)

+ 140
- 2
runner/runner.go View File

@@ -2,13 +2,17 @@ package runner
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"io/ioutil"
5 6
 	"os"
6 7
 	"os/exec"
7 8
 	"path/filepath"
9
+	"strconv"
8 10
 	"strings"
9 11
 	"time"
10 12
 
11 13
 	"git.rootprojects.org/root/go-serviceman/service"
14
+
15
+	ps "github.com/mitchellh/go-ps"
12 16
 )
13 17
 
14 18
 // Filled in on init by runner_windows.go
@@ -17,7 +21,9 @@ var shellArgs = []string{}
17 21
 // Notes on spawning a child process
18 22
 // https://groups.google.com/forum/#!topic/golang-nuts/shST-SDqIp4
19 23
 
20
-func Run(conf *service.Service) {
24
+// Start will execute the service, and write the PID and logs out to the log directory
25
+func Start(conf *service.Service) error {
26
+	pid := os.Getpid()
21 27
 	originalBackoff := 1 * time.Second
22 28
 	maxBackoff := 1 * time.Minute
23 29
 	threshold := 5 * time.Second
@@ -26,6 +32,17 @@ func Run(conf *service.Service) {
26 32
 	failures := 0
27 33
 	logfile := filepath.Join(conf.Logdir, conf.Name+".log")
28 34
 
35
+	if oldPid, exename, err := getProcess(conf); nil == err {
36
+		return fmt.Errorf("%q may already be running as %q (pid %d)", conf.Name, exename, oldPid)
37
+	}
38
+
39
+	go func() {
40
+		for {
41
+			maybeWritePidFile(pid, conf)
42
+			time.Sleep(1 * time.Second)
43
+		}
44
+	}()
45
+
29 46
 	binpath := conf.Exec
30 47
 	args := []string{}
31 48
 	if "" != conf.Interpreter {
@@ -84,7 +101,7 @@ func Run(conf *service.Service) {
84 101
 			backoff = originalBackoff
85 102
 			failures = 0
86 103
 		} else {
87
-			failures += 1
104
+			failures++
88 105
 			fmt.Fprintf(lf, "Waiting %s to restart %q (%d consequtive immediate exits)\n", backoff, conf.Name, failures)
89 106
 			time.Sleep(backoff)
90 107
 			backoff *= 2
@@ -93,4 +110,125 @@ func Run(conf *service.Service) {
93 110
 			}
94 111
 		}
95 112
 	}
113
+
114
+	return nil
115
+}
116
+
117
+// Stop will find and stop another serviceman runner instance by it's PID
118
+func Stop(conf *service.Service) error {
119
+	i := 0
120
+	var err error
121
+	for {
122
+		if i >= 3 {
123
+			return err
124
+		}
125
+		i++
126
+		oldPid, exename, err2 := getProcess(conf)
127
+		err = err2
128
+		switch err {
129
+		case nil:
130
+			fmt.Printf("killing old process %q with pid %d\n", exename, oldPid)
131
+			err := kill(oldPid)
132
+			if nil != err {
133
+				return err
134
+			}
135
+			return waitForProcessToDie(oldPid)
136
+		case ErrNoPidFile:
137
+			return err
138
+		case ErrNoProcess:
139
+			return err
140
+		case ErrInvalidPidFile:
141
+			fallthrough
142
+		default:
143
+			// waiting a little bit since the PID is written every second
144
+			time.Sleep(400 * time.Millisecond)
145
+		}
146
+	}
147
+
148
+	return fmt.Errorf("unexpected error: %s", err)
149
+}
150
+
151
+// Restart calls Stop, ignoring any failure, and then Start, returning any failure
152
+func Restart(conf *service.Service) error {
153
+	_ = Stop(conf)
154
+	return Start(conf)
155
+}
156
+
157
+var ErrNoPidFile = fmt.Errorf("no pid file")
158
+var ErrInvalidPidFile = fmt.Errorf("malformed pid file")
159
+var ErrNoProcess = fmt.Errorf("process not found by pid")
160
+
161
+func waitForProcessToDie(pid int) error {
162
+	exename := "unknown"
163
+	for i := 0; i < 10; i++ {
164
+		px, err := ps.FindProcess(pid)
165
+		if nil != err {
166
+			return nil
167
+		}
168
+		if nil == px {
169
+			return nil
170
+		}
171
+		exename = px.Executable()
172
+		time.Sleep(1 * time.Second)
173
+	}
174
+	return fmt.Errorf("process %q (%d) just won't die", exename, pid)
175
+}
176
+
177
+func getProcess(conf *service.Service) (int, string, error) {
178
+	// TODO make Pidfile() a property of conf?
179
+	pidFile := filepath.Join(conf.Logdir, conf.Name+".pid")
180
+	b, err := ioutil.ReadFile(pidFile)
181
+	if nil != err {
182
+		return 0, "", ErrNoPidFile
183
+	}
184
+
185
+	s := strings.TrimSpace(string(b))
186
+	oldPid, err := strconv.Atoi(s)
187
+	if nil != err {
188
+		return 0, "", ErrInvalidPidFile
189
+	}
190
+
191
+	px, err := ps.FindProcess(oldPid)
192
+	if nil != err {
193
+		return 0, "", err
194
+	}
195
+	if nil == px {
196
+		return 0, "", ErrNoProcess
197
+	}
198
+
199
+	_, err = os.FindProcess(oldPid)
200
+	if nil != err {
201
+		return 0, "", err
202
+	}
203
+
204
+	exename := px.Executable()
205
+	return oldPid, exename, nil
206
+}
207
+
208
+// TODO error out if can't write to PID or log
209
+func maybeWritePidFile(pid int, conf *service.Service) bool {
210
+	newPid := []byte(strconv.Itoa(pid))
211
+
212
+	// TODO use a specific PID dir? meh...
213
+	pidFile := filepath.Join(conf.Logdir, conf.Name+".pid")
214
+	b, err := ioutil.ReadFile(pidFile)
215
+	if nil != err {
216
+		ioutil.WriteFile(pidFile, newPid, 0644)
217
+		return true
218
+	}
219
+
220
+	s := strings.TrimSpace(string(b))
221
+	oldPid, err := strconv.Atoi(s)
222
+	if nil != err {
223
+		ioutil.WriteFile(pidFile, newPid, 0644)
224
+		return true
225
+	}
226
+
227
+	if oldPid != pid {
228
+		Stop(conf)
229
+		ioutil.WriteFile(pidFile, newPid, 0644)
230
+		return true
231
+	}
232
+
233
+	return false
96 234
 }

+ 13
- 1
runner/runner_notwindows.go View File

@@ -2,7 +2,19 @@
2 2
 
3 3
 package runner
4 4
 
5
-import "os/exec"
5
+import (
6
+	"os"
7
+	"os/exec"
8
+)
6 9
 
7 10
 func backgroundCmd(cmd *exec.Cmd) {
8 11
 }
12
+
13
+func kill(pid int) error {
14
+	p, err := os.FindProcess(pid)
15
+	// already died
16
+	if nil != err {
17
+		return nil
18
+	}
19
+	return p.Kill()
20
+}

+ 13
- 0
runner/runner_windows.go View File

@@ -1,10 +1,23 @@
1 1
 package runner
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"os/exec"
6
+	"strconv"
5 7
 	"syscall"
6 8
 )
7 9
 
8 10
 func backgroundCmd(cmd *exec.Cmd) {
9 11
 	cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
10 12
 }
13
+
14
+func kill(pid int) error {
15
+	// Kill the whole processes tree (all children and grandchildren)
16
+	cmd := exec.Command("taskkill", "/pid", strconv.Itoa(pid), "/T", "/F")
17
+	b, err := cmd.CombinedOutput()
18
+	if nil != err {
19
+		return fmt.Errorf("%s: %s", err.Error(), string(b))
20
+	}
21
+
22
+	return nil
23
+}

+ 6
- 1
service/service.go View File

@@ -71,7 +71,7 @@ type Service struct {
71 71
 	MultiuserProtection bool              `json:"multiuser_protection,omitempty"`
72 72
 }
73 73
 
74
-func (s *Service) Normalize(force bool) {
74
+func (s *Service) NormalizeWithoutPath() {
75 75
 	if "" == s.Name {
76 76
 		ext := filepath.Ext(s.Exec)
77 77
 		base := filepath.Base(s.Exec[:len(s.Exec)-len(ext)])
@@ -93,11 +93,16 @@ func (s *Service) Normalize(force bool) {
93 93
 			os.Exit(4)
94 94
 			return
95 95
 		}
96
+		s.Home = home
96 97
 		s.Local = filepath.Join(home, ".local")
97 98
 		s.Logdir = filepath.Join(home, ".local", "share", s.Name, "var", "log")
98 99
 	} else {
99 100
 		s.Logdir = "/var/log/" + s.Name
100 101
 	}
102
+}
103
+
104
+func (s *Service) Normalize(force bool) {
105
+	s.NormalizeWithoutPath()
101 106
 
102 107
 	// Check to see if Exec exists
103 108
 	//   /whatever => must exist exactly

+ 112
- 10
serviceman.go View File

@@ -22,8 +22,12 @@ var GitVersion = "v0.0.0"
22 22
 var GitTimestamp = time.Now().Format(time.RFC3339)
23 23
 
24 24
 func usage() {
25
-	fmt.Println("Usage: serviceman add ./foo-app -- --foo-arg")
26
-	fmt.Println("Usage: serviceman run --config ./foo-app.json")
25
+	fmt.Println("Usage:")
26
+	fmt.Println("\tserviceman <command> --help")
27
+	fmt.Println("\tserviceman add ./foo-app -- --foo-arg")
28
+	fmt.Println("\tserviceman run --config ./foo-app.json")
29
+	fmt.Println("\tserviceman start <name>")
30
+	fmt.Println("\tserviceman stop <name>")
27 31
 }
28 32
 
29 33
 func main() {
@@ -38,10 +42,14 @@ func main() {
38 42
 	switch top {
39 43
 	case "version":
40 44
 		fmt.Println(GitVersion, GitTimestamp, GitRev)
41
-	case "add":
42
-		add()
43 45
 	case "run":
44 46
 		run()
47
+	case "add":
48
+		add()
49
+	case "start":
50
+		start()
51
+	case "stop":
52
+		stop()
45 53
 	default:
46 54
 		fmt.Fprintf(os.Stderr, "Unknown argument %s\n", top)
47 55
 		usage()
@@ -106,7 +114,7 @@ func add() {
106 114
 
107 115
 	execpath, err := manager.WhereIs(args[0])
108 116
 	if nil != err {
109
-		fmt.Fprintf(os.Stderr, "Error: '%s' could not be found.\n", args[0])
117
+		fmt.Fprintf(os.Stderr, "Error: '%s' could not be found in PATH or working directory.\n", args[0])
110 118
 		if !force {
111 119
 			os.Exit(3)
112 120
 			return
@@ -131,7 +139,12 @@ func add() {
131 139
 	}
132 140
 
133 141
 	err = manager.Install(conf)
134
-	if nil != err {
142
+	switch e := err.(type) {
143
+	case nil:
144
+		// do nothing
145
+	case *manager.ErrDaemonize:
146
+		runAsDaemon(e.DaemonArgs[0], e.DaemonArgs[1:]...)
147
+	default:
135 148
 		fmt.Fprintf(os.Stderr, "%s\n", err)
136 149
 	}
137 150
 
@@ -139,6 +152,88 @@ func add() {
139 152
 	fmt.Println()
140 153
 }
141 154
 
155
+func start() {
156
+	forUser := false
157
+	forSystem := false
158
+	flag.BoolVar(&forSystem, "system", false, "attempt to add system service as an unprivileged/unelevated user")
159
+	flag.BoolVar(&forUser, "user", false, "add user space / user mode service even when admin/root/sudo/elevated")
160
+	flag.Parse()
161
+
162
+	args := flag.Args()
163
+	if 1 != len(args) {
164
+		fmt.Println("Usage: serviceman start <name>")
165
+		os.Exit(1)
166
+	}
167
+
168
+	if forUser && forSystem {
169
+		fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
170
+		os.Exit(1)
171
+		return
172
+	}
173
+
174
+	conf := &service.Service{
175
+		Name:    args[0],
176
+		Restart: false,
177
+	}
178
+	if forUser {
179
+		conf.System = false
180
+	} else if forSystem {
181
+		conf.System = true
182
+	} else {
183
+		conf.System = manager.IsPrivileged()
184
+	}
185
+	conf.NormalizeWithoutPath()
186
+
187
+	err := manager.Start(conf)
188
+	switch e := err.(type) {
189
+	case nil:
190
+		// do nothing
191
+	case *manager.ErrDaemonize:
192
+		runAsDaemon(e.DaemonArgs[0], e.DaemonArgs[1:]...)
193
+	default:
194
+		fmt.Println(err)
195
+		os.Exit(127)
196
+	}
197
+}
198
+
199
+func stop() {
200
+	forUser := false
201
+	forSystem := false
202
+	flag.BoolVar(&forSystem, "system", false, "attempt to add system service as an unprivileged/unelevated user")
203
+	flag.BoolVar(&forUser, "user", false, "add user space / user mode service even when admin/root/sudo/elevated")
204
+	flag.Parse()
205
+
206
+	args := flag.Args()
207
+	if 1 != len(args) {
208
+		fmt.Println("Usage: serviceman stop <name>")
209
+		os.Exit(1)
210
+	}
211
+
212
+	if forUser && forSystem {
213
+		fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
214
+		os.Exit(1)
215
+		return
216
+	}
217
+
218
+	conf := &service.Service{
219
+		Name:    args[0],
220
+		Restart: false,
221
+	}
222
+	if forUser {
223
+		conf.System = false
224
+	} else if forSystem {
225
+		conf.System = true
226
+	} else {
227
+		conf.System = manager.IsPrivileged()
228
+	}
229
+	conf.NormalizeWithoutPath()
230
+
231
+	if err := manager.Stop(conf); nil != err {
232
+		fmt.Println(err)
233
+		os.Exit(127)
234
+	}
235
+}
236
+
142 237
 func run() {
143 238
 	var confpath string
144 239
 	var daemonize bool
@@ -183,7 +278,8 @@ func run() {
183 278
 		os.Exit(400)
184 279
 	}
185 280
 
186
-	s.Normalize(false)
281
+	force := false
282
+	s.Normalize(force)
187 283
 	fmt.Printf("All output will be directed to the logs at:\n\t%s\n", s.Logdir)
188 284
 	err = os.MkdirAll(s.Logdir, 0755)
189 285
 	if nil != err {
@@ -193,11 +289,17 @@ func run() {
193 289
 
194 290
 	if !daemonize {
195 291
 		//fmt.Fprintf(os.Stdout, "Running %s %s %s\n", s.Interpreter, s.Exec, strings.Join(s.Argv, " "))
196
-		runner.Run(s)
292
+		if err := runner.Start(s); nil != err {
293
+			fmt.Println("Error:", err)
294
+		}
197 295
 		return
198 296
 	}
199 297
 
200
-	cmd := exec.Command(os.Args[0], "run", "--config", confpath)
298
+	runAsDaemon(os.Args[0], "run", "--config", confpath)
299
+}
300
+
301
+func runAsDaemon(bin string, args ...string) {
302
+	cmd := exec.Command(bin, args...)
201 303
 	// for debugging
202 304
 	/*
203 305
 		out, err := cmd.CombinedOutput()
@@ -207,7 +309,7 @@ func run() {
207 309
 		fmt.Println(string(out))
208 310
 	*/
209 311
 
210
-	err = cmd.Start()
312
+	err := cmd.Start()
211 313
 	if nil != err {
212 314
 		fmt.Fprintf(os.Stderr, "%s\n", err)
213 315
 		os.Exit(500)

+ 1
- 0
vendor/github.com/mitchellh/go-ps/.gitignore View File

@@ -0,0 +1 @@
1
+.vagrant/

+ 4
- 0
vendor/github.com/mitchellh/go-ps/.travis.yml View File

@@ -0,0 +1,4 @@
1
+language: go
2
+
3
+go:
4
+  - 1.2.1

+ 21
- 0
vendor/github.com/mitchellh/go-ps/LICENSE.md View File

@@ -0,0 +1,21 @@
1
+The MIT License (MIT)
2
+
3
+Copyright (c) 2014 Mitchell Hashimoto
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in
13
+all copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+THE SOFTWARE.

+ 34
- 0
vendor/github.com/mitchellh/go-ps/README.md View File

@@ -0,0 +1,34 @@
1
+# Process List Library for Go
2
+
3
+go-ps is a library for Go that implements OS-specific APIs to list and
4
+manipulate processes in a platform-safe way. The library can find and
5
+list processes on Linux, Mac OS X, Solaris, and Windows.
6
+
7
+If you're new to Go, this library has a good amount of advanced Go educational
8
+value as well. It uses some advanced features of Go: build tags, accessing
9
+DLL methods for Windows, cgo for Darwin, etc.
10
+
11
+How it works:
12
+
13
+  * **Darwin** uses the `sysctl` syscall to retrieve the process table.
14
+  * **Unix** uses the procfs at `/proc` to inspect the process tree.
15
+  * **Windows** uses the Windows API, and methods such as
16
+    `CreateToolhelp32Snapshot` to get a point-in-time snapshot of
17
+    the process table.
18
+
19
+## Installation
20
+
21
+Install using standard `go get`:
22
+
23
+```
24
+$ go get github.com/mitchellh/go-ps
25
+...
26
+```
27
+
28
+## TODO
29
+
30
+Want to contribute? Here is a short TODO list of things that aren't
31
+implemented for this library that would be nice:
32
+
33
+  * FreeBSD support
34
+  * Plan9 support

+ 43
- 0
vendor/github.com/mitchellh/go-ps/Vagrantfile View File

@@ -0,0 +1,43 @@
1
+# -*- mode: ruby -*-
2
+# vi: set ft=ruby :
3
+
4
+# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
5
+VAGRANTFILE_API_VERSION = "2"
6
+
7
+Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
8
+  config.vm.box = "chef/ubuntu-12.04"
9
+
10
+  config.vm.provision "shell", inline: $script
11
+
12
+  ["vmware_fusion", "vmware_workstation"].each do |p|
13
+    config.vm.provider "p" do |v|
14
+      v.vmx["memsize"] = "1024"
15
+      v.vmx["numvcpus"] = "2"
16
+      v.vmx["cpuid.coresPerSocket"] = "1"
17
+    end
18
+  end
19
+end
20
+
21
+$script = <<SCRIPT
22
+SRCROOT="/opt/go"
23
+
24
+# Install Go
25
+sudo apt-get update
26
+sudo apt-get install -y build-essential mercurial
27
+sudo hg clone -u release https://code.google.com/p/go ${SRCROOT}
28
+cd ${SRCROOT}/src
29
+sudo ./all.bash
30
+
31
+# Setup the GOPATH
32
+sudo mkdir -p /opt/gopath
33
+cat <<EOF >/tmp/gopath.sh
34
+export GOPATH="/opt/gopath"
35
+export PATH="/opt/go/bin:\$GOPATH/bin:\$PATH"
36
+EOF
37
+sudo mv /tmp/gopath.sh /etc/profile.d/gopath.sh
38
+sudo chmod 0755 /etc/profile.d/gopath.sh
39
+
40
+# Make sure the gopath is usable by bamboo
41
+sudo chown -R vagrant:vagrant $SRCROOT
42
+sudo chown -R vagrant:vagrant /opt/gopath
43
+SCRIPT

+ 40
- 0
vendor/github.com/mitchellh/go-ps/process.go View File

@@ -0,0 +1,40 @@
1
+// ps provides an API for finding and listing processes in a platform-agnostic
2
+// way.
3
+//
4
+// NOTE: If you're reading these docs online via GoDocs or some other system,
5
+// you might only see the Unix docs. This project makes heavy use of
6
+// platform-specific implementations. We recommend reading the source if you
7
+// are interested.
8
+package ps
9
+
10
+// Process is the generic interface that is implemented on every platform
11
+// and provides common operations for processes.
12
+type Process interface {
13
+	// Pid is the process ID for this process.
14
+	Pid() int
15
+
16
+	// PPid is the parent process ID for this process.
17
+	PPid() int
18
+
19
+	// Executable name running this process. This is not a path to the
20
+	// executable.
21
+	Executable() string
22
+}
23
+
24
+// Processes returns all processes.
25
+//
26
+// This of course will be a point-in-time snapshot of when this method was
27
+// called. Some operating systems don't provide snapshot capability of the
28
+// process table, in which case the process table returned might contain
29
+// ephemeral entities that happened to be running when this was called.
30
+func Processes() ([]Process, error) {
31
+	return processes()
32
+}
33
+
34
+// FindProcess looks up a single process by pid.
35
+//
36
+// Process will be nil and error will be nil if a matching process is
37
+// not found.
38
+func FindProcess(pid int) (Process, error) {
39
+	return findProcess(pid)
40
+}

+ 138
- 0
vendor/github.com/mitchellh/go-ps/process_darwin.go View File

@@ -0,0 +1,138 @@
1
+// +build darwin
2
+
3
+package ps
4
+
5
+import (
6
+	"bytes"
7
+	"encoding/binary"
8
+	"syscall"
9
+	"unsafe"
10
+)
11
+
12
+type DarwinProcess struct {
13
+	pid    int
14
+	ppid   int
15
+	binary string
16
+}
17
+
18
+func (p *DarwinProcess) Pid() int {
19
+	return p.pid
20
+}
21
+
22
+func (p *DarwinProcess) PPid() int {
23
+	return p.ppid
24
+}
25
+
26
+func (p *DarwinProcess) Executable() string {
27
+	return p.binary
28
+}
29
+
30
+func findProcess(pid int) (Process, error) {
31
+	ps, err := processes()
32
+	if err != nil {
33
+		return nil, err
34
+	}
35
+
36
+	for _, p := range ps {
37
+		if p.Pid() == pid {
38
+			return p, nil
39
+		}
40
+	}
41
+
42
+	return nil, nil
43
+}
44
+
45
+func processes() ([]Process, error) {
46
+	buf, err := darwinSyscall()
47
+	if err != nil {
48
+		return nil, err
49
+	}
50
+
51
+	procs := make([]*kinfoProc, 0, 50)
52
+	k := 0
53
+	for i := _KINFO_STRUCT_SIZE; i < buf.Len(); i += _KINFO_STRUCT_SIZE {
54
+		proc := &kinfoProc{}
55
+		err = binary.Read(bytes.NewBuffer(buf.Bytes()[k:i]), binary.LittleEndian, proc)
56
+		if err != nil {
57
+			return nil, err
58
+		}
59
+
60
+		k = i
61
+		procs = append(procs, proc)
62
+	}
63
+
64
+	darwinProcs := make([]Process, len(procs))
65
+	for i, p := range procs {
66
+		darwinProcs[i] = &DarwinProcess{
67
+			pid:    int(p.Pid),
68
+			ppid:   int(p.PPid),
69
+			binary: darwinCstring(p.Comm),
70
+		}
71
+	}
72
+
73
+	return darwinProcs, nil
74
+}
75
+
76
+func darwinCstring(s [16]byte) string {
77
+	i := 0
78
+	for _, b := range s {
79
+		if b != 0 {
80
+			i++
81
+		} else {
82
+			break
83
+		}
84
+	}
85
+
86
+	return string(s[:i])
87
+}
88
+
89
+func darwinSyscall() (*bytes.Buffer, error) {
90
+	mib := [4]int32{_CTRL_KERN, _KERN_PROC, _KERN_PROC_ALL, 0}
91
+	size := uintptr(0)
92
+
93
+	_, _, errno := syscall.Syscall6(
94
+		syscall.SYS___SYSCTL,
95
+		uintptr(unsafe.Pointer(&mib[0])),
96
+		4,
97
+		0,
98
+		uintptr(unsafe.Pointer(&size)),
99
+		0,
100
+		0)
101
+
102
+	if errno != 0 {
103
+		return nil, errno
104
+	}
105
+
106
+	bs := make([]byte, size)
107
+	_, _, errno = syscall.Syscall6(
108
+		syscall.SYS___SYSCTL,
109
+		uintptr(unsafe.Pointer(&mib[0])),
110
+		4,
111
+		uintptr(unsafe.Pointer(&bs[0])),
112
+		uintptr(unsafe.Pointer(&size)),
113
+		0,
114
+		0)
115
+
116
+	if errno != 0 {
117
+		return nil, errno
118
+	}
119
+
120
+	return bytes.NewBuffer(bs[0:size]), nil
121
+}
122
+
123
+const (
124
+	_CTRL_KERN         = 1
125
+	_KERN_PROC         = 14
126
+	_KERN_PROC_ALL     = 0
127
+	_KINFO_STRUCT_SIZE = 648
128
+)
129
+
130
+type kinfoProc struct {
131
+	_    [40]byte
132
+	Pid  int32
133
+	_    [199]byte
134
+	Comm [16]byte
135
+	_    [301]byte
136
+	PPid int32
137
+	_    [84]byte
138
+}

+ 260
- 0
vendor/github.com/mitchellh/go-ps/process_freebsd.go View File

@@ -0,0 +1,260 @@
1
+// +build freebsd,amd64
2
+
3
+package ps
4
+
5
+import (
6
+	"bytes"
7
+	"encoding/binary"
8
+	"syscall"
9
+	"unsafe"
10
+)
11
+
12
+// copied from sys/sysctl.h
13
+const (
14
+	CTL_KERN           = 1  // "high kernel": proc, limits
15
+	KERN_PROC          = 14 // struct: process entries
16
+	KERN_PROC_PID      = 1  // by process id
17
+	KERN_PROC_PROC     = 8  // only return procs
18
+	KERN_PROC_PATHNAME = 12 // path to executable
19
+)
20
+
21
+// copied from sys/user.h
22
+type Kinfo_proc struct {
23
+	Ki_structsize   int32
24
+	Ki_layout       int32
25
+	Ki_args         int64
26
+	Ki_paddr        int64
27
+	Ki_addr         int64
28
+	Ki_tracep       int64
29
+	Ki_textvp       int64
30
+	Ki_fd           int64
31
+	Ki_vmspace      int64
32
+	Ki_wchan        int64
33
+	Ki_pid          int32
34
+	Ki_ppid         int32
35
+	Ki_pgid         int32
36
+	Ki_tpgid        int32
37
+	Ki_sid          int32
38
+	Ki_tsid         int32
39
+	Ki_jobc         [2]byte
40
+	Ki_spare_short1 [2]byte
41
+	Ki_tdev         int32
42
+	Ki_siglist      [16]byte
43
+	Ki_sigmask      [16]byte
44
+	Ki_sigignore    [16]byte
45
+	Ki_sigcatch     [16]byte
46
+	Ki_uid          int32
47
+	Ki_ruid         int32
48
+	Ki_svuid        int32
49
+	Ki_rgid         int32
50
+	Ki_svgid        int32
51
+	Ki_ngroups      [2]byte
52
+	Ki_spare_short2 [2]byte
53
+	Ki_groups       [64]byte
54
+	Ki_size         int64
55
+	Ki_rssize       int64
56
+	Ki_swrss        int64
57
+	Ki_tsize        int64
58
+	Ki_dsize        int64
59
+	Ki_ssize        int64
60
+	Ki_xstat        [2]byte
61
+	Ki_acflag       [2]byte
62
+	Ki_pctcpu       int32
63
+	Ki_estcpu       int32
64
+	Ki_slptime      int32
65
+	Ki_swtime       int32
66
+	Ki_cow          int32
67
+	Ki_runtime      int64
68
+	Ki_start        [16]byte
69
+	Ki_childtime    [16]byte
70
+	Ki_flag         int64
71
+	Ki_kiflag       int64
72
+	Ki_traceflag    int32
73
+	Ki_stat         [1]byte
74
+	Ki_nice         [1]byte
75
+	Ki_lock         [1]byte
76
+	Ki_rqindex      [1]byte
77
+	Ki_oncpu        [1]byte
78
+	Ki_lastcpu      [1]byte
79
+	Ki_ocomm        [17]byte
80
+	Ki_wmesg        [9]byte
81
+	Ki_login        [18]byte
82
+	Ki_lockname     [9]byte
83
+	Ki_comm         [20]byte
84
+	Ki_emul         [17]byte
85
+	Ki_sparestrings [68]byte
86
+	Ki_spareints    [36]byte
87
+	Ki_cr_flags     int32
88
+	Ki_jid          int32
89
+	Ki_numthreads   int32
90
+	Ki_tid          int32
91
+	Ki_pri          int32
92
+	Ki_rusage       [144]byte
93
+	Ki_rusage_ch    [144]byte
94
+	Ki_pcb          int64
95
+	Ki_kstack       int64
96
+	Ki_udata        int64
97
+	Ki_tdaddr       int64
98
+	Ki_spareptrs    [48]byte
99
+	Ki_spareint64s  [96]byte
100
+	Ki_sflag        int64
101
+	Ki_tdflags      int64
102
+}
103
+
104
+// UnixProcess is an implementation of Process that contains Unix-specific
105
+// fields and information.
106
+type UnixProcess struct {
107
+	pid   int
108
+	ppid  int
109
+	state rune
110
+	pgrp  int
111
+	sid   int
112
+
113
+	binary string
114
+}
115
+
116
+func (p *UnixProcess) Pid() int {
117
+	return p.pid
118
+}
119
+
120
+func (p *UnixProcess) PPid() int {
121
+	return p.ppid
122
+}
123
+
124
+func (p *UnixProcess) Executable() string {
125
+	return p.binary
126
+}
127
+
128
+// Refresh reloads all the data associated with this process.
129
+func (p *UnixProcess) Refresh() error {
130
+
131
+	mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PID, int32(p.pid)}
132
+
133
+	buf, length, err := call_syscall(mib)
134
+	if err != nil {
135
+		return err
136
+	}
137
+	proc_k := Kinfo_proc{}
138
+	if length != uint64(unsafe.Sizeof(proc_k)) {
139
+		return err
140
+	}
141
+
142
+	k, err := parse_kinfo_proc(buf)
143
+	if err != nil {
144
+		return err
145
+	}
146
+
147
+	p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k)
148
+	return nil
149
+}
150
+
151
+func copy_params(k *Kinfo_proc) (int, int, int, string) {
152
+	n := -1
153
+	for i, b := range k.Ki_comm {
154
+		if b == 0 {
155
+			break
156
+		}
157
+		n = i + 1
158
+	}
159
+	comm := string(k.Ki_comm[:n])
160
+
161
+	return int(k.Ki_ppid), int(k.Ki_pgid), int(k.Ki_sid), comm
162
+}
163
+
164
+func findProcess(pid int) (Process, error) {
165
+	mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, int32(pid)}
166
+
167
+	_, _, err := call_syscall(mib)
168
+	if err != nil {
169
+		return nil, err
170
+	}
171
+
172
+	return newUnixProcess(pid)
173
+}
174
+
175
+func processes() ([]Process, error) {
176
+	results := make([]Process, 0, 50)
177
+
178
+	mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0}
179
+	buf, length, err := call_syscall(mib)
180
+	if err != nil {
181
+		return results, err
182
+	}
183
+
184
+	// get kinfo_proc size
185
+	k := Kinfo_proc{}
186
+	procinfo_len := int(unsafe.Sizeof(k))
187
+	count := int(length / uint64(procinfo_len))
188
+
189
+	// parse buf to procs
190
+	for i := 0; i < count; i++ {
191
+		b := buf[i*procinfo_len : i*procinfo_len+procinfo_len]
192
+		k, err := parse_kinfo_proc(b)
193
+		if err != nil {
194
+			continue
195
+		}
196
+		p, err := newUnixProcess(int(k.Ki_pid))
197
+		if err != nil {
198
+			continue
199
+		}
200
+		p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k)
201
+
202
+		results = append(results, p)
203
+	}
204
+
205
+	return results, nil
206
+}
207
+
208
+func parse_kinfo_proc(buf []byte) (Kinfo_proc, error) {
209
+	var k Kinfo_proc
210
+	br := bytes.NewReader(buf)
211
+	err := binary.Read(br, binary.LittleEndian, &k)
212
+	if err != nil {
213
+		return k, err
214
+	}
215
+
216
+	return k, nil
217
+}
218
+
219
+func call_syscall(mib []int32) ([]byte, uint64, error) {
220
+	miblen := uint64(len(mib))
221
+
222
+	// get required buffer size
223
+	length := uint64(0)
224
+	_, _, err := syscall.RawSyscall6(
225
+		syscall.SYS___SYSCTL,
226
+		uintptr(unsafe.Pointer(&mib[0])),
227
+		uintptr(miblen),
228
+		0,
229
+		uintptr(unsafe.Pointer(&length)),
230
+		0,
231
+		0)
232
+	if err != 0 {
233
+		b := make([]byte, 0)
234
+		return b, length, err
235
+	}
236
+	if length == 0 {
237
+		b := make([]byte, 0)
238
+		return b, length, err
239
+	}
240
+	// get proc info itself
241
+	buf := make([]byte, length)
242
+	_, _, err = syscall.RawSyscall6(
243
+		syscall.SYS___SYSCTL,
244
+		uintptr(unsafe.Pointer(&mib[0])),
245
+		uintptr(miblen),
246
+		uintptr(unsafe.Pointer(&buf[0])),
247
+		uintptr(unsafe.Pointer(&length)),
248
+		0,
249
+		0)
250
+	if err != 0 {
251
+		return buf, length, err
252
+	}
253
+
254
+	return buf, length, nil
255
+}
256
+
257
+func newUnixProcess(pid int) (*UnixProcess, error) {
258
+	p := &UnixProcess{pid: pid}
259
+	return p, p.Refresh()
260
+}

+ 35
- 0
vendor/github.com/mitchellh/go-ps/process_linux.go View File

@@ -0,0 +1,35 @@
1
+// +build linux
2
+
3
+package ps
4
+
5
+import (
6
+	"fmt"
7
+	"io/ioutil"
8
+	"strings"
9
+)
10
+
11
+// Refresh reloads all the data associated with this process.
12
+func (p *UnixProcess) Refresh() error {
13
+	statPath := fmt.Sprintf("/proc/%d/stat", p.pid)
14
+	dataBytes, err := ioutil.ReadFile(statPath)
15
+	if err != nil {
16
+		return err
17
+	}
18
+
19
+	// First, parse out the image name
20
+	data := string(dataBytes)
21
+	binStart := strings.IndexRune(data, '(') + 1
22
+	binEnd := strings.IndexRune(data[binStart:], ')')
23
+	p.binary = data[binStart : binStart+binEnd]
24
+
25
+	// Move past the image name and start parsing the rest
26
+	data = data[binStart+binEnd+2:]
27
+	_, err = fmt.Sscanf(data,
28
+		"%c %d %d %d",
29
+		&p.state,
30
+		&p.ppid,
31
+		&p.pgrp,
32
+		&p.sid)
33
+
34
+	return err
35
+}

+ 96
- 0
vendor/github.com/mitchellh/go-ps/process_solaris.go View File

@@ -0,0 +1,96 @@
1
+// +build solaris
2
+
3
+package ps
4
+
5
+import (
6
+	"encoding/binary"
7
+	"fmt"
8
+	"os"
9
+)
10
+
11
+type ushort_t uint16
12
+
13
+type id_t int32
14
+type pid_t int32
15
+type uid_t int32
16
+type gid_t int32
17
+
18
+type dev_t uint64
19
+type size_t uint64
20
+type uintptr_t uint64
21
+
22
+type timestruc_t [16]byte
23
+
24
+// This is copy from /usr/include/sys/procfs.h
25
+type psinfo_t struct {
26
+	Pr_flag   int32     /* process flags (DEPRECATED; do not use) */
27
+	Pr_nlwp   int32     /* number of active lwps in the process */
28
+	Pr_pid    pid_t     /* unique process id */
29
+	Pr_ppid   pid_t     /* process id of parent */
30
+	Pr_pgid   pid_t     /* pid of process group leader */
31
+	Pr_sid    pid_t     /* session id */
32
+	Pr_uid    uid_t     /* real user id */
33
+	Pr_euid   uid_t     /* effective user id */
34
+	Pr_gid    gid_t     /* real group id */
35
+	Pr_egid   gid_t     /* effective group id */
36
+	Pr_addr   uintptr_t /* address of process */
37
+	Pr_size   size_t    /* size of process image in Kbytes */
38
+	Pr_rssize size_t    /* resident set size in Kbytes */
39
+	Pr_pad1   size_t
40
+	Pr_ttydev dev_t /* controlling tty device (or PRNODEV) */
41
+
42
+	// Guess this following 2 ushort_t values require a padding to properly
43
+	// align to the 64bit mark.
44
+	Pr_pctcpu   ushort_t /* % of recent cpu time used by all lwps */
45
+	Pr_pctmem   ushort_t /* % of system memory used by process */
46
+	Pr_pad64bit [4]byte
47
+
48
+	Pr_start    timestruc_t /* process start time, from the epoch */
49
+	Pr_time     timestruc_t /* usr+sys cpu time for this process */
50
+	Pr_ctime    timestruc_t /* usr+sys cpu time for reaped children */
51
+	Pr_fname    [16]byte    /* name of execed file */
52
+	Pr_psargs   [80]byte    /* initial characters of arg list */
53
+	Pr_wstat    int32       /* if zombie, the wait() status */
54
+	Pr_argc     int32       /* initial argument count */
55
+	Pr_argv     uintptr_t   /* address of initial argument vector */
56
+	Pr_envp     uintptr_t   /* address of initial environment vector */
57
+	Pr_dmodel   [1]byte     /* data model of the process */
58
+	Pr_pad2     [3]byte
59
+	Pr_taskid   id_t      /* task id */
60
+	Pr_projid   id_t      /* project id */
61
+	Pr_nzomb    int32     /* number of zombie lwps in the process */
62
+	Pr_poolid   id_t      /* pool id */
63
+	Pr_zoneid   id_t      /* zone id */
64
+	Pr_contract id_t      /* process contract */
65
+	Pr_filler   int32     /* reserved for future use */
66
+	Pr_lwp      [128]byte /* information for representative lwp */
67
+}
68
+
69
+func (p *UnixProcess) Refresh() error {
70
+	var psinfo psinfo_t
71
+
72
+	path := fmt.Sprintf("/proc/%d/psinfo", p.pid)
73
+	fh, err := os.Open(path)
74
+	if err != nil {
75
+		return err
76
+	}
77
+	defer fh.Close()
78
+
79
+	err = binary.Read(fh, binary.LittleEndian, &psinfo)
80
+	if err != nil {
81
+		return err
82
+	}
83
+
84
+	p.ppid = int(psinfo.Pr_ppid)
85
+	p.binary = toString(psinfo.Pr_fname[:], 16)
86
+	return nil
87
+}
88
+
89
+func toString(array []byte, len int) string {
90
+	for i := 0; i < len; i++ {
91
+		if array[i] == 0 {
92
+			return string(array[:i])
93
+		}
94
+	}
95
+	return string(array[:])
96
+}

+ 101
- 0
vendor/github.com/mitchellh/go-ps/process_unix.go View File

@@ -0,0 +1,101 @@
1
+// +build linux solaris
2
+
3
+package ps
4
+
5
+import (
6
+	"fmt"
7
+	"io"
8
+	"os"
9
+	"strconv"
10
+)
11
+
12
+// UnixProcess is an implementation of Process that contains Unix-specific
13
+// fields and information.
14
+type UnixProcess struct {
15
+	pid   int
16
+	ppid  int
17
+	state rune
18
+	pgrp  int
19
+	sid   int
20
+
21
+	binary string
22
+}
23
+
24
+func (p *UnixProcess) Pid() int {
25
+	return p.pid
26
+}
27
+
28
+func (p *UnixProcess) PPid() int {
29
+	return p.ppid
30
+}
31
+
32
+func (p *UnixProcess) Executable() string {
33
+	return p.binary
34
+}
35
+
36
+func findProcess(pid int) (Process, error) {
37
+	dir := fmt.Sprintf("/proc/%d", pid)
38
+	_, err := os.Stat(dir)
39
+	if err != nil {
40
+		if os.IsNotExist(err) {
41
+			return nil, nil
42
+		}
43
+
44
+		return nil, err
45
+	}
46
+
47
+	return newUnixProcess(pid)
48
+}
49
+
50
+func processes() ([]Process, error) {
51
+	d, err := os.Open("/proc")
52
+	if err != nil {
53
+		return nil, err
54
+	}
55
+	defer d.Close()
56
+
57
+	results := make([]Process, 0, 50)
58
+	for {
59
+		fis, err := d.Readdir(10)
60
+		if err == io.EOF {
61
+			break
62
+		}
63
+		if err != nil {
64
+			return nil, err
65
+		}
66
+
67
+		for _, fi := range fis {
68
+			// We only care about directories, since all pids are dirs
69
+			if !fi.IsDir() {
70
+				continue
71
+			}
72
+
73
+			// We only care if the name starts with a numeric
74
+			name := fi.Name()
75
+			if name[0] < '0' || name[0] > '9' {
76
+				continue
77
+			}
78
+
79
+			// From this point forward, any errors we just ignore, because
80
+			// it might simply be that the process doesn't exist anymore.
81
+			pid, err := strconv.ParseInt(name, 10, 0)
82
+			if err != nil {
83
+				continue
84
+			}
85
+
86
+			p, err := newUnixProcess(int(pid))
87
+			if err != nil {
88
+				continue
89
+			}
90
+
91
+			results = append(results, p)
92
+		}
93
+	}
94
+
95
+	return results, nil
96
+}
97
+
98
+func newUnixProcess(pid int) (*UnixProcess, error) {
99
+	p := &UnixProcess{pid: pid}
100
+	return p, p.Refresh()
101
+}

+ 119
- 0
vendor/github.com/mitchellh/go-ps/process_windows.go View File

@@ -0,0 +1,119 @@
1
+// +build windows
2
+
3
+package ps
4
+
5
+import (
6
+	"fmt"
7
+	"syscall"
8
+	"unsafe"
9
+)
10
+
11
+// Windows API functions
12
+var (
13
+	modKernel32                  = syscall.NewLazyDLL("kernel32.dll")
14
+	procCloseHandle              = modKernel32.NewProc("CloseHandle")
15
+	procCreateToolhelp32Snapshot = modKernel32.NewProc("CreateToolhelp32Snapshot")
16
+	procProcess32First           = modKernel32.NewProc("Process32FirstW")
17
+	procProcess32Next            = modKernel32.NewProc("Process32NextW")
18
+)
19
+
20
+// Some constants from the Windows API
21
+const (
22
+	ERROR_NO_MORE_FILES = 0x12
23
+	MAX_PATH            = 260
24
+)
25
+
26
+// PROCESSENTRY32 is the Windows API structure that contains a process's
27
+// information.
28
+type PROCESSENTRY32 struct {
29
+	Size              uint32
30
+	CntUsage          uint32
31
+	ProcessID         uint32
32
+	DefaultHeapID     uintptr
33
+	ModuleID          uint32
34
+	CntThreads        uint32
35
+	ParentProcessID   uint32
36
+	PriorityClassBase int32
37
+	Flags             uint32
38
+	ExeFile           [MAX_PATH]uint16
39
+}
40
+
41
+// WindowsProcess is an implementation of Process for Windows.
42
+type WindowsProcess struct {
43
+	pid  int
44
+	ppid int
45
+	exe  string
46
+}
47
+
48
+func (p *WindowsProcess) Pid() int {
49
+	return p.pid
50
+}
51
+
52
+func (p *WindowsProcess) PPid() int {
53
+	return p.ppid
54
+}
55
+
56
+func (p *WindowsProcess) Executable() string {
57
+	return p.exe
58
+}
59
+
60
+func newWindowsProcess(e *PROCESSENTRY32) *WindowsProcess {
61
+	// Find when the string ends for decoding
62
+	end := 0
63
+	for {
64
+		if e.ExeFile[end] == 0 {
65
+			break
66
+		}
67
+		end++
68
+	}
69
+
70
+	return &WindowsProcess{
71
+		pid:  int(e.ProcessID),
72
+		ppid: int(e.ParentProcessID),
73
+		exe:  syscall.UTF16ToString(e.ExeFile[:end]),
74
+	}
75
+}
76
+
77
+func findProcess(pid int) (Process, error) {
78
+	ps, err := processes()
79
+	if err != nil {
80
+		return nil, err
81
+	}
82
+
83
+	for _, p := range ps {
84
+		if p.Pid() == pid {
85
+			return p, nil
86
+		}
87
+	}
88
+
89
+	return nil, nil
90
+}
91
+
92
+func processes() ([]Process, error) {
93
+	handle, _, _ := procCreateToolhelp32Snapshot.Call(
94
+		0x00000002,
95
+		0)
96
+	if handle < 0 {
97
+		return nil, syscall.GetLastError()
98
+	}
99
+	defer procCloseHandle.Call(handle)
100
+
101
+	var entry PROCESSENTRY32
102
+	entry.Size = uint32(unsafe.Sizeof(entry))
103
+	ret, _, _ := procProcess32First.Call(handle, uintptr(unsafe.Pointer(&entry)))
104
+	if ret == 0 {
105
+		return nil, fmt.Errorf("Error retrieving process info.")
106
+	}
107
+
108
+	results := make([]Process, 0, 50)
109
+	for {
110
+		results = append(results, newWindowsProcess(&entry))
111
+
112
+		ret, _, _ := procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry)))
113
+		if ret == 0 {
114
+			break
115
+		}
116
+	}
117
+
118
+	return results, nil
119
+}

+ 2
- 0
vendor/modules.txt View File

@@ -32,6 +32,8 @@ github.com/mattn/go-colorable
32 32
 github.com/mattn/go-isatty
33 33
 # github.com/mattn/go-runewidth v0.0.3
34 34
 github.com/mattn/go-runewidth
35
+# github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936
36
+github.com/mitchellh/go-ps
35 37
 # github.com/mitchellh/go-wordwrap v1.0.0
36 38
 github.com/mitchellh/go-wordwrap
37 39
 # github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e

Loading…
Cancel
Save