daemon/oci_windows.go
94d70d83
 package daemon
 
 import (
7c29103a
 	"fmt"
 	"io/ioutil"
 	"path/filepath"
e89b6e8c
 	"runtime"
7c29103a
 	"strings"
 
7d705a73
 	containertypes "github.com/docker/docker/api/types/container"
94d70d83
 	"github.com/docker/docker/container"
7c29103a
 	"github.com/docker/docker/layer"
94d70d83
 	"github.com/docker/docker/oci"
846baf1f
 	"github.com/docker/docker/pkg/sysinfo"
f1545882
 	"github.com/docker/docker/pkg/system"
02309170
 	"github.com/opencontainers/runtime-spec/specs-go"
069fdc8a
 	"golang.org/x/sys/windows"
7c29103a
 	"golang.org/x/sys/windows/registry"
 )
 
 const (
 	credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
 	credentialSpecFileLocation     = "CredentialSpecs"
94d70d83
 )
 
02309170
 func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
f1545882
 	img, err := daemon.GetImage(string(c.ImageID))
 	if err != nil {
 		return nil, err
 	}
 
 	s := oci.DefaultOSSpec(img.OS)
94d70d83
 
 	linkedEnv, err := daemon.setupLinkedContainers(c)
 	if err != nil {
 		return nil, err
 	}
 
f42033ba
 	// Note, unlike Unix, we do NOT call into SetupWorkingDirectory as
 	// this is done in VMCompute. Further, we couldn't do it for Hyper-V
 	// containers anyway.
94d70d83
 
 	// In base spec
 	s.Hostname = c.FullHostname()
 
bd4e8aa6
 	if err := daemon.setupSecretDir(c); err != nil {
 		return nil, err
 	}
 
e0d533b1
 	if err := daemon.setupConfigDir(c); err != nil {
 		return nil, err
 	}
 
94d70d83
 	// In s.Mounts
 	mounts, err := daemon.setupMounts(c)
 	if err != nil {
 		return nil, err
 	}
bd4e8aa6
 
 	var isHyperV bool
 	if c.HostConfig.Isolation.IsDefault() {
 		// Container using default isolation, so take the default from the daemon configuration
 		isHyperV = daemon.defaultIsolation.IsHyperV()
 	} else {
 		// Container may be requesting an explicit isolation mode.
 		isHyperV = c.HostConfig.Isolation.IsHyperV()
 	}
 
7c29103a
 	if isHyperV {
 		s.Windows.HyperV = &specs.WindowsHyperV{}
 	}
 
e0d533b1
 	// If the container has not been started, and has configs or secrets
39bcaee4
 	// secrets, create symlinks to each config and secret. If it has been
e0d533b1
 	// started before, the symlinks should have already been created. Also, it
 	// is important to not mount a Hyper-V  container that has been started
 	// before, to protect the host from the container; for example, from
 	// malicious mutation of NTFS data structures.
 	if !c.HasBeenStartedBefore && (len(c.SecretReferences) > 0 || len(c.ConfigReferences) > 0) {
bd4e8aa6
 		// The container file system is mounted before this function is called,
 		// except for Hyper-V containers, so mount it here in that case.
 		if isHyperV {
 			if err := daemon.Mount(c); err != nil {
 				return nil, err
 			}
e0d533b1
 			defer daemon.Unmount(c)
bd4e8aa6
 		}
e0d533b1
 		if err := c.CreateSecretSymlinks(); err != nil {
 			return nil, err
bd4e8aa6
 		}
e0d533b1
 		if err := c.CreateConfigSymlinks(); err != nil {
bd4e8aa6
 			return nil, err
 		}
 	}
 
 	if m := c.SecretMounts(); m != nil {
 		mounts = append(mounts, m...)
 	}
 
e0d533b1
 	if m := c.ConfigMounts(); m != nil {
 		mounts = append(mounts, m...)
 	}
 
