github.com/scorpionis/docker@v1.6.0-rc7/daemon/execdriver/native/driver.go (about) 1 // +build linux,cgo 2 3 package native 4 5 import ( 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "strings" 14 "sync" 15 "syscall" 16 "time" 17 18 log "github.com/Sirupsen/logrus" 19 "github.com/docker/docker/daemon/execdriver" 20 "github.com/docker/docker/pkg/reexec" 21 sysinfo "github.com/docker/docker/pkg/system" 22 "github.com/docker/docker/pkg/term" 23 "github.com/docker/libcontainer" 24 "github.com/docker/libcontainer/apparmor" 25 "github.com/docker/libcontainer/cgroups/systemd" 26 "github.com/docker/libcontainer/configs" 27 "github.com/docker/libcontainer/system" 28 "github.com/docker/libcontainer/utils" 29 ) 30 31 const ( 32 DriverName = "native" 33 Version = "0.2" 34 ) 35 36 type driver struct { 37 root string 38 initPath string 39 activeContainers map[string]libcontainer.Container 40 machineMemory int64 41 factory libcontainer.Factory 42 sync.Mutex 43 } 44 45 func NewDriver(root, initPath string) (*driver, error) { 46 meminfo, err := sysinfo.ReadMemInfo() 47 if err != nil { 48 return nil, err 49 } 50 51 if err := os.MkdirAll(root, 0700); err != nil { 52 return nil, err 53 } 54 // native driver root is at docker_root/execdriver/native. Put apparmor at docker_root 55 if err := apparmor.InstallDefaultProfile(); err != nil { 56 return nil, err 57 } 58 cgm := libcontainer.Cgroupfs 59 if systemd.UseSystemd() { 60 cgm = libcontainer.SystemdCgroups 61 } 62 63 f, err := libcontainer.New( 64 root, 65 cgm, 66 libcontainer.InitPath(reexec.Self(), DriverName), 67 ) 68 if err != nil { 69 return nil, err 70 } 71 72 return &driver{ 73 root: root, 74 initPath: initPath, 75 activeContainers: make(map[string]libcontainer.Container), 76 machineMemory: meminfo.MemTotal, 77 factory: f, 78 }, nil 79 } 80 81 type execOutput struct { 82 exitCode int 83 err error 84 } 85 86 func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) { 87 // take the Command and populate the libcontainer.Config from it 88 container, err := d.createContainer(c) 89 if err != nil { 90 return execdriver.ExitStatus{ExitCode: -1}, err 91 } 92 93 var term execdriver.Terminal 94 95 p := &libcontainer.Process{ 96 Args: append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...), 97 Env: c.ProcessConfig.Env, 98 Cwd: c.WorkingDir, 99 User: c.ProcessConfig.User, 100 } 101 102 if c.ProcessConfig.Tty { 103 rootuid, err := container.HostUID() 104 if err != nil { 105 return execdriver.ExitStatus{ExitCode: -1}, err 106 } 107 cons, err := p.NewConsole(rootuid) 108 if err != nil { 109 return execdriver.ExitStatus{ExitCode: -1}, err 110 } 111 term, err = NewTtyConsole(cons, pipes, rootuid) 112 } else { 113 p.Stdout = pipes.Stdout 114 p.Stderr = pipes.Stderr 115 r, w, err := os.Pipe() 116 if err != nil { 117 return execdriver.ExitStatus{ExitCode: -1}, err 118 } 119 if pipes.Stdin != nil { 120 go func() { 121 io.Copy(w, pipes.Stdin) 122 w.Close() 123 }() 124 p.Stdin = r 125 } 126 term = &execdriver.StdConsole{} 127 } 128 if err != nil { 129 return execdriver.ExitStatus{ExitCode: -1}, err 130 } 131 c.ProcessConfig.Terminal = term 132 133 cont, err := d.factory.Create(c.ID, container) 134 if err != nil { 135 return execdriver.ExitStatus{ExitCode: -1}, err 136 } 137 d.Lock() 138 d.activeContainers[c.ID] = cont 139 d.Unlock() 140 defer func() { 141 cont.Destroy() 142 d.cleanContainer(c.ID) 143 }() 144 145 if err := cont.Start(p); err != nil { 146 return execdriver.ExitStatus{ExitCode: -1}, err 147 } 148 149 if startCallback != nil { 150 pid, err := p.Pid() 151 if err != nil { 152 p.Signal(os.Kill) 153 p.Wait() 154 return execdriver.ExitStatus{ExitCode: -1}, err 155 } 156 startCallback(&c.ProcessConfig, pid) 157 } 158 159 oom := notifyOnOOM(cont) 160 waitF := p.Wait 161 if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) { 162 // we need such hack for tracking processes with inerited fds, 163 // because cmd.Wait() waiting for all streams to be copied 164 waitF = waitInPIDHost(p, cont) 165 } 166 ps, err := waitF() 167 if err != nil { 168 execErr, ok := err.(*exec.ExitError) 169 if !ok { 170 return execdriver.ExitStatus{ExitCode: -1}, err 171 } 172 ps = execErr.ProcessState 173 } 174 cont.Destroy() 175 _, oomKill := <-oom 176 return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil 177 } 178 179 // notifyOnOOM returns a channel that signals if the container received an OOM notification 180 // for any process. If it is unable to subscribe to OOM notifications then a closed 181 // channel is returned as it will be non-blocking and return the correct result when read. 182 func notifyOnOOM(container libcontainer.Container) <-chan struct{} { 183 oom, err := container.NotifyOOM() 184 if err != nil { 185 log.Warnf("Your kernel does not support OOM notifications: %s", err) 186 c := make(chan struct{}) 187 close(c) 188 return c 189 } 190 return oom 191 } 192 193 func killCgroupProcs(c libcontainer.Container) { 194 var procs []*os.Process 195 if err := c.Pause(); err != nil { 196 log.Warn(err) 197 } 198 pids, err := c.Processes() 199 if err != nil { 200 // don't care about childs if we can't get them, this is mostly because cgroup already deleted 201 log.Warnf("Failed to get processes from container %s: %v", c.ID(), err) 202 } 203 for _, pid := range pids { 204 if p, err := os.FindProcess(pid); err == nil { 205 procs = append(procs, p) 206 if err := p.Kill(); err != nil { 207 log.Warn(err) 208 } 209 } 210 } 211 if err := c.Resume(); err != nil { 212 log.Warn(err) 213 } 214 for _, p := range procs { 215 if _, err := p.Wait(); err != nil { 216 log.Warn(err) 217 } 218 } 219 } 220 221 func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) { 222 return func() (*os.ProcessState, error) { 223 pid, err := p.Pid() 224 if err != nil { 225 return nil, err 226 } 227 228 process, err := os.FindProcess(pid) 229 s, err := process.Wait() 230 if err != nil { 231 execErr, ok := err.(*exec.ExitError) 232 if !ok { 233 return s, err 234 } 235 s = execErr.ProcessState 236 } 237 killCgroupProcs(c) 238 p.Wait() 239 return s, err 240 } 241 } 242 243 func (d *driver) Kill(c *execdriver.Command, sig int) error { 244 active := d.activeContainers[c.ID] 245 if active == nil { 246 return fmt.Errorf("active container for %s does not exist", c.ID) 247 } 248 state, err := active.State() 249 if err != nil { 250 return err 251 } 252 return syscall.Kill(state.InitProcessPid, syscall.Signal(sig)) 253 } 254 255 func (d *driver) Pause(c *execdriver.Command) error { 256 active := d.activeContainers[c.ID] 257 if active == nil { 258 return fmt.Errorf("active container for %s does not exist", c.ID) 259 } 260 return active.Pause() 261 } 262 263 func (d *driver) Unpause(c *execdriver.Command) error { 264 active := d.activeContainers[c.ID] 265 if active == nil { 266 return fmt.Errorf("active container for %s does not exist", c.ID) 267 } 268 return active.Resume() 269 } 270 271 func (d *driver) Terminate(c *execdriver.Command) error { 272 defer d.cleanContainer(c.ID) 273 // lets check the start time for the process 274 active := d.activeContainers[c.ID] 275 if active == nil { 276 return fmt.Errorf("active container for %s does not exist", c.ID) 277 } 278 state, err := active.State() 279 if err != nil { 280 return err 281 } 282 pid := state.InitProcessPid 283 284 currentStartTime, err := system.GetProcessStartTime(pid) 285 if err != nil { 286 return err 287 } 288 289 if state.InitProcessStartTime == currentStartTime { 290 err = syscall.Kill(pid, 9) 291 syscall.Wait4(pid, nil, 0, nil) 292 } 293 294 return err 295 296 } 297 298 func (d *driver) Info(id string) execdriver.Info { 299 return &info{ 300 ID: id, 301 driver: d, 302 } 303 } 304 305 func (d *driver) Name() string { 306 return fmt.Sprintf("%s-%s", DriverName, Version) 307 } 308 309 func (d *driver) GetPidsForContainer(id string) ([]int, error) { 310 d.Lock() 311 active := d.activeContainers[id] 312 d.Unlock() 313 314 if active == nil { 315 return nil, fmt.Errorf("active container for %s does not exist", id) 316 } 317 return active.Processes() 318 } 319 320 func (d *driver) writeContainerFile(container *configs.Config, id string) error { 321 data, err := json.Marshal(container) 322 if err != nil { 323 return err 324 } 325 return ioutil.WriteFile(filepath.Join(d.root, id, "container.json"), data, 0655) 326 } 327 328 func (d *driver) cleanContainer(id string) error { 329 d.Lock() 330 delete(d.activeContainers, id) 331 d.Unlock() 332 return os.RemoveAll(filepath.Join(d.root, id)) 333 } 334 335 func (d *driver) createContainerRoot(id string) error { 336 return os.MkdirAll(filepath.Join(d.root, id), 0655) 337 } 338 339 func (d *driver) Clean(id string) error { 340 return os.RemoveAll(filepath.Join(d.root, id)) 341 } 342 343 func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) { 344 c := d.activeContainers[id] 345 if c == nil { 346 return nil, execdriver.ErrNotRunning 347 } 348 now := time.Now() 349 stats, err := c.Stats() 350 if err != nil { 351 return nil, err 352 } 353 memoryLimit := c.Config().Cgroups.Memory 354 // if the container does not have any memory limit specified set the 355 // limit to the machines memory 356 if memoryLimit == 0 { 357 memoryLimit = d.machineMemory 358 } 359 return &execdriver.ResourceStats{ 360 Stats: stats, 361 Read: now, 362 MemoryLimit: memoryLimit, 363 }, nil 364 } 365 366 func getEnv(key string, env []string) string { 367 for _, pair := range env { 368 parts := strings.Split(pair, "=") 369 if parts[0] == key { 370 return parts[1] 371 } 372 } 373 return "" 374 } 375 376 type TtyConsole struct { 377 console libcontainer.Console 378 } 379 380 func NewTtyConsole(console libcontainer.Console, pipes *execdriver.Pipes, rootuid int) (*TtyConsole, error) { 381 tty := &TtyConsole{ 382 console: console, 383 } 384 385 if err := tty.AttachPipes(pipes); err != nil { 386 tty.Close() 387 return nil, err 388 } 389 390 return tty, nil 391 } 392 393 func (t *TtyConsole) Master() libcontainer.Console { 394 return t.console 395 } 396 397 func (t *TtyConsole) Resize(h, w int) error { 398 return term.SetWinsize(t.console.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) 399 } 400 401 func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes) error { 402 go func() { 403 if wb, ok := pipes.Stdout.(interface { 404 CloseWriters() error 405 }); ok { 406 defer wb.CloseWriters() 407 } 408 409 io.Copy(pipes.Stdout, t.console) 410 }() 411 412 if pipes.Stdin != nil { 413 go func() { 414 io.Copy(t.console, pipes.Stdin) 415 416 pipes.Stdin.Close() 417 }() 418 } 419 420 return nil 421 } 422 423 func (t *TtyConsole) Close() error { 424 return t.console.Close() 425 }