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