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