1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/container.go

1478 lines
39 KiB
Go
Raw Normal View History

2013-01-18 19:13:39 -05:00
package docker
import (
"bytes"
2013-01-18 19:13:39 -05:00
"encoding/json"
"errors"
"fmt"
2013-10-31 19:57:45 -04:00
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/term"
2013-05-14 18:37:35 -04:00
"github.com/dotcloud/docker/utils"
2013-01-29 16:50:27 -05:00
"github.com/kr/pty"
2013-01-18 19:13:39 -05:00
"io"
"io/ioutil"
"log"
2013-08-29 18:21:32 -04:00
"net"
2013-01-18 19:13:39 -05:00
"os"
"os/exec"
"path"
"strconv"
2013-04-19 22:29:13 -04:00
"strings"
2013-11-21 15:21:03 -05:00
"sync"
2013-01-18 19:13:39 -05:00
"syscall"
"time"
2013-01-18 19:13:39 -05:00
)
var (
ErrNotATTY = errors.New("The PTY is not a file")
ErrNoTTY = errors.New("No PTY found")
)
2013-01-18 19:13:39 -05:00
type Container struct {
2013-11-21 15:21:03 -05:00
sync.Mutex
2013-11-07 18:58:03 -05:00
root string // Path to the "home" of the container, including metadata.
rootfs string // Path to the root filesystem of the container.
2013-06-04 14:00:22 -04:00
ID string
Created time.Time
2013-01-18 19:13:39 -05:00
Path string
Args []string
Config *Config
State State
Image string
2013-01-18 19:13:39 -05:00
network *NetworkInterface
NetworkSettings *NetworkSettings
2013-01-18 19:13:39 -05:00
SysInitPath string
ResolvConfPath string
HostnamePath string
HostsPath string
Name string
Driver string
cmd *exec.Cmd
2013-05-14 18:37:35 -04:00
stdout *utils.WriteBroadcaster
stderr *utils.WriteBroadcaster
stdin io.ReadCloser
stdinPipe io.WriteCloser
2013-04-04 15:12:22 -04:00
ptyMaster io.Closer
runtime *Runtime
waitLock chan struct{}
Volumes map[string]string
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
// Easier than migrating older container configs :)
VolumesRW map[string]bool
hostConfig *HostConfig
activeLinks map[string]*Link
2013-01-18 19:13:39 -05:00
}
// Note: the Config structure should hold only portable information about the container.
// Here, "portable" means "independent from the host we are running on".
// Non-portable information *should* appear in HostConfig.
2013-01-18 19:13:39 -05:00
type Config struct {
Hostname string
2013-08-29 18:21:32 -04:00
Domainname string
User string
Memory int64 // Memory limit (in bytes)
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
CpuShares int64 // CPU shares (relative weight vs. other containers)
AttachStdin bool
AttachStdout bool
AttachStderr bool
PortSpecs []string // Deprecated - Can be in the format of 8080/tcp
ExposedPorts map[Port]struct{}
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
Env []string
Cmd []string
Dns []string
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
Volumes map[string]struct{}
VolumesFrom string
WorkingDir string
Entrypoint []string
NetworkDisabled bool
}
type HostConfig struct {
Binds []string
ContainerIDFile string
LxcConf []KeyValuePair
Privileged bool
PortBindings map[Port][]PortBinding
Links []string
PublishAllPorts bool
}
type BindMap struct {
SrcPath string
DstPath string
Mode string
}
var (
ErrContainerStart = errors.New("The container failed to start. Unkown error")
ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.")
ErrInvalidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
ErrConflictAttachDetach = errors.New("Conflicting options: -a and -d")
ErrConflictDetachAutoRemove = errors.New("Conflicting options: -rm and -d")
)
type KeyValuePair struct {
Key string
Value string
}
type PortBinding struct {
HostIp string
HostPort string
}
// 80/tcp
type Port string
func (p Port) Proto() string {
parts := strings.Split(string(p), "/")
if len(parts) == 1 {
return "tcp"
}
return parts[1]
}
func (p Port) Port() string {
return strings.Split(string(p), "/")[0]
}
func (p Port) Int() int {
i, err := parsePort(p.Port())
if err != nil {
panic(err)
}
return i
}
func NewPort(proto, port string) Port {
return Port(fmt.Sprintf("%s/%s", port, proto))
}
type PortMapping map[string]string // Deprecated
Add support for UDP (closes #33) API Changes ----------- The port notation is extended to support "/udp" or "/tcp" at the *end* of the specifier string (and defaults to tcp if "/tcp" or "/udp" are missing) `docker ps` now shows UDP ports as "frontend->backend/udp". Nothing changes for TCP ports. `docker inspect` now displays two sub-dictionaries: "Tcp" and "Udp", under "PortMapping" in "NetworkSettings". Theses changes stand true for the values returned by the HTTP API too. This changeset will definitely break tools built upon the API (or upon `docker inspect`). A less intrusive way to add UDP ports in `docker inspect` would be to simply add "/udp" for UDP ports but it will still break existing applications which tries to convert the whole field to an integer. I believe that having two TCP/UDP sub-dictionaries is better because it makes the whole thing more clear and more easy to parse right away (i.e: you don't have to check the format of the string, split it and convert the right part to an integer) Code Changes ------------ Significant changes in network.go: - A second PortAllocator is instantiated for the UDP range; - PortMapper maintains separate mapping for TCP and UDP; - The extPorts array in NetworkInterface is now an array of Nat objects (so we can know on which protocol a given port was mapped when NetworkInterface.Release() is called); - TCP proxying on localhost has been moved away in network_proxy.go. localhost proxy code rewrite in network_proxy.go: We have to proxy the traffic between localhost:frontend-port and container:backend-port because Netfilter doesn't work properly on the loopback interface and DNAT iptable rules aren't applied there. - Goroutines in the TCP proxying code are now explicitly stopped when the proxy is stopped; - UDP connection tracking using a map (more infos in [1]); - Support for IPv6 (to be more accurate, the code is transparent to the Go net package, so you can use, tcp/tcp4/tcp6/udp/udp4/udp6); - Single Proxy interface for both UDP and TCP proxying; - Full test suite. [1] https://github.com/dotcloud/docker/issues/33#issuecomment-20010400
2013-06-11 18:46:23 -04:00
type NetworkSettings struct {
2013-06-04 14:00:22 -04:00
IPAddress string
IPPrefixLen int
Gateway string
2013-04-03 18:05:03 -04:00
Bridge string
PortMapping map[string]PortMapping // Deprecated
Ports map[Port][]PortBinding
}
func (settings *NetworkSettings) PortMappingAPI() []APIPort {
var mapping []APIPort
for port, bindings := range settings.Ports {
p, _ := parsePort(port.Port())
if len(bindings) == 0 {
mapping = append(mapping, APIPort{
PublicPort: int64(p),
Type: port.Proto(),
})
continue
}
for _, binding := range bindings {
p, _ := parsePort(port.Port())
h, _ := parsePort(binding.HostPort)
mapping = append(mapping, APIPort{
PrivatePort: int64(p),
PublicPort: int64(h),
Type: port.Proto(),
IP: binding.HostIp,
})
}
Add support for UDP (closes #33) API Changes ----------- The port notation is extended to support "/udp" or "/tcp" at the *end* of the specifier string (and defaults to tcp if "/tcp" or "/udp" are missing) `docker ps` now shows UDP ports as "frontend->backend/udp". Nothing changes for TCP ports. `docker inspect` now displays two sub-dictionaries: "Tcp" and "Udp", under "PortMapping" in "NetworkSettings". Theses changes stand true for the values returned by the HTTP API too. This changeset will definitely break tools built upon the API (or upon `docker inspect`). A less intrusive way to add UDP ports in `docker inspect` would be to simply add "/udp" for UDP ports but it will still break existing applications which tries to convert the whole field to an integer. I believe that having two TCP/UDP sub-dictionaries is better because it makes the whole thing more clear and more easy to parse right away (i.e: you don't have to check the format of the string, split it and convert the right part to an integer) Code Changes ------------ Significant changes in network.go: - A second PortAllocator is instantiated for the UDP range; - PortMapper maintains separate mapping for TCP and UDP; - The extPorts array in NetworkInterface is now an array of Nat objects (so we can know on which protocol a given port was mapped when NetworkInterface.Release() is called); - TCP proxying on localhost has been moved away in network_proxy.go. localhost proxy code rewrite in network_proxy.go: We have to proxy the traffic between localhost:frontend-port and container:backend-port because Netfilter doesn't work properly on the loopback interface and DNAT iptable rules aren't applied there. - Goroutines in the TCP proxying code are now explicitly stopped when the proxy is stopped; - UDP connection tracking using a map (more infos in [1]); - Support for IPv6 (to be more accurate, the code is transparent to the Go net package, so you can use, tcp/tcp4/tcp6/udp/udp4/udp6); - Single Proxy interface for both UDP and TCP proxying; - Full test suite. [1] https://github.com/dotcloud/docker/issues/33#issuecomment-20010400
2013-06-11 18:46:23 -04:00
}
return mapping
2013-04-19 22:29:13 -04:00
}
// Inject the io.Reader at the given path. Note: do not close the reader
func (container *Container) Inject(file io.Reader, pth string) error {
if err := container.EnsureMounted(); err != nil {
return fmt.Errorf("inject: error mounting container %s: %s", container.ID, err)
}
// Return error if path exists
destPath := path.Join(container.RootfsPath(), pth)
if _, err := os.Stat(destPath); err == nil {
// Since err is nil, the path could be stat'd and it exists
return fmt.Errorf("%s exists", pth)
2013-11-07 15:19:24 -05:00
} else if !os.IsNotExist(err) {
// Expect err might be that the file doesn't exist, so
2013-11-07 15:19:24 -05:00
// if it's some other error, return that.
return err
}
// Make sure the directory exists
if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil {
return err
}
dest, err := os.Create(destPath)
if err != nil {
return err
}
defer dest.Close()
if _, err := io.Copy(dest, file); err != nil {
return err
}
return nil
}
func (container *Container) Cmd() *exec.Cmd {
return container.cmd
}
func (container *Container) When() time.Time {
return container.Created
}
func (container *Container) FromDisk() error {
data, err := ioutil.ReadFile(container.jsonPath())
if err != nil {
return err
}
// Load container settings
// udp broke compat of docker.PortMapping, but it's not used when loading a container, we can skip it
if err := json.Unmarshal(data, container); err != nil && !strings.Contains(err.Error(), "docker.PortMapping") {
return err
}
return container.readHostConfig()
}
func (container *Container) ToDisk() (err error) {
data, err := json.Marshal(container)
2013-01-18 19:13:39 -05:00
if err != nil {
return
2013-01-18 19:13:39 -05:00
}
err = ioutil.WriteFile(container.jsonPath(), data, 0666)
if err != nil {
return
}
return container.writeHostConfig()
2013-01-18 19:13:39 -05:00
}
func (container *Container) readHostConfig() error {
container.hostConfig = &HostConfig{}
// If the hostconfig file does not exist, do not read it.
// (We still have to initialize container.hostConfig,
// but that's OK, since we just did that above.)
_, err := os.Stat(container.hostConfigPath())
if os.IsNotExist(err) {
return nil
}
data, err := ioutil.ReadFile(container.hostConfigPath())
if err != nil {
return err
}
return json.Unmarshal(data, container.hostConfig)
}
func (container *Container) writeHostConfig() (err error) {
data, err := json.Marshal(container.hostConfig)
if err != nil {
return
}
return ioutil.WriteFile(container.hostConfigPath(), data, 0666)
}
func (container *Container) generateEnvConfig(env []string) error {
data, err := json.Marshal(env)
if err != nil {
return err
}
ioutil.WriteFile(container.EnvConfigPath(), data, 0600)
return nil
}
func (container *Container) generateLXCConfig() error {
fo, err := os.Create(container.lxcConfigPath())
2013-01-18 19:13:39 -05:00
if err != nil {
return err
}
defer fo.Close()
return LxcTemplateCompiled.Execute(fo, container)
2013-01-18 19:13:39 -05:00
}
func (container *Container) startPty() error {
2013-04-04 15:12:22 -04:00
ptyMaster, ptySlave, err := pty.Open()
if err != nil {
return err
}
2013-04-04 15:12:22 -04:00
container.ptyMaster = ptyMaster
container.cmd.Stdout = ptySlave
container.cmd.Stderr = ptySlave
// Copy the PTYs to our broadcasters
go func() {
defer container.stdout.CloseWriters()
utils.Debugf("startPty: begin of stdout pipe")
2013-04-04 15:12:22 -04:00
io.Copy(container.stdout, ptyMaster)
utils.Debugf("startPty: end of stdout pipe")
}()
// stdin
if container.Config.OpenStdin {
2013-04-04 15:12:22 -04:00
container.cmd.Stdin = ptySlave
container.cmd.SysProcAttr.Setctty = true
go func() {
defer container.stdin.Close()
utils.Debugf("startPty: begin of stdin pipe")
2013-04-04 15:12:22 -04:00
io.Copy(ptyMaster, container.stdin)
utils.Debugf("startPty: end of stdin pipe")
}()
}
if err := container.cmd.Start(); err != nil {
return err
}
2013-04-04 15:12:22 -04:00
ptySlave.Close()
return nil
}
func (container *Container) start() error {
container.cmd.Stdout = container.stdout
container.cmd.Stderr = container.stderr
if container.Config.OpenStdin {
stdin, err := container.cmd.StdinPipe()
if err != nil {
return err
}
go func() {
defer stdin.Close()
utils.Debugf("start: begin of stdin pipe")
io.Copy(stdin, container.stdin)
utils.Debugf("start: end of stdin pipe")
}()
}
return container.cmd.Start()
}
func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
var cStdout, cStderr io.ReadCloser
var nJobs int
errors := make(chan error, 3)
if stdin != nil && container.Config.OpenStdin {
nJobs += 1
if cStdin, err := container.StdinPipe(); err != nil {
errors <- err
} else {
go func() {
utils.Debugf("attach: stdin: begin")
defer utils.Debugf("attach: stdin: end")
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
if container.Config.StdinOnce && !container.Config.Tty {
defer cStdin.Close()
} else {
if cStdout != nil {
defer cStdout.Close()
}
if cStderr != nil {
defer cStderr.Close()
}
}
if container.Config.Tty {
2013-05-14 18:37:35 -04:00
_, err = utils.CopyEscapable(cStdin, stdin)
} else {
_, err = io.Copy(cStdin, stdin)
}
if err == io.ErrClosedPipe {
err = nil
}
if err != nil {
utils.Errorf("attach: stdin: %s", err)
}
errors <- err
}()
}
}
if stdout != nil {
nJobs += 1
if p, err := container.StdoutPipe(); err != nil {
errors <- err
} else {
cStdout = p
go func() {
utils.Debugf("attach: stdout: begin")
defer utils.Debugf("attach: stdout: end")
// If we are in StdinOnce mode, then close stdin
2013-09-23 20:53:02 -04:00
if container.Config.StdinOnce && stdin != nil {
defer stdin.Close()
}
if stdinCloser != nil {
defer stdinCloser.Close()
}
_, err := io.Copy(stdout, cStdout)
if err == io.ErrClosedPipe {
err = nil
}
if err != nil {
utils.Errorf("attach: stdout: %s", err)
}
errors <- err
}()
}
} else {
go func() {
2013-06-04 17:35:32 -04:00
if stdinCloser != nil {
defer stdinCloser.Close()
}
if cStdout, err := container.StdoutPipe(); err != nil {
utils.Errorf("attach: stdout pipe: %s", err)
2013-06-04 17:35:32 -04:00
} else {
io.Copy(&utils.NopWriter{}, cStdout)
}
}()
}
if stderr != nil {
nJobs += 1
if p, err := container.StderrPipe(); err != nil {
errors <- err
} else {
cStderr = p
go func() {
utils.Debugf("attach: stderr: begin")
defer utils.Debugf("attach: stderr: end")
// If we are in StdinOnce mode, then close stdin
2013-09-23 20:53:02 -04:00
if container.Config.StdinOnce && stdin != nil {
defer stdin.Close()
}
if stdinCloser != nil {
defer stdinCloser.Close()
}
_, err := io.Copy(stderr, cStderr)
if err == io.ErrClosedPipe {
err = nil
}
if err != nil {
utils.Errorf("attach: stderr: %s", err)
}
errors <- err
}()
}
} else {
go func() {
2013-06-04 17:35:32 -04:00
if stdinCloser != nil {
defer stdinCloser.Close()
}
2013-06-10 14:08:40 -04:00
if cStderr, err := container.StderrPipe(); err != nil {
utils.Errorf("attach: stdout pipe: %s", err)
2013-06-04 17:35:32 -04:00
} else {
io.Copy(&utils.NopWriter{}, cStderr)
}
}()
}
2013-05-14 18:37:35 -04:00
return utils.Go(func() error {
if cStdout != nil {
defer cStdout.Close()
}
if cStderr != nil {
defer cStderr.Close()
}
// FIXME: how to clean up the stdin goroutine without the unwanted side effect
// of closing the passed stdin? Add an intermediary io.Pipe?
for i := 0; i < nJobs; i += 1 {
utils.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
if err := <-errors; err != nil {
utils.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err)
return err
}
utils.Debugf("attach: job %d completed successfully", i+1)
}
utils.Debugf("attach: all jobs completed successfully")
return nil
})
}
func (container *Container) Start() (err error) {
2013-11-21 15:21:03 -05:00
container.Lock()
defer container.Unlock()
if container.State.IsRunning() {
return fmt.Errorf("The container %s is already running.", container.ID)
}
2013-10-16 16:12:56 -04:00
defer func() {
if err != nil {
container.cleanup()
2013-10-16 15:01:55 -04:00
}
2013-10-16 16:12:56 -04:00
}()
if err := container.EnsureMounted(); err != nil {
return err
}
if container.runtime.networkManager.disabled {
container.Config.NetworkDisabled = true
container.buildHostnameAndHostsFiles("127.0.1.1")
2013-10-16 16:12:56 -04:00
} else {
if err := container.allocateNetwork(); err != nil {
return err
}
container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
2013-10-16 16:12:56 -04:00
}
2013-10-16 16:12:56 -04:00
// Make sure the config is compatible with the current kernel
if container.Config.Memory > 0 && !container.runtime.capabilities.MemoryLimit {
log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
container.Config.Memory = 0
}
if container.Config.Memory > 0 && !container.runtime.capabilities.SwapLimit {
log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
container.Config.MemorySwap = -1
}
2013-10-16 16:12:56 -04:00
if container.runtime.capabilities.IPv4ForwardingDisabled {
log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work")
}
2013-10-16 16:12:56 -04:00
if container.Volumes == nil || len(container.Volumes) == 0 {
container.Volumes = make(map[string]string)
container.VolumesRW = make(map[string]bool)
}
2013-10-16 16:12:56 -04:00
// Apply volumes from another container if requested
if err := container.applyExternalVolumes(); err != nil {
return err
}
if err := container.createVolumes(); err != nil {
return err
2013-10-16 16:12:56 -04:00
}
2013-04-09 21:19:55 -04:00
if err := container.generateLXCConfig(); err != nil {
2013-10-16 16:12:56 -04:00
return err
}
var lxcStart string = "lxc-start"
if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor {
lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined")
}
2013-10-16 16:12:56 -04:00
params := []string{
lxcStart,
2013-10-16 16:12:56 -04:00
"-n", container.ID,
"-f", container.lxcConfigPath(),
"--",
"/.dockerinit",
}
2013-10-16 16:12:56 -04:00
// Networking
if !container.Config.NetworkDisabled {
network := container.NetworkSettings
params = append(params,
"-g", network.Gateway,
"-i", fmt.Sprintf("%s/%d", network.IPAddress, network.IPPrefixLen),
)
2013-10-16 16:12:56 -04:00
}
2013-10-16 16:12:56 -04:00
// User
if container.Config.User != "" {
params = append(params, "-u", container.Config.User)
}
// Setup environment
env := []string{
"HOME=/",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=" + container.Config.Hostname,
2013-10-16 16:12:56 -04:00
}
if container.Config.Tty {
env = append(env, "TERM=xterm")
}
if container.hostConfig.Privileged {
params = append(params, "-privileged")
}
// Init any links between the parent and children
runtime := container.runtime
children, err := runtime.Children(container.Name)
if err != nil {
return err
}
if len(children) > 0 {
container.activeLinks = make(map[string]*Link, len(children))
// If we encounter an error make sure that we rollback any network
// config and ip table changes
rollback := func() {
for _, link := range container.activeLinks {
link.Disable()
}
container.activeLinks = nil
}
for p, child := range children {
link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface)
if err != nil {
rollback()
return err
}
container.activeLinks[link.Alias()] = link
if err := link.Enable(); err != nil {
rollback()
return err
}
for _, envVar := range link.ToEnv() {
env = append(env, envVar)
}
}
}
for _, elem := range container.Config.Env {
env = append(env, elem)
}
if err := container.generateEnvConfig(env); err != nil {
return err
}
2013-10-16 16:12:56 -04:00
if container.Config.WorkingDir != "" {
workingDir := path.Clean(container.Config.WorkingDir)
utils.Debugf("[working dir] working dir is %s", workingDir)
2013-10-16 16:12:56 -04:00
if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil {
return nil
2013-10-16 15:01:55 -04:00
}
2013-01-18 19:13:39 -05:00
2013-10-16 16:12:56 -04:00
params = append(params,
"-w", workingDir,
)
}
2013-03-29 11:46:06 -04:00
2013-10-16 16:12:56 -04:00
// Program
params = append(params, "--", container.Path)
params = append(params, container.Args...)
2013-10-16 15:01:55 -04:00
if RootIsShared() {
// lxc-start really needs / to be non-shared, or all kinds of stuff break
// when lxc-start unmount things and those unmounts propagate to the main
// mount namespace.
// What we really want is to clone into a new namespace and then
// mount / MS_REC|MS_SLAVE, but since we can't really clone or fork
// without exec in go we have to do this horrible shell hack...
shellString :=
"mount --make-rslave /; exec " +
utils.ShellQuoteArguments(params)
params = []string{
"unshare", "-m", "--", "/bin/sh", "-c", shellString,
}
}
container.cmd = exec.Command(params[0], params[1:]...)
2013-10-16 16:12:56 -04:00
// Setup logging of stdout and stderr to disk
if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
return err
}
if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil {
return err
}
2013-10-16 15:01:55 -04:00
2013-10-16 16:12:56 -04:00
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
2013-10-16 15:01:55 -04:00
2013-10-16 16:12:56 -04:00
if container.Config.Tty {
err = container.startPty()
} else {
err = container.start()
}
if err != nil {
2013-10-16 16:12:56 -04:00
return err
2013-01-18 19:13:39 -05:00
}
2013-10-16 16:12:56 -04:00
// FIXME: save state on disk *first*, then converge
// this way disk state is used as a journal, eg. we can restore after crash etc.
2013-11-21 15:21:03 -05:00
container.State.SetRunning(container.cmd.Process.Pid)
2013-10-16 16:12:56 -04:00
// Init the lock
container.waitLock = make(chan struct{})
container.ToDisk()
go container.monitor()
2013-11-21 15:21:03 -05:00
defer utils.Debugf("Container running: %v", container.State.IsRunning())
// We wait for the container to be fully running.
// Timeout after 5 seconds. In case of broken pipe, just retry.
// Note: The container can run and finish correctly before
// the end of this loop
for now := time.Now(); time.Since(now) < 5*time.Second; {
// If the container dies while waiting for it, just return
2013-11-21 15:21:03 -05:00
if !container.State.IsRunning() {
return nil
}
output, err := exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
if err != nil {
utils.Debugf("Error with lxc-info: %s (%s)", err, output)
output, err = exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
if err != nil {
utils.Debugf("Second Error with lxc-info: %s (%s)", err, output)
return err
}
}
if strings.Contains(string(output), "RUNNING") {
return nil
}
2013-11-21 15:21:03 -05:00
utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.IsRunning(), bytes.TrimSpace(output))
time.Sleep(50 * time.Millisecond)
}
2013-11-21 15:21:03 -05:00
if container.State.IsRunning() {
return ErrContainerStartTimeout
}
return ErrContainerStart
2013-01-18 19:13:39 -05:00
}
func (container *Container) getBindMap() (map[string]BindMap, error) {
// Create the requested bind mounts
binds := make(map[string]BindMap)
// Define illegal container destinations
illegalDsts := []string{"/", "."}
for _, bind := range container.hostConfig.Binds {
// FIXME: factorize bind parsing in parseBind
var src, dst, mode string
arr := strings.Split(bind, ":")
if len(arr) == 2 {
src = arr[0]
dst = arr[1]
mode = "rw"
} else if len(arr) == 3 {
src = arr[0]
dst = arr[1]
mode = arr[2]
} else {
return nil, fmt.Errorf("Invalid bind specification: %s", bind)
}
// Bail if trying to mount to an illegal destination
for _, illegal := range illegalDsts {
if dst == illegal {
return nil, fmt.Errorf("Illegal bind destination: %s", dst)
}
}
bindMap := BindMap{
SrcPath: src,
DstPath: dst,
Mode: mode,
}
binds[path.Clean(dst)] = bindMap
}
return binds, nil
}
func (container *Container) createVolumes() error {
binds, err := container.getBindMap()
if err != nil {
return err
}
volumesDriver := container.runtime.volumes.driver
// Create the requested volumes if they don't exist
for volPath := range container.Config.Volumes {
volPath = path.Clean(volPath)
volIsDir := true
// Skip existing volumes
if _, exists := container.Volumes[volPath]; exists {
continue
}
var srcPath string
var isBindMount bool
srcRW := false
// If an external bind is defined for this volume, use that as a source
if bindMap, exists := binds[volPath]; exists {
isBindMount = true
srcPath = bindMap.SrcPath
if strings.ToLower(bindMap.Mode) == "rw" {
srcRW = true
}
if stat, err := os.Lstat(bindMap.SrcPath); err != nil {
return err
} else {
volIsDir = stat.IsDir()
}
// Otherwise create an directory in $ROOT/volumes/ and use that
} else {
// Do not pass a container as the parameter for the volume creation.
// The graph driver using the container's information ( Image ) to
// create the parent.
c, err := container.runtime.volumes.Create(nil, nil, "", "", nil)
if err != nil {
return err
}
srcPath, err = volumesDriver.Get(c.ID)
if err != nil {
return fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", volumesDriver, c.ID, err)
}
srcRW = true // RW by default
}
container.Volumes[volPath] = srcPath
container.VolumesRW[volPath] = srcRW
// Create the mountpoint
volPath = path.Join(container.RootfsPath(), volPath)
rootVolPath, err := utils.FollowSymlinkInScope(volPath, container.RootfsPath())
if err != nil {
2013-12-18 14:40:00 -05:00
return err
}
if _, err := os.Stat(rootVolPath); err != nil {
if os.IsNotExist(err) {
if volIsDir {
if err := os.MkdirAll(rootVolPath, 0755); err != nil {
return err
}
} else {
if err := os.MkdirAll(path.Dir(rootVolPath), 0755); err != nil {
return err
}
if f, err := os.OpenFile(rootVolPath, os.O_CREATE, 0755); err != nil {
return err
} else {
f.Close()
}
}
}
}
// Do not copy or change permissions if we are mounting from the host
if srcRW && !isBindMount {
volList, err := ioutil.ReadDir(rootVolPath)
if err != nil {
return err
}
if len(volList) > 0 {
srcList, err := ioutil.ReadDir(srcPath)
if err != nil {
return err
}
if len(srcList) == 0 {
// If the source volume is empty copy files from the root into the volume
if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil {
return err
}
var stat syscall.Stat_t
if err := syscall.Stat(rootVolPath, &stat); err != nil {
return err
}
var srcStat syscall.Stat_t
if err := syscall.Stat(srcPath, &srcStat); err != nil {
return err
}
// Change the source volume's ownership if it differs from the root
// files that where just copied
if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid {
if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil {
return err
}
}
}
}
}
}
return nil
}
func (container *Container) applyExternalVolumes() error {
if container.Config.VolumesFrom != "" {
containerSpecs := strings.Split(container.Config.VolumesFrom, ",")
for _, containerSpec := range containerSpecs {
mountRW := true
specParts := strings.SplitN(containerSpec, ":", 2)
switch len(specParts) {
case 0:
return fmt.Errorf("Malformed volumes-from specification: %s", container.Config.VolumesFrom)
case 2:
switch specParts[1] {
case "ro":
mountRW = false
case "rw": // mountRW is already true
default:
return fmt.Errorf("Malformed volumes-from speficication: %s", containerSpec)
}
}
c := container.runtime.Get(specParts[0])
if c == nil {
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.ID)
}
for volPath, id := range c.Volumes {
if _, exists := container.Volumes[volPath]; exists {
continue
}
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return err
}
container.Volumes[volPath] = id
if isRW, exists := c.VolumesRW[volPath]; exists {
container.VolumesRW[volPath] = isRW && mountRW
}
}
}
}
return nil
}
2013-01-18 19:13:39 -05:00
func (container *Container) Run() error {
if err := container.Start(); err != nil {
2013-01-18 19:13:39 -05:00
return err
}
container.Wait()
return nil
}
func (container *Container) Output() (output []byte, err error) {
pipe, err := container.StdoutPipe()
if err != nil {
return nil, err
}
defer pipe.Close()
if err := container.Start(); err != nil {
2013-01-18 19:13:39 -05:00
return nil, err
}
output, err = ioutil.ReadAll(pipe)
container.Wait()
return output, err
}
// Container.StdinPipe returns a WriteCloser which can be used to feed data
// to the standard input of the container's active process.
// Container.StdoutPipe and Container.StderrPipe each return a ReadCloser
// which can be used to retrieve the standard output (and error) generated
// by the container's active process. The output (and error) are actually
// copied and delivered to all StdoutPipe and StderrPipe consumers, using
// a kind of "broadcaster".
func (container *Container) StdinPipe() (io.WriteCloser, error) {
return container.stdinPipe, nil
}
2013-01-18 19:13:39 -05:00
func (container *Container) StdoutPipe() (io.ReadCloser, error) {
reader, writer := io.Pipe()
2013-07-11 13:18:28 -04:00
container.stdout.AddWriter(writer, "")
2013-05-14 18:37:35 -04:00
return utils.NewBufReader(reader), nil
2013-01-18 19:13:39 -05:00
}
func (container *Container) StderrPipe() (io.ReadCloser, error) {
reader, writer := io.Pipe()
2013-07-11 13:18:28 -04:00
container.stderr.AddWriter(writer, "")
2013-05-14 18:37:35 -04:00
return utils.NewBufReader(reader), nil
2013-01-18 19:13:39 -05:00
}
func (container *Container) buildHostnameAndHostsFiles(IP string) {
container.HostnamePath = path.Join(container.root, "hostname")
ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
hostsContent := []byte(`
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
`)
container.HostsPath = path.Join(container.root, "hosts")
if container.Config.Domainname != "" {
hostsContent = append([]byte(fmt.Sprintf("%s\t%s.%s %s\n", IP, container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...)
} else {
hostsContent = append([]byte(fmt.Sprintf("%s\t%s\n", IP, container.Config.Hostname)), hostsContent...)
}
ioutil.WriteFile(container.HostsPath, hostsContent, 0644)
}
func (container *Container) allocateNetwork() error {
if container.Config.NetworkDisabled {
return nil
}
2013-11-21 15:21:03 -05:00
var (
iface *NetworkInterface
err error
)
if container.State.IsGhost() {
if manager := container.runtime.networkManager; manager.disabled {
iface = &NetworkInterface{disabled: true}
} else {
iface = &NetworkInterface{
2013-08-29 18:21:32 -04:00
IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask},
Gateway: manager.bridgeNetwork.IP,
manager: manager,
2013-08-29 18:21:32 -04:00
}
2013-11-07 15:19:24 -05:00
if iface != nil && iface.IPNet.IP != nil {
ipNum := ipToInt(iface.IPNet.IP)
manager.ipAllocator.inUse[ipNum] = struct{}{}
} else {
iface, err = container.runtime.networkManager.Allocate()
if err != nil {
return err
}
}
}
} else {
iface, err = container.runtime.networkManager.Allocate()
if err != nil {
return err
}
}
if container.Config.PortSpecs != nil {
utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
if err := migratePortMappings(container.Config, container.hostConfig); err != nil {
return err
}
container.Config.PortSpecs = nil
if err := container.writeHostConfig(); err != nil {
return err
}
}
2013-11-21 15:21:03 -05:00
var (
portSpecs = make(map[Port]struct{})
bindings = make(map[Port][]PortBinding)
)
2013-11-21 15:21:03 -05:00
if !container.State.IsGhost() {
if container.Config.ExposedPorts != nil {
portSpecs = container.Config.ExposedPorts
2013-08-27 15:14:21 -04:00
}
if container.hostConfig.PortBindings != nil {
bindings = container.hostConfig.PortBindings
}
} else {
if container.NetworkSettings.Ports != nil {
for port, binding := range container.NetworkSettings.Ports {
portSpecs[port] = struct{}{}
bindings[port] = binding
}
2013-08-27 15:14:21 -04:00
}
}
2013-08-27 15:14:21 -04:00
container.NetworkSettings.PortMapping = nil
for port := range portSpecs {
binding := bindings[port]
2013-11-01 17:01:32 -04:00
if container.hostConfig.PublishAllPorts && len(binding) == 0 {
binding = append(binding, PortBinding{})
}
for i := 0; i < len(binding); i++ {
b := binding[i]
nat, err := iface.AllocatePort(port, b)
if err != nil {
iface.Release()
return err
}
utils.Debugf("Allocate port: %s:%s->%s", nat.Binding.HostIp, port, nat.Binding.HostPort)
binding[i] = nat.Binding
}
bindings[port] = binding
}
container.writeHostConfig()
container.NetworkSettings.Ports = bindings
container.network = iface
2013-04-03 18:05:03 -04:00
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
2013-06-04 14:00:22 -04:00
container.NetworkSettings.IPAddress = iface.IPNet.IP.String()
container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size()
container.NetworkSettings.Gateway = iface.Gateway.String()
return nil
}
2013-04-01 01:04:59 -04:00
func (container *Container) releaseNetwork() {
2013-10-16 17:39:03 -04:00
if container.Config.NetworkDisabled || container.network == nil {
return
}
container.network.Release()
container.network = nil
container.NetworkSettings = &NetworkSettings{}
}
// FIXME: replace this with a control socket within dockerinit
func (container *Container) waitLxc() error {
for {
2013-06-04 14:00:22 -04:00
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
2013-06-04 09:51:12 -04:00
if err != nil {
return err
2013-06-04 09:51:12 -04:00
}
if !strings.Contains(string(output), "RUNNING") {
return nil
}
time.Sleep(500 * time.Millisecond)
}
}
func (container *Container) monitor() {
2013-01-22 18:03:40 -05:00
// Wait for the program to exit
// If the command does not exist, try to wait via lxc
// (This probably happens only for ghost containers, i.e. containers that were running when Docker started)
if container.cmd == nil {
utils.Debugf("monitor: waiting for container %s using waitLxc", container.ID)
if err := container.waitLxc(); err != nil {
utils.Errorf("monitor: while waiting for container %s, waitLxc had a problem: %s", container.ID, err)
}
} else {
utils.Debugf("monitor: waiting for container %s using cmd.Wait", container.ID)
if err := container.cmd.Wait(); err != nil {
// Since non-zero exit status and signal terminations will cause err to be non-nil,
// we have to actually discard it. Still, log it anyway, just in case.
utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID)
}
}
utils.Debugf("monitor: container %s finished", container.ID)
2013-09-20 14:31:00 -04:00
2013-06-04 09:51:12 -04:00
exitCode := -1
if container.cmd != nil {
exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
}
2013-09-20 16:36:19 -04:00
if container.runtime != nil && container.runtime.srv != nil {
container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image))
2013-09-20 16:36:19 -04:00
}
2013-01-22 18:03:40 -05:00
// Cleanup
2013-10-16 15:01:55 -04:00
container.cleanup()
// Re-create a brand new stdin pipe once the container exited
if container.Config.OpenStdin {
container.stdin, container.stdinPipe = io.Pipe()
}
// Report status back
2013-11-21 15:21:03 -05:00
container.State.SetStopped(exitCode)
2013-10-16 15:01:55 -04:00
// Release the lock
close(container.waitLock)
if err := container.ToDisk(); err != nil {
// FIXME: there is a race condition here which causes this to fail during the unit tests.
// If another goroutine was waiting for Wait() to return before removing the container's root
// from the filesystem... At this point it may already have done so.
// This is because State.setStopped() has already been called, and has caused Wait()
// to return.
// FIXME: why are we serializing running state to disk in the first place?
//log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err)
}
}
func (container *Container) cleanup() {
container.releaseNetwork()
// Disable all active links
if container.activeLinks != nil {
for _, link := range container.activeLinks {
link.Disable()
}
}
if container.Config.OpenStdin {
if err := container.stdin.Close(); err != nil {
utils.Errorf("%s: Error close stdin: %s", container.ID, err)
}
}
if err := container.stdout.CloseWriters(); err != nil {
utils.Errorf("%s: Error close stdout: %s", container.ID, err)
}
if err := container.stderr.CloseWriters(); err != nil {
utils.Errorf("%s: Error close stderr: %s", container.ID, err)
}
2013-04-04 15:12:22 -04:00
if container.ptyMaster != nil {
if err := container.ptyMaster.Close(); err != nil {
utils.Errorf("%s: Error closing Pty master: %s", container.ID, err)
}
}
if err := container.Unmount(); err != nil {
2013-06-04 14:00:22 -04:00
log.Printf("%v: Failed to umount filesystem: %v", container.ID, err)
}
2013-01-18 19:13:39 -05:00
}
func (container *Container) kill(sig int) error {
2013-11-21 15:21:03 -05:00
container.Lock()
defer container.Unlock()
2013-11-21 15:21:03 -05:00
if !container.State.IsRunning() {
return nil
}
if output, err := exec.Command("lxc-kill", "-n", container.ID, strconv.Itoa(sig)).CombinedOutput(); err != nil {
log.Printf("error killing container %s (%s, %s)", utils.TruncateID(container.ID), output, err)
return err
}
return nil
}
func (container *Container) Kill() error {
2013-11-21 15:21:03 -05:00
if !container.State.IsRunning() {
return nil
}
// 1. Send SIGKILL
if err := container.kill(9); err != nil {
return err
2013-01-18 19:13:39 -05:00
}
// 2. Wait for the process to die, in last resort, try to kill the process directly
if err := container.WaitTimeout(10 * time.Second); err != nil {
if container.cmd == nil {
return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", utils.TruncateID(container.ID))
}
log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", utils.TruncateID(container.ID))
if err := container.cmd.Process.Kill(); err != nil {
return err
}
}
container.Wait()
return nil
}
func (container *Container) Stop(seconds int) error {
2013-11-21 15:21:03 -05:00
if !container.State.IsRunning() {
2013-01-18 19:13:39 -05:00
return nil
}
// 1. Send a SIGTERM
if err := container.kill(15); err != nil {
utils.Debugf("Error sending kill SIGTERM: %s", err)
log.Print("Failed to send SIGTERM to the process, force killing")
if err := container.kill(9); err != nil {
return err
}
}
// 2. Wait for the process to exit on its own
if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
2013-06-04 14:00:22 -04:00
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
// 3. If it doesn't, then send SIGKILL
if err := container.Kill(); err != nil {
return err
}
}
2013-01-18 19:13:39 -05:00
return nil
}
func (container *Container) Restart(seconds int) error {
if err := container.Stop(seconds); err != nil {
2013-01-22 18:03:40 -05:00
return err
}
return container.Start()
2013-01-22 18:03:40 -05:00
}
// Wait blocks until the container stops running, then returns its exit code.
func (container *Container) Wait() int {
<-container.waitLock
2013-11-21 15:21:03 -05:00
return container.State.GetExitCode()
2013-01-18 19:13:39 -05:00
}
2013-05-23 22:33:28 -04:00
func (container *Container) Resize(h, w int) error {
pty, ok := container.ptyMaster.(*os.File)
if !ok {
return fmt.Errorf("ptyMaster does not have Fd() method")
}
return term.SetWinsize(pty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
2013-05-23 22:33:28 -04:00
}
2013-10-31 19:57:45 -04:00
func (container *Container) ExportRw() (archive.Archive, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err
}
if container.runtime == nil {
return nil, fmt.Errorf("Can't load storage driver for unregistered container %s", container.ID)
}
return container.runtime.Diff(container)
}
2013-10-31 19:57:45 -04:00
func (container *Container) Export() (archive.Archive, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err
}
2013-10-31 19:57:45 -04:00
return archive.Tar(container.RootfsPath(), archive.Uncompressed)
}
func (container *Container) WaitTimeout(timeout time.Duration) error {
done := make(chan bool)
go func() {
container.Wait()
done <- true
}()
select {
case <-time.After(timeout):
return fmt.Errorf("Timed Out")
case <-done:
return nil
}
}
func (container *Container) EnsureMounted() error {
// FIXME: EnsureMounted is deprecated because drivers are now responsible
// for re-entrant mounting in their Get() method.
return container.Mount()
}
func (container *Container) Mount() error {
return container.runtime.Mount(container)
}
func (container *Container) Changes() ([]archive.Change, error) {
return container.runtime.Changes(container)
}
2013-03-21 20:47:23 -04:00
func (container *Container) GetImage() (*Image, error) {
if container.runtime == nil {
return nil, fmt.Errorf("Can't get image of unregistered container")
}
return container.runtime.graph.Get(container.Image)
}
func (container *Container) Unmount() error {
return container.runtime.Unmount(container)
}
func (container *Container) logPath(name string) string {
2013-06-04 14:00:22 -04:00
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.ID, name))
}
func (container *Container) ReadLog(name string) (io.Reader, error) {
return os.Open(container.logPath(name))
}
func (container *Container) hostConfigPath() string {
2013-07-02 13:02:42 -04:00
return path.Join(container.root, "hostconfig.json")
}
func (container *Container) jsonPath() string {
return path.Join(container.root, "config.json")
}
func (container *Container) EnvConfigPath() string {
return path.Join(container.root, "config.env")
}
func (container *Container) lxcConfigPath() string {
return path.Join(container.root, "config.lxc")
}
// This method must be exported to be used from the lxc template
func (container *Container) RootfsPath() string {
return container.rootfs
}
2013-06-04 14:00:22 -04:00
func validateID(id string) error {
if id == "" {
return fmt.Errorf("Invalid empty id")
}
return nil
}
2013-05-13 09:10:26 -04:00
// GetSize, return real size, virtual size
func (container *Container) GetSize() (int64, int64) {
var (
sizeRw, sizeRootfs int64
err error
driver = container.runtime.driver
)
2013-05-13 09:10:26 -04:00
if err := container.EnsureMounted(); err != nil {
utils.Errorf("Warning: failed to compute size of container rootfs %s: %s", container.ID, err)
return sizeRw, sizeRootfs
}
if differ, ok := container.runtime.driver.(graphdriver.Differ); ok {
sizeRw, err = differ.DiffSize(container.ID)
if err != nil {
utils.Errorf("Warning: driver %s couldn't return diff size of container %s: %s", driver, container.ID, err)
// FIXME: GetSize should return an error. Not changing it now in case
// there is a side-effect.
sizeRw = -1
}
} else {
changes, _ := container.Changes()
if changes != nil {
sizeRw = archive.ChangesSize(container.RootfsPath(), changes)
} else {
sizeRw = -1
}
}
2013-11-20 17:51:04 -05:00
if _, err = os.Stat(container.RootfsPath()); err != nil {
if sizeRootfs, err = utils.TreeSize(container.RootfsPath()); err != nil {
sizeRootfs = -1
2013-05-13 09:10:26 -04:00
}
}
return sizeRw, sizeRootfs
}
2013-10-31 19:57:45 -04:00
func (container *Container) Copy(resource string) (archive.Archive, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err
}
var filter []string
basePath := path.Join(container.RootfsPath(), resource)
stat, err := os.Stat(basePath)
if err != nil {
return nil, err
}
if !stat.IsDir() {
d, f := path.Split(basePath)
basePath = d
filter = []string{f}
} else {
filter = []string{path.Base(basePath)}
basePath = path.Dir(basePath)
}
2013-11-11 17:30:38 -05:00
return archive.TarFilter(basePath, &archive.TarOptions{
Compression: archive.Uncompressed,
Includes: filter,
Recursive: true,
})
}
// Returns true if the container exposes a certain port
func (container *Container) Exposes(p Port) bool {
_, exists := container.Config.ExposedPorts[p]
return exists
}
func (container *Container) GetPtyMaster() (*os.File, error) {
if container.ptyMaster == nil {
return nil, ErrNoTTY
}
if pty, ok := container.ptyMaster.(*os.File); ok {
return pty, nil
}
return nil, ErrNotATTY
}