github.com/dougm/docker@v1.5.0/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 sysinfo "github.com/docker/docker/pkg/system" 21 "github.com/docker/docker/pkg/term" 22 "github.com/docker/libcontainer" 23 "github.com/docker/libcontainer/apparmor" 24 "github.com/docker/libcontainer/cgroups/fs" 25 "github.com/docker/libcontainer/cgroups/systemd" 26 consolepkg "github.com/docker/libcontainer/console" 27 "github.com/docker/libcontainer/namespaces" 28 _ "github.com/docker/libcontainer/namespaces/nsenter" 29 "github.com/docker/libcontainer/system" 30 ) 31 32 const ( 33 DriverName = "native" 34 Version = "0.2" 35 ) 36 37 type activeContainer struct { 38 container *libcontainer.Config 39 cmd *exec.Cmd 40 } 41 42 type driver struct { 43 root string 44 initPath string 45 activeContainers map[string]*activeContainer 46 machineMemory int64 47 sync.Mutex 48 } 49 50 func NewDriver(root, initPath string) (*driver, error) { 51 meminfo, err := sysinfo.ReadMemInfo() 52 if err != nil { 53 return nil, err 54 } 55 56 if err := os.MkdirAll(root, 0700); err != nil { 57 return nil, err 58 } 59 // native driver root is at docker_root/execdriver/native. Put apparmor at docker_root 60 if err := apparmor.InstallDefaultProfile(); err != nil { 61 return nil, err 62 } 63 return &driver{ 64 root: root, 65 initPath: initPath, 66 activeContainers: make(map[string]*activeContainer), 67 machineMemory: meminfo.MemTotal, 68 }, nil 69 } 70 71 type execOutput struct { 72 exitCode int 73 err error 74 } 75 76 func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) { 77 // take the Command and populate the libcontainer.Config from it 78 container, err := d.createContainer(c) 79 if err != nil { 80 return execdriver.ExitStatus{ExitCode: -1}, err 81 } 82 83 var term execdriver.Terminal 84 85 if c.ProcessConfig.Tty { 86 term, err = NewTtyConsole(&c.ProcessConfig, pipes) 87 } else { 88 term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes) 89 } 90 if err != nil { 91 return execdriver.ExitStatus{ExitCode: -1}, err 92 } 93 c.ProcessConfig.Terminal = term 94 95 d.Lock() 96 d.activeContainers[c.ID] = &activeContainer{ 97 container: container, 98 cmd: &c.ProcessConfig.Cmd, 99 } 100 d.Unlock() 101 102 var ( 103 dataPath = filepath.Join(d.root, c.ID) 104 args = append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...) 105 ) 106 107 if err := d.createContainerRoot(c.ID); err != nil { 108 return execdriver.ExitStatus{ExitCode: -1}, err 109 } 110 defer d.cleanContainer(c.ID) 111 112 if err := d.writeContainerFile(container, c.ID); err != nil { 113 return execdriver.ExitStatus{ExitCode: -1}, err 114 } 115 116 execOutputChan := make(chan execOutput, 1) 117 waitForStart := make(chan struct{}) 118 119 go func() { 120 exitCode, err := namespaces.Exec(container, c.ProcessConfig.Stdin, c.ProcessConfig.Stdout, c.ProcessConfig.Stderr, c.ProcessConfig.Console, dataPath, args, func(container *libcontainer.Config, console, dataPath, init string, child *os.File, args []string) *exec.Cmd { 121 c.ProcessConfig.Path = d.initPath 122 c.ProcessConfig.Args = append([]string{ 123 DriverName, 124 "-console", console, 125 "-pipe", "3", 126 "-root", filepath.Join(d.root, c.ID), 127 "--", 128 }, args...) 129 130 // set this to nil so that when we set the clone flags anything else is reset 131 c.ProcessConfig.SysProcAttr = &syscall.SysProcAttr{ 132 Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)), 133 } 134 c.ProcessConfig.ExtraFiles = []*os.File{child} 135 136 c.ProcessConfig.Env = container.Env 137 c.ProcessConfig.Dir = container.RootFs 138 139 return &c.ProcessConfig.Cmd 140 }, func() { 141 close(waitForStart) 142 if startCallback != nil { 143 c.ContainerPid = c.ProcessConfig.Process.Pid 144 startCallback(&c.ProcessConfig, c.ContainerPid) 145 } 146 }) 147 execOutputChan <- execOutput{exitCode, err} 148 }() 149 150 select { 151 case execOutput := <-execOutputChan: 152 return execdriver.ExitStatus{ExitCode: execOutput.exitCode}, execOutput.err 153 case <-waitForStart: 154 break 155 } 156 157 oomKill := false 158 state, err := libcontainer.GetState(filepath.Join(d.root, c.ID)) 159 if err == nil { 160 oomKillNotification, err := libcontainer.NotifyOnOOM(state) 161 if err == nil { 162 _, oomKill = <-oomKillNotification 163 } else { 164 log.Warnf("WARNING: Your kernel does not support OOM notifications: %s", err) 165 } 166 } else { 167 log.Warnf("Failed to get container state, oom notify will not work: %s", err) 168 } 169 // wait for the container to exit. 170 execOutput := <-execOutputChan 171 172 return execdriver.ExitStatus{ExitCode: execOutput.exitCode, OOMKilled: oomKill}, execOutput.err 173 } 174 175 func (d *driver) Kill(p *execdriver.Command, sig int) error { 176 return syscall.Kill(p.ProcessConfig.Process.Pid, syscall.Signal(sig)) 177 } 178 179 func (d *driver) Pause(c *execdriver.Command) error { 180 active := d.activeContainers[c.ID] 181 if active == nil { 182 return fmt.Errorf("active container for %s does not exist", c.ID) 183 } 184 active.container.Cgroups.Freezer = "FROZEN" 185 if systemd.UseSystemd() { 186 return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer) 187 } 188 return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer) 189 } 190 191 func (d *driver) Unpause(c *execdriver.Command) error { 192 active := d.activeContainers[c.ID] 193 if active == nil { 194 return fmt.Errorf("active container for %s does not exist", c.ID) 195 } 196 active.container.Cgroups.Freezer = "THAWED" 197 if systemd.UseSystemd() { 198 return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer) 199 } 200 return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer) 201 } 202 203 func (d *driver) Terminate(p *execdriver.Command) error { 204 // lets check the start time for the process 205 state, err := libcontainer.GetState(filepath.Join(d.root, p.ID)) 206 if err != nil { 207 if !os.IsNotExist(err) { 208 return err 209 } 210 // TODO: Remove this part for version 1.2.0 211 // This is added only to ensure smooth upgrades from pre 1.1.0 to 1.1.0 212 data, err := ioutil.ReadFile(filepath.Join(d.root, p.ID, "start")) 213 if err != nil { 214 // if we don't have the data on disk then we can assume the process is gone 215 // because this is only removed after we know the process has stopped 216 if os.IsNotExist(err) { 217 return nil 218 } 219 return err 220 } 221 state = &libcontainer.State{InitStartTime: string(data)} 222 } 223 224 currentStartTime, err := system.GetProcessStartTime(p.ProcessConfig.Process.Pid) 225 if err != nil { 226 return err 227 } 228 229 if state.InitStartTime == currentStartTime { 230 err = syscall.Kill(p.ProcessConfig.Process.Pid, 9) 231 syscall.Wait4(p.ProcessConfig.Process.Pid, nil, 0, nil) 232 } 233 d.cleanContainer(p.ID) 234 235 return err 236 237 } 238 239 func (d *driver) Info(id string) execdriver.Info { 240 return &info{ 241 ID: id, 242 driver: d, 243 } 244 } 245 246 func (d *driver) Name() string { 247 return fmt.Sprintf("%s-%s", DriverName, Version) 248 } 249 250 func (d *driver) GetPidsForContainer(id string) ([]int, error) { 251 d.Lock() 252 active := d.activeContainers[id] 253 d.Unlock() 254 255 if active == nil { 256 return nil, fmt.Errorf("active container for %s does not exist", id) 257 } 258 c := active.container.Cgroups 259 260 if systemd.UseSystemd() { 261 return systemd.GetPids(c) 262 } 263 return fs.GetPids(c) 264 } 265 266 func (d *driver) writeContainerFile(container *libcontainer.Config, id string) error { 267 data, err := json.Marshal(container) 268 if err != nil { 269 return err 270 } 271 return ioutil.WriteFile(filepath.Join(d.root, id, "container.json"), data, 0655) 272 } 273 274 func (d *driver) cleanContainer(id string) error { 275 d.Lock() 276 delete(d.activeContainers, id) 277 d.Unlock() 278 return os.RemoveAll(filepath.Join(d.root, id, "container.json")) 279 } 280 281 func (d *driver) createContainerRoot(id string) error { 282 return os.MkdirAll(filepath.Join(d.root, id), 0655) 283 } 284 285 func (d *driver) Clean(id string) error { 286 return os.RemoveAll(filepath.Join(d.root, id)) 287 } 288 289 func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) { 290 c := d.activeContainers[id] 291 state, err := libcontainer.GetState(filepath.Join(d.root, id)) 292 if err != nil { 293 if os.IsNotExist(err) { 294 return nil, execdriver.ErrNotRunning 295 } 296 return nil, err 297 } 298 now := time.Now() 299 stats, err := libcontainer.GetStats(nil, state) 300 if err != nil { 301 return nil, err 302 } 303 memoryLimit := c.container.Cgroups.Memory 304 // if the container does not have any memory limit specified set the 305 // limit to the machines memory 306 if memoryLimit == 0 { 307 memoryLimit = d.machineMemory 308 } 309 return &execdriver.ResourceStats{ 310 Read: now, 311 ContainerStats: stats, 312 MemoryLimit: memoryLimit, 313 }, nil 314 } 315 316 func getEnv(key string, env []string) string { 317 for _, pair := range env { 318 parts := strings.Split(pair, "=") 319 if parts[0] == key { 320 return parts[1] 321 } 322 } 323 return "" 324 } 325 326 type TtyConsole struct { 327 MasterPty *os.File 328 } 329 330 func NewTtyConsole(processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes) (*TtyConsole, error) { 331 ptyMaster, console, err := consolepkg.CreateMasterAndConsole() 332 if err != nil { 333 return nil, err 334 } 335 336 tty := &TtyConsole{ 337 MasterPty: ptyMaster, 338 } 339 340 if err := tty.AttachPipes(&processConfig.Cmd, pipes); err != nil { 341 tty.Close() 342 return nil, err 343 } 344 345 processConfig.Console = console 346 347 return tty, nil 348 } 349 350 func (t *TtyConsole) Master() *os.File { 351 return t.MasterPty 352 } 353 354 func (t *TtyConsole) Resize(h, w int) error { 355 return term.SetWinsize(t.MasterPty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) 356 } 357 358 func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) error { 359 go func() { 360 if wb, ok := pipes.Stdout.(interface { 361 CloseWriters() error 362 }); ok { 363 defer wb.CloseWriters() 364 } 365 366 io.Copy(pipes.Stdout, t.MasterPty) 367 }() 368 369 if pipes.Stdin != nil { 370 go func() { 371 io.Copy(t.MasterPty, pipes.Stdin) 372 373 pipes.Stdin.Close() 374 }() 375 } 376 377 return nil 378 } 379 380 func (t *TtyConsole) Close() error { 381 return t.MasterPty.Close() 382 }