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