github.com/jzwlqx/containerd@v0.2.5/containerd-shim/process.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "strconv" 13 "sync" 14 "syscall" 15 "time" 16 17 "github.com/docker/containerd/specs" 18 "github.com/tonistiigi/fifo" 19 "golang.org/x/net/context" 20 ) 21 22 var errRuntime = errors.New("shim: runtime execution error") 23 24 type checkpoint struct { 25 // Timestamp is the time that checkpoint happened 26 Created time.Time `json:"created"` 27 // Name is the name of the checkpoint 28 Name string `json:"name"` 29 // TCP checkpoints open tcp connections 30 TCP bool `json:"tcp"` 31 // UnixSockets persists unix sockets in the checkpoint 32 UnixSockets bool `json:"unixSockets"` 33 // Shell persists tty sessions in the checkpoint 34 Shell bool `json:"shell"` 35 // Exit exits the container after the checkpoint is finished 36 Exit bool `json:"exit"` 37 // EmptyNS tells CRIU not to restore a particular namespace 38 EmptyNS []string `json:"emptyNS,omitempty"` 39 } 40 41 type processState struct { 42 specs.ProcessSpec 43 Exec bool `json:"exec"` 44 Stdin string `json:"containerdStdin"` 45 Stdout string `json:"containerdStdout"` 46 Stderr string `json:"containerdStderr"` 47 RuntimeArgs []string `json:"runtimeArgs"` 48 NoPivotRoot bool `json:"noPivotRoot"` 49 CheckpointPath string `json:"checkpoint"` 50 RootUID int `json:"rootUID"` 51 RootGID int `json:"rootGID"` 52 } 53 54 type process struct { 55 sync.WaitGroup 56 id string 57 bundle string 58 stdio *stdio 59 exec bool 60 containerPid int 61 checkpoint *checkpoint 62 checkpointPath string 63 shimIO *IO 64 stdinCloser io.Closer 65 console *os.File 66 consolePath string 67 state *processState 68 runtime string 69 } 70 71 func newProcess(id, bundle, runtimeName string) (*process, error) { 72 p := &process{ 73 id: id, 74 bundle: bundle, 75 runtime: runtimeName, 76 } 77 s, err := loadProcess() 78 if err != nil { 79 return nil, err 80 } 81 p.state = s 82 if s.CheckpointPath != "" { 83 cpt, err := loadCheckpoint(s.CheckpointPath) 84 if err != nil { 85 return nil, err 86 } 87 p.checkpoint = cpt 88 p.checkpointPath = s.CheckpointPath 89 } 90 if err := p.openIO(); err != nil { 91 return nil, err 92 } 93 return p, nil 94 } 95 96 func loadProcess() (*processState, error) { 97 f, err := os.Open("process.json") 98 if err != nil { 99 return nil, err 100 } 101 defer f.Close() 102 var s processState 103 if err := json.NewDecoder(f).Decode(&s); err != nil { 104 return nil, err 105 } 106 return &s, nil 107 } 108 109 func loadCheckpoint(checkpointPath string) (*checkpoint, error) { 110 f, err := os.Open(filepath.Join(checkpointPath, "config.json")) 111 if err != nil { 112 return nil, err 113 } 114 defer f.Close() 115 var cpt checkpoint 116 if err := json.NewDecoder(f).Decode(&cpt); err != nil { 117 return nil, err 118 } 119 return &cpt, nil 120 } 121 122 func (p *process) create() error { 123 cwd, err := os.Getwd() 124 if err != nil { 125 return err 126 } 127 logPath := filepath.Join(cwd, "log.json") 128 args := append([]string{ 129 "--log", logPath, 130 "--log-format", "json", 131 }, p.state.RuntimeArgs...) 132 if p.state.Exec { 133 args = append(args, "exec", 134 "-d", 135 "--process", filepath.Join(cwd, "process.json"), 136 "--console", p.consolePath, 137 ) 138 } else if p.checkpoint != nil { 139 args = append(args, "restore", 140 "-d", 141 "--image-path", p.checkpointPath, 142 "--work-path", filepath.Join(p.checkpointPath, "criu.work", "restore-"+time.Now().Format(time.RFC3339)), 143 ) 144 add := func(flags ...string) { 145 args = append(args, flags...) 146 } 147 if p.checkpoint.Shell { 148 add("--shell-job") 149 } 150 if p.checkpoint.TCP { 151 add("--tcp-established") 152 } 153 if p.checkpoint.UnixSockets { 154 add("--ext-unix-sk") 155 } 156 if p.state.NoPivotRoot { 157 add("--no-pivot") 158 } 159 for _, ns := range p.checkpoint.EmptyNS { 160 add("--empty-ns", ns) 161 } 162 163 } else { 164 args = append(args, "create", 165 "--bundle", p.bundle, 166 "--console", p.consolePath, 167 ) 168 if p.state.NoPivotRoot { 169 args = append(args, "--no-pivot") 170 } 171 } 172 args = append(args, 173 "--pid-file", filepath.Join(cwd, "pid"), 174 p.id, 175 ) 176 cmd := exec.Command(p.runtime, args...) 177 cmd.Dir = p.bundle 178 cmd.Stdin = p.stdio.stdin 179 cmd.Stdout = p.stdio.stdout 180 cmd.Stderr = p.stdio.stderr 181 // Call out to setPDeathSig to set SysProcAttr as elements are platform specific 182 cmd.SysProcAttr = setPDeathSig() 183 184 if err := cmd.Start(); err != nil { 185 if exErr, ok := err.(*exec.Error); ok { 186 if exErr.Err == exec.ErrNotFound || exErr.Err == os.ErrNotExist { 187 return fmt.Errorf("%s not installed on system", p.runtime) 188 } 189 } 190 return err 191 } 192 p.stdio.stdout.Close() 193 p.stdio.stderr.Close() 194 if err := cmd.Wait(); err != nil { 195 if _, ok := err.(*exec.ExitError); ok { 196 return errRuntime 197 } 198 return err 199 } 200 data, err := ioutil.ReadFile("pid") 201 if err != nil { 202 return err 203 } 204 pid, err := strconv.Atoi(string(data)) 205 if err != nil { 206 return err 207 } 208 p.containerPid = pid 209 return nil 210 } 211 212 func (p *process) pid() int { 213 return p.containerPid 214 } 215 216 func (p *process) delete() error { 217 if !p.state.Exec { 218 cmd := exec.Command(p.runtime, append(p.state.RuntimeArgs, "delete", p.id)...) 219 cmd.SysProcAttr = setPDeathSig() 220 out, err := cmd.CombinedOutput() 221 if err != nil { 222 return fmt.Errorf("%s: %v", out, err) 223 } 224 } 225 return nil 226 } 227 228 // openIO opens the pre-created fifo's for use with the container 229 // in RDWR so that they remain open if the other side stops listening 230 func (p *process) openIO() error { 231 p.stdio = &stdio{} 232 var ( 233 uid = p.state.RootUID 234 gid = p.state.RootGID 235 ) 236 237 ctx, _ := context.WithTimeout(context.Background(), 15*time.Second) 238 239 stdinCloser, err := fifo.OpenFifo(ctx, p.state.Stdin, syscall.O_WRONLY|syscall.O_NONBLOCK, 0) 240 if err != nil { 241 return err 242 } 243 p.stdinCloser = stdinCloser 244 245 if p.state.Terminal { 246 master, console, err := newConsole(uid, gid) 247 if err != nil { 248 return err 249 } 250 p.console = master 251 p.consolePath = console 252 stdin, err := fifo.OpenFifo(ctx, p.state.Stdin, syscall.O_RDONLY, 0) 253 if err != nil { 254 return err 255 } 256 go io.Copy(master, stdin) 257 stdoutw, err := fifo.OpenFifo(ctx, p.state.Stdout, syscall.O_WRONLY, 0) 258 if err != nil { 259 return err 260 } 261 stdoutr, err := fifo.OpenFifo(ctx, p.state.Stdout, syscall.O_RDONLY, 0) 262 if err != nil { 263 return err 264 } 265 p.Add(1) 266 go func() { 267 io.Copy(stdoutw, master) 268 master.Close() 269 stdoutr.Close() 270 stdoutw.Close() 271 p.Done() 272 }() 273 return nil 274 } 275 i, err := p.initializeIO(uid) 276 if err != nil { 277 return err 278 } 279 p.shimIO = i 280 // non-tty 281 for name, dest := range map[string]func(wc io.WriteCloser, rc io.Closer){ 282 p.state.Stdout: func(wc io.WriteCloser, rc io.Closer) { 283 p.Add(1) 284 go func() { 285 io.Copy(wc, i.Stdout) 286 p.Done() 287 wc.Close() 288 rc.Close() 289 }() 290 }, 291 p.state.Stderr: func(wc io.WriteCloser, rc io.Closer) { 292 p.Add(1) 293 go func() { 294 io.Copy(wc, i.Stderr) 295 p.Done() 296 wc.Close() 297 rc.Close() 298 }() 299 }, 300 } { 301 fw, err := fifo.OpenFifo(ctx, name, syscall.O_WRONLY, 0) 302 if err != nil { 303 return err 304 } 305 fr, err := fifo.OpenFifo(ctx, name, syscall.O_RDONLY, 0) 306 if err != nil { 307 return err 308 } 309 dest(fw, fr) 310 } 311 312 f, err := fifo.OpenFifo(ctx, p.state.Stdin, syscall.O_RDONLY, 0) 313 if err != nil { 314 return err 315 } 316 go func() { 317 io.Copy(i.Stdin, f) 318 i.Stdin.Close() 319 f.Close() 320 }() 321 322 return nil 323 } 324 325 // IO holds all 3 standard io Reader/Writer (stdin,stdout,stderr) 326 type IO struct { 327 Stdin io.WriteCloser 328 Stdout io.ReadCloser 329 Stderr io.ReadCloser 330 } 331 332 func (p *process) initializeIO(rootuid int) (i *IO, err error) { 333 var fds []uintptr 334 i = &IO{} 335 // cleanup in case of an error 336 defer func() { 337 if err != nil { 338 for _, fd := range fds { 339 syscall.Close(int(fd)) 340 } 341 } 342 }() 343 // STDIN 344 r, w, err := os.Pipe() 345 if err != nil { 346 return nil, err 347 } 348 fds = append(fds, r.Fd(), w.Fd()) 349 p.stdio.stdin, i.Stdin = r, w 350 // STDOUT 351 if r, w, err = os.Pipe(); err != nil { 352 return nil, err 353 } 354 fds = append(fds, r.Fd(), w.Fd()) 355 p.stdio.stdout, i.Stdout = w, r 356 // STDERR 357 if r, w, err = os.Pipe(); err != nil { 358 return nil, err 359 } 360 fds = append(fds, r.Fd(), w.Fd()) 361 p.stdio.stderr, i.Stderr = w, r 362 // change ownership of the pipes in case we are in a user namespace 363 for _, fd := range fds { 364 if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil { 365 return nil, err 366 } 367 } 368 return i, nil 369 } 370 func (p *process) Close() error { 371 return p.stdio.Close() 372 } 373 374 type stdio struct { 375 stdin *os.File 376 stdout *os.File 377 stderr *os.File 378 } 379 380 func (s *stdio) Close() error { 381 err := s.stdin.Close() 382 if oerr := s.stdout.Close(); err == nil { 383 err = oerr 384 } 385 if oerr := s.stderr.Close(); err == nil { 386 err = oerr 387 } 388 return err 389 }