847690583f
Because we are launching alot of different runc commands to do operations there is a race between doing a `cmd.Wait()` and getting the sigchld and reaping it. We can remove the sigchild reaper from containerd as long as we make sure we reap the shim process if we are the parent, i.e. not restored. Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
252 lines
5.4 KiB
Go
252 lines
5.4 KiB
Go
package runtime
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"syscall"
|
|
|
|
"github.com/docker/containerd/specs"
|
|
)
|
|
|
|
type Process interface {
|
|
io.Closer
|
|
|
|
// ID of the process.
|
|
// This is either "init" when it is the container's init process or
|
|
// it is a user provided id for the process similar to the container id
|
|
ID() string
|
|
CloseStdin() error
|
|
Resize(int, int) error
|
|
// ExitFD returns the fd the provides an event when the process exits
|
|
ExitFD() int
|
|
// ExitStatus returns the exit status of the process or an error if it
|
|
// has not exited
|
|
ExitStatus() (int, error)
|
|
// Spec returns the process spec that created the process
|
|
Spec() specs.ProcessSpec
|
|
// Signal sends the provided signal to the process
|
|
Signal(os.Signal) error
|
|
// Container returns the container that the process belongs to
|
|
Container() Container
|
|
// Stdio of the container
|
|
Stdio() Stdio
|
|
// SystemPid is the pid on the system
|
|
SystemPid() int
|
|
// State returns if the process is running or not
|
|
State() State
|
|
// Wait reaps the shim process if avaliable
|
|
Wait()
|
|
}
|
|
|
|
type processConfig struct {
|
|
id string
|
|
root string
|
|
processSpec specs.ProcessSpec
|
|
spec *specs.Spec
|
|
c *container
|
|
stdio Stdio
|
|
exec bool
|
|
checkpoint string
|
|
}
|
|
|
|
func newProcess(config *processConfig) (*process, error) {
|
|
p := &process{
|
|
root: config.root,
|
|
id: config.id,
|
|
container: config.c,
|
|
spec: config.processSpec,
|
|
stdio: config.stdio,
|
|
}
|
|
uid, gid, err := getRootIDs(config.spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f, err := os.Create(filepath.Join(config.root, "process.json"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
ps := ProcessState{
|
|
ProcessSpec: config.processSpec,
|
|
Exec: config.exec,
|
|
PlatformProcessState: PlatformProcessState{
|
|
Checkpoint: config.checkpoint,
|
|
RootUID: uid,
|
|
RootGID: gid,
|
|
},
|
|
Stdin: config.stdio.Stdin,
|
|
Stdout: config.stdio.Stdout,
|
|
Stderr: config.stdio.Stderr,
|
|
RuntimeArgs: config.c.runtimeArgs,
|
|
NoPivotRoot: config.c.noPivotRoot,
|
|
}
|
|
|
|
if err := json.NewEncoder(f).Encode(ps); err != nil {
|
|
return nil, err
|
|
}
|
|
exit, err := getExitPipe(filepath.Join(config.root, ExitFile))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
control, err := getControlPipe(filepath.Join(config.root, ControlFile))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.exitPipe = exit
|
|
p.controlPipe = control
|
|
return p, nil
|
|
}
|
|
|
|
func loadProcess(root, id string, c *container, s *ProcessState) (*process, error) {
|
|
p := &process{
|
|
root: root,
|
|
id: id,
|
|
container: c,
|
|
spec: s.ProcessSpec,
|
|
stdio: Stdio{
|
|
Stdin: s.Stdin,
|
|
Stdout: s.Stdout,
|
|
Stderr: s.Stderr,
|
|
},
|
|
}
|
|
if _, err := p.getPidFromFile(); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := p.ExitStatus(); err != nil {
|
|
if err == ErrProcessNotExited {
|
|
exit, err := getExitPipe(filepath.Join(root, ExitFile))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.exitPipe = exit
|
|
return p, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
type process struct {
|
|
root string
|
|
id string
|
|
pid int
|
|
exitPipe *os.File
|
|
controlPipe *os.File
|
|
container *container
|
|
spec specs.ProcessSpec
|
|
stdio Stdio
|
|
cmd *exec.Cmd
|
|
}
|
|
|
|
func (p *process) ID() string {
|
|
return p.id
|
|
}
|
|
|
|
func (p *process) Container() Container {
|
|
return p.container
|
|
}
|
|
|
|
func (p *process) SystemPid() int {
|
|
return p.pid
|
|
}
|
|
|
|
// ExitFD returns the fd of the exit pipe
|
|
func (p *process) ExitFD() int {
|
|
return int(p.exitPipe.Fd())
|
|
}
|
|
|
|
func (p *process) CloseStdin() error {
|
|
_, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 0, 0, 0)
|
|
return err
|
|
}
|
|
|
|
func (p *process) Resize(w, h int) error {
|
|
_, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 1, w, h)
|
|
return err
|
|
}
|
|
|
|
func (p *process) ExitStatus() (int, error) {
|
|
data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile))
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return -1, ErrProcessNotExited
|
|
}
|
|
return -1, err
|
|
}
|
|
if len(data) == 0 {
|
|
return -1, ErrProcessNotExited
|
|
}
|
|
return strconv.Atoi(string(data))
|
|
}
|
|
|
|
func (p *process) Spec() specs.ProcessSpec {
|
|
return p.spec
|
|
}
|
|
|
|
func (p *process) Stdio() Stdio {
|
|
return p.stdio
|
|
}
|
|
|
|
// Close closes any open files and/or resouces on the process
|
|
func (p *process) Close() error {
|
|
return p.exitPipe.Close()
|
|
}
|
|
|
|
func (p *process) State() State {
|
|
if p.pid == 0 {
|
|
return Stopped
|
|
}
|
|
err := syscall.Kill(p.pid, 0)
|
|
if err != nil && err == syscall.ESRCH {
|
|
return Stopped
|
|
}
|
|
return Running
|
|
}
|
|
|
|
func (p *process) getPidFromFile() (int, error) {
|
|
data, err := ioutil.ReadFile(filepath.Join(p.root, "pid"))
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
i, err := strconv.Atoi(string(data))
|
|
if err != nil {
|
|
return -1, errInvalidPidInt
|
|
}
|
|
p.pid = i
|
|
return i, nil
|
|
}
|
|
|
|
// Wait will reap the shim process
|
|
func (p *process) Wait() {
|
|
if p.cmd != nil {
|
|
p.cmd.Wait()
|
|
}
|
|
}
|
|
|
|
func getExitPipe(path string) (*os.File, error) {
|
|
if err := syscall.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
|
|
return nil, err
|
|
}
|
|
// add NONBLOCK in case the other side has already closed or else
|
|
// this function would never return
|
|
return os.OpenFile(path, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
|
|
}
|
|
|
|
func getControlPipe(path string) (*os.File, error) {
|
|
if err := syscall.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
|
|
return nil, err
|
|
}
|
|
return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0)
|
|
}
|
|
|
|
// Signal sends the provided signal to the process
|
|
func (p *process) Signal(s os.Signal) error {
|
|
return syscall.Kill(p.pid, s.(syscall.Signal))
|
|
}
|