github.com/runcom/containerd@v0.0.0-20160708090337-9bff9f934c0d/runtime/process.go (about) 1 package runtime 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "strconv" 12 "syscall" 13 14 "github.com/docker/containerd/specs" 15 "golang.org/x/sys/unix" 16 ) 17 18 // Process holds the operation allowed on a container's process 19 type Process interface { 20 io.Closer 21 22 // ID of the process. 23 // This is either "init" when it is the container's init process or 24 // it is a user provided id for the process similar to the container id 25 ID() string 26 // Start unblocks the associated container init process. 27 // This should only be called on the process with ID "init" 28 Start() error 29 CloseStdin() error 30 Resize(int, int) error 31 // ExitFD returns the fd the provides an event when the process exits 32 ExitFD() int 33 // ExitStatus returns the exit status of the process or an error if it 34 // has not exited 35 ExitStatus() (int, error) 36 // Spec returns the process spec that created the process 37 Spec() specs.ProcessSpec 38 // Signal sends the provided signal to the process 39 Signal(os.Signal) error 40 // Container returns the container that the process belongs to 41 Container() Container 42 // Stdio of the container 43 Stdio() Stdio 44 // SystemPid is the pid on the system 45 SystemPid() int 46 // State returns if the process is running or not 47 State() State 48 // Wait reaps the shim process if avaliable 49 Wait() 50 } 51 52 type processConfig struct { 53 id string 54 root string 55 processSpec specs.ProcessSpec 56 spec *specs.Spec 57 c *container 58 stdio Stdio 59 exec bool 60 checkpoint string 61 } 62 63 func newProcess(config *processConfig) (*process, error) { 64 p := &process{ 65 root: config.root, 66 id: config.id, 67 container: config.c, 68 spec: config.processSpec, 69 stdio: config.stdio, 70 cmdDoneCh: make(chan struct{}), 71 } 72 uid, gid, err := getRootIDs(config.spec) 73 if err != nil { 74 return nil, err 75 } 76 f, err := os.Create(filepath.Join(config.root, "process.json")) 77 if err != nil { 78 return nil, err 79 } 80 defer f.Close() 81 82 ps := ProcessState{ 83 ProcessSpec: config.processSpec, 84 Exec: config.exec, 85 PlatformProcessState: PlatformProcessState{ 86 Checkpoint: config.checkpoint, 87 RootUID: uid, 88 RootGID: gid, 89 }, 90 Stdin: config.stdio.Stdin, 91 Stdout: config.stdio.Stdout, 92 Stderr: config.stdio.Stderr, 93 RuntimeArgs: config.c.runtimeArgs, 94 NoPivotRoot: config.c.noPivotRoot, 95 } 96 97 if err := json.NewEncoder(f).Encode(ps); err != nil { 98 return nil, err 99 } 100 exit, err := getExitPipe(filepath.Join(config.root, ExitFile)) 101 if err != nil { 102 return nil, err 103 } 104 control, err := getControlPipe(filepath.Join(config.root, ControlFile)) 105 if err != nil { 106 return nil, err 107 } 108 p.exitPipe = exit 109 p.controlPipe = control 110 return p, nil 111 } 112 113 func loadProcess(root, id string, c *container, s *ProcessState) (*process, error) { 114 p := &process{ 115 root: root, 116 id: id, 117 container: c, 118 spec: s.ProcessSpec, 119 stdio: Stdio{ 120 Stdin: s.Stdin, 121 Stdout: s.Stdout, 122 Stderr: s.Stderr, 123 }, 124 } 125 if _, err := p.getPidFromFile(); err != nil { 126 return nil, err 127 } 128 if _, err := p.ExitStatus(); err != nil { 129 if err == ErrProcessNotExited { 130 exit, err := getExitPipe(filepath.Join(root, ExitFile)) 131 if err != nil { 132 return nil, err 133 } 134 p.exitPipe = exit 135 136 control, err := getControlPipe(filepath.Join(root, ControlFile)) 137 if err != nil { 138 return nil, err 139 } 140 p.controlPipe = control 141 142 return p, nil 143 } 144 return nil, err 145 } 146 return p, nil 147 } 148 149 type process struct { 150 root string 151 id string 152 pid int 153 exitPipe *os.File 154 controlPipe *os.File 155 container *container 156 spec specs.ProcessSpec 157 stdio Stdio 158 cmd *exec.Cmd 159 cmdSuccess bool 160 cmdDoneCh chan struct{} 161 } 162 163 func (p *process) ID() string { 164 return p.id 165 } 166 167 func (p *process) Container() Container { 168 return p.container 169 } 170 171 func (p *process) SystemPid() int { 172 return p.pid 173 } 174 175 // ExitFD returns the fd of the exit pipe 176 func (p *process) ExitFD() int { 177 return int(p.exitPipe.Fd()) 178 } 179 180 func (p *process) CloseStdin() error { 181 _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 0, 0, 0) 182 return err 183 } 184 185 func (p *process) Resize(w, h int) error { 186 _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 1, w, h) 187 return err 188 } 189 190 func (p *process) ExitStatus() (int, error) { 191 data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile)) 192 if err != nil { 193 if os.IsNotExist(err) { 194 return -1, ErrProcessNotExited 195 } 196 return -1, err 197 } 198 if len(data) == 0 { 199 return -1, ErrProcessNotExited 200 } 201 return strconv.Atoi(string(data)) 202 } 203 204 func (p *process) Spec() specs.ProcessSpec { 205 return p.spec 206 } 207 208 func (p *process) Stdio() Stdio { 209 return p.stdio 210 } 211 212 // Close closes any open files and/or resouces on the process 213 func (p *process) Close() error { 214 err := p.exitPipe.Close() 215 if cerr := p.controlPipe.Close(); err == nil { 216 err = cerr 217 } 218 return err 219 } 220 221 func (p *process) State() State { 222 if p.pid == 0 { 223 return Stopped 224 } 225 err := syscall.Kill(p.pid, 0) 226 if err != nil && err == syscall.ESRCH { 227 return Stopped 228 } 229 return Running 230 } 231 232 func (p *process) getPidFromFile() (int, error) { 233 data, err := ioutil.ReadFile(filepath.Join(p.root, "pid")) 234 if err != nil { 235 return -1, err 236 } 237 i, err := strconv.Atoi(string(data)) 238 if err != nil { 239 return -1, errInvalidPidInt 240 } 241 p.pid = i 242 return i, nil 243 } 244 245 // Wait will reap the shim process 246 func (p *process) Wait() { 247 if p.cmdDoneCh != nil { 248 <-p.cmdDoneCh 249 } 250 } 251 252 func getExitPipe(path string) (*os.File, error) { 253 if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) { 254 return nil, err 255 } 256 // add NONBLOCK in case the other side has already closed or else 257 // this function would never return 258 return os.OpenFile(path, syscall.O_RDONLY|syscall.O_NONBLOCK, 0) 259 } 260 261 func getControlPipe(path string) (*os.File, error) { 262 if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) { 263 return nil, err 264 } 265 return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0) 266 } 267 268 // Signal sends the provided signal to the process 269 func (p *process) Signal(s os.Signal) error { 270 return syscall.Kill(p.pid, s.(syscall.Signal)) 271 } 272 273 // Start unblocks the associated container init process. 274 // This should only be called on the process with ID "init" 275 func (p *process) Start() error { 276 if p.ID() == InitProcessID { 277 var ( 278 errC = make(chan error, 1) 279 args = append(p.container.runtimeArgs, "start", p.container.id) 280 cmd = exec.Command(p.container.runtime, args...) 281 ) 282 go func() { 283 out, err := cmd.CombinedOutput() 284 if err != nil { 285 errC <- fmt.Errorf("%s: %q", err.Error(), out) 286 } 287 errC <- nil 288 }() 289 select { 290 case err := <-errC: 291 if err != nil { 292 return err 293 } 294 case <-p.cmdDoneCh: 295 if !p.cmdSuccess { 296 cmd.Process.Kill() 297 cmd.Wait() 298 return ErrShimExited 299 } 300 err := <-errC 301 if err != nil { 302 return err 303 } 304 } 305 } 306 return nil 307 }