-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlaunch.go
136 lines (124 loc) · 3.63 KB
/
launch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
* Copyright (c) 2016-2017, Randy Westlund. All rights reserved.
* This code is under the BSD-2-Clause license.
*
* This file contains the logic for spawning child processes.
*/
package main
import (
"log"
"os"
"os/exec"
"os/user"
"strconv"
"syscall"
"time"
)
// Take a processConfig and channels. Launch the process and send a status
// struct on the running channel, wait for it to exit, and signal completion
// by returning a status struct on the completion channel, which includes a
// possible error.
func launchProcess(pc processConfig, runningChan, doneChan chan launchStatus) {
log.Println("Process", pc.Name, "\tlaunching")
// Convert p.args to a slice, so the process gets separate arguments.
var cmd = exec.Command(pc.Path, pc.Args...)
// Set the process PGID to not match paladin's, so that it receives
// signals separately.
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
// Set the current working directory for the process.
if pc.Cwd != "" {
cmd.Dir = pc.Cwd
} else {
cmd.Dir = "/var/empty"
}
// If there's an output file for stdout specified, use it.
if pc.Stdout != "" {
var stdoutFile, err = os.OpenFile(pc.Stdout,
os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664)
if err != nil {
log.Println("Failed to open log file", pc.Stdout, "\n", err)
doneChan <- launchStatus{Name: pc.Name, Err: err}
return
}
defer stdoutFile.Close()
cmd.Stdout = stdoutFile
} else {
// If not, use /dev/null.
cmd.Stdout = nil
}
// If there's an output file for stderr that isn't the same as stdout.
if pc.Stderr != "" && pc.Stderr != pc.Stdout {
var stderrFile, err = os.OpenFile(pc.Stderr,
os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664)
if err != nil {
log.Println("Failed to open log file", pc.Stderr, "\n", err)
doneChan <- launchStatus{Name: pc.Name, Err: err}
return
}
defer stderrFile.Close()
cmd.Stderr = stderrFile
} else {
// Follow stdout unless stderr is set.
cmd.Stderr = cmd.Stdout
}
// Set user and group.
var uid, gid, err = getUIDAndGID(pc.User, pc.Group)
if err != nil {
doneChan <- launchStatus{Name: pc.Name, Err: err}
return
}
if uid != 0 || gid != 0 {
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
}
// Fire off the child process, then wait for it to complete.
var startTime = time.Now()
err = cmd.Start()
if err != nil {
log.Println("Process", pc.Name, "\tfailed to start", err.Error())
doneChan <- launchStatus{Name: pc.Name, Err: err}
return
}
// Signal that the process is running.
runningChan <- launchStatus{Name: pc.Name, Pid: cmd.Process.Pid}
// Wait for it to finish.
err = cmd.Wait()
var duration = time.Since(startTime)
if err != nil {
log.Println("Process", pc.Name, "\tfailed to run")
doneChan <- launchStatus{Name: pc.Name, Err: err, Duration: duration}
return
}
// Signal completion.
doneChan <- launchStatus{Name: pc.Name, Err: nil, Duration: duration}
}
// Get the numeric uid and gid for the given user and group.
func getUIDAndGID(u string, g string) (uint32, uint32, error) {
var uid int
var gid int
// Set user and group.
if g != "" {
groupObj, err := user.LookupGroup(g)
if err != nil {
log.Println("Failed to lookup gid", err.Error())
return 0, 0, err
}
gid, err = strconv.Atoi(groupObj.Gid)
if err != nil {
log.Println("Failed to parse gid", err.Error())
return 0, 0, err
}
}
if u != "" {
userObj, err := user.Lookup(u)
if err != nil {
log.Println("Failed to lookup uid", err.Error())
return 0, 0, err
}
uid, err = strconv.Atoi(userObj.Uid)
if err != nil {
log.Println("Failed to parse uid", err.Error())
return 0, 0, err
}
}
return uint32(uid), uint32(gid), nil
}