94d70d83
 	for _, mount := range mounts {
02309170
 		m := specs.Mount{
94d70d83
 			Source:      mount.Source,
 			Destination: mount.Destination,
bb585b9c
 		}
 		if !mount.Writable {
 			m.Options = append(m.Options, "ro")
 		}
e89b6e8c
 		if img.OS != runtime.GOOS {
 			m.Type = "bind"
 			m.Options = append(m.Options, "rbind")
 			m.Options = append(m.Options, fmt.Sprintf("uvmpath=/tmp/gcs/%s/binds", c.ID))
 		}
bb585b9c
 		s.Mounts = append(s.Mounts, m)
94d70d83
 	}
 
 	// In s.Process
6fa02397
 	s.Process.Args = append([]string{c.Path}, c.Args...)
19645521
 	if !c.Config.ArgsEscaped && img.OS == "windows" {
6fa02397
 		s.Process.Args = escapeArgs(s.Process.Args)
94d70d83
 	}
f1545882
 
94d70d83
 	s.Process.Cwd = c.Config.WorkingDir
f1545882
 	s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv)
 	if c.Config.Tty {
 		s.Process.Terminal = c.Config.Tty
7c29103a
 		s.Process.ConsoleSize = &specs.Box{
 			Height: c.HostConfig.ConsoleSize[0],
 			Width:  c.HostConfig.ConsoleSize[1],
 		}
f1545882
 	}
 	s.Process.User.Username = c.Config.User
 
7c29103a
 	// Get the layer path for each layer.
 	max := len(img.RootFS.DiffIDs)
 	for i := 1; i <= max; i++ {
 		img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
 		layerPath, err := layer.GetLayerPath(daemon.stores[c.Platform].layerStore, img.RootFS.ChainID())
 		if err != nil {
 			return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[c.Platform].layerStore, img.RootFS.ChainID(), err)
 		}
 		// Reverse order, expecting parent most first
 		s.Windows.LayerFolders = append([]string{layerPath}, s.Windows.LayerFolders...)
 	}
 	m, err := c.RWLayer.Metadata()
 	if err != nil {
 		return nil, fmt.Errorf("failed to get layer metadata - %s", err)
 	}
 	s.Windows.LayerFolders = append(s.Windows.LayerFolders, m["dir"])
 
 	dnsSearch := daemon.getDNSSearchSettings(c)
 
 	// Get endpoints for the libnetwork allocated networks to the container
 	var epList []string
 	AllowUnqualifiedDNSQuery := false
 	gwHNSID := ""
 	if c.NetworkSettings != nil {
 		for n := range c.NetworkSettings.Networks {
 			sn, err := daemon.FindNetwork(n)
 			if err != nil {
 				continue
 			}
 
 			ep, err := c.GetEndpointInNetwork(sn)
 			if err != nil {
 				continue
 			}
 
 			data, err := ep.DriverInfo()
 			if err != nil {
 				continue
 			}
 
 			if data["GW_INFO"] != nil {
 				gwInfo := data["GW_INFO"].(map[string]interface{})
 				if gwInfo["hnsid"] != nil {
 					gwHNSID = gwInfo["hnsid"].(string)
 				}
 			}
 
 			if data["hnsid"] != nil {
 				epList = append(epList, data["hnsid"].(string))
 			}
 
 			if data["AllowUnqualifiedDNSQuery"] != nil {
 				AllowUnqualifiedDNSQuery = true
 			}
 		}
 	}
 
 	var networkSharedContainerID string
 	if c.HostConfig.NetworkMode.IsContainer() {
 		networkSharedContainerID = c.NetworkSharedContainerID
 		for _, ep := range c.SharedEndpointList {
 			epList = append(epList, ep)
 		}
 	}
 
 	if gwHNSID != "" {
 		epList = append(epList, gwHNSID)
 	}
 
 	s.Windows.Network = &specs.WindowsNetwork{
 		AllowUnqualifiedDNSQuery:   AllowUnqualifiedDNSQuery,
 		DNSSearchList:              dnsSearch,
 		EndpointList:               epList,
 		NetworkSharedContainerName: networkSharedContainerID,
 	}
 
f1545882
 	if img.OS == "windows" {
7c29103a
 		if err := daemon.createSpecWindowsFields(c, &s, isHyperV); err != nil {
 			return nil, err
 		}
f1545882
 	} else {
 		// TODO @jhowardmsft LCOW Support. Modify this check when running in dual-mode
 		if system.LCOWSupported() && img.OS == "linux" {
 			daemon.createSpecLinuxFields(c, &s)
 		}
 	}
 
 	return (*specs.Spec)(&s), nil
 }
 
 // Sets the Windows-specific fields of the OCI spec
