github.com/allencloud/containerd@v0.2.5/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) getPidFromFile() (int, error) { 357 data, err := ioutil.ReadFile(filepath.Join(p.root, "pid")) 358 if err != nil { 359 return -1, err 360 } 361 i, err := strconv.Atoi(string(data)) 362 if err != nil { 363 return -1, errInvalidPidInt 364 } 365 p.pid = i 366 return i, nil 367 } 368 369 func (p *process) readStartTime() (string, error) { 370 return readProcStatField(p.pid, 22) 371 } 372 373 func (p *process) saveStartTime() error { 374 startTime, err := p.readStartTime() 375 if err != nil { 376 return err 377 } 378 379 p.startTime = startTime 380 return ioutil.WriteFile(filepath.Join(p.root, StartTimeFile), []byte(startTime), 0644) 381 } 382 383 func (p *process) isSameProcess() (bool, error) { 384 // for backward compat assume it's the same if startTime wasn't set 385 if p.startTime == "" { 386 return true, nil 387 } 388 if p.pid == 0 { 389 _, err := p.getPidFromFile() 390 if err != nil { 391 return false, err 392 } 393 } 394 395 startTime, err := p.readStartTime() 396 if err != nil { 397 return false, err 398 } 399 400 return startTime == p.startTime, nil 401 } 402 403 // Wait will reap the shim process 404 func (p *process) Wait() { 405 if p.cmdDoneCh != nil { 406 <-p.cmdDoneCh 407 } 408 } 409 410 func getExitPipe(path string) (*os.File, error) { 411 if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) { 412 return nil, err 413 } 414 // add NONBLOCK in case the other side has already closed or else 415 // this function would never return 416 return os.OpenFile(path, syscall.O_RDONLY|syscall.O_NONBLOCK, 0) 417 } 418 419 func getControlPipe(path string) (*os.File, error) { 420 if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) { 421 return nil, err 422 } 423 return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0) 424 } 425 426 // Signal sends the provided signal to the process 427 func (p *process) Signal(s os.Signal) error { 428 return syscall.Kill(p.pid, s.(syscall.Signal)) 429 } 430 431 // Start unblocks the associated container init process. 432 // This should only be called on the process with ID "init" 433 func (p *process) Start() error { 434 if p.ID() == InitProcessID { 435 var ( 436 errC = make(chan error, 1) 437 args = append(p.container.runtimeArgs, "start", p.container.id) 438 cmd = exec.Command(p.container.runtime, args...) 439 ) 440 go func() { 441 out, err := cmd.CombinedOutput() 442 if err != nil { 443 errC <- fmt.Errorf("%s: %q", err.Error(), out) 444 } 445 errC <- nil 446 }() 447 select { 448 case err := <-errC: 449 if err != nil { 450 return err 451 } 452 case <-p.cmdDoneCh: 453 if !p.cmdSuccess { 454 if cmd.Process != nil { 455 cmd.Process.Kill() 456 } 457 cmd.Wait() 458 return ErrShimExited 459 } 460 err := <-errC 461 if err != nil { 462 return err 463 } 464 } 465 } 466 return nil 467 }