github.com/mgoltzsche/ctnr@v0.7.1-alpha/run/librunner/process.go (about) 1 package librunner 2 3 import ( 4 "fmt" 5 "os" 6 "strconv" 7 "sync" 8 "syscall" 9 "time" 10 11 "github.com/coreos/go-systemd/activation" 12 exterrors "github.com/mgoltzsche/ctnr/pkg/errors" 13 "github.com/mgoltzsche/ctnr/pkg/log" 14 "github.com/mgoltzsche/ctnr/run" 15 "github.com/opencontainers/runc/libcontainer" 16 "github.com/opencontainers/runc/libcontainer/configs" 17 "github.com/opencontainers/runtime-spec/specs-go" 18 "github.com/pkg/errors" 19 ) 20 21 type Process struct { 22 args []string 23 container *Container 24 process libcontainer.Process 25 io run.ContainerIO 26 tty *tty 27 terminal bool 28 running bool 29 mutex *sync.Mutex 30 wait *sync.WaitGroup 31 log log.Loggers 32 err error 33 } 34 35 func NewProcess(container *Container, p *specs.Process, io run.ContainerIO, loggers log.Loggers) (r *Process, err error) { 36 // Create container process (see https://github.com/opencontainers/runc/blob/v1.0.0-rc4/utils_linux.go: startContainer->runner.run->newProcess) 37 r = &Process{ 38 args: p.Args, 39 container: container, 40 io: io, 41 terminal: p.Terminal, 42 mutex: &sync.Mutex{}, 43 wait: &sync.WaitGroup{}, 44 log: loggers, 45 process: libcontainer.Process{ 46 Args: p.Args, 47 Env: p.Env, 48 User: fmt.Sprintf("%d:%d", p.User.UID, p.User.GID), 49 Cwd: p.Cwd, 50 Stdout: io.Stdout, 51 Stderr: io.Stderr, 52 Stdin: io.Stdin, 53 AppArmorProfile: p.ApparmorProfile, 54 Label: p.SelinuxLabel, 55 NoNewPrivileges: &p.NoNewPrivileges, 56 }, 57 } 58 lp := r.process 59 for _, gid := range p.User.AdditionalGids { 60 lp.AdditionalGroups = append(lp.AdditionalGroups, strconv.FormatUint(uint64(gid), 10)) 61 } 62 if p.Capabilities != nil { 63 lp.Capabilities = &configs.Capabilities{ 64 Bounding: p.Capabilities.Bounding, 65 Effective: p.Capabilities.Effective, 66 Inheritable: p.Capabilities.Inheritable, 67 Permitted: p.Capabilities.Permitted, 68 Ambient: p.Capabilities.Ambient, 69 } 70 } 71 if p.Rlimits != nil { 72 for _, rlimit := range p.Rlimits { 73 rl, err := createLibContainerRlimit(rlimit) 74 if err != nil { 75 return nil, errors.New("new process: " + err.Error()) 76 } 77 lp.Rlimits = append(lp.Rlimits, rl) 78 } 79 } 80 if os.Getenv("LISTEN_FDS") != "" { 81 // Add systemd file descriptors 82 lp.ExtraFiles = activation.Files(false) 83 } 84 return 85 } 86 87 func (p *Process) Start() (err error) { 88 if len(p.args) == 0 { 89 return errors.New("start process: no args specified") 90 } 91 p.log.Debug.WithField("args", p.args).Println("Starting process") 92 93 p.mutex.Lock() 94 defer p.mutex.Unlock() 95 defer exterrors.Wrapd(&err, "run process") 96 97 if p.running { 98 return errors.New("process already started") 99 } 100 101 // Configure stdIO/terminal 102 p.tty, err = setupIO(&p.process, p.container.container, p.terminal, false, "") 103 if err != nil { 104 return 105 } 106 107 // Run container process 108 if err = p.container.container.Run(&p.process); err != nil { 109 p.tty.Close() 110 return 111 } 112 p.running = true 113 114 p.wait.Add(1) 115 go p.handleTermination() 116 return nil 117 } 118 119 func (p *Process) handleTermination() { 120 defer p.wait.Done() 121 122 // Wait for process 123 _, err := p.process.Wait() 124 125 err = run.NewExitError(err, p.container.ID()) 126 logger := p.log.Debug 127 if exiterr, ok := err.(*run.ExitError); ok { 128 logger = logger.WithField("code", exiterr.Code()) 129 } 130 logger.WithField("args", p.args).Println("Process terminated") 131 132 p.mutex.Lock() 133 defer p.mutex.Unlock() 134 135 // Register process error 136 p.err = err 137 138 // Release TTY 139 // TODO: reject tty CLI option when process is detached and no console socket provided 140 err = p.tty.Close() // ATTENTION: deadlock when detached process and tty enabled 141 p.err = exterrors.Append(p.err, err) 142 p.tty = nil 143 p.running = false 144 } 145 146 func (c *Process) Stop() { 147 if c.running { 148 return 149 } 150 151 go c.stop() 152 } 153 154 func (p *Process) stop() bool { 155 // Terminate container orderly 156 p.mutex.Lock() 157 158 if !p.running { 159 p.mutex.Unlock() 160 return false 161 } 162 163 p.log.Debug.WithField("args", p.args).Println("Stopping process") 164 165 if err := p.process.Signal(syscall.SIGINT); err != nil { 166 p.log.Debug.Println("Failed to send SIGINT to process:", err) 167 } 168 p.mutex.Unlock() 169 170 quit := make(chan bool, 1) 171 go func() { 172 p.wait.Wait() 173 quit <- true 174 }() 175 select { 176 case <-time.After(time.Duration(10000000)): // TODO: read value from OCI runtime configuration 177 // Kill container after timeout 178 if p.running { 179 p.log.Warn.WithField("args", p.args).Println("Killing process (stop timeout exceeded)") 180 if err := p.process.Signal(syscall.SIGKILL); err != nil { 181 errlog := p.log.Error 182 if pid, e := p.process.Pid(); e == nil { 183 errlog = errlog.WithField("pid", pid) 184 } 185 errlog.Println("Failed to kill process:", err) 186 } 187 p.wait.Wait() 188 } 189 <-quit 190 case <-quit: 191 } 192 close(quit) 193 return true 194 } 195 196 // Waits for the container process to terminate and returns the process' error if any 197 func (p *Process) Wait() (err error) { 198 p.wait.Wait() 199 p.mutex.Lock() 200 defer p.mutex.Unlock() 201 err = p.err 202 p.err = nil 203 return err 204 } 205 206 func (p *Process) Close() (err error) { 207 p.stop() 208 p.mutex.Lock() 209 defer p.mutex.Unlock() 210 if p.running { 211 err = p.err 212 p.err = nil 213 if p.tty != nil { 214 err = exterrors.Append(err, p.tty.Close()) 215 p.tty = nil 216 } 217 } 218 return 219 }