7c29103a
 func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) error {
c2d18342
 	if len(s.Process.Cwd) == 0 {
 		// We default to C:\ to workaround the oddity of the case that the
 		// default directory for cmd running as LocalSystem (or
 		// ContainerAdministrator) is c:\windows\system32. Hence docker run
 		// <image> cmd will by default end in c:\windows\system32, rather
 		// than 'root' (/) on Linux. The oddity is that if you have a dockerfile
 		// which has no WORKDIR and has a COPY file ., . will be interpreted
 		// as c:\. Hence, setting it to default of c:\ makes for consistency.
 		s.Process.Cwd = `C:\`
 	}
94d70d83
 
f1545882
 	s.Root.Readonly = false // Windows does not support a read-only root filesystem
8f76a1d0
 	if !isHyperV {
7a7357da
 		s.Root.Path = c.BaseFS.Path() // This is not set for Hyper-V containers
7c29103a
 		if !strings.HasSuffix(s.Root.Path, `\`) {
 			s.Root.Path = s.Root.Path + `\` // Ensure a correctly formatted volume GUID path \\?\Volume{GUID}\
 		}
8f76a1d0
 	}
94d70d83
 
7c29103a
 	// First boot optimization
 	s.Windows.IgnoreFlushesDuringBoot = !c.HasBeenStartedBefore
 
94d70d83
 	// In s.Windows.Resources
02309170
 	cpuShares := uint16(c.HostConfig.CPUShares)
425973cb
 	cpuMaximum := uint16(c.HostConfig.CPUPercent) * 100
3b5af0a2
 	cpuCount := uint64(c.HostConfig.CPUCount)
846baf1f
 	if c.HostConfig.NanoCPUs > 0 {
3b5af0a2
 		if isHyperV {
 			cpuCount = uint64(c.HostConfig.NanoCPUs / 1e9)
 			leftoverNanoCPUs := c.HostConfig.NanoCPUs % 1e9
 			if leftoverNanoCPUs != 0 {
 				cpuCount++
425973cb
 				cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(cpuCount) / (1e9 / 10000))
 				if cpuMaximum < 1 {
 					// The requested NanoCPUs is so small that we rounded to 0, use 1 instead
 					cpuMaximum = 1
 				}
3b5af0a2
 			}
 		} else {
425973cb
 			cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(sysinfo.NumCPU()) / (1e9 / 10000))
 			if cpuMaximum < 1 {
b3649f40
 				// The requested NanoCPUs is so small that we rounded to 0, use 1 instead
425973cb
 				cpuMaximum = 1
b3649f40
 			}
3b5af0a2
 		}
846baf1f
 	}
02309170
 	memoryLimit := uint64(c.HostConfig.Memory)
 	s.Windows.Resources = &specs.WindowsResources{
 		CPU: &specs.WindowsCPUResources{
425973cb
 			Maximum: &cpuMaximum,
ea8c6908
 			Shares:  &cpuShares,
4e15420b
 			Count:   &cpuCount,
94d70d83
 		},
02309170
 		Memory: &specs.WindowsMemoryResources{
 			Limit: &memoryLimit,
94d70d83
 		},
02309170
 		Storage: &specs.WindowsStorageResources{
8df20663
 			Bps:  &c.HostConfig.IOMaximumBandwidth,
 			Iops: &c.HostConfig.IOMaximumIOps,
94d70d83
 		},
 	}
7c29103a
 
 	// Read and add credentials from the security options if a credential spec has been provided.
 	if c.HostConfig.SecurityOpt != nil {
 		cs := ""
 		for _, sOpt := range c.HostConfig.SecurityOpt {
 			sOpt = strings.ToLower(sOpt)
 			if !strings.Contains(sOpt, "=") {
 				return fmt.Errorf("invalid security option: no equals sign in supplied value %s", sOpt)
 			}
 			var splitsOpt []string
 			splitsOpt = strings.SplitN(sOpt, "=", 2)
 			if len(splitsOpt) != 2 {
 				return fmt.Errorf("invalid security option: %s", sOpt)
 			}
 			if splitsOpt[0] != "credentialspec" {
 				return fmt.Errorf("security option not supported: %s", splitsOpt[0])
 			}
 
 			var (
 				match   bool
 				csValue string
 				err     error
 			)
 			if match, csValue = getCredentialSpec("file://", splitsOpt[1]); match {
 				if csValue == "" {
 					return fmt.Errorf("no value supplied for file:// credential spec security option")
 				}
 				if cs, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(csValue)); err != nil {
 					return err
 				}
 			} else if match, csValue = getCredentialSpec("registry://", splitsOpt[1]); match {
 				if csValue == "" {
 					return fmt.Errorf("no value supplied for registry:// credential spec security option")
 				}
 				if cs, err = readCredentialSpecRegistry(c.ID, csValue); err != nil {
 					return err
 				}
 			} else {
 				return fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
 			}
 		}
 		s.Windows.CredentialSpec = cs
 	}
 
 	// Assume we are not starting a container for a servicing operation
 	s.Windows.Servicing = false
 
 	return nil
f1545882
 }
 
 // Sets the Linux-specific fields of the OCI spec
 // TODO: @jhowardmsft LCOW Support. We need to do a lot more pulling in what can
 // be pulled in from oci_linux.go.
 func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spec) {
 	if len(s.Process.Cwd) == 0 {
 		s.Process.Cwd = `/`
 	}
 	s.Root.Path = "rootfs"
 	s.Root.Readonly = c.HostConfig.ReadonlyRootfs
94d70d83
 }
 
 func escapeArgs(args []string) []string {
 	escapedArgs := make([]string, len(args))
 	for i, a := range args {
069fdc8a
 		escapedArgs[i] = windows.EscapeArg(a)
94d70d83
 	}
 	return escapedArgs
 }
7d705a73
 
 // mergeUlimits merge the Ulimits from HostConfig with daemon defaults, and update HostConfig
 // It will do nothing on non-Linux platform
 func (daemon *Daemon) mergeUlimits(c *containertypes.HostConfig) {
 	return
 }
7c29103a
 
 // getCredentialSpec is a helper function to get the value of a credential spec supplied
 // on the CLI, stripping the prefix
 func getCredentialSpec(prefix, value string) (bool, string) {
 	if strings.HasPrefix(value, prefix) {
 		return true, strings.TrimPrefix(value, prefix)
 	}
 	return false, ""
 }
 
 // readCredentialSpecRegistry is a helper function to read a credential spec from
 // the registry. If not found, we return an empty string and warn in the log.
 // This allows for staging on machines which do not have the necessary components.
 func readCredentialSpecRegistry(id, name string) (string, error) {
 	var (
 		k   registry.Key
 		err error
 		val string
 	)
 	if k, err = registry.OpenKey(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE); err != nil {
 		return "", fmt.Errorf("failed handling spec %q for container %s - %s could not be opened", name, id, credentialSpecRegistryLocation)
 	}
 	if val, _, err = k.GetStringValue(name); err != nil {
 		if err == registry.ErrNotExist {
 			return "", fmt.Errorf("credential spec %q for container %s as it was not found", name, id)
 		}
 		return "", fmt.Errorf("error %v reading credential spec %q from registry for container %s", err, name, id)
 	}
 	return val, nil
 }
 
 // readCredentialSpecFile is a helper function to read a credential spec from
 // a file. If not found, we return an empty string and warn in the log.
 // This allows for staging on machines which do not have the necessary components.
 func readCredentialSpecFile(id, root, location string) (string, error) {
 	if filepath.IsAbs(location) {
 		return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
 	}
 	base := filepath.Join(root, credentialSpecFileLocation)
 	full := filepath.Join(base, location)
 	if !strings.HasPrefix(full, base) {
 		return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
 	}
 	bcontents, err := ioutil.ReadFile(full)
 	if err != nil {
 		return "", fmt.Errorf("credential spec '%s' for container %s as the file could not be read: %q", full, id, err)
 	}
 	return string(bcontents[:]), nil
 }