-
Notifications
You must be signed in to change notification settings - Fork 0
/
launcher.go
117 lines (104 loc) · 3.17 KB
/
launcher.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
package pico
import (
"errors"
"fmt"
"os"
"os/exec"
"github.com/tuxdude/zzzlogi"
"golang.org/x/sys/unix"
)
// serviceLauncher allows launching services.
type serviceLauncher struct {
// Logger used by the service launcher.
log zzzlogi.Logger
// Service repository.
repo launcherRepo
}
// launcherRepo is the repository interface used by the launcher to add
// the launched services to the repository.
type launcherRepo interface {
addService(serv *launchedServiceOrHook)
}
// launchHook launches the specified hook and waits till it terminates
// prior to exiting.
func launchHook(log zzzlogi.Logger, repo launcherRepo, hook *Hook) error {
sl := &serviceLauncher{
log: log,
repo: repo,
}
return sl.startService(false, hook.Cmd, hook.Args...)
}
// launchServices launches the specified list of services and updates the
// service list in the specified repository.
func launchServices(log zzzlogi.Logger, repo launcherRepo, services ...*Service) error {
sl := &serviceLauncher{
log: log,
repo: repo,
}
return sl.launchServices(services...)
}
// launchServices launches the specified list of services and updates the
// service list in the specified repository.
func (s *serviceLauncher) launchServices(services ...*Service) error {
multiServiceMode := len(services) > 1
for _, serv := range services {
err := s.startService(multiServiceMode, serv.Cmd, serv.Args...)
if err != nil {
return err
}
}
return nil
}
// startService launches the specified service binary invoking it with the
// specified list of command line arguments.
func (s *serviceLauncher) startService(multiServiceMode bool, bin string, args ...string) error {
cmd := exec.Command(bin, args...)
cmd.SysProcAttr = &unix.SysProcAttr{
// Use a new process group for the child.
Setpgid: true,
}
if !multiServiceMode {
// Only in single service mode we redirect stdin to the
// one and only service that is being launched.
cmd.Stdin = os.Stdin
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
return fmt.Errorf("failed to launch service %q %q, reason: %v", bin, args, err)
}
if !multiServiceMode {
// Only in single service mode, attach the stdin TTY (if present) to
// the one and only service launched.
err := s.attachStdinTTY(cmd.Process.Pid)
if err != nil {
return err
}
}
proc := &launchedServiceOrHook{}
proc.pid = cmd.Process.Pid
proc.entity.cmd = bin
proc.entity.args = make([]string, len(args))
copy(proc.entity.args, args)
s.repo.addService(proc)
s.log.Infof("Launched service %q pid: %d", bin, proc.pid)
return nil
}
// attachStdinTTY attaches the TTY to the process with the specified pid.
func (s *serviceLauncher) attachStdinTTY(pid int) error {
err := unix.IoctlSetPointerInt(unix.Stdin, unix.TIOCSPGRP, pid)
if err == nil {
s.log.Debugf("Attached TTY of stdin to pid: %d", pid)
return nil
}
var errNo unix.Errno
if errors.As(err, &errNo) {
if errNo == unix.ENOTTY {
s.log.Debugf("No stdin TTY found to attach, ignoring")
return nil
}
return fmt.Errorf("tcsetpgrp failed attempting to attach stdin TTY, errno: %v", errNo)
}
return fmt.Errorf("tcsetpgrp failed attempting to attach stdin TTY, reason: %T %v", err, err)
}