diff --git a/components/engine/daemon/README.md b/components/engine/daemon/README.md deleted file mode 100644 index 1778983fb3..0000000000 --- a/components/engine/daemon/README.md +++ /dev/null @@ -1,9 +0,0 @@ -This directory contains code pertaining to running containers and storing images - -Code pertaining to running containers: - - - execdriver - -Code pertaining to storing images: - - - graphdriver diff --git a/components/engine/daemon/execdriver/driver.go b/components/engine/daemon/execdriver/driver.go deleted file mode 100644 index 1a626929a4..0000000000 --- a/components/engine/daemon/execdriver/driver.go +++ /dev/null @@ -1,133 +0,0 @@ -package execdriver - -import ( - "errors" - "io" - "os/exec" - "time" - - "github.com/opencontainers/runc/libcontainer" -) - -// Context is a generic key value pair that allows -// arbitrary data to be sent -type Context map[string]string - -// Define error messages -var ( - ErrNotRunning = errors.New("Container is not running") - ErrWaitTimeoutReached = errors.New("Wait timeout reached") - ErrDriverAlreadyRegistered = errors.New("A driver already registered this docker init function") - ErrDriverNotFound = errors.New("The requested docker init has not been found") -) - -// DriverCallback defines a callback function which is used in "Run" and "Exec". -// This allows work to be done in the parent process when the child is passing -// through PreStart, Start and PostStop events. -// Callbacks are provided a processConfig pointer and the pid of the child. -// The channel will be used to notify the OOM events. -type DriverCallback func(processConfig *ProcessConfig, pid int, chOOM <-chan struct{}) error - -// Hooks is a struct containing function pointers to callbacks -// used by any execdriver implementation exploiting hooks capabilities -type Hooks struct { - // PreStart is called before container's CMD/ENTRYPOINT is executed - PreStart []DriverCallback - // Start is called after the container's process is full started - Start DriverCallback - // PostStop is called after the container process exits - PostStop []DriverCallback -} - -// Terminal represents a pseudo TTY, it is for when -// using a container interactively. -type Terminal interface { - io.Closer - Resize(height, width int) error -} - -// Driver is an interface for drivers to implement -// including all basic functions a driver should have -type Driver interface { - // Run executes the process, blocks until the process exits and returns - // the exit code. It's the last stage on Docker side for running a container. - Run(c *Command, pipes *Pipes, hooks Hooks) (ExitStatus, error) - - // Exec executes the process in an existing container, blocks until the - // process exits and returns the exit code. - Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, hooks Hooks) (int, error) - - // Kill sends signals to process in container. - Kill(c *Command, sig int) error - - // Pause pauses a container. - Pause(c *Command) error - - // Unpause unpauses a container. - Unpause(c *Command) error - - // Name returns the name of the driver. - Name() string - - // GetPidsForContainer returns a list of pid for the processes running in a container. - GetPidsForContainer(id string) ([]int, error) - - // Terminate kills a container by sending signal SIGKILL. - Terminate(c *Command) error - - // Clean removes all traces of container exec. - Clean(id string) error - - // Stats returns resource stats for a running container - Stats(id string) (*ResourceStats, error) - - // Update updates resource configs for a container - Update(c *Command) error - - // SupportsHooks refers to the driver capability to exploit pre/post hook functionality - SupportsHooks() bool -} - -// CommonResources contains the resource configs for a driver that are -// common across platforms. -type CommonResources struct { - Memory int64 `json:"memory"` - MemoryReservation int64 `json:"memory_reservation"` - CPUShares int64 `json:"cpu_shares"` - BlkioWeight uint16 `json:"blkio_weight"` -} - -// ResourceStats contains information about resource usage by a container. -type ResourceStats struct { - *libcontainer.Stats - Read time.Time `json:"read"` - MemoryLimit int64 `json:"memory_limit"` - SystemUsage uint64 `json:"system_usage"` -} - -// CommonProcessConfig is the common platform agnostic part of the ProcessConfig -// structure that describes a process that will be run inside a container. -type CommonProcessConfig struct { - exec.Cmd `json:"-"` - - Tty bool `json:"tty"` - Entrypoint string `json:"entrypoint"` - Arguments []string `json:"arguments"` - Terminal Terminal `json:"-"` // standard or tty terminal -} - -// CommonCommand is the common platform agnostic part of the Command structure -// which wraps an os/exec.Cmd to add more metadata -type CommonCommand struct { - ContainerPid int `json:"container_pid"` // the pid for the process inside a container - ID string `json:"id"` - MountLabel string `json:"mount_label"` // TODO Windows. More involved, but can be factored out - Mounts []Mount `json:"mounts"` - Network *Network `json:"network"` - ProcessConfig ProcessConfig `json:"process_config"` // Describes the init process of the container. - ProcessLabel string `json:"process_label"` // TODO Windows. More involved, but can be factored out - Resources *Resources `json:"resources"` - Rootfs string `json:"rootfs"` // root fs of the container - WorkingDir string `json:"working_dir"` - TmpDir string `json:"tmpdir"` // Directory used to store docker tmpdirs. -} diff --git a/components/engine/daemon/execdriver/driver_unix.go b/components/engine/daemon/execdriver/driver_unix.go deleted file mode 100644 index 988737df19..0000000000 --- a/components/engine/daemon/execdriver/driver_unix.go +++ /dev/null @@ -1,323 +0,0 @@ -// +build !windows - -package execdriver - -import ( - "encoding/json" - "io/ioutil" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/docker/docker/daemon/execdriver/native/template" - "github.com/docker/docker/pkg/idtools" - "github.com/docker/docker/pkg/mount" - "github.com/docker/go-units" - "github.com/opencontainers/runc/libcontainer" - "github.com/opencontainers/runc/libcontainer/cgroups/fs" - "github.com/opencontainers/runc/libcontainer/configs" - blkiodev "github.com/opencontainers/runc/libcontainer/configs" -) - -// Mount contains information for a mount operation. -type Mount struct { - Source string `json:"source"` - Destination string `json:"destination"` - Writable bool `json:"writable"` - Data string `json:"data"` - Propagation string `json:"mountpropagation"` -} - -// Resources contains all resource configs for a driver. -// Currently these are all for cgroup configs. -type Resources struct { - CommonResources - - // Fields below here are platform specific - - BlkioWeightDevice []*blkiodev.WeightDevice `json:"blkio_weight_device"` - BlkioThrottleReadBpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_read_bps_device"` - BlkioThrottleWriteBpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_write_bps_device"` - BlkioThrottleReadIOpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_read_iops_device"` - BlkioThrottleWriteIOpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_write_iops_device"` - MemorySwap int64 `json:"memory_swap"` - KernelMemory int64 `json:"kernel_memory"` - CPUQuota int64 `json:"cpu_quota"` - CpusetCpus string `json:"cpuset_cpus"` - CpusetMems string `json:"cpuset_mems"` - CPUPeriod int64 `json:"cpu_period"` - Rlimits []*units.Rlimit `json:"rlimits"` - OomKillDisable bool `json:"oom_kill_disable"` - PidsLimit int64 `json:"pids_limit"` - MemorySwappiness int64 `json:"memory_swappiness"` -} - -// ProcessConfig is the platform specific structure that describes a process -// that will be run inside a container. -type ProcessConfig struct { - CommonProcessConfig - - // Fields below here are platform specific - Privileged bool `json:"privileged"` - User string `json:"user"` - Console string `json:"-"` // dev/console path -} - -// Ipc settings of the container -// It is for IPC namespace setting. Usually different containers -// have their own IPC namespace, however this specifies to use -// an existing IPC namespace. -// You can join the host's or a container's IPC namespace. -type Ipc struct { - ContainerID string `json:"container_id"` // id of the container to join ipc. - HostIpc bool `json:"host_ipc"` -} - -// Pid settings of the container -// It is for PID namespace setting. Usually different containers -// have their own PID namespace, however this specifies to use -// an existing PID namespace. -// Joining the host's PID namespace is currently the only supported -// option. -type Pid struct { - HostPid bool `json:"host_pid"` -} - -// UTS settings of the container -// It is for UTS namespace setting. Usually different containers -// have their own UTS namespace, however this specifies to use -// an existing UTS namespace. -// Joining the host's UTS namespace is currently the only supported -// option. -type UTS struct { - HostUTS bool `json:"host_uts"` -} - -// Network settings of the container -type Network struct { - Mtu int `json:"mtu"` - ContainerID string `json:"container_id"` // id of the container to join network. - NamespacePath string `json:"namespace_path"` - HostNetworking bool `json:"host_networking"` -} - -// Command wraps an os/exec.Cmd to add more metadata -type Command struct { - CommonCommand - - // Fields below here are platform specific - - AllowedDevices []*configs.Device `json:"allowed_devices"` - AppArmorProfile string `json:"apparmor_profile"` - AutoCreatedDevices []*configs.Device `json:"autocreated_devices"` - CapAdd []string `json:"cap_add"` - CapDrop []string `json:"cap_drop"` - CgroupParent string `json:"cgroup_parent"` // The parent cgroup for this command. - GIDMapping []idtools.IDMap `json:"gidmapping"` - GroupAdd []string `json:"group_add"` - Ipc *Ipc `json:"ipc"` - OomScoreAdj int `json:"oom_score_adj"` - Pid *Pid `json:"pid"` - ReadonlyRootfs bool `json:"readonly_rootfs"` - RemappedRoot *User `json:"remap_root"` - SeccompProfile string `json:"seccomp_profile"` - UIDMapping []idtools.IDMap `json:"uidmapping"` - UTS *UTS `json:"uts"` - NoNewPrivileges bool `json:"no_new_privileges"` -} - -// SetRootPropagation sets the root mount propagation mode. -func SetRootPropagation(config *configs.Config, propagation int) { - config.RootPropagation = propagation -} - -// InitContainer is the initialization of a container config. -// It returns the initial configs for a container. It's mostly -// defined by the default template. -func InitContainer(c *Command) *configs.Config { - container := template.New() - - container.Hostname = getEnv("HOSTNAME", c.ProcessConfig.Env) - container.Cgroups.Name = c.ID - container.Cgroups.Resources.AllowedDevices = c.AllowedDevices - container.Devices = filterDevices(c.AutoCreatedDevices, (c.RemappedRoot.UID != 0)) - container.Rootfs = c.Rootfs - container.Readonlyfs = c.ReadonlyRootfs - // This can be overridden later by driver during mount setup based - // on volume options - SetRootPropagation(container, mount.RPRIVATE) - container.Cgroups.Parent = c.CgroupParent - - // check to see if we are running in ramdisk to disable pivot root - container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" - - return container -} - -func filterDevices(devices []*configs.Device, userNamespacesEnabled bool) []*configs.Device { - if !userNamespacesEnabled { - return devices - } - - filtered := []*configs.Device{} - // if we have user namespaces enabled, these devices will not be created - // because of the mknod limitation in the kernel for an unprivileged process. - // Rather, they will be bind-mounted, which will only work if they exist; - // check for existence and remove non-existent entries from the list - for _, device := range devices { - if _, err := os.Stat(device.Path); err == nil { - filtered = append(filtered, device) - } - } - return filtered -} - -func getEnv(key string, env []string) string { - for _, pair := range env { - parts := strings.SplitN(pair, "=", 2) - if parts[0] == key { - return parts[1] - } - } - return "" -} - -// SetupCgroups setups cgroup resources for a container. -func SetupCgroups(container *configs.Config, c *Command) error { - if c.Resources != nil { - container.Cgroups.Resources.CpuShares = c.Resources.CPUShares - container.Cgroups.Resources.Memory = c.Resources.Memory - container.Cgroups.Resources.MemoryReservation = c.Resources.MemoryReservation - container.Cgroups.Resources.MemorySwap = c.Resources.MemorySwap - container.Cgroups.Resources.KernelMemory = c.Resources.KernelMemory - container.Cgroups.Resources.CpusetCpus = c.Resources.CpusetCpus - container.Cgroups.Resources.CpusetMems = c.Resources.CpusetMems - container.Cgroups.Resources.CpuPeriod = c.Resources.CPUPeriod - container.Cgroups.Resources.CpuQuota = c.Resources.CPUQuota - container.Cgroups.Resources.BlkioWeight = c.Resources.BlkioWeight - container.Cgroups.Resources.BlkioWeightDevice = c.Resources.BlkioWeightDevice - container.Cgroups.Resources.BlkioThrottleReadBpsDevice = c.Resources.BlkioThrottleReadBpsDevice - container.Cgroups.Resources.BlkioThrottleWriteBpsDevice = c.Resources.BlkioThrottleWriteBpsDevice - container.Cgroups.Resources.BlkioThrottleReadIOPSDevice = c.Resources.BlkioThrottleReadIOpsDevice - container.Cgroups.Resources.BlkioThrottleWriteIOPSDevice = c.Resources.BlkioThrottleWriteIOpsDevice - container.Cgroups.Resources.OomKillDisable = c.Resources.OomKillDisable - container.Cgroups.Resources.PidsLimit = c.Resources.PidsLimit - container.Cgroups.Resources.MemorySwappiness = c.Resources.MemorySwappiness - } - - return nil -} - -// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo. -func getNetworkInterfaceStats(interfaceName string) (*libcontainer.NetworkInterface, error) { - out := &libcontainer.NetworkInterface{Name: interfaceName} - // This can happen if the network runtime information is missing - possible if the - // container was created by an old version of libcontainer. - if interfaceName == "" { - return out, nil - } - type netStatsPair struct { - // Where to write the output. - Out *uint64 - // The network stats file to read. - File string - } - // Ingress for host veth is from the container. Hence tx_bytes stat on the host veth is actually number of bytes received by the container. - netStats := []netStatsPair{ - {Out: &out.RxBytes, File: "tx_bytes"}, - {Out: &out.RxPackets, File: "tx_packets"}, - {Out: &out.RxErrors, File: "tx_errors"}, - {Out: &out.RxDropped, File: "tx_dropped"}, - - {Out: &out.TxBytes, File: "rx_bytes"}, - {Out: &out.TxPackets, File: "rx_packets"}, - {Out: &out.TxErrors, File: "rx_errors"}, - {Out: &out.TxDropped, File: "rx_dropped"}, - } - for _, netStat := range netStats { - data, err := readSysfsNetworkStats(interfaceName, netStat.File) - if err != nil { - return nil, err - } - *(netStat.Out) = data - } - return out, nil -} - -// Reads the specified statistics available under /sys/class/net//statistics -func readSysfsNetworkStats(ethInterface, statsFile string) (uint64, error) { - data, err := ioutil.ReadFile(filepath.Join("/sys/class/net", ethInterface, "statistics", statsFile)) - if err != nil { - return 0, err - } - return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64) -} - -// Stats collects all the resource usage information from a container. -func Stats(containerDir string, containerMemoryLimit int64, machineMemory int64) (*ResourceStats, error) { - f, err := os.Open(filepath.Join(containerDir, "state.json")) - if err != nil { - return nil, err - } - defer f.Close() - - type network struct { - Type string - HostInterfaceName string - } - - state := struct { - CgroupPaths map[string]string `json:"cgroup_paths"` - Networks []network - }{} - - if err := json.NewDecoder(f).Decode(&state); err != nil { - return nil, err - } - now := time.Now() - - mgr := fs.Manager{Paths: state.CgroupPaths} - cstats, err := mgr.GetStats() - if err != nil { - return nil, err - } - stats := &libcontainer.Stats{CgroupStats: cstats} - // if the container does not have any memory limit specified set the - // limit to the machines memory - memoryLimit := containerMemoryLimit - if memoryLimit == 0 { - memoryLimit = machineMemory - } - for _, iface := range state.Networks { - switch iface.Type { - case "veth": - istats, err := getNetworkInterfaceStats(iface.HostInterfaceName) - if err != nil { - return nil, err - } - stats.Interfaces = append(stats.Interfaces, istats) - } - } - return &ResourceStats{ - Stats: stats, - Read: now, - MemoryLimit: memoryLimit, - }, nil -} - -// User contains the uid and gid representing a Unix user -type User struct { - UID int `json:"root_uid"` - GID int `json:"root_gid"` -} - -// ExitStatus provides exit reasons for a container. -type ExitStatus struct { - // The exit code with which the container exited. - ExitCode int - - // Whether the container encountered an OOM. - OOMKilled bool -} diff --git a/components/engine/daemon/execdriver/driver_windows.go b/components/engine/daemon/execdriver/driver_windows.go deleted file mode 100644 index 4b2505623f..0000000000 --- a/components/engine/daemon/execdriver/driver_windows.go +++ /dev/null @@ -1,66 +0,0 @@ -package execdriver - -import "github.com/docker/go-connections/nat" - -// Mount contains information for a mount operation. -type Mount struct { - Source string `json:"source"` - Destination string `json:"destination"` - Writable bool `json:"writable"` -} - -// Resources contains all resource configs for a driver. -// Currently these are all for cgroup configs. -type Resources struct { - CommonResources - - // Fields below here are platform specific -} - -// ProcessConfig is the platform specific structure that describes a process -// that will be run inside a container. -type ProcessConfig struct { - CommonProcessConfig - - // Fields below here are platform specific - ConsoleSize [2]int `json:"-"` // h,w of initial console size -} - -// Network settings of the container -type Network struct { - Interface *NetworkInterface `json:"interface"` - ContainerID string `json:"container_id"` // id of the container to join network. -} - -// NetworkInterface contains network configs for a driver -type NetworkInterface struct { - MacAddress string `json:"mac"` - Bridge string `json:"bridge"` - IPAddress string `json:"ip"` - - // PortBindings is the port mapping between the exposed port in the - // container and the port on the host. - PortBindings nat.PortMap `json:"port_bindings"` -} - -// Command wraps an os/exec.Cmd to add more metadata -type Command struct { - CommonCommand - - // Fields below here are platform specific - - FirstStart bool `json:"first_start"` // Optimization for first boot of Windows - Hostname string `json:"hostname"` // Windows sets the hostname in the execdriver - LayerFolder string `json:"layer_folder"` // Layer folder for a command - LayerPaths []string `json:"layer_paths"` // Layer paths for a command - Isolation string `json:"isolation"` // Isolation technology for the container - ArgsEscaped bool `json:"args_escaped"` // True if args are already escaped - HvPartition bool `json:"hv_partition"` // True if it's an hypervisor partition - EpList []string `json:"endpoints"` // List of network endpoints for HNS -} - -// ExitStatus provides exit reasons for a container. -type ExitStatus struct { - // The exit code with which the container exited. - ExitCode int -} diff --git a/components/engine/daemon/execdriver/execdrivers/execdrivers_freebsd.go b/components/engine/daemon/execdriver/execdrivers/execdrivers_freebsd.go deleted file mode 100644 index 6a65201081..0000000000 --- a/components/engine/daemon/execdriver/execdrivers/execdrivers_freebsd.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build freebsd - -package execdrivers - -import ( - "fmt" - - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/pkg/sysinfo" -) - -// NewDriver returns a new execdriver.Driver from the given name configured with the provided options. -func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { - return nil, fmt.Errorf("jail driver not yet supported on FreeBSD") -} diff --git a/components/engine/daemon/execdriver/execdrivers/execdrivers_linux.go b/components/engine/daemon/execdriver/execdrivers/execdrivers_linux.go deleted file mode 100644 index 1ba57f4173..0000000000 --- a/components/engine/daemon/execdriver/execdrivers/execdrivers_linux.go +++ /dev/null @@ -1,16 +0,0 @@ -// +build linux - -package execdrivers - -import ( - "path" - - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/daemon/execdriver/native" - "github.com/docker/docker/pkg/sysinfo" -) - -// NewDriver returns a new execdriver.Driver from the given name configured with the provided options. -func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { - return native.NewDriver(path.Join(root, "execdriver", "native"), options) -} diff --git a/components/engine/daemon/execdriver/execdrivers/execdrivers_windows.go b/components/engine/daemon/execdriver/execdrivers/execdrivers_windows.go deleted file mode 100644 index bc4e64f623..0000000000 --- a/components/engine/daemon/execdriver/execdrivers/execdrivers_windows.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build windows - -package execdrivers - -import ( - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/daemon/execdriver/windows" - "github.com/docker/docker/pkg/sysinfo" -) - -// NewDriver returns a new execdriver.Driver from the given name configured with the provided options. -func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { - return windows.NewDriver(root, options) -} diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go deleted file mode 100644 index d85898f673..0000000000 --- a/components/engine/daemon/execdriver/native/create.go +++ /dev/null @@ -1,514 +0,0 @@ -// +build linux,cgo - -package native - -import ( - "fmt" - "path/filepath" - "strings" - "syscall" - - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/pkg/mount" - "github.com/docker/docker/profiles/seccomp" - - "github.com/docker/docker/volume" - "github.com/opencontainers/runc/libcontainer/apparmor" - "github.com/opencontainers/runc/libcontainer/configs" - "github.com/opencontainers/runc/libcontainer/devices" -) - -// createContainer populates and configures the container type with the -// data provided by the execdriver.Command -func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks) (container *configs.Config, err error) { - container = execdriver.InitContainer(c) - - if err := d.createIpc(container, c); err != nil { - return nil, err - } - - if err := d.createPid(container, c); err != nil { - return nil, err - } - - if err := d.createUTS(container, c); err != nil { - return nil, err - } - - if err := d.setupRemappedRoot(container, c); err != nil { - return nil, err - } - - if err := d.createNetwork(container, c, hooks); err != nil { - return nil, err - } - - if c.ProcessConfig.Privileged { - if !container.Readonlyfs { - // clear readonly for /sys - for i := range container.Mounts { - if container.Mounts[i].Destination == "/sys" { - container.Mounts[i].Flags &= ^syscall.MS_RDONLY - } - } - container.ReadonlyPaths = nil - } - - // clear readonly for cgroup - for i := range container.Mounts { - if container.Mounts[i].Device == "cgroup" { - container.Mounts[i].Flags &= ^syscall.MS_RDONLY - } - } - - container.MaskPaths = nil - if err := d.setPrivileged(container); err != nil { - return nil, err - } - } else { - if err := d.setCapabilities(container, c); err != nil { - return nil, err - } - - if c.SeccompProfile == "" { - container.Seccomp, err = seccomp.GetDefaultProfile() - if err != nil { - return nil, err - } - } - } - // add CAP_ prefix to all caps for new libcontainer update to match - // the spec format. - for i, s := range container.Capabilities { - if !strings.HasPrefix(s, "CAP_") { - container.Capabilities[i] = fmt.Sprintf("CAP_%s", s) - } - } - container.AdditionalGroups = c.GroupAdd - - if c.AppArmorProfile != "" { - container.AppArmorProfile = c.AppArmorProfile - } - - if c.SeccompProfile != "" && c.SeccompProfile != "unconfined" { - container.Seccomp, err = seccomp.LoadProfile(c.SeccompProfile) - if err != nil { - return nil, err - } - } - - if err := execdriver.SetupCgroups(container, c); err != nil { - return nil, err - } - - container.OomScoreAdj = c.OomScoreAdj - - if container.Readonlyfs { - for i := range container.Mounts { - switch container.Mounts[i].Destination { - case "/proc", "/dev", "/dev/pts", "/dev/mqueue": - continue - } - container.Mounts[i].Flags |= syscall.MS_RDONLY - } - - /* These paths must be remounted as r/o */ - container.ReadonlyPaths = append(container.ReadonlyPaths, "/dev") - } - - if err := d.setupMounts(container, c); err != nil { - return nil, err - } - - d.setupLabels(container, c) - d.setupRlimits(container, c) - - container.NoNewPrivileges = c.NoNewPrivileges - return container, nil -} - -func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command, hooks execdriver.Hooks) error { - if c.Network == nil { - return nil - } - if c.Network.ContainerID != "" { - d.Lock() - active := d.activeContainers[c.Network.ContainerID] - d.Unlock() - - if active == nil { - return fmt.Errorf("%s is not a valid running container to join", c.Network.ContainerID) - } - - state, err := active.State() - if err != nil { - return err - } - - container.Namespaces.Add(configs.NEWNET, state.NamespacePaths[configs.NEWNET]) - return nil - } - - if c.Network.NamespacePath != "" { - container.Namespaces.Add(configs.NEWNET, c.Network.NamespacePath) - return nil - } - // only set up prestart hook if the namespace path is not set (this should be - // all cases *except* for --net=host shared networking) - container.Hooks = &configs.Hooks{ - Prestart: []configs.Hook{ - configs.NewFunctionHook(func(s configs.HookState) error { - if len(hooks.PreStart) > 0 { - for _, fnHook := range hooks.PreStart { - // A closed channel for OOM is returned here as it will be - // non-blocking and return the correct result when read. - chOOM := make(chan struct{}) - close(chOOM) - if err := fnHook(&c.ProcessConfig, s.Pid, chOOM); err != nil { - return err - } - } - } - return nil - }), - }, - } - return nil -} - -func (d *Driver) createIpc(container *configs.Config, c *execdriver.Command) error { - if c.Ipc.HostIpc { - container.Namespaces.Remove(configs.NEWIPC) - return nil - } - - if c.Ipc.ContainerID != "" { - d.Lock() - active := d.activeContainers[c.Ipc.ContainerID] - d.Unlock() - - if active == nil { - return fmt.Errorf("%s is not a valid running container to join", c.Ipc.ContainerID) - } - - state, err := active.State() - if err != nil { - return err - } - container.Namespaces.Add(configs.NEWIPC, state.NamespacePaths[configs.NEWIPC]) - } - - return nil -} - -func (d *Driver) createPid(container *configs.Config, c *execdriver.Command) error { - if c.Pid.HostPid { - container.Namespaces.Remove(configs.NEWPID) - return nil - } - - return nil -} - -func (d *Driver) createUTS(container *configs.Config, c *execdriver.Command) error { - if c.UTS.HostUTS { - container.Namespaces.Remove(configs.NEWUTS) - container.Hostname = "" - return nil - } - - return nil -} - -func (d *Driver) setupRemappedRoot(container *configs.Config, c *execdriver.Command) error { - if c.RemappedRoot.UID == 0 { - container.Namespaces.Remove(configs.NEWUSER) - return nil - } - - // convert the Docker daemon id map to the libcontainer variant of the same struct - // this keeps us from having to import libcontainer code across Docker client + daemon packages - cuidMaps := []configs.IDMap{} - cgidMaps := []configs.IDMap{} - for _, idMap := range c.UIDMapping { - cuidMaps = append(cuidMaps, configs.IDMap(idMap)) - } - for _, idMap := range c.GIDMapping { - cgidMaps = append(cgidMaps, configs.IDMap(idMap)) - } - container.UidMappings = cuidMaps - container.GidMappings = cgidMaps - - for _, node := range container.Devices { - node.Uid = uint32(c.RemappedRoot.UID) - node.Gid = uint32(c.RemappedRoot.GID) - } - // TODO: until a kernel/mount solution exists for handling remount in a user namespace, - // we must clear the readonly flag for the cgroups mount (@mrunalp concurs) - for i := range container.Mounts { - if container.Mounts[i].Device == "cgroup" { - container.Mounts[i].Flags &= ^syscall.MS_RDONLY - } - } - - return nil -} - -func (d *Driver) setPrivileged(container *configs.Config) (err error) { - container.Capabilities = execdriver.GetAllCapabilities() - container.Cgroups.Resources.AllowAllDevices = true - - hostDevices, err := devices.HostDevices() - if err != nil { - return err - } - container.Devices = hostDevices - - if apparmor.IsEnabled() { - container.AppArmorProfile = "unconfined" - } - return nil -} - -func (d *Driver) setCapabilities(container *configs.Config, c *execdriver.Command) (err error) { - container.Capabilities, err = execdriver.TweakCapabilities(container.Capabilities, c.CapAdd, c.CapDrop) - return err -} - -func (d *Driver) setupRlimits(container *configs.Config, c *execdriver.Command) { - if c.Resources == nil { - return - } - - for _, rlimit := range c.Resources.Rlimits { - container.Rlimits = append(container.Rlimits, configs.Rlimit{ - Type: rlimit.Type, - Hard: rlimit.Hard, - Soft: rlimit.Soft, - }) - } -} - -// If rootfs mount propagation is RPRIVATE, that means all the volumes are -// going to be private anyway. There is no need to apply per volume -// propagation on top. This is just an optimization so that cost of per volume -// propagation is paid only if user decides to make some volume non-private -// which will force rootfs mount propagation to be non RPRIVATE. -func checkResetVolumePropagation(container *configs.Config) { - if container.RootPropagation != mount.RPRIVATE { - return - } - for _, m := range container.Mounts { - m.PropagationFlags = nil - } -} - -func getMountInfo(mountinfo []*mount.Info, dir string) *mount.Info { - for _, m := range mountinfo { - if m.Mountpoint == dir { - return m - } - } - return nil -} - -// Get the source mount point of directory passed in as argument. Also return -// optional fields. -func getSourceMount(source string) (string, string, error) { - // Ensure any symlinks are resolved. - sourcePath, err := filepath.EvalSymlinks(source) - if err != nil { - return "", "", err - } - - mountinfos, err := mount.GetMounts() - if err != nil { - return "", "", err - } - - mountinfo := getMountInfo(mountinfos, sourcePath) - if mountinfo != nil { - return sourcePath, mountinfo.Optional, nil - } - - path := sourcePath - for { - path = filepath.Dir(path) - - mountinfo = getMountInfo(mountinfos, path) - if mountinfo != nil { - return path, mountinfo.Optional, nil - } - - if path == "/" { - break - } - } - - // If we are here, we did not find parent mount. Something is wrong. - return "", "", fmt.Errorf("Could not find source mount of %s", source) -} - -// Ensure mount point on which path is mounted, is shared. -func ensureShared(path string) error { - sharedMount := false - - sourceMount, optionalOpts, err := getSourceMount(path) - if err != nil { - return err - } - // Make sure source mount point is shared. - optsSplit := strings.Split(optionalOpts, " ") - for _, opt := range optsSplit { - if strings.HasPrefix(opt, "shared:") { - sharedMount = true - break - } - } - - if !sharedMount { - return fmt.Errorf("Path %s is mounted on %s but it is not a shared mount.", path, sourceMount) - } - return nil -} - -// Ensure mount point on which path is mounted, is either shared or slave. -func ensureSharedOrSlave(path string) error { - sharedMount := false - slaveMount := false - - sourceMount, optionalOpts, err := getSourceMount(path) - if err != nil { - return err - } - // Make sure source mount point is shared. - optsSplit := strings.Split(optionalOpts, " ") - for _, opt := range optsSplit { - if strings.HasPrefix(opt, "shared:") { - sharedMount = true - break - } else if strings.HasPrefix(opt, "master:") { - slaveMount = true - break - } - } - - if !sharedMount && !slaveMount { - return fmt.Errorf("Path %s is mounted on %s but it is not a shared or slave mount.", path, sourceMount) - } - return nil -} - -func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) error { - userMounts := make(map[string]struct{}) - for _, m := range c.Mounts { - userMounts[m.Destination] = struct{}{} - } - - // Filter out mounts that are overridden by user supplied mounts - var defaultMounts []*configs.Mount - _, mountDev := userMounts["/dev"] - for _, m := range container.Mounts { - if _, ok := userMounts[m.Destination]; !ok { - if mountDev && strings.HasPrefix(m.Destination, "/dev/") { - container.Devices = nil - continue - } - defaultMounts = append(defaultMounts, m) - } - } - container.Mounts = defaultMounts - - mountPropagationMap := map[string]int{ - "private": mount.PRIVATE, - "rprivate": mount.RPRIVATE, - "shared": mount.SHARED, - "rshared": mount.RSHARED, - "slave": mount.SLAVE, - "rslave": mount.RSLAVE, - } - - for _, m := range c.Mounts { - for _, cm := range container.Mounts { - if cm.Destination == m.Destination { - return fmt.Errorf("Duplicate mount point '%s'", m.Destination) - } - } - - if m.Source == "tmpfs" { - var ( - data = "size=65536k" - flags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV - err error - ) - if m.Data != "" { - flags, data, err = mount.ParseTmpfsOptions(m.Data) - if err != nil { - return err - } - } - container.Mounts = append(container.Mounts, &configs.Mount{ - Source: m.Source, - Destination: m.Destination, - Data: data, - Device: "tmpfs", - Flags: flags, - PropagationFlags: []int{mountPropagationMap[volume.DefaultPropagationMode]}, - }) - continue - } - flags := syscall.MS_BIND | syscall.MS_REC - var pFlag int - if !m.Writable { - flags |= syscall.MS_RDONLY - } - - // Determine property of RootPropagation based on volume - // properties. If a volume is shared, then keep root propagation - // shared. This should work for slave and private volumes too. - // - // For slave volumes, it can be either [r]shared/[r]slave. - // - // For private volumes any root propagation value should work. - - pFlag = mountPropagationMap[m.Propagation] - if pFlag == mount.SHARED || pFlag == mount.RSHARED { - if err := ensureShared(m.Source); err != nil { - return err - } - rootpg := container.RootPropagation - if rootpg != mount.SHARED && rootpg != mount.RSHARED { - execdriver.SetRootPropagation(container, mount.SHARED) - } - } else if pFlag == mount.SLAVE || pFlag == mount.RSLAVE { - if err := ensureSharedOrSlave(m.Source); err != nil { - return err - } - rootpg := container.RootPropagation - if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE { - execdriver.SetRootPropagation(container, mount.RSLAVE) - } - } - - mount := &configs.Mount{ - Source: m.Source, - Destination: m.Destination, - Device: "bind", - Flags: flags, - } - - if pFlag != 0 { - mount.PropagationFlags = []int{pFlag} - } - - container.Mounts = append(container.Mounts, mount) - } - - checkResetVolumePropagation(container) - return nil -} - -func (d *Driver) setupLabels(container *configs.Config, c *execdriver.Command) { - container.ProcessLabel = c.ProcessLabel - container.MountLabel = c.MountLabel -} diff --git a/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go deleted file mode 100644 index a765305013..0000000000 --- a/components/engine/daemon/execdriver/native/driver.go +++ /dev/null @@ -1,606 +0,0 @@ -// +build linux,cgo - -package native - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" - "sync" - "syscall" - "time" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/pkg/parsers" - "github.com/docker/docker/pkg/pools" - "github.com/docker/docker/pkg/reexec" - sysinfo "github.com/docker/docker/pkg/system" - "github.com/docker/docker/pkg/term" - aaprofile "github.com/docker/docker/profiles/apparmor" - "github.com/opencontainers/runc/libcontainer" - "github.com/opencontainers/runc/libcontainer/apparmor" - "github.com/opencontainers/runc/libcontainer/cgroups/systemd" - "github.com/opencontainers/runc/libcontainer/configs" - "github.com/opencontainers/runc/libcontainer/system" - "github.com/opencontainers/runc/libcontainer/utils" -) - -// Define constants for native driver -const ( - DriverName = "native" - Version = "0.2" - - defaultApparmorProfile = "docker-default" -) - -// Driver contains all information for native driver, -// it implements execdriver.Driver. -type Driver struct { - root string - activeContainers map[string]libcontainer.Container - machineMemory int64 - factory libcontainer.Factory - sync.Mutex -} - -// NewDriver returns a new native driver, called from NewDriver of execdriver. -func NewDriver(root string, options []string) (*Driver, error) { - meminfo, err := sysinfo.ReadMemInfo() - if err != nil { - return nil, err - } - - if err := sysinfo.MkdirAll(root, 0700); err != nil { - return nil, err - } - - if apparmor.IsEnabled() { - if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil { - apparmorProfiles := []string{defaultApparmorProfile} - - // Allow daemon to run if loading failed, but are active - // (possibly through another run, manually, or via system startup) - for _, policy := range apparmorProfiles { - if err := aaprofile.IsLoaded(policy); err != nil { - return nil, fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy) - } - } - } - } - - // choose cgroup manager - // this makes sure there are no breaking changes to people - // who upgrade from versions without native.cgroupdriver opt - cgm := libcontainer.Cgroupfs - - // parse the options - for _, option := range options { - key, val, err := parsers.ParseKeyValueOpt(option) - if err != nil { - return nil, err - } - key = strings.ToLower(key) - switch key { - case "native.cgroupdriver": - // override the default if they set options - switch val { - case "systemd": - if systemd.UseSystemd() { - cgm = libcontainer.SystemdCgroups - } else { - // warn them that they chose the wrong driver - logrus.Warn("You cannot use systemd as native.cgroupdriver, using cgroupfs instead") - } - case "cgroupfs": - cgm = libcontainer.Cgroupfs - default: - return nil, fmt.Errorf("Unknown native.cgroupdriver given %q. try cgroupfs or systemd", val) - } - default: - return nil, fmt.Errorf("Unknown option %s\n", key) - } - } - - f, err := libcontainer.New( - root, - cgm, - libcontainer.InitPath(reexec.Self(), DriverName), - ) - if err != nil { - return nil, err - } - - return &Driver{ - root: root, - activeContainers: make(map[string]libcontainer.Container), - machineMemory: meminfo.MemTotal, - factory: f, - }, nil -} - -// Run implements the exec driver Driver interface, -// it calls libcontainer APIs to run a container. -func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) { - destroyed := false - var err error - c.TmpDir, err = ioutil.TempDir("", c.ID) - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - defer os.RemoveAll(c.TmpDir) - - // take the Command and populate the libcontainer.Config from it - container, err := d.createContainer(c, hooks) - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - - p := &libcontainer.Process{ - Args: append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...), - Env: c.ProcessConfig.Env, - Cwd: c.WorkingDir, - User: c.ProcessConfig.User, - } - - wg := sync.WaitGroup{} - writers, err := setupPipes(container, &c.ProcessConfig, p, pipes, &wg) - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - - cont, err := d.factory.Create(c.ID, container) - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - - if err := cont.Start(p); err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - d.Lock() - d.activeContainers[c.ID] = cont - d.Unlock() - defer func() { - if !destroyed { - cont.Destroy() - } - d.cleanContainer(c.ID) - }() - - //close the write end of any opened pipes now that they are dup'ed into the container - for _, writer := range writers { - writer.Close() - } - // 'oom' is used to emit 'oom' events to the eventstream, 'oomKilled' is used - // to set the 'OOMKilled' flag in state - oom := notifyOnOOM(cont) - oomKilled := notifyOnOOM(cont) - if hooks.Start != nil { - pid, err := p.Pid() - if err != nil { - p.Signal(os.Kill) - p.Wait() - return execdriver.ExitStatus{ExitCode: -1}, err - } - hooks.Start(&c.ProcessConfig, pid, oom) - } - - waitF := p.Wait - if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) { - // we need such hack for tracking processes with inherited fds, - // because cmd.Wait() waiting for all streams to be copied - waitF = waitInPIDHost(p, cont) - } - ps, err := waitF() - if err != nil { - execErr, ok := err.(*exec.ExitError) - if !ok { - return execdriver.ExitStatus{ExitCode: -1}, err - } - ps = execErr.ProcessState - } - // wait for all IO goroutine copiers to finish - wg.Wait() - - cont.Destroy() - destroyed = true - // oomKilled will have an oom event if any process within the container was - // OOM killed at any time, not only if the init process OOMed. - // - // Perhaps we only want the OOMKilled flag to be set if the OOM - // resulted in a container death, but there isn't a good way to do this - // because the kernel's cgroup oom notification does not provide information - // such as the PID. This could be heuristically done by checking that the OOM - // happened within some very small time slice for the container dying (and - // optionally exit-code 137), but I don't think the cgroup oom notification - // can be used to reliably determine this - // - // Even if there were multiple OOMs, it's sufficient to read one value - // because libcontainer's oom notify will discard the channel after the - // cgroup is destroyed - _, oomKill := <-oomKilled - return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil -} - -// notifyOnOOM returns a channel that signals if the container received an OOM notification -// for any process. If it is unable to subscribe to OOM notifications then a closed -// channel is returned as it will be non-blocking and return the correct result when read. -func notifyOnOOM(container libcontainer.Container) <-chan struct{} { - oom, err := container.NotifyOOM() - if err != nil { - logrus.Warnf("Your kernel does not support OOM notifications: %s", err) - c := make(chan struct{}) - close(c) - return c - } - return oom -} - -func killCgroupProcs(c libcontainer.Container) { - var procs []*os.Process - if err := c.Pause(); err != nil { - logrus.Warn(err) - } - pids, err := c.Processes() - if err != nil { - // don't care about childs if we can't get them, this is mostly because cgroup already deleted - logrus.Warnf("Failed to get processes from container %s: %v", c.ID(), err) - } - for _, pid := range pids { - if p, err := os.FindProcess(pid); err == nil { - procs = append(procs, p) - if err := p.Kill(); err != nil { - logrus.Warn(err) - } - } - } - if err := c.Resume(); err != nil { - logrus.Warn(err) - } - for _, p := range procs { - if _, err := p.Wait(); err != nil { - logrus.Warn(err) - } - } -} - -func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) { - return func() (*os.ProcessState, error) { - pid, err := p.Pid() - if err != nil { - return nil, err - } - - process, err := os.FindProcess(pid) - s, err := process.Wait() - if err != nil { - execErr, ok := err.(*exec.ExitError) - if !ok { - return s, err - } - s = execErr.ProcessState - } - killCgroupProcs(c) - p.Wait() - return s, err - } -} - -// Kill implements the exec driver Driver interface. -func (d *Driver) Kill(c *execdriver.Command, sig int) error { - d.Lock() - active := d.activeContainers[c.ID] - d.Unlock() - if active == nil { - return fmt.Errorf("active container for %s does not exist", c.ID) - } - state, err := active.State() - if err != nil { - return err - } - if state.InitProcessPid == -1 { - return fmt.Errorf("avoid sending signal %d to container %s with pid -1", sig, c.ID) - } - return syscall.Kill(state.InitProcessPid, syscall.Signal(sig)) -} - -// Pause implements the exec driver Driver interface, -// it calls libcontainer API to pause a container. -func (d *Driver) Pause(c *execdriver.Command) error { - d.Lock() - active := d.activeContainers[c.ID] - d.Unlock() - if active == nil { - return fmt.Errorf("active container for %s does not exist", c.ID) - } - return active.Pause() -} - -// Unpause implements the exec driver Driver interface, -// it calls libcontainer API to unpause a container. -func (d *Driver) Unpause(c *execdriver.Command) error { - d.Lock() - active := d.activeContainers[c.ID] - d.Unlock() - if active == nil { - return fmt.Errorf("active container for %s does not exist", c.ID) - } - return active.Resume() -} - -// Terminate implements the exec driver Driver interface. -func (d *Driver) Terminate(c *execdriver.Command) error { - defer d.cleanContainer(c.ID) - container, err := d.factory.Load(c.ID) - if err != nil { - return err - } - defer container.Destroy() - state, err := container.State() - if err != nil { - return err - } - pid := state.InitProcessPid - currentStartTime, err := system.GetProcessStartTime(pid) - if err != nil { - return err - } - if state.InitProcessStartTime == currentStartTime { - err = syscall.Kill(pid, 9) - syscall.Wait4(pid, nil, 0, nil) - } - return err -} - -// Name implements the exec driver Driver interface. -func (d *Driver) Name() string { - return fmt.Sprintf("%s-%s", DriverName, Version) -} - -// GetPidsForContainer implements the exec driver Driver interface. -func (d *Driver) GetPidsForContainer(id string) ([]int, error) { - d.Lock() - active := d.activeContainers[id] - d.Unlock() - - if active == nil { - return nil, fmt.Errorf("active container for %s does not exist", id) - } - return active.Processes() -} - -func (d *Driver) cleanContainer(id string) error { - d.Lock() - delete(d.activeContainers, id) - d.Unlock() - return os.RemoveAll(filepath.Join(d.root, id)) -} - -func (d *Driver) createContainerRoot(id string) error { - return os.MkdirAll(filepath.Join(d.root, id), 0655) -} - -// Clean implements the exec driver Driver interface. -func (d *Driver) Clean(id string) error { - return os.RemoveAll(filepath.Join(d.root, id)) -} - -// Stats implements the exec driver Driver interface. -func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) { - d.Lock() - c := d.activeContainers[id] - d.Unlock() - if c == nil { - return nil, execdriver.ErrNotRunning - } - now := time.Now() - stats, err := c.Stats() - if err != nil { - return nil, err - } - memoryLimit := c.Config().Cgroups.Resources.Memory - // if the container does not have any memory limit specified set the - // limit to the machines memory - if memoryLimit == 0 { - memoryLimit = d.machineMemory - } - return &execdriver.ResourceStats{ - Stats: stats, - Read: now, - MemoryLimit: memoryLimit, - }, nil -} - -// Update updates configs for a container -func (d *Driver) Update(c *execdriver.Command) error { - d.Lock() - cont := d.activeContainers[c.ID] - d.Unlock() - if cont == nil { - return execdriver.ErrNotRunning - } - config := cont.Config() - if err := execdriver.SetupCgroups(&config, c); err != nil { - return err - } - - if err := cont.Set(config); err != nil { - return err - } - - return nil -} - -// TtyConsole implements the exec driver Terminal interface. -type TtyConsole struct { - console libcontainer.Console -} - -// NewTtyConsole returns a new TtyConsole struct. -func NewTtyConsole(console libcontainer.Console, pipes *execdriver.Pipes, wg *sync.WaitGroup) (*TtyConsole, error) { - tty := &TtyConsole{ - console: console, - } - - if err := tty.AttachPipes(pipes, wg); err != nil { - tty.Close() - return nil, err - } - - return tty, nil -} - -// Resize implements Resize method of Terminal interface -func (t *TtyConsole) Resize(h, w int) error { - return term.SetWinsize(t.console.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) -} - -// AttachPipes attaches given pipes to TtyConsole -func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes, wg *sync.WaitGroup) error { - wg.Add(1) - go func() { - defer wg.Done() - if wb, ok := pipes.Stdout.(interface { - CloseWriters() error - }); ok { - defer wb.CloseWriters() - } - - pools.Copy(pipes.Stdout, t.console) - }() - - if pipes.Stdin != nil { - go func() { - pools.Copy(t.console, pipes.Stdin) - - pipes.Stdin.Close() - }() - } - - return nil -} - -// Close implements Close method of Terminal interface -func (t *TtyConsole) Close() error { - return t.console.Close() -} - -func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes, wg *sync.WaitGroup) ([]io.WriteCloser, error) { - - writers := []io.WriteCloser{} - - rootuid, err := container.HostUID() - if err != nil { - return writers, err - } - - if processConfig.Tty { - cons, err := p.NewConsole(rootuid) - if err != nil { - return writers, err - } - term, err := NewTtyConsole(cons, pipes, wg) - if err != nil { - return writers, err - } - processConfig.Terminal = term - return writers, nil - } - // not a tty--set up stdio pipes - term := &execdriver.StdConsole{} - processConfig.Terminal = term - - // if we are not in a user namespace, there is no reason to go through - // the hassle of setting up os-level pipes with proper (remapped) ownership - // so we will do the prior shortcut for non-userns containers - if rootuid == 0 { - p.Stdout = pipes.Stdout - p.Stderr = pipes.Stderr - - r, w, err := os.Pipe() - if err != nil { - return writers, err - } - if pipes.Stdin != nil { - go func() { - io.Copy(w, pipes.Stdin) - w.Close() - }() - p.Stdin = r - } - return writers, nil - } - - // if we have user namespaces enabled (rootuid != 0), we will set - // up os pipes for stderr, stdout, stdin so we can chown them to - // the proper ownership to allow for proper access to the underlying - // fds - var fds []uintptr - - copyPipes := func(out io.Writer, in io.ReadCloser) { - defer wg.Done() - io.Copy(out, in) - in.Close() - } - - //setup stdout - r, w, err := os.Pipe() - if err != nil { - w.Close() - return writers, err - } - writers = append(writers, w) - fds = append(fds, r.Fd(), w.Fd()) - if pipes.Stdout != nil { - wg.Add(1) - go copyPipes(pipes.Stdout, r) - } - term.Closers = append(term.Closers, r) - p.Stdout = w - - //setup stderr - r, w, err = os.Pipe() - if err != nil { - w.Close() - return writers, err - } - writers = append(writers, w) - fds = append(fds, r.Fd(), w.Fd()) - if pipes.Stderr != nil { - wg.Add(1) - go copyPipes(pipes.Stderr, r) - } - term.Closers = append(term.Closers, r) - p.Stderr = w - - //setup stdin - r, w, err = os.Pipe() - if err != nil { - r.Close() - return writers, err - } - fds = append(fds, r.Fd(), w.Fd()) - if pipes.Stdin != nil { - go func() { - io.Copy(w, pipes.Stdin) - w.Close() - }() - p.Stdin = r - } - for _, fd := range fds { - if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil { - return writers, fmt.Errorf("Failed to chown pipes fd: %v", err) - } - } - return writers, nil -} - -// SupportsHooks implements the execdriver Driver interface. -// The libcontainer/runC-based native execdriver does exploit the hook mechanism -func (d *Driver) SupportsHooks() bool { - return true -} diff --git a/components/engine/daemon/execdriver/native/driver_unsupported.go b/components/engine/daemon/execdriver/native/driver_unsupported.go deleted file mode 100644 index 0162f1bf77..0000000000 --- a/components/engine/daemon/execdriver/native/driver_unsupported.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build !linux - -package native - -import ( - "fmt" - - "github.com/docker/docker/daemon/execdriver" -) - -// NewDriver returns a new native driver, called from NewDriver of execdriver. -func NewDriver(root string, options []string) (execdriver.Driver, error) { - return nil, fmt.Errorf("native driver not supported on non-linux") -} diff --git a/components/engine/daemon/execdriver/native/driver_unsupported_nocgo.go b/components/engine/daemon/execdriver/native/driver_unsupported_nocgo.go deleted file mode 100644 index bc18d1ba5b..0000000000 --- a/components/engine/daemon/execdriver/native/driver_unsupported_nocgo.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build linux,!cgo - -package native - -import ( - "fmt" - - "github.com/docker/docker/daemon/execdriver" -) - -// NewDriver returns a new native driver, called from NewDriver of execdriver. -func NewDriver(root string, options []string) (execdriver.Driver, error) { - return nil, fmt.Errorf("native driver not supported on non-linux") -} diff --git a/components/engine/daemon/execdriver/native/exec.go b/components/engine/daemon/execdriver/native/exec.go deleted file mode 100644 index d62fe5f405..0000000000 --- a/components/engine/daemon/execdriver/native/exec.go +++ /dev/null @@ -1,96 +0,0 @@ -// +build linux - -package native - -import ( - "fmt" - "os" - "os/exec" - "strings" - "sync" - "syscall" - - "github.com/docker/docker/daemon/execdriver" - "github.com/opencontainers/runc/libcontainer" - // Blank import 'nsenter' so that init in that package will call c - // function 'nsexec()' to do 'setns' before Go runtime take over, - // it's used for join to exist ns like 'docker exec' command. - _ "github.com/opencontainers/runc/libcontainer/nsenter" - "github.com/opencontainers/runc/libcontainer/utils" -) - -// Exec implements the exec driver Driver interface, -// it calls libcontainer APIs to execute a container. -func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) { - active := d.activeContainers[c.ID] - if active == nil { - return -1, fmt.Errorf("No active container exists with ID %s", c.ID) - } - - user := processConfig.User - if c.RemappedRoot.UID != 0 && user == "" { - //if user namespaces are enabled, set user explicitly so uid/gid is set to 0 - //otherwise we end up with the overflow id and no permissions (65534) - user = "0" - } - - p := &libcontainer.Process{ - Args: append([]string{processConfig.Entrypoint}, processConfig.Arguments...), - Env: c.ProcessConfig.Env, - Cwd: c.WorkingDir, - User: user, - } - - if processConfig.Privileged { - p.Capabilities = execdriver.GetAllCapabilities() - } - // add CAP_ prefix to all caps for new libcontainer update to match - // the spec format. - for i, s := range p.Capabilities { - if !strings.HasPrefix(s, "CAP_") { - p.Capabilities[i] = fmt.Sprintf("CAP_%s", s) - } - } - - config := active.Config() - wg := sync.WaitGroup{} - writers, err := setupPipes(&config, processConfig, p, pipes, &wg) - if err != nil { - return -1, err - } - - if err := active.Start(p); err != nil { - return -1, err - } - //close the write end of any opened pipes now that they are dup'ed into the container - for _, writer := range writers { - writer.Close() - } - - if hooks.Start != nil { - pid, err := p.Pid() - if err != nil { - p.Signal(os.Kill) - p.Wait() - return -1, err - } - - // A closed channel for OOM is returned here as it will be - // non-blocking and return the correct result when read. - chOOM := make(chan struct{}) - close(chOOM) - hooks.Start(&c.ProcessConfig, pid, chOOM) - } - - ps, err := p.Wait() - if err != nil { - exitErr, ok := err.(*exec.ExitError) - if !ok { - return -1, err - } - ps = exitErr.ProcessState - } - // wait for all IO goroutine copiers to finish - wg.Wait() - return utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), nil -} diff --git a/components/engine/daemon/execdriver/native/init.go b/components/engine/daemon/execdriver/native/init.go deleted file mode 100644 index 015b1678c3..0000000000 --- a/components/engine/daemon/execdriver/native/init.go +++ /dev/null @@ -1,40 +0,0 @@ -// +build linux - -package native - -import ( - "fmt" - "os" - "runtime" - - "github.com/docker/docker/pkg/reexec" - "github.com/opencontainers/runc/libcontainer" -) - -func init() { - reexec.Register(DriverName, initializer) -} - -func fatal(err error) { - if lerr, ok := err.(libcontainer.Error); ok { - lerr.Detail(os.Stderr) - os.Exit(1) - } - - fmt.Fprintln(os.Stderr, err) - os.Exit(1) -} - -func initializer() { - runtime.GOMAXPROCS(1) - runtime.LockOSThread() - factory, err := libcontainer.New("") - if err != nil { - fatal(err) - } - if err := factory.StartInitialization(); err != nil { - fatal(err) - } - - panic("unreachable") -} diff --git a/components/engine/daemon/execdriver/native/template/default_template_linux.go b/components/engine/daemon/execdriver/native/template/default_template_linux.go deleted file mode 100644 index 073bcac9a3..0000000000 --- a/components/engine/daemon/execdriver/native/template/default_template_linux.go +++ /dev/null @@ -1,106 +0,0 @@ -package template - -import ( - "syscall" - - "github.com/opencontainers/runc/libcontainer/apparmor" - "github.com/opencontainers/runc/libcontainer/configs" -) - -const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV - -// New returns the docker default configuration for libcontainer -func New() *configs.Config { - container := &configs.Config{ - Capabilities: []string{ - "CHOWN", - "DAC_OVERRIDE", - "FSETID", - "FOWNER", - "MKNOD", - "NET_RAW", - "SETGID", - "SETUID", - "SETFCAP", - "SETPCAP", - "NET_BIND_SERVICE", - "SYS_CHROOT", - "KILL", - "AUDIT_WRITE", - }, - Namespaces: configs.Namespaces([]configs.Namespace{ - {Type: "NEWNS"}, - {Type: "NEWUTS"}, - {Type: "NEWIPC"}, - {Type: "NEWPID"}, - {Type: "NEWNET"}, - {Type: "NEWUSER"}, - }), - Cgroups: &configs.Cgroup{ - ScopePrefix: "docker", // systemd only - Resources: &configs.Resources{ - AllowAllDevices: false, - MemorySwappiness: -1, - }, - }, - Mounts: []*configs.Mount{ - { - Source: "proc", - Destination: "/proc", - Device: "proc", - Flags: defaultMountFlags, - }, - { - Source: "tmpfs", - Destination: "/dev", - Device: "tmpfs", - Flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, - Data: "mode=755", - }, - { - Source: "devpts", - Destination: "/dev/pts", - Device: "devpts", - Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, - Data: "newinstance,ptmxmode=0666,mode=0620,gid=5", - }, - { - Source: "mqueue", - Destination: "/dev/mqueue", - Device: "mqueue", - Flags: defaultMountFlags, - }, - { - Source: "sysfs", - Destination: "/sys", - Device: "sysfs", - Flags: defaultMountFlags | syscall.MS_RDONLY, - }, - { - Source: "cgroup", - Destination: "/sys/fs/cgroup", - Device: "cgroup", - Flags: defaultMountFlags | syscall.MS_RDONLY, - }, - }, - MaskPaths: []string{ - "/proc/kcore", - "/proc/latency_stats", - "/proc/timer_stats", - }, - ReadonlyPaths: []string{ - "/proc/asound", - "/proc/bus", - "/proc/fs", - "/proc/irq", - "/proc/sys", - "/proc/sysrq-trigger", - }, - } - - if apparmor.IsEnabled() { - container.AppArmorProfile = "docker-default" - } - - return container -} diff --git a/components/engine/daemon/execdriver/native/template/default_template_unsupported.go b/components/engine/daemon/execdriver/native/template/default_template_unsupported.go deleted file mode 100644 index b01ec1d70d..0000000000 --- a/components/engine/daemon/execdriver/native/template/default_template_unsupported.go +++ /dev/null @@ -1,3 +0,0 @@ -// +build !linux - -package template diff --git a/components/engine/daemon/execdriver/pipes.go b/components/engine/daemon/execdriver/pipes.go deleted file mode 100644 index fdddaee9b0..0000000000 --- a/components/engine/daemon/execdriver/pipes.go +++ /dev/null @@ -1,24 +0,0 @@ -package execdriver - -import ( - "io" -) - -// Pipes is a wrapper around a container's output for -// stdin, stdout, stderr -type Pipes struct { - Stdin io.ReadCloser - Stdout, Stderr io.Writer -} - -// NewPipes returns a wrapper around a container's output -func NewPipes(stdin io.ReadCloser, stdout, stderr io.Writer, useStdin bool) *Pipes { - p := &Pipes{ - Stdout: stdout, - Stderr: stderr, - } - if useStdin { - p.Stdin = stdin - } - return p -} diff --git a/components/engine/daemon/execdriver/termconsole.go b/components/engine/daemon/execdriver/termconsole.go deleted file mode 100644 index 888836020f..0000000000 --- a/components/engine/daemon/execdriver/termconsole.go +++ /dev/null @@ -1,55 +0,0 @@ -package execdriver - -import ( - "io" - "os/exec" -) - -// StdConsole defines standard console operations for execdriver -type StdConsole struct { - // Closers holds io.Closer references for closing at terminal close time - Closers []io.Closer -} - -// NewStdConsole returns a new StdConsole struct -func NewStdConsole(processConfig *ProcessConfig, pipes *Pipes) (*StdConsole, error) { - std := &StdConsole{} - - if err := std.AttachPipes(&processConfig.Cmd, pipes); err != nil { - return nil, err - } - return std, nil -} - -// AttachPipes attaches given pipes to exec.Cmd -func (s *StdConsole) AttachPipes(command *exec.Cmd, pipes *Pipes) error { - command.Stdout = pipes.Stdout - command.Stderr = pipes.Stderr - - if pipes.Stdin != nil { - stdin, err := command.StdinPipe() - if err != nil { - return err - } - - go func() { - defer stdin.Close() - io.Copy(stdin, pipes.Stdin) - }() - } - return nil -} - -// Resize implements Resize method of Terminal interface -func (s *StdConsole) Resize(h, w int) error { - // we do not need to resize a non tty - return nil -} - -// Close implements Close method of Terminal interface -func (s *StdConsole) Close() error { - for _, c := range s.Closers { - c.Close() - } - return nil -} diff --git a/components/engine/daemon/execdriver/utils_unix.go b/components/engine/daemon/execdriver/utils_unix.go deleted file mode 100644 index e9f48e5c69..0000000000 --- a/components/engine/daemon/execdriver/utils_unix.go +++ /dev/null @@ -1,125 +0,0 @@ -// +build !windows - -package execdriver - -import ( - "fmt" - "strings" - - "github.com/docker/docker/pkg/stringutils" - "github.com/syndtr/gocapability/capability" -) - -var capabilityList Capabilities - -func init() { - last := capability.CAP_LAST_CAP - // hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap - if last == capability.Cap(63) { - last = capability.CAP_BLOCK_SUSPEND - } - for _, cap := range capability.List() { - if cap > last { - continue - } - capabilityList = append(capabilityList, - &CapabilityMapping{ - Key: strings.ToUpper(cap.String()), - Value: cap, - }, - ) - } -} - -type ( - // CapabilityMapping maps linux capability name to its value of capability.Cap type - // Capabilities is one of the security systems in Linux Security Module (LSM) - // framework provided by the kernel. - // For more details on capabilities, see http://man7.org/linux/man-pages/man7/capabilities.7.html - CapabilityMapping struct { - Key string `json:"key,omitempty"` - Value capability.Cap `json:"value,omitempty"` - } - // Capabilities contains all CapabilityMapping - Capabilities []*CapabilityMapping -) - -// String returns of CapabilityMapping -func (c *CapabilityMapping) String() string { - return c.Key -} - -// GetCapability returns CapabilityMapping which contains specific key -func GetCapability(key string) *CapabilityMapping { - for _, capp := range capabilityList { - if capp.Key == key { - cpy := *capp - return &cpy - } - } - return nil -} - -// GetAllCapabilities returns all of the capabilities -func GetAllCapabilities() []string { - output := make([]string, len(capabilityList)) - for i, capability := range capabilityList { - output[i] = capability.String() - } - return output -} - -// TweakCapabilities can tweak capabilities by adding or dropping capabilities -// based on the basics capabilities. -func TweakCapabilities(basics, adds, drops []string) ([]string, error) { - var ( - newCaps []string - allCaps = GetAllCapabilities() - ) - - // look for invalid cap in the drop list - for _, cap := range drops { - if strings.ToLower(cap) == "all" { - continue - } - if !stringutils.InSlice(allCaps, cap) { - return nil, fmt.Errorf("Unknown capability drop: %q", cap) - } - } - - // handle --cap-add=all - if stringutils.InSlice(adds, "all") { - basics = allCaps - } - - if !stringutils.InSlice(drops, "all") { - for _, cap := range basics { - // skip `all` already handled above - if strings.ToLower(cap) == "all" { - continue - } - - // if we don't drop `all`, add back all the non-dropped caps - if !stringutils.InSlice(drops, cap) { - newCaps = append(newCaps, strings.ToUpper(cap)) - } - } - } - - for _, cap := range adds { - // skip `all` already handled above - if strings.ToLower(cap) == "all" { - continue - } - - if !stringutils.InSlice(allCaps, cap) { - return nil, fmt.Errorf("Unknown capability to add: %q", cap) - } - - // add cap if not already in the list - if !stringutils.InSlice(newCaps, cap) { - newCaps = append(newCaps, strings.ToUpper(cap)) - } - } - return newCaps, nil -} diff --git a/components/engine/daemon/execdriver/windows/clean.go b/components/engine/daemon/execdriver/windows/clean.go deleted file mode 100644 index 3a99cf86aa..0000000000 --- a/components/engine/daemon/execdriver/windows/clean.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build windows - -package windows - -// Clean implements the exec driver Driver interface. -func (d *Driver) Clean(id string) error { - return nil -} diff --git a/components/engine/daemon/execdriver/windows/commandlinebuilder.go b/components/engine/daemon/execdriver/windows/commandlinebuilder.go deleted file mode 100644 index 3ce8f46fb3..0000000000 --- a/components/engine/daemon/execdriver/windows/commandlinebuilder.go +++ /dev/null @@ -1,36 +0,0 @@ -//+build windows - -package windows - -import ( - "errors" - "syscall" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/daemon/execdriver" -) - -// createCommandLine creates a command line from the Entrypoint and args -// of the ProcessConfig. It escapes the arguments if they are not already -// escaped -func createCommandLine(processConfig *execdriver.ProcessConfig, alreadyEscaped bool) (commandLine string, err error) { - // While this should get caught earlier, just in case, validate that we - // have something to run. - if processConfig.Entrypoint == "" { - return "", errors.New("No entrypoint specified") - } - - // Build the command line of the process - commandLine = processConfig.Entrypoint - logrus.Debugf("Entrypoint: %s", processConfig.Entrypoint) - for _, arg := range processConfig.Arguments { - logrus.Debugf("appending %s", arg) - if !alreadyEscaped { - arg = syscall.EscapeArg(arg) - } - commandLine += " " + arg - } - - logrus.Debugf("commandLine: %s", commandLine) - return commandLine, nil -} diff --git a/components/engine/daemon/execdriver/windows/exec.go b/components/engine/daemon/execdriver/windows/exec.go deleted file mode 100644 index c9129a8bbd..0000000000 --- a/components/engine/daemon/execdriver/windows/exec.go +++ /dev/null @@ -1,89 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - "syscall" - - "github.com/Microsoft/hcsshim" - "github.com/Sirupsen/logrus" - "github.com/docker/docker/daemon/execdriver" -) - -// Exec implements the exec driver Driver interface. -func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) { - - var ( - term execdriver.Terminal - err error - exitCode int32 - ) - - active := d.activeContainers[c.ID] - if active == nil { - return -1, fmt.Errorf("Exec - No active container exists with ID %s", c.ID) - } - - createProcessParms := hcsshim.CreateProcessParams{ - EmulateConsole: processConfig.Tty, // Note NOT c.ProcessConfig.Tty - WorkingDirectory: c.WorkingDir, - } - - // Configure the environment for the process // Note NOT c.ProcessConfig.Env - createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env) - - // Create the commandline for the process // Note NOT c.ProcessConfig - createProcessParms.CommandLine, err = createCommandLine(processConfig, false) - - if err != nil { - return -1, err - } - - // Start the command running in the container. - pid, stdin, stdout, stderr, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !processConfig.Tty, createProcessParms) - if err != nil { - // TODO Windows: TP4 Workaround. In Hyper-V containers, there is a limitation - // of one exec per container. This should be fixed post TP4. CreateProcessInComputeSystem - // will return a specific error which we handle here to give a good error message - // back to the user instead of an inactionable "An invalid argument was supplied" - if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err == hcsshim.WSAEINVAL { - return -1, fmt.Errorf("The limit of docker execs per Hyper-V container has been exceeded") - } - logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) - return -1, err - } - - // Now that the process has been launched, begin copying data to and from - // the named pipes for the std handles. - setupPipes(stdin, stdout, stderr, pipes) - - // Note NOT c.ProcessConfig.Tty - if processConfig.Tty { - term = NewTtyConsole(c.ID, pid) - } else { - term = NewStdConsole() - } - processConfig.Terminal = term - - // Invoke the start callback - if hooks.Start != nil { - // A closed channel for OOM is returned here as it will be - // non-blocking and return the correct result when read. - chOOM := make(chan struct{}) - close(chOOM) - hooks.Start(&c.ProcessConfig, int(pid), chOOM) - } - - if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite); err != nil { - if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err == syscall.ERROR_BROKEN_PIPE { - logrus.Debugf("Exiting Run() after WaitForProcessInComputeSystem failed with recognised error %s", err) - return hcsshim.WaitErrExecFailed, nil - } - logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err) - return -1, err - } - - logrus.Debugln("Exiting Run()", c.ID) - return int(exitCode), nil -} diff --git a/components/engine/daemon/execdriver/windows/getpids.go b/components/engine/daemon/execdriver/windows/getpids.go deleted file mode 100644 index 5b1a9eb59a..0000000000 --- a/components/engine/daemon/execdriver/windows/getpids.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build windows - -package windows - -import "fmt" - -// GetPidsForContainer implements the exec driver Driver interface. -func (d *Driver) GetPidsForContainer(id string) ([]int, error) { - // TODO Windows: Implementation required. - return nil, fmt.Errorf("GetPidsForContainer: GetPidsForContainer() not implemented") -} diff --git a/components/engine/daemon/execdriver/windows/namedpipes.go b/components/engine/daemon/execdriver/windows/namedpipes.go deleted file mode 100644 index 14e744210b..0000000000 --- a/components/engine/daemon/execdriver/windows/namedpipes.go +++ /dev/null @@ -1,63 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - "io" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/daemon/execdriver" -) - -// General comment. Handling I/O for a container is very different to Linux. -// We use a named pipe to HCS to copy I/O both in and out of the container, -// very similar to how docker daemon communicates with a CLI. - -// startStdinCopy asynchronously copies an io.Reader to the container's -// process's stdin pipe and closes the pipe when there is no more data to copy. -func startStdinCopy(dst io.WriteCloser, src io.Reader) { - - // Anything that comes from the client stdin should be copied - // across to the stdin named pipe of the container. - go func() { - defer dst.Close() - bytes, err := io.Copy(dst, src) - log := fmt.Sprintf("Copied %d bytes from stdin.", bytes) - if err != nil { - log = log + " err=" + err.Error() - } - logrus.Debugf(log) - }() -} - -// startStdouterrCopy asynchronously copies data from the container's process's -// stdout or stderr pipe to an io.Writer and closes the pipe when there is no -// more data to copy. -func startStdouterrCopy(dst io.Writer, src io.ReadCloser, name string) { - // Anything that comes from the container named pipe stdout/err should be copied - // across to the stdout/err of the client - go func() { - defer src.Close() - bytes, err := io.Copy(dst, src) - log := fmt.Sprintf("Copied %d bytes from %s.", bytes, name) - if err != nil { - log = log + " err=" + err.Error() - } - logrus.Debugf(log) - }() -} - -// setupPipes starts the asynchronous copying of data to and from the named -// pipes used byt he HCS for the std handles. -func setupPipes(stdin io.WriteCloser, stdout, stderr io.ReadCloser, pipes *execdriver.Pipes) { - if stdin != nil { - startStdinCopy(stdin, pipes.Stdin) - } - if stdout != nil { - startStdouterrCopy(pipes.Stdout, stdout, "stdout") - } - if stderr != nil { - startStdouterrCopy(pipes.Stderr, stderr, "stderr") - } -} diff --git a/components/engine/daemon/execdriver/windows/pauseunpause.go b/components/engine/daemon/execdriver/windows/pauseunpause.go deleted file mode 100644 index c408669c3c..0000000000 --- a/components/engine/daemon/execdriver/windows/pauseunpause.go +++ /dev/null @@ -1,19 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - - "github.com/docker/docker/daemon/execdriver" -) - -// Pause implements the exec driver Driver interface. -func (d *Driver) Pause(c *execdriver.Command) error { - return fmt.Errorf("Windows: Containers cannot be paused") -} - -// Unpause implements the exec driver Driver interface. -func (d *Driver) Unpause(c *execdriver.Command) error { - return fmt.Errorf("Windows: Containers cannot be paused") -} diff --git a/components/engine/daemon/execdriver/windows/run.go b/components/engine/daemon/execdriver/windows/run.go deleted file mode 100644 index 8c13448b15..0000000000 --- a/components/engine/daemon/execdriver/windows/run.go +++ /dev/null @@ -1,366 +0,0 @@ -// +build windows - -package windows - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - "syscall" - "time" - - "github.com/Microsoft/hcsshim" - "github.com/Sirupsen/logrus" - "github.com/docker/docker/daemon/execdriver" -) - -// defaultContainerNAT is the default name of the container NAT device that is -// preconfigured on the server. -const defaultContainerNAT = "ContainerNAT" - -// Win32 error codes that are used for various workarounds -// These really should be ALL_CAPS to match golangs syscall library and standard -// Win32 error conventions, but golint insists on CamelCase. -const ( - CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string - ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started - ErrorBadPathname = syscall.Errno(161) // The specified path is invalid - ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object -) - -type layer struct { - ID string - Path string -} - -type portBinding struct { - Protocol string - InternalPort int - ExternalPort int -} - -type natSettings struct { - Name string - PortBindings []portBinding -} - -type networkConnection struct { - NetworkName string - // TODO Windows: Add Ip4Address string to this structure when hooked up in - // docker CLI. This is present in the HCS JSON handler. - EnableNat bool - Nat natSettings -} -type networkSettings struct { - MacAddress string -} - -type device struct { - DeviceType string - Connection interface{} - Settings interface{} -} - -type mappedDir struct { - HostPath string - ContainerPath string - ReadOnly bool -} - -type containerInit struct { - SystemType string // HCS requires this to be hard-coded to "Container" - Name string // Name of the container. We use the docker ID. - Owner string // The management platform that created this container - IsDummy bool // Used for development purposes. - VolumePath string // Windows volume path for scratch space - Devices []device // Devices used by the container - IgnoreFlushesDuringBoot bool // Optimization hint for container startup in Windows - LayerFolderPath string // Where the layer folders are located - Layers []layer // List of storage layers - ProcessorWeight int64 `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be omitted and HCS will default. - HostName string // Hostname - MappedDirectories []mappedDir // List of mapped directories (volumes/mounts) - SandboxPath string // Location of unmounted sandbox (used for Hyper-V containers, not Windows Server containers) - HvPartition bool // True if it a Hyper-V Container - EndpointList []string // List of endpoints to be attached to container -} - -// defaultOwner is a tag passed to HCS to allow it to differentiate between -// container creator management stacks. We hard code "docker" in the case -// of docker. -const defaultOwner = "docker" - -// Run implements the exec driver Driver interface -func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) { - - var ( - term execdriver.Terminal - err error - ) - - // Allocate Network only if there is no network interface - cu := &containerInit{ - SystemType: "Container", - Name: c.ID, - Owner: defaultOwner, - IsDummy: dummyMode, - VolumePath: c.Rootfs, - IgnoreFlushesDuringBoot: c.FirstStart, - LayerFolderPath: c.LayerFolder, - ProcessorWeight: c.Resources.CPUShares, - HostName: c.Hostname, - EndpointList: c.EpList, - } - - cu.HvPartition = c.HvPartition - - if cu.HvPartition { - cu.SandboxPath = filepath.Dir(c.LayerFolder) - } else { - cu.VolumePath = c.Rootfs - cu.LayerFolderPath = c.LayerFolder - } - - for _, layerPath := range c.LayerPaths { - _, filename := filepath.Split(layerPath) - g, err := hcsshim.NameToGuid(filename) - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - cu.Layers = append(cu.Layers, layer{ - ID: g.ToString(), - Path: layerPath, - }) - } - - // Add the mounts (volumes, bind mounts etc) to the structure - mds := make([]mappedDir, len(c.Mounts)) - for i, mount := range c.Mounts { - mds[i] = mappedDir{ - HostPath: mount.Source, - ContainerPath: mount.Destination, - ReadOnly: !mount.Writable} - } - cu.MappedDirectories = mds - - // TODO Windows. At some point, when there is CLI on docker run to - // enable the IP Address of the container to be passed into docker run, - // the IP Address needs to be wired through to HCS in the JSON. It - // would be present in c.Network.Interface.IPAddress. See matching - // TODO in daemon\container_windows.go, function populateCommand. - - if c.Network.Interface != nil { - - var pbs []portBinding - - // Enumerate through the port bindings specified by the user and convert - // them into the internal structure matching the JSON blob that can be - // understood by the HCS. - for i, v := range c.Network.Interface.PortBindings { - proto := strings.ToUpper(i.Proto()) - if proto != "TCP" && proto != "UDP" { - return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid protocol %s", i.Proto()) - } - - if len(v) > 1 { - return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support more than one host port in NAT settings") - } - - for _, v2 := range v { - var ( - iPort, ePort int - err error - ) - if len(v2.HostIP) != 0 { - return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support host IP addresses in NAT settings") - } - if ePort, err = strconv.Atoi(v2.HostPort); err != nil { - return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid container port %s: %s", v2.HostPort, err) - } - if iPort, err = strconv.Atoi(i.Port()); err != nil { - return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid internal port %s: %s", i.Port(), err) - } - if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 { - return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("specified NAT port is not in allowed range") - } - pbs = append(pbs, - portBinding{ExternalPort: ePort, - InternalPort: iPort, - Protocol: proto}) - } - } - - // TODO Windows: TP3 workaround. Allow the user to override the name of - // the Container NAT device through an environment variable. This will - // ultimately be a global daemon parameter on Windows, similar to -b - // for the name of the virtual switch (aka bridge). - cn := os.Getenv("DOCKER_CONTAINER_NAT") - if len(cn) == 0 { - cn = defaultContainerNAT - } - - dev := device{ - DeviceType: "Network", - Connection: &networkConnection{ - NetworkName: c.Network.Interface.Bridge, - // TODO Windows: Fixme, next line. Needs HCS fix. - EnableNat: false, - Nat: natSettings{ - Name: cn, - PortBindings: pbs, - }, - }, - } - - if c.Network.Interface.MacAddress != "" { - windowsStyleMAC := strings.Replace( - c.Network.Interface.MacAddress, ":", "-", -1) - dev.Settings = networkSettings{ - MacAddress: windowsStyleMAC, - } - } - cu.Devices = append(cu.Devices, dev) - } else { - logrus.Debugln("No network interface") - } - - configurationb, err := json.Marshal(cu) - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - - configuration := string(configurationb) - - // TODO Windows TP5 timeframe. Remove when TP4 is no longer supported. - // The following a workaround for Windows TP4 which has a networking - // bug which fairly frequently returns an error. Back off and retry. - maxAttempts := 5 - for i := 0; i < maxAttempts; i++ { - err = hcsshim.CreateComputeSystem(c.ID, configuration) - if err == nil { - break - } - - if !TP4RetryHack { - return execdriver.ExitStatus{ExitCode: -1}, err - } - - if herr, ok := err.(*hcsshim.HcsError); ok { - if herr.Err != syscall.ERROR_NOT_FOUND && // Element not found - herr.Err != syscall.ERROR_FILE_NOT_FOUND && // The system cannot find the file specified - herr.Err != ErrorNoNetwork && // The network is not present or not started - herr.Err != ErrorBadPathname && // The specified path is invalid - herr.Err != CoEClassstring && // Invalid class string - herr.Err != ErrorInvalidObject { // The object identifier does not represent a valid object - logrus.Debugln("Failed to create temporary container ", err) - return execdriver.ExitStatus{ExitCode: -1}, err - } - logrus.Warnf("Invoking Windows TP4 retry hack (%d of %d)", i, maxAttempts-1) - time.Sleep(50 * time.Millisecond) - } - } - - // Start the container - logrus.Debugln("Starting container ", c.ID) - err = hcsshim.StartComputeSystem(c.ID) - if err != nil { - logrus.Errorf("Failed to start compute system: %s", err) - return execdriver.ExitStatus{ExitCode: -1}, err - } - defer func() { - // Stop the container - if forceKill { - logrus.Debugf("Forcibly terminating container %s", c.ID) - if err := hcsshim.TerminateComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil { - logrus.Warnf("Ignoring error from TerminateComputeSystem %s", err) - } - } else { - logrus.Debugf("Shutting down container %s", c.ID) - if err := hcsshim.ShutdownComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil { - if herr, ok := err.(*hcsshim.HcsError); !ok || - (herr.Err != hcsshim.ERROR_SHUTDOWN_IN_PROGRESS && - herr.Err != ErrorBadPathname && - herr.Err != syscall.ERROR_PATH_NOT_FOUND) { - logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err) - } - } - } - }() - - createProcessParms := hcsshim.CreateProcessParams{ - EmulateConsole: c.ProcessConfig.Tty, - WorkingDirectory: c.WorkingDir, - ConsoleSize: c.ProcessConfig.ConsoleSize, - } - - // Configure the environment for the process - createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env) - - createProcessParms.CommandLine, err = createCommandLine(&c.ProcessConfig, c.ArgsEscaped) - - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - - // Start the command running in the container. - pid, stdin, stdout, stderr, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !c.ProcessConfig.Tty, createProcessParms) - if err != nil { - logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) - return execdriver.ExitStatus{ExitCode: -1}, err - } - - // Now that the process has been launched, begin copying data to and from - // the named pipes for the std handles. - setupPipes(stdin, stdout, stderr, pipes) - - //Save the PID as we'll need this in Kill() - logrus.Debugf("PID %d", pid) - c.ContainerPid = int(pid) - - if c.ProcessConfig.Tty { - term = NewTtyConsole(c.ID, pid) - } else { - term = NewStdConsole() - } - c.ProcessConfig.Terminal = term - - // Maintain our list of active containers. We'll need this later for exec - // and other commands. - d.Lock() - d.activeContainers[c.ID] = &activeContainer{ - command: c, - } - d.Unlock() - - if hooks.Start != nil { - // A closed channel for OOM is returned here as it will be - // non-blocking and return the correct result when read. - chOOM := make(chan struct{}) - close(chOOM) - hooks.Start(&c.ProcessConfig, int(pid), chOOM) - } - - exitCode, err := hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite) - if err != nil { - if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE { - logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err) - } - // Do NOT return err here as the container would have - // started, otherwise docker will deadlock. It's perfectly legitimate - // for WaitForProcessInComputeSystem to fail in situations such - // as the container being killed on another thread. - return execdriver.ExitStatus{ExitCode: hcsshim.WaitErrExecFailed}, nil - } - - logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID) - return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil -} - -// SupportsHooks implements the execdriver Driver interface. -// The windows driver does not support the hook mechanism -func (d *Driver) SupportsHooks() bool { - return false -} diff --git a/components/engine/daemon/execdriver/windows/stats.go b/components/engine/daemon/execdriver/windows/stats.go deleted file mode 100644 index 25bd49eaed..0000000000 --- a/components/engine/daemon/execdriver/windows/stats.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - - "github.com/docker/docker/daemon/execdriver" -) - -// Stats implements the exec driver Driver interface. -func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) { - return nil, fmt.Errorf("Windows: Stats not implemented") -} diff --git a/components/engine/daemon/execdriver/windows/stdconsole.go b/components/engine/daemon/execdriver/windows/stdconsole.go deleted file mode 100644 index fb1eda2a20..0000000000 --- a/components/engine/daemon/execdriver/windows/stdconsole.go +++ /dev/null @@ -1,24 +0,0 @@ -// +build windows - -package windows - -// StdConsole is for when using a container non-interactively -type StdConsole struct { -} - -// NewStdConsole returns a new StdConsole struct. -func NewStdConsole() *StdConsole { - return &StdConsole{} -} - -// Resize implements Resize method of Terminal interface. -func (s *StdConsole) Resize(h, w int) error { - // we do not need to resize a non tty - return nil -} - -// Close implements Close method of Terminal interface. -func (s *StdConsole) Close() error { - // nothing to close here - return nil -} diff --git a/components/engine/daemon/execdriver/windows/terminatekill.go b/components/engine/daemon/execdriver/windows/terminatekill.go deleted file mode 100644 index d20b0e2b9d..0000000000 --- a/components/engine/daemon/execdriver/windows/terminatekill.go +++ /dev/null @@ -1,49 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - "syscall" - - "github.com/Microsoft/hcsshim" - "github.com/Sirupsen/logrus" - "github.com/docker/docker/daemon/execdriver" -) - -// Terminate implements the exec driver Driver interface. -func (d *Driver) Terminate(p *execdriver.Command) error { - return kill(p.ID, p.ContainerPid, syscall.SIGTERM) -} - -// Kill implements the exec driver Driver interface. -func (d *Driver) Kill(p *execdriver.Command, sig int) error { - return kill(p.ID, p.ContainerPid, syscall.Signal(sig)) -} - -func kill(id string, pid int, sig syscall.Signal) error { - logrus.Debugf("WindowsExec: kill() id=%s pid=%d sig=%d", id, pid, sig) - var err error - context := fmt.Sprintf("kill: sig=%d pid=%d", sig, pid) - - if sig == syscall.SIGKILL || forceKill { - // Terminate the compute system - if err := hcsshim.TerminateComputeSystem(id, hcsshim.TimeoutInfinite, context); err != nil { - logrus.Errorf("Failed to terminate %s - %q", id, err) - } - - } else { - // Terminate Process - if err = hcsshim.TerminateProcessInComputeSystem(id, uint32(pid)); err != nil { - logrus.Warnf("Failed to terminate pid %d in %s: %q", pid, id, err) - // Ignore errors - err = nil - } - - // Shutdown the compute system - if err := hcsshim.ShutdownComputeSystem(id, hcsshim.TimeoutInfinite, context); err != nil { - logrus.Errorf("Failed to shutdown %s - %q", id, err) - } - } - return err -} diff --git a/components/engine/daemon/execdriver/windows/ttyconsole.go b/components/engine/daemon/execdriver/windows/ttyconsole.go deleted file mode 100644 index 78642f742a..0000000000 --- a/components/engine/daemon/execdriver/windows/ttyconsole.go +++ /dev/null @@ -1,32 +0,0 @@ -// +build windows - -package windows - -import ( - "github.com/Microsoft/hcsshim" -) - -// TtyConsole implements the exec driver Terminal interface. -type TtyConsole struct { - id string - processid uint32 -} - -// NewTtyConsole returns a new TtyConsole struct. -func NewTtyConsole(id string, processid uint32) *TtyConsole { - tty := &TtyConsole{ - id: id, - processid: processid, - } - return tty -} - -// Resize implements Resize method of Terminal interface. -func (t *TtyConsole) Resize(h, w int) error { - return hcsshim.ResizeConsoleInComputeSystem(t.id, t.processid, h, w) -} - -// Close implements Close method of Terminal interface. -func (t *TtyConsole) Close() error { - return nil -} diff --git a/components/engine/daemon/execdriver/windows/unsupported.go b/components/engine/daemon/execdriver/windows/unsupported.go deleted file mode 100644 index 89be21f9a9..0000000000 --- a/components/engine/daemon/execdriver/windows/unsupported.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build !windows - -package windows - -import ( - "fmt" - - "github.com/docker/docker/daemon/execdriver" -) - -// NewDriver returns a new execdriver.Driver -func NewDriver(root, initPath string) (execdriver.Driver, error) { - return nil, fmt.Errorf("Windows driver not supported on non-Windows") -} diff --git a/components/engine/daemon/execdriver/windows/update.go b/components/engine/daemon/execdriver/windows/update.go deleted file mode 100644 index a4c42a6b08..0000000000 --- a/components/engine/daemon/execdriver/windows/update.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build windows - -package windows - -import ( - "github.com/docker/docker/daemon/execdriver" -) - -// Update updates resource configs for a container. -func (d *Driver) Update(c *execdriver.Command) error { - // Updating resource isn't supported on Windows - // but we should return nil for enabling updating container - return nil -} diff --git a/components/engine/daemon/execdriver/windows/windows.go b/components/engine/daemon/execdriver/windows/windows.go deleted file mode 100644 index e6c8f10b91..0000000000 --- a/components/engine/daemon/execdriver/windows/windows.go +++ /dev/null @@ -1,123 +0,0 @@ -// +build windows - -package windows - -import ( - "fmt" - "strings" - "sync" - - "github.com/Microsoft/hcsshim" - "github.com/Sirupsen/logrus" - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/dockerversion" - "github.com/docker/docker/pkg/parsers" - "github.com/docker/engine-api/types/container" -) - -// TP4RetryHack is a hack to retry CreateComputeSystem if it fails with -// known return codes from Windows due to bugs in TP4. -var TP4RetryHack bool - -// This is a daemon development variable only and should not be -// used for running production containers on Windows. -var dummyMode bool - -// This allows the daemon to terminate containers rather than shutdown -// This allows the daemon to force kill (HCS terminate) rather than shutdown -var forceKill bool - -// DefaultIsolation allows users to specify a default isolation technology for -// when running a container on Windows. For example docker daemon -D -// --exec-opt isolation=hyperv will cause Windows to always run containers -// as Hyper-V containers unless otherwise specified. -var DefaultIsolation container.Isolation = "process" - -// Define name and version for windows -var ( - DriverName = "Windows 1854" - Version = dockerversion.Version + " " + dockerversion.GitCommit -) - -type activeContainer struct { - command *execdriver.Command -} - -// Driver contains all information for windows driver, -// it implements execdriver.Driver -type Driver struct { - root string - activeContainers map[string]*activeContainer - sync.Mutex -} - -// Name implements the exec driver Driver interface. -func (d *Driver) Name() string { - return fmt.Sprintf("\n Name: %s\n Build: %s \n Default Isolation: %s", DriverName, Version, DefaultIsolation) -} - -// NewDriver returns a new windows driver, called from NewDriver of execdriver. -func NewDriver(root string, options []string) (*Driver, error) { - - for _, option := range options { - key, val, err := parsers.ParseKeyValueOpt(option) - if err != nil { - return nil, err - } - key = strings.ToLower(key) - switch key { - - case "dummy": - switch val { - case "1": - dummyMode = true - logrus.Warn("Using dummy mode in Windows exec driver. This is for development use only!") - } - - case "forcekill": - switch val { - case "1": - forceKill = true - logrus.Warn("Using force kill mode in Windows exec driver. This is for testing purposes only.") - } - - case "isolation": - if !container.Isolation(val).IsValid() { - return nil, fmt.Errorf("Unrecognised exec driver option 'isolation':'%s'", val) - } - if container.Isolation(val).IsHyperV() { - DefaultIsolation = "hyperv" - } - logrus.Infof("Windows default isolation: '%s'", val) - default: - return nil, fmt.Errorf("Unrecognised exec driver option %s\n", key) - } - } - - // TODO Windows TP5 timeframe. Remove this next block of code once TP4 - // is no longer supported. Also remove the workaround in run.go. - // - // Hack for TP4. - // This overcomes an issue on TP4 which causes CreateComputeSystem to - // intermittently fail. It's predominantly here to make Windows to Windows - // CI more reliable. - TP4RetryHack = hcsshim.IsTP4() - - return &Driver{ - root: root, - activeContainers: make(map[string]*activeContainer), - }, nil -} - -// setupEnvironmentVariables convert a string array of environment variables -// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc. -func setupEnvironmentVariables(a []string) map[string]string { - r := make(map[string]string) - for _, s := range a { - arr := strings.Split(s, "=") - if len(arr) == 2 { - r[arr[0]] = arr[1] - } - } - return r -}