github.com/docker/containerd@v0.2.9-0.20170509230648-8ef7df579710/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 "strings" 13 "sync" 14 "syscall" 15 "time" 16 17 "github.com/Sirupsen/logrus" 18 "github.com/containerd/containerd/osutils" 19 "github.com/containerd/containerd/specs" 20 "golang.org/x/sys/unix" 21 ) 22 23 // Process holds the operation allowed on a container's process 24 type Process interface { 25 io.Closer 26 27 // ID of the process. 28 // This is either "init" when it is the container's init process or 29 // it is a user provided id for the process similar to the container id 30 ID() string 31 // Start unblocks the associated container init process. 32 // This should only be called on the process with ID "init" 33 Start() error 34 CloseStdin() error 35 Resize(int, int) error 36 // ExitFD returns the fd the provides an event when the process exits 37 ExitFD() int 38 // ExitStatus returns the exit status of the process or an error if it 39 // has not exited 40 ExitStatus() (uint32, error) 41 // Spec returns the process spec that created the process 42 Spec() specs.ProcessSpec 43 // Signal sends the provided signal to the process 44 Signal(os.Signal) error 45 // Container returns the container that the process belongs to 46 Container() Container 47 // Stdio of the container 48 Stdio() Stdio 49 // SystemPid is the pid on the system 50 SystemPid() int 51 // State returns if the process is running or not 52 State() State 53 // Wait reaps the shim process if avaliable 54 Wait() 55 } 56 57 type processConfig struct { 58 id string 59 root string 60 processSpec specs.ProcessSpec 61 spec *specs.Spec 62 c *container 63 stdio Stdio 64 exec bool 65 checkpoint string 66 } 67 68 func newProcess(config *processConfig) (*process, error) { 69 p := &process{ 70 root: config.root, 71 id: config.id, 72 container: config.c, 73 spec: config.processSpec, 74 stdio: config.stdio, 75 cmdDoneCh: make(chan struct{}), 76 state: Running, 77 } 78 uid, gid, err := getRootIDs(config.spec) 79 if err != nil { 80 return nil, err 81 } 82 f, err := os.Create(filepath.Join(config.root, "process.json")) 83 if err != nil { 84 return nil, err 85 } 86 defer f.Close() 87 88 ps := ProcessState{ 89 ProcessSpec: config.processSpec, 90 Exec: config.exec, 91 PlatformProcessState: PlatformProcessState{ 92 Checkpoint: config.checkpoint, 93 RootUID: uid, 94 RootGID: gid, 95 }, 96 Stdin: config.stdio.Stdin, 97 Stdout: config.stdio.Stdout, 98 Stderr: config.stdio.Stderr, 99 RuntimeArgs: config.c.runtimeArgs, 100 NoPivotRoot: config.c.noPivotRoot, 101 } 102 103 if err := json.NewEncoder(f).Encode(ps); err != nil { 104 return nil, err 105 } 106 exit, err := getExitPipe(filepath.Join(config.root, ExitFile)) 107 if err != nil { 108 return nil, err 109 } 110 control, err := getControlPipe(filepath.Join(config.root, ControlFile)) 111 if err != nil { 112 return nil, err 113 } 114 p.exitPipe = exit 115 p.controlPipe = control 116 return p, nil 117 } 118 119 func loadProcess(root, id string, c *container, s *ProcessState) (*process, error) { 120 p := &process{ 121 root: root, 122 id: id, 123 container: c, 124 spec: s.ProcessSpec, 125 stdio: Stdio{ 126 Stdin: s.Stdin, 127 Stdout: s.Stdout, 128 Stderr: s.Stderr, 129 }, 130 state: Stopped, 131 } 132 133 startTime, err := ioutil.ReadFile(filepath.Join(p.root, StartTimeFile)) 134 if err != nil && !os.IsNotExist(err) { 135 return nil, err 136 } 137 p.startTime = string(startTime) 138 139 if _, err := p.getPidFromFile(); err != nil { 140 return nil, err 141 } 142 if _, err := p.ExitStatus(); err != nil { 143 if err == ErrProcessNotExited { 144 exit, err := getExitPipe(filepath.Join(root, ExitFile)) 145 if err != nil { 146 return nil, err 147 } 148 p.exitPipe = exit 149 150 control, err := getControlPipe(filepath.Join(root, ControlFile)) 151 if err != nil { 152 return nil, err 153 } 154 p.controlPipe = control 155 156 p.state = Running 157 return p, nil 158 } 159 return nil, err 160 } 161 return p, nil 162 } 163 164 func readProcStatField(pid int, field int) (string, error) { 165 data, err := ioutil.ReadFile(filepath.Join(string(filepath.Separator), "proc", strconv.Itoa(pid), "stat")) 166 if err != nil { 167 return "", err 168 } 169 170 if field > 2 { 171 // First, split out the name since he could contains spaces. 172 parts := strings.Split(string(data), ") ") 173 // Now split out the rest, we end up with 2 fields less 174 parts = strings.Split(parts[1], " ") 175 return parts[field-2-1], nil // field count start at 1 in manual 176 } 177 178 parts := strings.Split(string(data), " (") 179 180 if field == 1 { 181 return parts[0], nil 182 } 183 184 parts = strings.Split(parts[1], ") ") 185 return parts[0], nil 186 } 187 188 type process struct { 189 root string 190 id string 191 pid int 192 exitPipe *os.File 193 controlPipe *os.File 194 container *container 195 spec specs.ProcessSpec 196 stdio Stdio 197 cmd *exec.Cmd 198 cmdSuccess bool 199 cmdDoneCh chan struct{} 200 state State 201 stateLock sync.Mutex 202 startTime string 203 } 204 205 func (p *process) ID() string { 206 return p.id 207 } 208 209 func (p *process) Container() Container { 210 return p.container 211 } 212 213 func (p *process) SystemPid() int { 214 return p.pid 215 } 216 217 // ExitFD returns the fd of the exit pipe 218 func (p *process) ExitFD() int { 219 return int(p.exitPipe.Fd()) 220 } 221 222 func (p *process) CloseStdin() error { 223 _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 0, 0, 0) 224 return err 225 } 226 227 func (p *process) Resize(w, h int) error { 228 _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 1, w, h) 229 return err 230 } 231 232 func (p *process) updateExitStatusFile(status uint32) (uint32, error) { 233 p.stateLock.Lock() 234 p.state = Stopped 235 p.stateLock.Unlock() 236 err := ioutil.WriteFile(filepath.Join(p.root, ExitStatusFile), []byte(fmt.Sprintf("%u", status)), 0644) 237 return status, err 238 } 239 240 func (p *process) handleSigkilledShim(rst uint32, rerr error) (uint32, error) { 241 if p.cmd == nil || p.cmd.Process == nil { 242 e := unix.Kill(p.pid, 0) 243 if e == syscall.ESRCH { 244 logrus.Warnf("containerd: %s:%s (pid %d) does not exist", p.container.id, p.id, p.pid) 245 // The process died while containerd was down (probably of 246 // SIGKILL, but no way to be sure) 247 return p.updateExitStatusFile(UnknownStatus) 248 } 249 250 // If it's not the same process, just mark it stopped and set 251 // the status to the UnknownStatus value (i.e. 255) 252 if same, err := p.isSameProcess(); !same { 253 logrus.Warnf("containerd: %s:%s (pid %d) is not the same process anymore (%v)", p.container.id, p.id, p.pid, err) 254 // Create the file so we get the exit event generated once monitor kicks in 255 // without having to go through all this process again 256 return p.updateExitStatusFile(UnknownStatus) 257 } 258 259 ppid, err := readProcStatField(p.pid, 4) 260 if err != nil { 261 return rst, fmt.Errorf("could not check process ppid: %v (%v)", err, rerr) 262 } 263 if ppid == "1" { 264 logrus.Warnf("containerd: %s:%s shim died, killing associated process", p.container.id, p.id) 265 unix.Kill(p.pid, syscall.SIGKILL) 266 if err != nil && err != syscall.ESRCH { 267 return UnknownStatus, fmt.Errorf("containerd: unable to SIGKILL %s:%s (pid %v): %v", p.container.id, p.id, p.pid, err) 268 } 269 270 // wait for the process to die 271 for { 272 e := unix.Kill(p.pid, 0) 273 if e == syscall.ESRCH { 274 break 275 } 276 time.Sleep(5 * time.Millisecond) 277 } 278 // Create the file so we get the exit event generated once monitor kicks in 279 // without having to go through all this process again 280 return p.updateExitStatusFile(128 + uint32(syscall.SIGKILL)) 281 } 282 283 return rst, rerr 284 } 285 286 // Possible that the shim was SIGKILLED 287 e := unix.Kill(p.cmd.Process.Pid, 0) 288 if e != syscall.ESRCH { 289 return rst, rerr 290 } 291 292 // Ensure we got the shim ProcessState 293 <-p.cmdDoneCh 294 295 shimStatus := p.cmd.ProcessState.Sys().(syscall.WaitStatus) 296 if shimStatus.Signaled() && shimStatus.Signal() == syscall.SIGKILL { 297 logrus.Debugf("containerd: ExitStatus(container: %s, process: %s): shim was SIGKILL'ed reaping its child with pid %d", p.container.id, p.id, p.pid) 298 299 rerr = nil 300 rst = 128 + uint32(shimStatus.Signal()) 301 302 p.stateLock.Lock() 303 p.state = Stopped 304 p.stateLock.Unlock() 305 } 306 307 return rst, rerr 308 } 309 310 func (p *process) ExitStatus() (rst uint32, rerr error) { 311 data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile)) 312 defer func() { 313 if rerr != nil { 314 rst, rerr = p.handleSigkilledShim(rst, rerr) 315 } 316 }() 317 if err != nil { 318 if os.IsNotExist(err) { 319 return UnknownStatus, ErrProcessNotExited 320 } 321 return UnknownStatus, err 322 } 323 if len(data) == 0 { 324 return UnknownStatus, ErrProcessNotExited 325 } 326 p.stateLock.Lock() 327 p.state = Stopped 328 p.stateLock.Unlock() 329 330 i, err := strconv.ParseUint(string(data), 10, 32) 331 return uint32(i), err 332 } 333 334 func (p *process) Spec() specs.ProcessSpec { 335 return p.spec 336 } 337 338 func (p *process) Stdio() Stdio { 339 return p.stdio 340 } 341 342 // Close closes any open files and/or resouces on the process 343 func (p *process) Close() error { 344 err := p.exitPipe.Close() 345 if cerr := p.controlPipe.Close(); err == nil { 346 err = cerr 347 } 348 return err 349 } 350 351 func (p *process) State() State { 352 p.stateLock.Lock() 353 defer p.stateLock.Unlock() 354 return p.state 355 } 356 357 func (p *process) readStartTime() (string, error) { 358 return readProcStatField(p.pid, 22) 359 } 360 361 func (p *process) saveStartTime() error { 362 startTime, err := p.readStartTime() 363 if err != nil { 364 return err 365 } 366 367 p.startTime = startTime 368 return ioutil.WriteFile(filepath.Join(p.root, StartTimeFile), []byte(startTime), 0644) 369 } 370 371 func (p *process) isSameProcess() (bool, error) { 372 if p.pid == 0 { 373 _, err := p.getPidFromFile() 374 if err != nil { 375 return false, err 376 } 377 } 378 379 // for backward compat assume it's the same if startTime wasn't set 380 if p.startTime == "" { 381 // Sometimes the process dies before we can get the starttime, 382 // check that the process actually exists 383 if err := unix.Kill(p.pid, 0); err != syscall.ESRCH { 384 return true, nil 385 } 386 return false, nil 387 } 388 389 startTime, err := p.readStartTime() 390 if err != nil { 391 return false, err 392 } 393 394 return startTime == p.startTime, nil 395 } 396 397 // Wait will reap the shim process 398 func (p *process) Wait() { 399 if p.cmdDoneCh != nil { 400 <-p.cmdDoneCh 401 } 402 } 403 404 func getExitPipe(path string) (*os.File, error) { 405 if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) { 406 return nil, err 407 } 408 // add NONBLOCK in case the other side has already closed or else 409 // this function would never return 410 return os.OpenFile(path, syscall.O_RDONLY|syscall.O_NONBLOCK, 0) 411 } 412 413 func getControlPipe(path string) (*os.File, error) { 414 if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) { 415 return nil, err 416 } 417 return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0) 418 } 419 420 // Signal sends the provided signal to the process 421 func (p *process) Signal(s os.Signal) error { 422 return syscall.Kill(p.pid, s.(syscall.Signal)) 423 } 424 425 // Start unblocks the associated container init process. 426 // This should only be called on the process with ID "init" 427 func (p *process) Start() error { 428 if p.ID() == InitProcessID { 429 var ( 430 errC = make(chan error, 1) 431 args = append(p.container.runtimeArgs, "start", p.container.id) 432 cmd = exec.Command(p.container.runtime, args...) 433 ) 434 go func() { 435 out, err := cmd.CombinedOutput() 436 if err != nil { 437 errC <- fmt.Errorf("%s: %q", err.Error(), out) 438 } 439 errC <- nil 440 }() 441 select { 442 case err := <-errC: 443 if err != nil { 444 return err 445 } 446 case <-p.cmdDoneCh: 447 if !p.cmdSuccess { 448 if cmd.Process != nil { 449 cmd.Process.Kill() 450 } 451 cmd.Wait() 452 return ErrShimExited 453 } 454 err := <-errC 455 if err != nil { 456 return err 457 } 458 } 459 } 460 return nil 461 } 462 463 // Delete delete any resources held by the container 464 func (p *process) Delete() error { 465 var ( 466 args = append(p.container.runtimeArgs, "delete", "-f", p.container.id) 467 cmd = exec.Command(p.container.runtime, args...) 468 ) 469 470 cmd.SysProcAttr = osutils.SetPDeathSig() 471 out, err := cmd.CombinedOutput() 472 if err != nil { 473 return fmt.Errorf("%s: %v", out, err) 474 } 475 return nil 476 }