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