github.com/mforkel/docker-ce-i386@v17.12.1-ce-rc2+incompatible/components/engine/daemon/oci_windows.go (about) 1 package daemon 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "path/filepath" 8 "runtime" 9 "strings" 10 11 containertypes "github.com/docker/docker/api/types/container" 12 "github.com/docker/docker/container" 13 "github.com/docker/docker/layer" 14 "github.com/docker/docker/oci" 15 "github.com/docker/docker/pkg/sysinfo" 16 "github.com/docker/docker/pkg/system" 17 "github.com/opencontainers/runtime-spec/specs-go" 18 "golang.org/x/sys/windows" 19 "golang.org/x/sys/windows/registry" 20 ) 21 22 const ( 23 credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs` 24 credentialSpecFileLocation = "CredentialSpecs" 25 ) 26 27 func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) { 28 img, err := daemon.GetImage(string(c.ImageID)) 29 if err != nil { 30 return nil, err 31 } 32 33 s := oci.DefaultOSSpec(img.OS) 34 35 linkedEnv, err := daemon.setupLinkedContainers(c) 36 if err != nil { 37 return nil, err 38 } 39 40 // Note, unlike Unix, we do NOT call into SetupWorkingDirectory as 41 // this is done in VMCompute. Further, we couldn't do it for Hyper-V 42 // containers anyway. 43 44 // In base spec 45 s.Hostname = c.FullHostname() 46 47 if err := daemon.setupSecretDir(c); err != nil { 48 return nil, err 49 } 50 51 if err := daemon.setupConfigDir(c); err != nil { 52 return nil, err 53 } 54 55 // In s.Mounts 56 mounts, err := daemon.setupMounts(c) 57 if err != nil { 58 return nil, err 59 } 60 61 var isHyperV bool 62 if c.HostConfig.Isolation.IsDefault() { 63 // Container using default isolation, so take the default from the daemon configuration 64 isHyperV = daemon.defaultIsolation.IsHyperV() 65 } else { 66 // Container may be requesting an explicit isolation mode. 67 isHyperV = c.HostConfig.Isolation.IsHyperV() 68 } 69 70 if isHyperV { 71 s.Windows.HyperV = &specs.WindowsHyperV{} 72 } 73 74 // If the container has not been started, and has configs or secrets 75 // secrets, create symlinks to each config and secret. If it has been 76 // started before, the symlinks should have already been created. Also, it 77 // is important to not mount a Hyper-V container that has been started 78 // before, to protect the host from the container; for example, from 79 // malicious mutation of NTFS data structures. 80 if !c.HasBeenStartedBefore && (len(c.SecretReferences) > 0 || len(c.ConfigReferences) > 0) { 81 // The container file system is mounted before this function is called, 82 // except for Hyper-V containers, so mount it here in that case. 83 if isHyperV { 84 if err := daemon.Mount(c); err != nil { 85 return nil, err 86 } 87 defer daemon.Unmount(c) 88 } 89 if err := c.CreateSecretSymlinks(); err != nil { 90 return nil, err 91 } 92 if err := c.CreateConfigSymlinks(); err != nil { 93 return nil, err 94 } 95 } 96 97 if m := c.SecretMounts(); m != nil { 98 mounts = append(mounts, m...) 99 } 100 101 if m := c.ConfigMounts(); m != nil { 102 mounts = append(mounts, m...) 103 } 104 105 for _, mount := range mounts { 106 m := specs.Mount{ 107 Source: mount.Source, 108 Destination: mount.Destination, 109 } 110 if !mount.Writable { 111 m.Options = append(m.Options, "ro") 112 } 113 if img.OS != runtime.GOOS { 114 m.Type = "bind" 115 m.Options = append(m.Options, "rbind") 116 m.Options = append(m.Options, fmt.Sprintf("uvmpath=/tmp/gcs/%s/binds", c.ID)) 117 } 118 s.Mounts = append(s.Mounts, m) 119 } 120 121 // In s.Process 122 s.Process.Args = append([]string{c.Path}, c.Args...) 123 if !c.Config.ArgsEscaped && img.OS == "windows" { 124 s.Process.Args = escapeArgs(s.Process.Args) 125 } 126 127 s.Process.Cwd = c.Config.WorkingDir 128 s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv) 129 if c.Config.Tty { 130 s.Process.Terminal = c.Config.Tty 131 s.Process.ConsoleSize = &specs.Box{ 132 Height: c.HostConfig.ConsoleSize[0], 133 Width: c.HostConfig.ConsoleSize[1], 134 } 135 } 136 s.Process.User.Username = c.Config.User 137 138 // Get the layer path for each layer. 139 max := len(img.RootFS.DiffIDs) 140 for i := 1; i <= max; i++ { 141 img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i] 142 layerPath, err := layer.GetLayerPath(daemon.stores[c.OS].layerStore, img.RootFS.ChainID()) 143 if err != nil { 144 return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[c.OS].layerStore, img.RootFS.ChainID(), err) 145 } 146 // Reverse order, expecting parent most first 147 s.Windows.LayerFolders = append([]string{layerPath}, s.Windows.LayerFolders...) 148 } 149 if c.RWLayer == nil { 150 return nil, errors.New("RWLayer of container " + c.ID + " is unexpectedly nil") 151 } 152 m, err := c.RWLayer.Metadata() 153 if err != nil { 154 return nil, fmt.Errorf("failed to get layer metadata - %s", err) 155 } 156 s.Windows.LayerFolders = append(s.Windows.LayerFolders, m["dir"]) 157 158 dnsSearch := daemon.getDNSSearchSettings(c) 159 160 // Get endpoints for the libnetwork allocated networks to the container 161 var epList []string 162 AllowUnqualifiedDNSQuery := false 163 gwHNSID := "" 164 if c.NetworkSettings != nil { 165 for n := range c.NetworkSettings.Networks { 166 sn, err := daemon.FindNetwork(n) 167 if err != nil { 168 continue 169 } 170 171 ep, err := c.GetEndpointInNetwork(sn) 172 if err != nil { 173 continue 174 } 175 176 data, err := ep.DriverInfo() 177 if err != nil { 178 continue 179 } 180 181 if data["GW_INFO"] != nil { 182 gwInfo := data["GW_INFO"].(map[string]interface{}) 183 if gwInfo["hnsid"] != nil { 184 gwHNSID = gwInfo["hnsid"].(string) 185 } 186 } 187 188 if data["hnsid"] != nil { 189 epList = append(epList, data["hnsid"].(string)) 190 } 191 192 if data["AllowUnqualifiedDNSQuery"] != nil { 193 AllowUnqualifiedDNSQuery = true 194 } 195 } 196 } 197 198 var networkSharedContainerID string 199 if c.HostConfig.NetworkMode.IsContainer() { 200 networkSharedContainerID = c.NetworkSharedContainerID 201 for _, ep := range c.SharedEndpointList { 202 epList = append(epList, ep) 203 } 204 } 205 206 if gwHNSID != "" { 207 epList = append(epList, gwHNSID) 208 } 209 210 s.Windows.Network = &specs.WindowsNetwork{ 211 AllowUnqualifiedDNSQuery: AllowUnqualifiedDNSQuery, 212 DNSSearchList: dnsSearch, 213 EndpointList: epList, 214 NetworkSharedContainerName: networkSharedContainerID, 215 } 216 217 if img.OS == "windows" { 218 if err := daemon.createSpecWindowsFields(c, &s, isHyperV); err != nil { 219 return nil, err 220 } 221 } else { 222 // TODO @jhowardmsft LCOW Support. Modify this check when running in dual-mode 223 if system.LCOWSupported() && img.OS == "linux" { 224 daemon.createSpecLinuxFields(c, &s) 225 } 226 } 227 228 return (*specs.Spec)(&s), nil 229 } 230 231 // Sets the Windows-specific fields of the OCI spec 232 func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) error { 233 if len(s.Process.Cwd) == 0 { 234 // We default to C:\ to workaround the oddity of the case that the 235 // default directory for cmd running as LocalSystem (or 236 // ContainerAdministrator) is c:\windows\system32. Hence docker run 237 // <image> cmd will by default end in c:\windows\system32, rather 238 // than 'root' (/) on Linux. The oddity is that if you have a dockerfile 239 // which has no WORKDIR and has a COPY file ., . will be interpreted 240 // as c:\. Hence, setting it to default of c:\ makes for consistency. 241 s.Process.Cwd = `C:\` 242 } 243 244 s.Root.Readonly = false // Windows does not support a read-only root filesystem 245 if !isHyperV { 246 s.Root.Path = c.BaseFS.Path() // This is not set for Hyper-V containers 247 if !strings.HasSuffix(s.Root.Path, `\`) { 248 s.Root.Path = s.Root.Path + `\` // Ensure a correctly formatted volume GUID path \\?\Volume{GUID}\ 249 } 250 } 251 252 // First boot optimization 253 s.Windows.IgnoreFlushesDuringBoot = !c.HasBeenStartedBefore 254 255 // In s.Windows.Resources 256 cpuShares := uint16(c.HostConfig.CPUShares) 257 cpuMaximum := uint16(c.HostConfig.CPUPercent) * 100 258 cpuCount := uint64(c.HostConfig.CPUCount) 259 if c.HostConfig.NanoCPUs > 0 { 260 if isHyperV { 261 cpuCount = uint64(c.HostConfig.NanoCPUs / 1e9) 262 leftoverNanoCPUs := c.HostConfig.NanoCPUs % 1e9 263 if leftoverNanoCPUs != 0 { 264 cpuCount++ 265 cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(cpuCount) / (1e9 / 10000)) 266 if cpuMaximum < 1 { 267 // The requested NanoCPUs is so small that we rounded to 0, use 1 instead 268 cpuMaximum = 1 269 } 270 } 271 } else { 272 cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(sysinfo.NumCPU()) / (1e9 / 10000)) 273 if cpuMaximum < 1 { 274 // The requested NanoCPUs is so small that we rounded to 0, use 1 instead 275 cpuMaximum = 1 276 } 277 } 278 } 279 memoryLimit := uint64(c.HostConfig.Memory) 280 s.Windows.Resources = &specs.WindowsResources{ 281 CPU: &specs.WindowsCPUResources{ 282 Maximum: &cpuMaximum, 283 Shares: &cpuShares, 284 Count: &cpuCount, 285 }, 286 Memory: &specs.WindowsMemoryResources{ 287 Limit: &memoryLimit, 288 }, 289 Storage: &specs.WindowsStorageResources{ 290 Bps: &c.HostConfig.IOMaximumBandwidth, 291 Iops: &c.HostConfig.IOMaximumIOps, 292 }, 293 } 294 295 // Read and add credentials from the security options if a credential spec has been provided. 296 if c.HostConfig.SecurityOpt != nil { 297 cs := "" 298 for _, sOpt := range c.HostConfig.SecurityOpt { 299 sOpt = strings.ToLower(sOpt) 300 if !strings.Contains(sOpt, "=") { 301 return fmt.Errorf("invalid security option: no equals sign in supplied value %s", sOpt) 302 } 303 var splitsOpt []string 304 splitsOpt = strings.SplitN(sOpt, "=", 2) 305 if len(splitsOpt) != 2 { 306 return fmt.Errorf("invalid security option: %s", sOpt) 307 } 308 if splitsOpt[0] != "credentialspec" { 309 return fmt.Errorf("security option not supported: %s", splitsOpt[0]) 310 } 311 312 var ( 313 match bool 314 csValue string 315 err error 316 ) 317 if match, csValue = getCredentialSpec("file://", splitsOpt[1]); match { 318 if csValue == "" { 319 return fmt.Errorf("no value supplied for file:// credential spec security option") 320 } 321 if cs, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(csValue)); err != nil { 322 return err 323 } 324 } else if match, csValue = getCredentialSpec("registry://", splitsOpt[1]); match { 325 if csValue == "" { 326 return fmt.Errorf("no value supplied for registry:// credential spec security option") 327 } 328 if cs, err = readCredentialSpecRegistry(c.ID, csValue); err != nil { 329 return err 330 } 331 } else { 332 return fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value") 333 } 334 } 335 s.Windows.CredentialSpec = cs 336 } 337 338 // Assume we are not starting a container for a servicing operation 339 s.Windows.Servicing = false 340 341 return nil 342 } 343 344 // Sets the Linux-specific fields of the OCI spec 345 // TODO: @jhowardmsft LCOW Support. We need to do a lot more pulling in what can 346 // be pulled in from oci_linux.go. 347 func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spec) { 348 if len(s.Process.Cwd) == 0 { 349 s.Process.Cwd = `/` 350 } 351 s.Root.Path = "rootfs" 352 s.Root.Readonly = c.HostConfig.ReadonlyRootfs 353 } 354 355 func escapeArgs(args []string) []string { 356 escapedArgs := make([]string, len(args)) 357 for i, a := range args { 358 escapedArgs[i] = windows.EscapeArg(a) 359 } 360 return escapedArgs 361 } 362 363 // mergeUlimits merge the Ulimits from HostConfig with daemon defaults, and update HostConfig 364 // It will do nothing on non-Linux platform 365 func (daemon *Daemon) mergeUlimits(c *containertypes.HostConfig) { 366 return 367 } 368 369 // getCredentialSpec is a helper function to get the value of a credential spec supplied 370 // on the CLI, stripping the prefix 371 func getCredentialSpec(prefix, value string) (bool, string) { 372 if strings.HasPrefix(value, prefix) { 373 return true, strings.TrimPrefix(value, prefix) 374 } 375 return false, "" 376 } 377 378 // readCredentialSpecRegistry is a helper function to read a credential spec from 379 // the registry. If not found, we return an empty string and warn in the log. 380 // This allows for staging on machines which do not have the necessary components. 381 func readCredentialSpecRegistry(id, name string) (string, error) { 382 var ( 383 k registry.Key 384 err error 385 val string 386 ) 387 if k, err = registry.OpenKey(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE); err != nil { 388 return "", fmt.Errorf("failed handling spec %q for container %s - %s could not be opened", name, id, credentialSpecRegistryLocation) 389 } 390 if val, _, err = k.GetStringValue(name); err != nil { 391 if err == registry.ErrNotExist { 392 return "", fmt.Errorf("credential spec %q for container %s as it was not found", name, id) 393 } 394 return "", fmt.Errorf("error %v reading credential spec %q from registry for container %s", err, name, id) 395 } 396 return val, nil 397 } 398 399 // readCredentialSpecFile is a helper function to read a credential spec from 400 // a file. If not found, we return an empty string and warn in the log. 401 // This allows for staging on machines which do not have the necessary components. 402 func readCredentialSpecFile(id, root, location string) (string, error) { 403 if filepath.IsAbs(location) { 404 return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute") 405 } 406 base := filepath.Join(root, credentialSpecFileLocation) 407 full := filepath.Join(base, location) 408 if !strings.HasPrefix(full, base) { 409 return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base) 410 } 411 bcontents, err := ioutil.ReadFile(full) 412 if err != nil { 413 return "", fmt.Errorf("credential spec '%s' for container %s as the file could not be read: %q", full, id, err) 414 } 415 return string(bcontents[:]), nil 416 }