github.com/alexandrev/docker@v1.9.0/daemon/execdriver/lxc/driver.go (about) 1 // +build linux 2 3 package lxc 4 5 import ( 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "os/exec" 13 "path" 14 "path/filepath" 15 "runtime" 16 "strconv" 17 "strings" 18 "sync" 19 "syscall" 20 "time" 21 22 "github.com/Sirupsen/logrus" 23 "github.com/docker/docker/daemon/execdriver" 24 "github.com/docker/docker/pkg/stringutils" 25 sysinfo "github.com/docker/docker/pkg/system" 26 "github.com/docker/docker/pkg/term" 27 "github.com/docker/docker/pkg/version" 28 "github.com/kr/pty" 29 "github.com/opencontainers/runc/libcontainer" 30 "github.com/opencontainers/runc/libcontainer/cgroups" 31 "github.com/opencontainers/runc/libcontainer/configs" 32 "github.com/opencontainers/runc/libcontainer/system" 33 "github.com/opencontainers/runc/libcontainer/user" 34 "github.com/vishvananda/netns" 35 ) 36 37 // DriverName for lxc driver 38 const DriverName = "lxc" 39 40 // ErrExec defines unsupported error message 41 var ErrExec = errors.New("Unsupported: Exec is not supported by the lxc driver") 42 43 // Driver contains all information for lxc driver, 44 // it implements execdriver.Driver 45 type Driver struct { 46 root string // root path for the driver to use 47 libPath string 48 initPath string 49 apparmor bool 50 sharedRoot bool 51 activeContainers map[string]*activeContainer 52 machineMemory int64 53 sync.Mutex 54 } 55 56 type activeContainer struct { 57 container *configs.Config 58 cmd *exec.Cmd 59 } 60 61 // NewDriver returns a new lxc driver, called from NewDriver of execdriver 62 func NewDriver(root, libPath, initPath string, apparmor bool) (*Driver, error) { 63 if err := os.MkdirAll(root, 0700); err != nil { 64 return nil, err 65 } 66 // setup unconfined symlink 67 if err := linkLxcStart(root); err != nil { 68 return nil, err 69 } 70 meminfo, err := sysinfo.ReadMemInfo() 71 if err != nil { 72 return nil, err 73 } 74 return &Driver{ 75 apparmor: apparmor, 76 root: root, 77 libPath: libPath, 78 initPath: initPath, 79 sharedRoot: rootIsShared(), 80 activeContainers: make(map[string]*activeContainer), 81 machineMemory: meminfo.MemTotal, 82 }, nil 83 } 84 85 // Name implements the exec driver Driver interface. 86 func (d *Driver) Name() string { 87 version := d.version() 88 return fmt.Sprintf("%s-%s", DriverName, version) 89 } 90 91 func setupNetNs(nsPath string) (*os.Process, error) { 92 runtime.LockOSThread() 93 defer runtime.UnlockOSThread() 94 95 origns, err := netns.Get() 96 if err != nil { 97 return nil, err 98 } 99 defer origns.Close() 100 101 f, err := os.OpenFile(nsPath, os.O_RDONLY, 0) 102 if err != nil { 103 return nil, fmt.Errorf("failed to get network namespace %q: %v", nsPath, err) 104 } 105 defer f.Close() 106 107 nsFD := f.Fd() 108 if err := netns.Set(netns.NsHandle(nsFD)); err != nil { 109 return nil, fmt.Errorf("failed to set network namespace %q: %v", nsPath, err) 110 } 111 defer netns.Set(origns) 112 113 cmd := exec.Command("/bin/sh", "-c", "while true; do sleep 1; done") 114 if err := cmd.Start(); err != nil { 115 return nil, fmt.Errorf("failed to start netns process: %v", err) 116 } 117 118 return cmd.Process, nil 119 } 120 121 func killNetNsProc(proc *os.Process) { 122 proc.Kill() 123 proc.Wait() 124 } 125 126 // Run implements the exec driver Driver interface, 127 // it calls 'exec.Cmd' to launch lxc commands to run a container. 128 func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) { 129 var ( 130 term execdriver.Terminal 131 err error 132 dataPath = d.containerDir(c.ID) 133 ) 134 135 if c.Network == nil || (c.Network.NamespacePath == "" && c.Network.ContainerID == "") { 136 return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("empty namespace path for non-container network") 137 } 138 139 container, err := d.createContainer(c) 140 if err != nil { 141 return execdriver.ExitStatus{ExitCode: -1}, err 142 } 143 144 if c.ProcessConfig.Tty { 145 term, err = NewTtyConsole(&c.ProcessConfig, pipes) 146 } else { 147 term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes) 148 } 149 if err != nil { 150 return execdriver.ExitStatus{ExitCode: -1}, err 151 } 152 c.ProcessConfig.Terminal = term 153 154 d.Lock() 155 d.activeContainers[c.ID] = &activeContainer{ 156 container: container, 157 cmd: &c.ProcessConfig.Cmd, 158 } 159 d.Unlock() 160 161 c.Mounts = append(c.Mounts, execdriver.Mount{ 162 Source: d.initPath, 163 Destination: c.InitPath, 164 Writable: false, 165 Private: true, 166 }) 167 168 if err := d.generateEnvConfig(c); err != nil { 169 return execdriver.ExitStatus{ExitCode: -1}, err 170 } 171 configPath, err := d.generateLXCConfig(c) 172 if err != nil { 173 return execdriver.ExitStatus{ExitCode: -1}, err 174 } 175 params := []string{ 176 "lxc-start", 177 "-n", c.ID, 178 "-f", configPath, 179 "-q", 180 } 181 182 // From lxc>=1.1 the default behavior is to daemonize containers after start 183 lxcVersion := version.Version(d.version()) 184 if lxcVersion.GreaterThanOrEqualTo(version.Version("1.1")) { 185 params = append(params, "-F") 186 } 187 188 proc := &os.Process{} 189 if c.Network.ContainerID != "" { 190 params = append(params, 191 "--share-net", c.Network.ContainerID, 192 ) 193 } else { 194 proc, err = setupNetNs(c.Network.NamespacePath) 195 if err != nil { 196 return execdriver.ExitStatus{ExitCode: -1}, err 197 } 198 199 pidStr := fmt.Sprintf("%d", proc.Pid) 200 params = append(params, 201 "--share-net", pidStr) 202 } 203 if c.Ipc != nil { 204 if c.Ipc.ContainerID != "" { 205 params = append(params, 206 "--share-ipc", c.Ipc.ContainerID, 207 ) 208 } else if c.Ipc.HostIpc { 209 params = append(params, 210 "--share-ipc", "1", 211 ) 212 } 213 } 214 215 params = append(params, 216 "--", 217 c.InitPath, 218 ) 219 220 if c.ProcessConfig.User != "" { 221 params = append(params, "-u", c.ProcessConfig.User) 222 } 223 224 if c.ProcessConfig.Privileged { 225 if d.apparmor { 226 params[0] = path.Join(d.root, "lxc-start-unconfined") 227 228 } 229 params = append(params, "-privileged") 230 } 231 232 if c.WorkingDir != "" { 233 params = append(params, "-w", c.WorkingDir) 234 } 235 236 params = append(params, "--", c.ProcessConfig.Entrypoint) 237 params = append(params, c.ProcessConfig.Arguments...) 238 239 if d.sharedRoot { 240 // lxc-start really needs / to be non-shared, or all kinds of stuff break 241 // when lxc-start unmount things and those unmounts propagate to the main 242 // mount namespace. 243 // What we really want is to clone into a new namespace and then 244 // mount / MS_REC|MS_SLAVE, but since we can't really clone or fork 245 // without exec in go we have to do this horrible shell hack... 246 shellString := 247 "mount --make-rslave /; exec " + 248 stringutils.ShellQuoteArguments(params) 249 250 params = []string{ 251 "unshare", "-m", "--", "/bin/sh", "-c", shellString, 252 } 253 } 254 logrus.Debugf("lxc params %s", params) 255 var ( 256 name = params[0] 257 arg = params[1:] 258 ) 259 aname, err := exec.LookPath(name) 260 if err != nil { 261 aname = name 262 } 263 c.ProcessConfig.Path = aname 264 c.ProcessConfig.Args = append([]string{name}, arg...) 265 266 if err := createDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil { 267 killNetNsProc(proc) 268 return execdriver.ExitStatus{ExitCode: -1}, err 269 } 270 271 if err := c.ProcessConfig.Start(); err != nil { 272 killNetNsProc(proc) 273 return execdriver.ExitStatus{ExitCode: -1}, err 274 } 275 276 var ( 277 waitErr error 278 waitLock = make(chan struct{}) 279 ) 280 281 go func() { 282 if err := c.ProcessConfig.Wait(); err != nil { 283 if _, ok := err.(*exec.ExitError); !ok { // Do not propagate the error if it's simply a status code != 0 284 waitErr = err 285 } 286 } 287 close(waitLock) 288 }() 289 290 terminate := func(terr error) (execdriver.ExitStatus, error) { 291 if c.ProcessConfig.Process != nil { 292 c.ProcessConfig.Process.Kill() 293 c.ProcessConfig.Wait() 294 } 295 return execdriver.ExitStatus{ExitCode: -1}, terr 296 } 297 // Poll lxc for RUNNING status 298 pid, err := d.waitForStart(c, waitLock) 299 if err != nil { 300 killNetNsProc(proc) 301 return terminate(err) 302 } 303 killNetNsProc(proc) 304 305 cgroupPaths, err := cgroupPaths(c.ID) 306 if err != nil { 307 return terminate(err) 308 } 309 310 state := &libcontainer.State{ 311 InitProcessPid: pid, 312 CgroupPaths: cgroupPaths, 313 } 314 315 f, err := os.Create(filepath.Join(dataPath, "state.json")) 316 if err != nil { 317 return terminate(err) 318 } 319 defer f.Close() 320 321 if err := json.NewEncoder(f).Encode(state); err != nil { 322 return terminate(err) 323 } 324 325 c.ContainerPid = pid 326 327 if hooks.Start != nil { 328 logrus.Debugf("Invoking startCallback") 329 chOOM := make(chan struct{}) 330 close(chOOM) 331 hooks.Start(&c.ProcessConfig, pid, chOOM) 332 } 333 334 oomKillNotification := notifyChannelOOM(cgroupPaths) 335 336 <-waitLock 337 exitCode := getExitCode(c) 338 339 _, oomKill := <-oomKillNotification 340 logrus.Debugf("oomKill error: %v, waitErr: %v", oomKill, waitErr) 341 342 // check oom error 343 if oomKill { 344 exitCode = 137 345 } 346 347 return execdriver.ExitStatus{ExitCode: exitCode, OOMKilled: oomKill}, waitErr 348 } 349 350 func notifyChannelOOM(paths map[string]string) <-chan struct{} { 351 oom, err := notifyOnOOM(paths) 352 if err != nil { 353 logrus.Warnf("Your kernel does not support OOM notifications: %s", err) 354 c := make(chan struct{}) 355 close(c) 356 return c 357 } 358 return oom 359 } 360 361 // copy from libcontainer 362 func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) { 363 dir := paths["memory"] 364 if dir == "" { 365 return nil, fmt.Errorf("There is no path for %q in state", "memory") 366 } 367 oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control")) 368 if err != nil { 369 return nil, err 370 } 371 fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0) 372 if syserr != 0 { 373 oomControl.Close() 374 return nil, syserr 375 } 376 377 eventfd := os.NewFile(fd, "eventfd") 378 379 eventControlPath := filepath.Join(dir, "cgroup.event_control") 380 data := fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd()) 381 if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil { 382 eventfd.Close() 383 oomControl.Close() 384 return nil, err 385 } 386 ch := make(chan struct{}) 387 go func() { 388 defer func() { 389 close(ch) 390 eventfd.Close() 391 oomControl.Close() 392 }() 393 buf := make([]byte, 8) 394 for { 395 if _, err := eventfd.Read(buf); err != nil { 396 logrus.Warn(err) 397 return 398 } 399 // When a cgroup is destroyed, an event is sent to eventfd. 400 // So if the control path is gone, return instead of notifying. 401 if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) { 402 logrus.Warn(err) 403 return 404 } 405 ch <- struct{}{} 406 } 407 }() 408 return ch, nil 409 } 410 411 // createContainer populates and configures the container type with the 412 // data provided by the execdriver.Command 413 func (d *Driver) createContainer(c *execdriver.Command) (*configs.Config, error) { 414 container := execdriver.InitContainer(c) 415 if err := execdriver.SetupCgroups(container, c); err != nil { 416 return nil, err 417 } 418 return container, nil 419 } 420 421 // Return an map of susbystem -> absolute container cgroup path 422 func cgroupPaths(containerID string) (map[string]string, error) { 423 subsystems, err := cgroups.GetAllSubsystems() 424 if err != nil { 425 return nil, err 426 } 427 logrus.Debugf("subsystems: %s", subsystems) 428 paths := make(map[string]string) 429 for _, subsystem := range subsystems { 430 cgroupRoot, cgroupDir, err := findCgroupRootAndDir(subsystem) 431 logrus.Debugf("cgroup path %s %s", cgroupRoot, cgroupDir) 432 if err != nil { 433 //unsupported subystem 434 continue 435 } 436 // if we are running dind 437 dockerPathIdx := strings.LastIndex(cgroupDir, "docker") 438 if dockerPathIdx != -1 { 439 cgroupDir = cgroupDir[:dockerPathIdx-1] 440 } 441 path := filepath.Join(cgroupRoot, cgroupDir, "lxc", containerID) 442 paths[subsystem] = path 443 } 444 445 return paths, nil 446 } 447 448 // this is copy from old libcontainer nodes.go 449 func createDeviceNodes(rootfs string, nodesToCreate []*configs.Device) error { 450 oldMask := syscall.Umask(0000) 451 defer syscall.Umask(oldMask) 452 453 for _, node := range nodesToCreate { 454 if err := createDeviceNode(rootfs, node); err != nil { 455 return err 456 } 457 } 458 return nil 459 } 460 461 // Creates the device node in the rootfs of the container. 462 func createDeviceNode(rootfs string, node *configs.Device) error { 463 var ( 464 dest = filepath.Join(rootfs, node.Path) 465 parent = filepath.Dir(dest) 466 ) 467 468 if err := os.MkdirAll(parent, 0755); err != nil { 469 return err 470 } 471 472 fileMode := node.FileMode 473 switch node.Type { 474 case 'c': 475 fileMode |= syscall.S_IFCHR 476 case 'b': 477 fileMode |= syscall.S_IFBLK 478 default: 479 return fmt.Errorf("%c is not a valid device type for device %s", node.Type, node.Path) 480 } 481 482 if err := syscall.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil && !os.IsExist(err) { 483 return fmt.Errorf("mknod %s %s", node.Path, err) 484 } 485 486 if err := syscall.Chown(dest, int(node.Uid), int(node.Gid)); err != nil { 487 return fmt.Errorf("chown %s to %d:%d", node.Path, node.Uid, node.Gid) 488 } 489 490 return nil 491 } 492 493 // setupUser changes the groups, gid, and uid for the user inside the container 494 // copy from libcontainer, cause not it's private 495 func setupUser(userSpec string) error { 496 // Set up defaults. 497 defaultExecUser := user.ExecUser{ 498 Uid: syscall.Getuid(), 499 Gid: syscall.Getgid(), 500 Home: "/", 501 } 502 passwdPath, err := user.GetPasswdPath() 503 if err != nil { 504 return err 505 } 506 groupPath, err := user.GetGroupPath() 507 if err != nil { 508 return err 509 } 510 execUser, err := user.GetExecUserPath(userSpec, &defaultExecUser, passwdPath, groupPath) 511 if err != nil { 512 return err 513 } 514 if err := syscall.Setgroups(execUser.Sgids); err != nil { 515 return err 516 } 517 if err := system.Setgid(execUser.Gid); err != nil { 518 return err 519 } 520 if err := system.Setuid(execUser.Uid); err != nil { 521 return err 522 } 523 // if we didn't get HOME already, set it based on the user's HOME 524 if envHome := os.Getenv("HOME"); envHome == "" { 525 if err := os.Setenv("HOME", execUser.Home); err != nil { 526 return err 527 } 528 } 529 return nil 530 } 531 532 // getExitCode returns the exit code of the process. 533 // If the process has not exited -1 will be returned. 534 func getExitCode(c *execdriver.Command) int { 535 if c.ProcessConfig.ProcessState == nil { 536 return -1 537 } 538 return c.ProcessConfig.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() 539 } 540 541 // Kill implements the exec driver Driver interface. 542 func (d *Driver) Kill(c *execdriver.Command, sig int) error { 543 if sig == 9 || c.ProcessConfig.Process == nil { 544 return killLxc(c.ID, sig) 545 } 546 547 return c.ProcessConfig.Process.Signal(syscall.Signal(sig)) 548 } 549 550 // Pause implements the exec driver Driver interface, 551 // it executes lxc-freeze to pause a container. 552 func (d *Driver) Pause(c *execdriver.Command) error { 553 _, err := exec.LookPath("lxc-freeze") 554 if err == nil { 555 output, errExec := exec.Command("lxc-freeze", "-n", c.ID).CombinedOutput() 556 if errExec != nil { 557 return fmt.Errorf("Err: %s Output: %s", errExec, output) 558 } 559 } 560 561 return err 562 } 563 564 // Unpause implements the exec driver Driver interface, 565 // it executes lxc-unfreeze to unpause a container. 566 func (d *Driver) Unpause(c *execdriver.Command) error { 567 _, err := exec.LookPath("lxc-unfreeze") 568 if err == nil { 569 output, errExec := exec.Command("lxc-unfreeze", "-n", c.ID).CombinedOutput() 570 if errExec != nil { 571 return fmt.Errorf("Err: %s Output: %s", errExec, output) 572 } 573 } 574 575 return err 576 } 577 578 // Terminate implements the exec driver Driver interface. 579 func (d *Driver) Terminate(c *execdriver.Command) error { 580 return killLxc(c.ID, 9) 581 } 582 583 func (d *Driver) version() string { 584 var ( 585 version string 586 output []byte 587 err error 588 ) 589 if _, errPath := exec.LookPath("lxc-version"); errPath == nil { 590 output, err = exec.Command("lxc-version").CombinedOutput() 591 } else { 592 output, err = exec.Command("lxc-start", "--version").CombinedOutput() 593 } 594 if err == nil { 595 version = strings.TrimSpace(string(output)) 596 if parts := strings.SplitN(version, ":", 2); len(parts) == 2 { 597 version = strings.TrimSpace(parts[1]) 598 } 599 } 600 return version 601 } 602 603 func killLxc(id string, sig int) error { 604 var ( 605 err error 606 output []byte 607 ) 608 _, err = exec.LookPath("lxc-kill") 609 if err == nil { 610 output, err = exec.Command("lxc-kill", "-n", id, strconv.Itoa(sig)).CombinedOutput() 611 } else { 612 // lxc-stop does not take arbitrary signals like lxc-kill does 613 output, err = exec.Command("lxc-stop", "-k", "-n", id).CombinedOutput() 614 } 615 if err != nil { 616 return fmt.Errorf("Err: %s Output: %s", err, output) 617 } 618 return nil 619 } 620 621 // wait for the process to start and return the pid for the process 622 func (d *Driver) waitForStart(c *execdriver.Command, waitLock chan struct{}) (int, error) { 623 var ( 624 err error 625 output []byte 626 ) 627 // We wait for the container to be fully running. 628 // Timeout after 5 seconds. In case of broken pipe, just retry. 629 // Note: The container can run and finish correctly before 630 // the end of this loop 631 for now := time.Now(); time.Since(now) < 5*time.Second; { 632 select { 633 case <-waitLock: 634 // If the process dies while waiting for it, just return 635 return -1, nil 636 default: 637 } 638 639 output, err = d.getInfo(c.ID) 640 if err == nil { 641 info, err := parseLxcInfo(string(output)) 642 if err != nil { 643 return -1, err 644 } 645 if info.Running { 646 return info.Pid, nil 647 } 648 } 649 time.Sleep(50 * time.Millisecond) 650 } 651 return -1, execdriver.ErrNotRunning 652 } 653 654 func (d *Driver) getInfo(id string) ([]byte, error) { 655 return exec.Command("lxc-info", "-n", id).CombinedOutput() 656 } 657 658 type info struct { 659 ID string 660 driver *Driver 661 } 662 663 func (i *info) IsRunning() bool { 664 var running bool 665 666 output, err := i.driver.getInfo(i.ID) 667 if err != nil { 668 logrus.Errorf("Error getting info for lxc container %s: %s (%s)", i.ID, err, output) 669 return false 670 } 671 if strings.Contains(string(output), "RUNNING") { 672 running = true 673 } 674 return running 675 } 676 677 // Info implements the exec driver Driver interface. 678 func (d *Driver) Info(id string) execdriver.Info { 679 return &info{ 680 ID: id, 681 driver: d, 682 } 683 } 684 685 func findCgroupRootAndDir(subsystem string) (string, string, error) { 686 cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem) 687 if err != nil { 688 return "", "", err 689 } 690 691 cgroupDir, err := cgroups.GetThisCgroupDir(subsystem) 692 if err != nil { 693 return "", "", err 694 } 695 return cgroupRoot, cgroupDir, nil 696 } 697 698 // GetPidsForContainer implements the exec driver Driver interface. 699 func (d *Driver) GetPidsForContainer(id string) ([]int, error) { 700 pids := []int{} 701 702 // cpu is chosen because it is the only non optional subsystem in cgroups 703 subsystem := "cpu" 704 cgroupRoot, cgroupDir, err := findCgroupRootAndDir(subsystem) 705 if err != nil { 706 return pids, err 707 } 708 709 filename := filepath.Join(cgroupRoot, cgroupDir, id, "tasks") 710 if _, err := os.Stat(filename); os.IsNotExist(err) { 711 // With more recent lxc versions use, cgroup will be in lxc/ 712 filename = filepath.Join(cgroupRoot, cgroupDir, "lxc", id, "tasks") 713 } 714 715 output, err := ioutil.ReadFile(filename) 716 if err != nil { 717 return pids, err 718 } 719 for _, p := range strings.Split(string(output), "\n") { 720 if len(p) == 0 { 721 continue 722 } 723 pid, err := strconv.Atoi(p) 724 if err != nil { 725 return pids, fmt.Errorf("Invalid pid '%s': %s", p, err) 726 } 727 pids = append(pids, pid) 728 } 729 return pids, nil 730 } 731 732 func linkLxcStart(root string) error { 733 sourcePath, err := exec.LookPath("lxc-start") 734 if err != nil { 735 return err 736 } 737 targetPath := path.Join(root, "lxc-start-unconfined") 738 739 if _, err := os.Lstat(targetPath); err != nil && !os.IsNotExist(err) { 740 return err 741 } else if err == nil { 742 if err := os.Remove(targetPath); err != nil { 743 return err 744 } 745 } 746 return os.Symlink(sourcePath, targetPath) 747 } 748 749 // TODO: This can be moved to the mountinfo reader in the mount pkg 750 func rootIsShared() bool { 751 if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { 752 for _, line := range strings.Split(string(data), "\n") { 753 cols := strings.Split(line, " ") 754 if len(cols) >= 6 && cols[4] == "/" { 755 return strings.HasPrefix(cols[6], "shared") 756 } 757 } 758 } 759 760 // No idea, probably safe to assume so 761 return true 762 } 763 764 func (d *Driver) containerDir(containerID string) string { 765 return path.Join(d.libPath, "containers", containerID) 766 } 767 768 func (d *Driver) generateLXCConfig(c *execdriver.Command) (string, error) { 769 root := path.Join(d.containerDir(c.ID), "config.lxc") 770 771 fo, err := os.Create(root) 772 if err != nil { 773 return "", err 774 } 775 defer fo.Close() 776 777 if err := lxcTemplateCompiled.Execute(fo, struct { 778 *execdriver.Command 779 AppArmor bool 780 }{ 781 Command: c, 782 AppArmor: d.apparmor, 783 }); err != nil { 784 return "", err 785 } 786 787 return root, nil 788 } 789 790 func (d *Driver) generateEnvConfig(c *execdriver.Command) error { 791 data, err := json.Marshal(c.ProcessConfig.Env) 792 if err != nil { 793 return err 794 } 795 p := path.Join(d.libPath, "containers", c.ID, "config.env") 796 c.Mounts = append(c.Mounts, execdriver.Mount{ 797 Source: p, 798 Destination: "/.dockerenv", 799 Writable: false, 800 Private: true, 801 }) 802 803 return ioutil.WriteFile(p, data, 0600) 804 } 805 806 // Clean implements the exec driver Driver interface, 807 // it's not implemented by lxc. 808 func (d *Driver) Clean(id string) error { 809 return nil 810 } 811 812 // TtyConsole implements the exec driver Terminal interface, 813 // it stores the master and slave ends of the container's pty. 814 type TtyConsole struct { 815 MasterPty *os.File 816 SlavePty *os.File 817 } 818 819 // NewTtyConsole returns a new TtyConsole struct. 820 // Wired up to the provided process config and stdin/stdout/stderr pipes. 821 func NewTtyConsole(processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes) (*TtyConsole, error) { 822 // lxc is special in that we cannot create the master outside of the container without 823 // opening the slave because we have nothing to provide to the cmd. We have to open both then do 824 // the crazy setup on command right now instead of passing the console path to lxc and telling it 825 // to open up that console. we save a couple of openfiles in the native driver because we can do 826 // this. 827 ptyMaster, ptySlave, err := pty.Open() 828 if err != nil { 829 return nil, err 830 } 831 832 tty := &TtyConsole{ 833 MasterPty: ptyMaster, 834 SlavePty: ptySlave, 835 } 836 837 if err := tty.AttachPipes(&processConfig.Cmd, pipes); err != nil { 838 tty.Close() 839 return nil, err 840 } 841 842 processConfig.Console = tty.SlavePty.Name() 843 844 return tty, nil 845 } 846 847 // Resize implements Resize method of Terminal interface 848 func (t *TtyConsole) Resize(h, w int) error { 849 return term.SetWinsize(t.MasterPty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) 850 } 851 852 // AttachPipes attaches given pipes to exec.Cmd 853 func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) error { 854 command.Stdout = t.SlavePty 855 command.Stderr = t.SlavePty 856 857 go func() { 858 if wb, ok := pipes.Stdout.(interface { 859 CloseWriters() error 860 }); ok { 861 defer wb.CloseWriters() 862 } 863 864 io.Copy(pipes.Stdout, t.MasterPty) 865 }() 866 867 if pipes.Stdin != nil { 868 command.Stdin = t.SlavePty 869 command.SysProcAttr.Setctty = true 870 871 go func() { 872 io.Copy(t.MasterPty, pipes.Stdin) 873 874 pipes.Stdin.Close() 875 }() 876 } 877 return nil 878 } 879 880 // Close implements Close method of Terminal interface 881 func (t *TtyConsole) Close() error { 882 t.SlavePty.Close() 883 return t.MasterPty.Close() 884 } 885 886 // Exec implements the exec driver Driver interface, 887 // it is not implemented by lxc. 888 func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) { 889 return -1, ErrExec 890 } 891 892 // Stats implements the exec driver Driver interface. 893 // Lxc doesn't implement it's own Stats, it does some trick by implementing 894 // execdriver.Stats to get stats info by libcontainer APIs. 895 func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) { 896 if _, ok := d.activeContainers[id]; !ok { 897 return nil, fmt.Errorf("%s is not a key in active containers", id) 898 } 899 return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory) 900 } 901 902 // SupportsHooks implements the execdriver Driver interface. 903 // The LXC execdriver does not support the hook mechanism, which is currently unique to runC/libcontainer. 904 func (d *Driver) SupportsHooks() bool { 905 return false 906 }