Browse Source

Merge pull request #33241 from Microsoft/jjh/multi-layerstore

LCOW: Support most operations excluding remote filesystem
John Stephens 8 years ago
parent
commit
930e689668
100 changed files with 1902 additions and 662 deletions
  1. 1 1
      api/common.go
  2. 1 1
      api/server/backend/build/backend.go
  3. 8 1
      api/server/backend/build/tag.go
  4. 2 2
      api/server/router/image/backend.go
  5. 39 2
      api/server/router/image/image_routes.go
  6. 1 0
      api/types/backend/build.go
  7. 4 0
      api/types/client.go
  8. 1 0
      api/types/configs.go
  9. 1 0
      api/types/types.go
  10. 3 3
      builder/builder.go
  11. 46 1
      builder/dockerfile/builder.go
  12. 3 1
      builder/dockerfile/builder_unix.go
  13. 6 1
      builder/dockerfile/builder_windows.go
  14. 2 1
      builder/dockerfile/containerbackend.go
  15. 10 7
      builder/dockerfile/dispatchers.go
  16. 11 6
      builder/dockerfile/dispatchers_test.go
  17. 9 3
      builder/dockerfile/evaluator.go
  18. 3 0
      builder/dockerfile/imagecontext.go
  19. 2 2
      builder/dockerfile/imageprobe.go
  20. 13 13
      builder/dockerfile/internals.go
  21. 3 2
      builder/dockerfile/internals_test.go
  22. 5 5
      builder/dockerfile/mockbackend_test.go
  23. 48 5
      builder/dockerfile/parser/parser.go
  24. 8 1
      cmd/dockerd/config.go
  25. 5 6
      cmd/dockerd/daemon.go
  26. 59 12
      container/container.go
  27. 4 12
      container/container_unit_test.go
  28. 0 36
      container/container_unix.go
  29. 2 19
      container/container_windows.go
  30. 16 12
      daemon/build.go
  31. 3 3
      daemon/cache.go
  32. 1 1
      daemon/cluster/executor/backend.go
  33. 9 1
      daemon/cluster/executor/container/adapter.go
  34. 6 8
      daemon/cluster/executor/container/health_test.go
  35. 7 7
      daemon/commit.go
  36. 3 3
      daemon/container.go
  37. 2 2
      daemon/container_operations_windows.go
  38. 29 4
      daemon/create.go
  39. 150 91
      daemon/daemon.go
  40. 1 1
      daemon/daemon_solaris.go
  41. 11 21
      daemon/daemon_test.go
  42. 11 3
      daemon/daemon_unix.go
  43. 12 2
      daemon/daemon_windows.go
  44. 2 2
      daemon/delete.go
  45. 3 5
      daemon/delete_test.go
  46. 21 17
      daemon/disk_usage.go
  47. 13 17
      daemon/events_test.go
  48. 5 3
      daemon/getsize_unix.go
  49. 497 0
      daemon/graphdriver/lcow/lcow.go
  50. 2 1
      daemon/graphdriver/register/register_windows.go
  51. 26 1
      daemon/graphdriver/windows/windows.go
  52. 11 15
      daemon/health_test.go
  53. 26 18
      daemon/image.go
  54. 25 25
      daemon/image_delete.go
  55. 14 2
      daemon/image_exporter.go
  56. 10 3
      daemon/image_history.go
  57. 12 6
      daemon/image_inspect.go
  58. 13 6
      daemon/image_pull.go
  59. 12 4
      daemon/image_push.go
  60. 4 4
      daemon/image_tag.go
  61. 38 15
      daemon/images.go
  62. 11 7
      daemon/import.go
  63. 21 3
      daemon/info.go
  64. 1 0
      daemon/inspect.go
  65. 3 3
      daemon/list.go
  66. 43 10
      daemon/oci_windows.go
  67. 13 5
      daemon/prune.go
  68. 1 1
      daemon/start.go
  69. 3 3
      daemon/start_windows.go
  70. 55 58
      daemon/volumes_unix_test.go
  71. 17 9
      distribution/config.go
  72. 3 1
      distribution/metadata/metadata.go
  73. 2 1
      distribution/metadata/v1_id_service_test.go
  74. 2 1
      distribution/metadata/v2_metadata_service_test.go
  75. 1 1
      distribution/pull_v1.go
  76. 34 13
      distribution/pull_v2.go
  77. 1 1
      distribution/push_v2.go
  78. 25 19
      distribution/xfer/download.go
  79. 16 6
      distribution/xfer/download_test.go
  80. 11 2
      image/image.go
  81. 13 1
      image/store.go
  82. 3 2
      image/store_test.go
  83. 22 5
      image/tarexport/load.go
  84. 4 0
      layer/empty.go
  85. 13 0
      layer/filestore_unix.go
  86. 35 0
      layer/filestore_windows.go
  87. 15 2
      layer/layer.go
  88. 27 7
      layer/layer_store.go
  89. 2 2
      layer/layer_store_windows.go
  90. 9 9
      layer/layer_test.go
  91. 6 6
      layer/migration_test.go
  92. 4 0
      layer/ro_layer.go
  93. 7 0
      layer/ro_layer_unix.go
  94. 7 0
      layer/ro_layer_windows.go
  95. 101 6
      libcontainerd/client_windows.go
  96. 20 5
      libcontainerd/container_windows.go
  97. 1 1
      libcontainerd/remote_unix.go
  98. 7 3
      migrate/v1/migratev1_test.go
  99. 48 3
      oci/defaults.go
  100. 0 20
      oci/defaults_solaris.go

+ 1 - 1
api/common.go

@@ -126,7 +126,7 @@ func MatchesContentType(contentType, expectedType string) bool {
 // LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
 // LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
 // otherwise generates a new one
 // otherwise generates a new one
 func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
 func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
-	err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700)
+	err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700, "")
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 1 - 1
api/server/backend/build/backend.go

@@ -17,7 +17,7 @@ import (
 // ImageComponent provides an interface for working with images
 // ImageComponent provides an interface for working with images
 type ImageComponent interface {
 type ImageComponent interface {
 	SquashImage(from string, to string) (string, error)
 	SquashImage(from string, to string) (string, error)
-	TagImageWithReference(image.ID, reference.Named) error
+	TagImageWithReference(image.ID, string, reference.Named) error
 }
 }
 
 
 // Backend provides build functionality to the API router
 // Backend provides build functionality to the API router

+ 8 - 1
api/server/backend/build/tag.go

@@ -3,9 +3,11 @@ package build
 import (
 import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
+	"runtime"
 
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
+	"github.com/docker/docker/pkg/system"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 )
 )
 
 
@@ -33,7 +35,12 @@ func NewTagger(backend ImageComponent, stdout io.Writer, names []string) (*Tagge
 // TagImages creates image tags for the imageID
 // TagImages creates image tags for the imageID
 func (bt *Tagger) TagImages(imageID image.ID) error {
 func (bt *Tagger) TagImages(imageID image.ID) error {
 	for _, rt := range bt.repoAndTags {
 	for _, rt := range bt.repoAndTags {
-		if err := bt.imageComponent.TagImageWithReference(imageID, rt); err != nil {
+		// TODO @jhowardmsft LCOW support. Will need revisiting.
+		platform := runtime.GOOS
+		if platform == "windows" && system.LCOWSupported() {
+			platform = "linux"
+		}
+		if err := bt.imageComponent.TagImageWithReference(imageID, platform, rt); err != nil {
 			return err
 			return err
 		}
 		}
 		fmt.Fprintf(bt.stdout, "Successfully tagged %s\n", reference.FamiliarString(rt))
 		fmt.Fprintf(bt.stdout, "Successfully tagged %s\n", reference.FamiliarString(rt))

+ 2 - 2
api/server/router/image/backend.go

@@ -35,12 +35,12 @@ type imageBackend interface {
 
 
 type importExportBackend interface {
 type importExportBackend interface {
 	LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error
 	LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error
-	ImportImage(src string, repository, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error
+	ImportImage(src string, repository, platform string, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error
 	ExportImage(names []string, outStream io.Writer) error
 	ExportImage(names []string, outStream io.Writer) error
 }
 }
 
 
 type registryBackend interface {
 type registryBackend interface {
-	PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
+	PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
 	PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
 	PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
 	SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, limit int, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
 	SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, limit int, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
 }
 }

+ 39 - 2
api/server/router/image/image_routes.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
+	"runtime"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 
 
@@ -17,6 +18,7 @@ import (
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/streamformatter"
+	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -85,6 +87,41 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
 	)
 	)
 	defer output.Close()
 	defer output.Close()
 
 
+	// TODO @jhowardmsft LCOW Support: Eventually we will need an API change
+	// so that platform comes from (for example) r.Form.Get("platform"). For
+	// the initial implementation, we assume that the platform is the
+	// runtime OS of the host. It will also need a validation function such
+	// as below which should be called after getting it from the API.
+	//
+	// Ensures the requested platform is valid and normalized
+	//func validatePlatform(req string) (string, error) {
+	//	req = strings.ToLower(req)
+	//	if req == "" {
+	//		req = runtime.GOOS // default to host platform
+	//	}
+	//	valid := []string{runtime.GOOS}
+	//
+	//	if runtime.GOOS == "windows" && system.LCOWSupported() {
+	//		valid = append(valid, "linux")
+	//	}
+	//
+	//	for _, item := range valid {
+	//		if req == item {
+	//			return req, nil
+	//		}
+	//	}
+	//	return "", fmt.Errorf("invalid platform requested: %s", req)
+	//}
+	//
+	// And in the call-site:
+	//	if platform, err = validatePlatform(platform); err != nil {
+	//		return err
+	//	}
+	platform := runtime.GOOS
+	if platform == "windows" && system.LCOWSupported() {
+		platform = "linux"
+	}
+
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
 
 
 	if image != "" { //pull
 	if image != "" { //pull
@@ -106,13 +143,13 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
 			}
 			}
 		}
 		}
 
 
-		err = s.backend.PullImage(ctx, image, tag, metaHeaders, authConfig, output)
+		err = s.backend.PullImage(ctx, image, tag, platform, metaHeaders, authConfig, output)
 	} else { //import
 	} else { //import
 		src := r.Form.Get("fromSrc")
 		src := r.Form.Get("fromSrc")
 		// 'err' MUST NOT be defined within this block, we need any error
 		// 'err' MUST NOT be defined within this block, we need any error
 		// generated from the download to be available to the output
 		// generated from the download to be available to the output
 		// stream processing below
 		// stream processing below
-		err = s.backend.ImportImage(src, repo, tag, message, r.Body, output, r.Form["changes"])
+		err = s.backend.ImportImage(src, repo, platform, tag, message, r.Body, output, r.Form["changes"])
 	}
 	}
 	if err != nil {
 	if err != nil {
 		if !output.Flushed() {
 		if !output.Flushed() {

+ 1 - 0
api/types/backend/build.go

@@ -40,4 +40,5 @@ type GetImageAndLayerOptions struct {
 	PullOption PullOption
 	PullOption PullOption
 	AuthConfig map[string]types.AuthConfig
 	AuthConfig map[string]types.AuthConfig
 	Output     io.Writer
 	Output     io.Writer
+	Platform   string
 }
 }

+ 4 - 0
api/types/client.go

@@ -178,6 +178,10 @@ type ImageBuildOptions struct {
 	SecurityOpt []string
 	SecurityOpt []string
 	ExtraHosts  []string // List of extra hosts
 	ExtraHosts  []string // List of extra hosts
 	Target      string
 	Target      string
+
+	// TODO @jhowardmsft LCOW Support: This will require extending to include
+	// `Platform string`, but is ommited for now as it's hard-coded temporarily
+	// to avoid API changes.
 }
 }
 
 
 // ImageBuildResponse holds information
 // ImageBuildResponse holds information

+ 1 - 0
api/types/configs.go

@@ -16,6 +16,7 @@ type ContainerCreateConfig struct {
 	HostConfig       *container.HostConfig
 	HostConfig       *container.HostConfig
 	NetworkingConfig *network.NetworkingConfig
 	NetworkingConfig *network.NetworkingConfig
 	AdjustCPUShares  bool
 	AdjustCPUShares  bool
+	Platform         string
 }
 }
 
 
 // ContainerRmConfig holds arguments for the container remove
 // ContainerRmConfig holds arguments for the container remove

+ 1 - 0
api/types/types.go

@@ -320,6 +320,7 @@ type ContainerJSONBase struct {
 	Name            string
 	Name            string
 	RestartCount    int
 	RestartCount    int
 	Driver          string
 	Driver          string
+	Platform        string
 	MountLabel      string
 	MountLabel      string
 	ProcessLabel    string
 	ProcessLabel    string
 	AppArmorProfile string
 	AppArmorProfile string

+ 3 - 3
builder/builder.go

@@ -43,7 +43,7 @@ type Backend interface {
 	// ContainerCreateWorkdir creates the workdir
 	// ContainerCreateWorkdir creates the workdir
 	ContainerCreateWorkdir(containerID string) error
 	ContainerCreateWorkdir(containerID string) error
 
 
-	CreateImage(config []byte, parent string) (Image, error)
+	CreateImage(config []byte, parent string, platform string) (Image, error)
 
 
 	ImageCacheBuilder
 	ImageCacheBuilder
 }
 }
@@ -78,7 +78,7 @@ type Result struct {
 // ImageCacheBuilder represents a generator for stateful image cache.
 // ImageCacheBuilder represents a generator for stateful image cache.
 type ImageCacheBuilder interface {
 type ImageCacheBuilder interface {
 	// MakeImageCache creates a stateful image cache.
 	// MakeImageCache creates a stateful image cache.
-	MakeImageCache(cacheFrom []string) ImageCache
+	MakeImageCache(cacheFrom []string, platform string) ImageCache
 }
 }
 
 
 // ImageCache abstracts an image cache.
 // ImageCache abstracts an image cache.
@@ -100,6 +100,6 @@ type Image interface {
 type ReleaseableLayer interface {
 type ReleaseableLayer interface {
 	Release() error
 	Release() error
 	Mount() (string, error)
 	Mount() (string, error)
-	Commit() (ReleaseableLayer, error)
+	Commit(platform string) (ReleaseableLayer, error)
 	DiffID() layer.DiffID
 	DiffID() layer.DiffID
 }
 }

+ 46 - 1
builder/dockerfile/builder.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
+	"runtime"
 	"strings"
 	"strings"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
@@ -20,6 +21,7 @@ import (
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/docker/pkg/system"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 	"golang.org/x/sync/syncmap"
 	"golang.org/x/sync/syncmap"
@@ -73,13 +75,24 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
 		}()
 		}()
 	}
 	}
 
 
+	// TODO @jhowardmsft LCOW support - this will require rework to allow both linux and Windows simultaneously.
+	// This is an interim solution to hardcode to linux if LCOW is turned on.
+	if dockerfile.Platform == "" {
+		dockerfile.Platform = runtime.GOOS
+		if dockerfile.Platform == "windows" && system.LCOWSupported() {
+			dockerfile.Platform = "linux"
+		}
+	}
+
 	builderOptions := builderOptions{
 	builderOptions := builderOptions{
 		Options:        config.Options,
 		Options:        config.Options,
 		ProgressWriter: config.ProgressWriter,
 		ProgressWriter: config.ProgressWriter,
 		Backend:        bm.backend,
 		Backend:        bm.backend,
 		PathCache:      bm.pathCache,
 		PathCache:      bm.pathCache,
 		Archiver:       bm.archiver,
 		Archiver:       bm.archiver,
+		Platform:       dockerfile.Platform,
 	}
 	}
+
 	return newBuilder(ctx, builderOptions).build(source, dockerfile)
 	return newBuilder(ctx, builderOptions).build(source, dockerfile)
 }
 }
 
 
@@ -90,6 +103,7 @@ type builderOptions struct {
 	ProgressWriter backend.ProgressWriter
 	ProgressWriter backend.ProgressWriter
 	PathCache      pathCache
 	PathCache      pathCache
 	Archiver       *archive.Archiver
 	Archiver       *archive.Archiver
+	Platform       string
 }
 }
 
 
 // Builder is a Dockerfile builder
 // Builder is a Dockerfile builder
@@ -113,14 +127,32 @@ type Builder struct {
 	pathCache        pathCache
 	pathCache        pathCache
 	containerManager *containerManager
 	containerManager *containerManager
 	imageProber      ImageProber
 	imageProber      ImageProber
+
+	// TODO @jhowardmft LCOW Support. This will be moved to options at a later
+	// stage, however that cannot be done now as it affects the public API
+	// if it were.
+	platform string
 }
 }
 
 
 // newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
 // newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
+// TODO @jhowardmsft LCOW support: Eventually platform can be moved into the builder
+// options, however, that would be an API change as it shares types.ImageBuildOptions.
 func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
 func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
 	config := options.Options
 	config := options.Options
 	if config == nil {
 	if config == nil {
 		config = new(types.ImageBuildOptions)
 		config = new(types.ImageBuildOptions)
 	}
 	}
+
+	// @jhowardmsft LCOW Support. For the time being, this is interim. Eventually
+	// will be moved to types.ImageBuildOptions, but it can't for now as that would
+	// be an API change.
+	if options.Platform == "" {
+		options.Platform = runtime.GOOS
+	}
+	if options.Platform == "windows" && system.LCOWSupported() {
+		options.Platform = "linux"
+	}
+
 	b := &Builder{
 	b := &Builder{
 		clientCtx:        clientCtx,
 		clientCtx:        clientCtx,
 		options:          config,
 		options:          config,
@@ -134,9 +166,11 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
 		buildStages:      newBuildStages(),
 		buildStages:      newBuildStages(),
 		imageSources:     newImageSources(clientCtx, options),
 		imageSources:     newImageSources(clientCtx, options),
 		pathCache:        options.PathCache,
 		pathCache:        options.PathCache,
-		imageProber:      newImageProber(options.Backend, config.CacheFrom, config.NoCache),
+		imageProber:      newImageProber(options.Backend, config.CacheFrom, options.Platform, config.NoCache),
 		containerManager: newContainerManager(options.Backend),
 		containerManager: newContainerManager(options.Backend),
+		platform:         options.Platform,
 	}
 	}
+
 	return b
 	return b
 }
 }
 
 
@@ -267,6 +301,17 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	// TODO @jhowardmsft LCOW support. For now, if LCOW enabled, switch to linux.
+	// Also explicitly set the platform. Ultimately this will be in the builder
+	// options, but we can't do that yet as it would change the API.
+	if dockerfile.Platform == "" {
+		dockerfile.Platform = runtime.GOOS
+	}
+	if dockerfile.Platform == "windows" && system.LCOWSupported() {
+		dockerfile.Platform = "linux"
+	}
+	b.platform = dockerfile.Platform
+
 	// ensure that the commands are valid
 	// ensure that the commands are valid
 	for _, n := range dockerfile.AST.Children {
 	for _, n := range dockerfile.AST.Children {
 		if !validCommitCommands[n.Value] {
 		if !validCommitCommands[n.Value] {

+ 3 - 1
builder/dockerfile/builder_unix.go

@@ -2,4 +2,6 @@
 
 
 package dockerfile
 package dockerfile
 
 
-var defaultShell = []string{"/bin/sh", "-c"}
+func defaultShellForPlatform(platform string) []string {
+	return []string{"/bin/sh", "-c"}
+}

+ 6 - 1
builder/dockerfile/builder_windows.go

@@ -1,3 +1,8 @@
 package dockerfile
 package dockerfile
 
 
-var defaultShell = []string{"cmd", "/S", "/C"}
+func defaultShellForPlatform(platform string) []string {
+	if platform == "linux" {
+		return []string{"/bin/sh", "-c"}
+	}
+	return []string{"cmd", "/S", "/C"}
+}

+ 2 - 1
builder/dockerfile/containerbackend.go

@@ -28,10 +28,11 @@ func newContainerManager(docker builder.ExecBackend) *containerManager {
 }
 }
 
 
 // Create a container
 // Create a container
-func (c *containerManager) Create(runConfig *container.Config, hostConfig *container.HostConfig) (container.ContainerCreateCreatedBody, error) {
+func (c *containerManager) Create(runConfig *container.Config, hostConfig *container.HostConfig, platform string) (container.ContainerCreateCreatedBody, error) {
 	container, err := c.backend.ContainerCreate(types.ContainerCreateConfig{
 	container, err := c.backend.ContainerCreate(types.ContainerCreateConfig{
 		Config:     runConfig,
 		Config:     runConfig,
 		HostConfig: hostConfig,
 		HostConfig: hostConfig,
+		Platform:   platform,
 	})
 	})
 	if err != nil {
 	if err != nil {
 		return container, err
 		return container, err

+ 10 - 7
builder/dockerfile/dispatchers.go

@@ -26,6 +26,7 @@ import (
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/pkg/jsonmessage"
 	"github.com/docker/docker/pkg/jsonmessage"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
+	"github.com/docker/docker/pkg/system"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 )
 )
@@ -274,10 +275,12 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, err
 		localOnly = true
 		localOnly = true
 	}
 	}
 
 
-	// Windows cannot support a container with no base image.
+	// Windows cannot support a container with no base image unless it is LCOW.
 	if name == api.NoBaseImageSpecifier {
 	if name == api.NoBaseImageSpecifier {
 		if runtime.GOOS == "windows" {
 		if runtime.GOOS == "windows" {
-			return nil, errors.New("Windows does not support FROM scratch")
+			if b.platform == "windows" || (b.platform != "windows" && !system.LCOWSupported()) {
+				return nil, errors.New("Windows does not support FROM scratch")
+			}
 		}
 		}
 		return scratchImage, nil
 		return scratchImage, nil
 	}
 	}
@@ -400,7 +403,7 @@ func workdir(req dispatchRequest) error {
 	}
 	}
 
 
 	comment := "WORKDIR " + runConfig.WorkingDir
 	comment := "WORKDIR " + runConfig.WorkingDir
-	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment))
+	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, req.builder.platform))
 	containerID, err := req.builder.probeAndCreate(req.state, runConfigWithCommentCmd)
 	containerID, err := req.builder.probeAndCreate(req.state, runConfigWithCommentCmd)
 	if err != nil || containerID == "" {
 	if err != nil || containerID == "" {
 		return err
 		return err
@@ -418,7 +421,7 @@ func workdir(req dispatchRequest) error {
 // the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under
 // the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under
 // Windows, in the event there is only one argument The difference in processing:
 // Windows, in the event there is only one argument The difference in processing:
 //
 //
-// RUN echo hi          # sh -c echo hi       (Linux)
+// RUN echo hi          # sh -c echo hi       (Linux and LCOW)
 // RUN echo hi          # cmd /S /C echo hi   (Windows)
 // RUN echo hi          # cmd /S /C echo hi   (Windows)
 // RUN [ "echo", "hi" ] # echo hi
 // RUN [ "echo", "hi" ] # echo hi
 //
 //
@@ -434,7 +437,7 @@ func run(req dispatchRequest) error {
 	stateRunConfig := req.state.runConfig
 	stateRunConfig := req.state.runConfig
 	args := handleJSONArgs(req.args, req.attributes)
 	args := handleJSONArgs(req.args, req.attributes)
 	if !req.attributes["json"] {
 	if !req.attributes["json"] {
-		args = append(getShell(stateRunConfig), args...)
+		args = append(getShell(stateRunConfig, req.builder.platform), args...)
 	}
 	}
 	cmdFromArgs := strslice.StrSlice(args)
 	cmdFromArgs := strslice.StrSlice(args)
 	buildArgs := req.builder.buildArgs.FilterAllowed(stateRunConfig.Env)
 	buildArgs := req.builder.buildArgs.FilterAllowed(stateRunConfig.Env)
@@ -519,7 +522,7 @@ func cmd(req dispatchRequest) error {
 	runConfig := req.state.runConfig
 	runConfig := req.state.runConfig
 	cmdSlice := handleJSONArgs(req.args, req.attributes)
 	cmdSlice := handleJSONArgs(req.args, req.attributes)
 	if !req.attributes["json"] {
 	if !req.attributes["json"] {
-		cmdSlice = append(getShell(runConfig), cmdSlice...)
+		cmdSlice = append(getShell(runConfig, req.builder.platform), cmdSlice...)
 	}
 	}
 
 
 	runConfig.Cmd = strslice.StrSlice(cmdSlice)
 	runConfig.Cmd = strslice.StrSlice(cmdSlice)
@@ -671,7 +674,7 @@ func entrypoint(req dispatchRequest) error {
 		runConfig.Entrypoint = nil
 		runConfig.Entrypoint = nil
 	default:
 	default:
 		// ENTRYPOINT echo hi
 		// ENTRYPOINT echo hi
-		runConfig.Entrypoint = strslice.StrSlice(append(getShell(runConfig), parsed[0]))
+		runConfig.Entrypoint = strslice.StrSlice(append(getShell(runConfig, req.builder.platform), parsed[0]))
 	}
 	}
 
 
 	// when setting the entrypoint if a CMD was not explicitly set then
 	// when setting the entrypoint if a CMD was not explicitly set then

+ 11 - 6
builder/dockerfile/dispatchers_test.go

@@ -63,7 +63,7 @@ func newBuilderWithMockBackend() *Builder {
 			Backend: mockBackend,
 			Backend: mockBackend,
 		}),
 		}),
 		buildStages:      newBuildStages(),
 		buildStages:      newBuildStages(),
-		imageProber:      newImageProber(mockBackend, nil, false),
+		imageProber:      newImageProber(mockBackend, nil, runtime.GOOS, false),
 		containerManager: newContainerManager(mockBackend),
 		containerManager: newContainerManager(mockBackend),
 	}
 	}
 	return b
 	return b
@@ -194,7 +194,7 @@ func TestFromScratch(t *testing.T) {
 	req := defaultDispatchReq(b, "scratch")
 	req := defaultDispatchReq(b, "scratch")
 	err := from(req)
 	err := from(req)
 
 
-	if runtime.GOOS == "windows" {
+	if runtime.GOOS == "windows" && !system.LCOWSupported() {
 		assert.EqualError(t, err, "Windows does not support FROM scratch")
 		assert.EqualError(t, err, "Windows does not support FROM scratch")
 		return
 		return
 	}
 	}
@@ -202,7 +202,12 @@ func TestFromScratch(t *testing.T) {
 	require.NoError(t, err)
 	require.NoError(t, err)
 	assert.True(t, req.state.hasFromImage())
 	assert.True(t, req.state.hasFromImage())
 	assert.Equal(t, "", req.state.imageID)
 	assert.Equal(t, "", req.state.imageID)
-	assert.Equal(t, []string{"PATH=" + system.DefaultPathEnv}, req.state.runConfig.Env)
+	// Windows does not set the default path. TODO @jhowardmsft LCOW support. This will need revisiting as we get further into the implementation
+	expected := "PATH=" + system.DefaultPathEnv(runtime.GOOS)
+	if runtime.GOOS == "windows" {
+		expected = ""
+	}
+	assert.Equal(t, []string{expected}, req.state.runConfig.Env)
 }
 }
 
 
 func TestFromWithArg(t *testing.T) {
 func TestFromWithArg(t *testing.T) {
@@ -469,7 +474,7 @@ func TestRunWithBuildArgs(t *testing.T) {
 
 
 	runConfig := &container.Config{}
 	runConfig := &container.Config{}
 	origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
 	origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
-	cmdWithShell := strslice.StrSlice(append(getShell(runConfig), "echo foo"))
+	cmdWithShell := strslice.StrSlice(append(getShell(runConfig, runtime.GOOS), "echo foo"))
 	envVars := []string{"|1", "one=two"}
 	envVars := []string{"|1", "one=two"}
 	cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
 	cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
 
 
@@ -483,10 +488,10 @@ func TestRunWithBuildArgs(t *testing.T) {
 	}
 	}
 
 
 	mockBackend := b.docker.(*MockBackend)
 	mockBackend := b.docker.(*MockBackend)
-	mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
+	mockBackend.makeImageCacheFunc = func(_ []string, _ string) builder.ImageCache {
 		return imageCache
 		return imageCache
 	}
 	}
-	b.imageProber = newImageProber(mockBackend, nil, false)
+	b.imageProber = newImageProber(mockBackend, nil, runtime.GOOS, false)
 	mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ReleaseableLayer, error) {
 	mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ReleaseableLayer, error) {
 		return &mockImage{
 		return &mockImage{
 			id:     "abcdef",
 			id:     "abcdef",

+ 9 - 3
builder/dockerfile/evaluator.go

@@ -22,6 +22,7 @@ package dockerfile
 import (
 import (
 	"bytes"
 	"bytes"
 	"fmt"
 	"fmt"
+	"runtime"
 	"strings"
 	"strings"
 
 
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
@@ -228,14 +229,19 @@ func (s *dispatchState) beginStage(stageName string, image builder.Image) {
 }
 }
 
 
 // Add the default PATH to runConfig.ENV if one exists for the platform and there
 // Add the default PATH to runConfig.ENV if one exists for the platform and there
-// is no PATH set. Note that windows won't have one as it's set by HCS
+// is no PATH set. Note that Windows containers on Windows won't have one as it's set by HCS
 func (s *dispatchState) setDefaultPath() {
 func (s *dispatchState) setDefaultPath() {
-	if system.DefaultPathEnv == "" {
+	// TODO @jhowardmsft LCOW Support - This will need revisiting later
+	platform := runtime.GOOS
+	if platform == "windows" && system.LCOWSupported() {
+		platform = "linux"
+	}
+	if system.DefaultPathEnv(platform) == "" {
 		return
 		return
 	}
 	}
 	envMap := opts.ConvertKVStringsToMap(s.runConfig.Env)
 	envMap := opts.ConvertKVStringsToMap(s.runConfig.Env)
 	if _, ok := envMap["PATH"]; !ok {
 	if _, ok := envMap["PATH"]; !ok {
-		s.runConfig.Env = append(s.runConfig.Env, "PATH="+system.DefaultPathEnv)
+		s.runConfig.Env = append(s.runConfig.Env, "PATH="+system.DefaultPathEnv(platform))
 	}
 	}
 }
 }
 
 

+ 3 - 0
builder/dockerfile/imagecontext.go

@@ -97,6 +97,8 @@ type imageSources struct {
 	cache     pathCache // TODO: remove
 	cache     pathCache // TODO: remove
 }
 }
 
 
+// TODO @jhowardmsft LCOW Support: Eventually, platform can be moved to options.Options.Platform,
+// and removed from builderOptions, but that can't be done yet as it would affect the API.
 func newImageSources(ctx context.Context, options builderOptions) *imageSources {
 func newImageSources(ctx context.Context, options builderOptions) *imageSources {
 	getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ReleaseableLayer, error) {
 	getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ReleaseableLayer, error) {
 		pullOption := backend.PullOptionNoPull
 		pullOption := backend.PullOptionNoPull
@@ -111,6 +113,7 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
 			PullOption: pullOption,
 			PullOption: pullOption,
 			AuthConfig: options.Options.AuthConfigs,
 			AuthConfig: options.Options.AuthConfigs,
 			Output:     options.ProgressWriter.Output,
 			Output:     options.ProgressWriter.Output,
+			Platform:   options.Platform,
 		})
 		})
 	}
 	}
 
 

+ 2 - 2
builder/dockerfile/imageprobe.go

@@ -19,13 +19,13 @@ type imageProber struct {
 	cacheBusted bool
 	cacheBusted bool
 }
 }
 
 
-func newImageProber(cacheBuilder builder.ImageCacheBuilder, cacheFrom []string, noCache bool) ImageProber {
+func newImageProber(cacheBuilder builder.ImageCacheBuilder, cacheFrom []string, platform string, noCache bool) ImageProber {
 	if noCache {
 	if noCache {
 		return &nopProber{}
 		return &nopProber{}
 	}
 	}
 
 
 	reset := func() builder.ImageCache {
 	reset := func() builder.ImageCache {
-		return cacheBuilder.MakeImageCache(cacheFrom)
+		return cacheBuilder.MakeImageCache(cacheFrom, platform)
 	}
 	}
 	return &imageProber{cache: reset(), reset: reset}
 	return &imageProber{cache: reset(), reset: reset}
 }
 }

+ 13 - 13
builder/dockerfile/internals.go

@@ -25,7 +25,7 @@ func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
 		return errors.New("Please provide a source image with `from` prior to commit")
 		return errors.New("Please provide a source image with `from` prior to commit")
 	}
 	}
 
 
-	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment))
+	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, b.platform))
 	hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
 	hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
 	if err != nil || hit {
 	if err != nil || hit {
 		return err
 		return err
@@ -65,7 +65,7 @@ func (b *Builder) commitContainer(dispatchState *dispatchState, id string, conta
 }
 }
 
 
 func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runConfig *container.Config) error {
 func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runConfig *container.Config) error {
-	newLayer, err := imageMount.Layer().Commit()
+	newLayer, err := imageMount.Layer().Commit(b.platform)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -84,7 +84,7 @@ func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runC
 		ContainerConfig: runConfig,
 		ContainerConfig: runConfig,
 		DiffID:          newLayer.DiffID(),
 		DiffID:          newLayer.DiffID(),
 		Config:          copyRunConfig(state.runConfig),
 		Config:          copyRunConfig(state.runConfig),
-	})
+	}, parentImage.OS)
 
 
 	// TODO: it seems strange to marshal this here instead of just passing in the
 	// TODO: it seems strange to marshal this here instead of just passing in the
 	// image struct
 	// image struct
@@ -93,7 +93,7 @@ func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runC
 		return errors.Wrap(err, "failed to encode image config")
 		return errors.Wrap(err, "failed to encode image config")
 	}
 	}
 
 
-	exportedImage, err := b.docker.CreateImage(config, state.imageID)
+	exportedImage, err := b.docker.CreateImage(config, state.imageID, parentImage.OS)
 	if err != nil {
 	if err != nil {
 		return errors.Wrapf(err, "failed to export image")
 		return errors.Wrapf(err, "failed to export image")
 	}
 	}
@@ -110,7 +110,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
 	// TODO: should this have been using origPaths instead of srcHash in the comment?
 	// TODO: should this have been using origPaths instead of srcHash in the comment?
 	runConfigWithCommentCmd := copyRunConfig(
 	runConfigWithCommentCmd := copyRunConfig(
 		state.runConfig,
 		state.runConfig,
-		withCmdCommentString(fmt.Sprintf("%s %s in %s ", inst.cmdName, srcHash, inst.dest)))
+		withCmdCommentString(fmt.Sprintf("%s %s in %s ", inst.cmdName, srcHash, inst.dest), b.platform))
 	hit, err := b.probeCache(state, runConfigWithCommentCmd)
 	hit, err := b.probeCache(state, runConfigWithCommentCmd)
 	if err != nil || hit {
 	if err != nil || hit {
 		return err
 		return err
@@ -190,9 +190,9 @@ func withCmd(cmd []string) runConfigModifier {
 
 
 // withCmdComment sets Cmd to a nop comment string. See withCmdCommentString for
 // withCmdComment sets Cmd to a nop comment string. See withCmdCommentString for
 // why there are two almost identical versions of this.
 // why there are two almost identical versions of this.
-func withCmdComment(comment string) runConfigModifier {
+func withCmdComment(comment string, platform string) runConfigModifier {
 	return func(runConfig *container.Config) {
 	return func(runConfig *container.Config) {
-		runConfig.Cmd = append(getShell(runConfig), "#(nop) ", comment)
+		runConfig.Cmd = append(getShell(runConfig, platform), "#(nop) ", comment)
 	}
 	}
 }
 }
 
 
@@ -200,9 +200,9 @@ func withCmdComment(comment string) runConfigModifier {
 // A few instructions (workdir, copy, add) used a nop comment that is a single arg
 // A few instructions (workdir, copy, add) used a nop comment that is a single arg
 // where as all the other instructions used a two arg comment string. This
 // where as all the other instructions used a two arg comment string. This
 // function implements the single arg version.
 // function implements the single arg version.
-func withCmdCommentString(comment string) runConfigModifier {
+func withCmdCommentString(comment string, platform string) runConfigModifier {
 	return func(runConfig *container.Config) {
 	return func(runConfig *container.Config) {
-		runConfig.Cmd = append(getShell(runConfig), "#(nop) "+comment)
+		runConfig.Cmd = append(getShell(runConfig, platform), "#(nop) "+comment)
 	}
 	}
 }
 }
 
 
@@ -229,9 +229,9 @@ func withEntrypointOverride(cmd []string, entrypoint []string) runConfigModifier
 
 
 // getShell is a helper function which gets the right shell for prefixing the
 // getShell is a helper function which gets the right shell for prefixing the
 // shell-form of RUN, ENTRYPOINT and CMD instructions
 // shell-form of RUN, ENTRYPOINT and CMD instructions
-func getShell(c *container.Config) []string {
+func getShell(c *container.Config, platform string) []string {
 	if 0 == len(c.Shell) {
 	if 0 == len(c.Shell) {
-		return append([]string{}, defaultShell[:]...)
+		return append([]string{}, defaultShellForPlatform(platform)[:]...)
 	}
 	}
 	return append([]string{}, c.Shell[:]...)
 	return append([]string{}, c.Shell[:]...)
 }
 }
@@ -256,13 +256,13 @@ func (b *Builder) probeAndCreate(dispatchState *dispatchState, runConfig *contai
 	}
 	}
 	// Set a log config to override any default value set on the daemon
 	// Set a log config to override any default value set on the daemon
 	hostConfig := &container.HostConfig{LogConfig: defaultLogConfig}
 	hostConfig := &container.HostConfig{LogConfig: defaultLogConfig}
-	container, err := b.containerManager.Create(runConfig, hostConfig)
+	container, err := b.containerManager.Create(runConfig, hostConfig, b.platform)
 	return container.ID, err
 	return container.ID, err
 }
 }
 
 
 func (b *Builder) create(runConfig *container.Config) (string, error) {
 func (b *Builder) create(runConfig *container.Config) (string, error) {
 	hostConfig := hostConfigFromOptions(b.options)
 	hostConfig := hostConfigFromOptions(b.options)
-	container, err := b.containerManager.Create(runConfig, hostConfig)
+	container, err := b.containerManager.Create(runConfig, hostConfig, b.platform)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}

+ 3 - 2
builder/dockerfile/internals_test.go

@@ -2,6 +2,7 @@ package dockerfile
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"runtime"
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
@@ -97,9 +98,9 @@ func TestCopyRunConfig(t *testing.T) {
 		},
 		},
 		{
 		{
 			doc:       "Set the command to a comment",
 			doc:       "Set the command to a comment",
-			modifiers: []runConfigModifier{withCmdComment("comment")},
+			modifiers: []runConfigModifier{withCmdComment("comment", runtime.GOOS)},
 			expected: &container.Config{
 			expected: &container.Config{
-				Cmd: append(defaultShell, "#(nop) ", "comment"),
+				Cmd: append(defaultShellForPlatform(runtime.GOOS), "#(nop) ", "comment"),
 				Env: defaultEnv,
 				Env: defaultEnv,
 			},
 			},
 		},
 		},

+ 5 - 5
builder/dockerfile/mockbackend_test.go

@@ -18,7 +18,7 @@ type MockBackend struct {
 	containerCreateFunc func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
 	containerCreateFunc func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
 	commitFunc          func(string, *backend.ContainerCommitConfig) (string, error)
 	commitFunc          func(string, *backend.ContainerCommitConfig) (string, error)
 	getImageFunc        func(string) (builder.Image, builder.ReleaseableLayer, error)
 	getImageFunc        func(string) (builder.Image, builder.ReleaseableLayer, error)
-	makeImageCacheFunc  func(cacheFrom []string) builder.ImageCache
+	makeImageCacheFunc  func(cacheFrom []string, platform string) builder.ImageCache
 }
 }
 
 
 func (m *MockBackend) ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool, attached chan struct{}) error {
 func (m *MockBackend) ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool, attached chan struct{}) error {
@@ -71,14 +71,14 @@ func (m *MockBackend) GetImageAndReleasableLayer(ctx context.Context, refOrID st
 	return &mockImage{id: "theid"}, &mockLayer{}, nil
 	return &mockImage{id: "theid"}, &mockLayer{}, nil
 }
 }
 
 
-func (m *MockBackend) MakeImageCache(cacheFrom []string) builder.ImageCache {
+func (m *MockBackend) MakeImageCache(cacheFrom []string, platform string) builder.ImageCache {
 	if m.makeImageCacheFunc != nil {
 	if m.makeImageCacheFunc != nil {
-		return m.makeImageCacheFunc(cacheFrom)
+		return m.makeImageCacheFunc(cacheFrom, platform)
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-func (m *MockBackend) CreateImage(config []byte, parent string) (builder.Image, error) {
+func (m *MockBackend) CreateImage(config []byte, parent string, platform string) (builder.Image, error) {
 	return nil, nil
 	return nil, nil
 }
 }
 
 
@@ -121,7 +121,7 @@ func (l *mockLayer) Mount() (string, error) {
 	return "mountPath", nil
 	return "mountPath", nil
 }
 }
 
 
-func (l *mockLayer) Commit() (builder.ReleaseableLayer, error) {
+func (l *mockLayer) Commit(string) (builder.ReleaseableLayer, error) {
 	return nil, nil
 	return nil, nil
 }
 }
 
 

+ 48 - 5
builder/dockerfile/parser/parser.go

@@ -7,11 +7,13 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"regexp"
 	"regexp"
+	"runtime"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"unicode"
 	"unicode"
 
 
 	"github.com/docker/docker/builder/dockerfile/command"
 	"github.com/docker/docker/builder/dockerfile/command"
+	"github.com/docker/docker/pkg/system"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 )
 )
 
 
@@ -79,22 +81,28 @@ func (node *Node) AddChild(child *Node, startLine, endLine int) {
 }
 }
 
 
 var (
 var (
-	dispatch           map[string]func(string, *Directive) (*Node, map[string]bool, error)
-	tokenWhitespace    = regexp.MustCompile(`[\t\v\f\r ]+`)
-	tokenEscapeCommand = regexp.MustCompile(`^#[ \t]*escape[ \t]*=[ \t]*(?P<escapechar>.).*$`)
-	tokenComment       = regexp.MustCompile(`^#.*$`)
+	dispatch             map[string]func(string, *Directive) (*Node, map[string]bool, error)
+	tokenWhitespace      = regexp.MustCompile(`[\t\v\f\r ]+`)
+	tokenEscapeCommand   = regexp.MustCompile(`^#[ \t]*escape[ \t]*=[ \t]*(?P<escapechar>.).*$`)
+	tokenPlatformCommand = regexp.MustCompile(`^#[ \t]*platform[ \t]*=[ \t]*(?P<platform>.*)$`)
+	tokenComment         = regexp.MustCompile(`^#.*$`)
 )
 )
 
 
 // DefaultEscapeToken is the default escape token
 // DefaultEscapeToken is the default escape token
 const DefaultEscapeToken = '\\'
 const DefaultEscapeToken = '\\'
 
 
+// DefaultPlatformToken is the platform assumed for the build if not explicitly provided
+var DefaultPlatformToken = runtime.GOOS
+
 // Directive is the structure used during a build run to hold the state of
 // Directive is the structure used during a build run to hold the state of
 // parsing directives.
 // parsing directives.
 type Directive struct {
 type Directive struct {
 	escapeToken           rune           // Current escape token
 	escapeToken           rune           // Current escape token
+	platformToken         string         // Current platform token
 	lineContinuationRegex *regexp.Regexp // Current line continuation regex
 	lineContinuationRegex *regexp.Regexp // Current line continuation regex
 	processingComplete    bool           // Whether we are done looking for directives
 	processingComplete    bool           // Whether we are done looking for directives
 	escapeSeen            bool           // Whether the escape directive has been seen
 	escapeSeen            bool           // Whether the escape directive has been seen
+	platformSeen          bool           // Whether the platform directive has been seen
 }
 }
 
 
 // setEscapeToken sets the default token for escaping characters in a Dockerfile.
 // setEscapeToken sets the default token for escaping characters in a Dockerfile.
@@ -107,6 +115,22 @@ func (d *Directive) setEscapeToken(s string) error {
 	return nil
 	return nil
 }
 }
 
 
+// setPlatformToken sets the default platform for pulling images in a Dockerfile.
+func (d *Directive) setPlatformToken(s string) error {
+	s = strings.ToLower(s)
+	valid := []string{runtime.GOOS}
+	if runtime.GOOS == "windows" && system.LCOWSupported() {
+		valid = append(valid, "linux")
+	}
+	for _, item := range valid {
+		if s == item {
+			d.platformToken = s
+			return nil
+		}
+	}
+	return fmt.Errorf("invalid PLATFORM '%s'. Must be one of %v", s, valid)
+}
+
 // possibleParserDirective looks for one or more parser directives '# escapeToken=<char>' and
 // possibleParserDirective looks for one or more parser directives '# escapeToken=<char>' and
 // '# platform=<string>'. Parser directives must precede any builder instruction
 // '# platform=<string>'. Parser directives must precede any builder instruction
 // or other comments, and cannot be repeated.
 // or other comments, and cannot be repeated.
@@ -128,6 +152,23 @@ func (d *Directive) possibleParserDirective(line string) error {
 		}
 		}
 	}
 	}
 
 
+	// TODO @jhowardmsft LCOW Support: Eventually this check can be removed,
+	// but only recognise a platform token if running in LCOW mode.
+	if runtime.GOOS == "windows" && system.LCOWSupported() {
+		tpcMatch := tokenPlatformCommand.FindStringSubmatch(strings.ToLower(line))
+		if len(tpcMatch) != 0 {
+			for i, n := range tokenPlatformCommand.SubexpNames() {
+				if n == "platform" {
+					if d.platformSeen == true {
+						return errors.New("only one platform parser directive can be used")
+					}
+					d.platformSeen = true
+					return d.setPlatformToken(tpcMatch[i])
+				}
+			}
+		}
+	}
+
 	d.processingComplete = true
 	d.processingComplete = true
 	return nil
 	return nil
 }
 }
@@ -136,6 +177,7 @@ func (d *Directive) possibleParserDirective(line string) error {
 func NewDefaultDirective() *Directive {
 func NewDefaultDirective() *Directive {
 	directive := Directive{}
 	directive := Directive{}
 	directive.setEscapeToken(string(DefaultEscapeToken))
 	directive.setEscapeToken(string(DefaultEscapeToken))
+	directive.setPlatformToken(runtime.GOOS)
 	return &directive
 	return &directive
 }
 }
 
 
@@ -200,6 +242,7 @@ func newNodeFromLine(line string, directive *Directive) (*Node, error) {
 type Result struct {
 type Result struct {
 	AST         *Node
 	AST         *Node
 	EscapeToken rune
 	EscapeToken rune
+	Platform    string
 }
 }
 
 
 // Parse reads lines from a Reader, parses the lines into an AST and returns
 // Parse reads lines from a Reader, parses the lines into an AST and returns
@@ -252,7 +295,7 @@ func Parse(rwc io.Reader) (*Result, error) {
 		}
 		}
 		root.AddChild(child, startLine, currentLine)
 		root.AddChild(child, startLine, currentLine)
 	}
 	}
-	return &Result{AST: root, EscapeToken: d.escapeToken}, nil
+	return &Result{AST: root, EscapeToken: d.escapeToken, Platform: d.platformToken}, nil
 }
 }
 
 
 func trimComments(src []byte) []byte {
 func trimComments(src []byte) []byte {

+ 8 - 1
cmd/dockerd/config.go

@@ -1,6 +1,8 @@
 package main
 package main
 
 
 import (
 import (
+	"runtime"
+
 	"github.com/docker/docker/daemon/config"
 	"github.com/docker/docker/daemon/config"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	"github.com/spf13/pflag"
 	"github.com/spf13/pflag"
@@ -33,7 +35,12 @@ func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
 
 
 	flags.BoolVarP(&conf.AutoRestart, "restart", "r", true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run")
 	flags.BoolVarP(&conf.AutoRestart, "restart", "r", true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run")
 	flags.MarkDeprecated("restart", "Please use a restart policy on docker run")
 	flags.MarkDeprecated("restart", "Please use a restart policy on docker run")
-	flags.StringVarP(&conf.GraphDriver, "storage-driver", "s", "", "Storage driver to use")
+
+	// Windows doesn't support setting the storage driver - there is no choice as to which ones to use.
+	if runtime.GOOS != "windows" {
+		flags.StringVarP(&conf.GraphDriver, "storage-driver", "s", "", "Storage driver to use")
+	}
+
 	flags.IntVar(&conf.Mtu, "mtu", 0, "Set the containers network MTU")
 	flags.IntVar(&conf.Mtu, "mtu", 0, "Set the containers network MTU")
 	flags.BoolVar(&conf.RawLogs, "raw-logs", false, "Full timestamps without ANSI coloring")
 	flags.BoolVar(&conf.RawLogs, "raw-logs", false, "Full timestamps without ANSI coloring")
 	flags.Var(opts.NewListOptsRef(&conf.DNS, opts.ValidateIPAddress), "dns", "DNS server to use")
 	flags.Var(opts.NewListOptsRef(&conf.DNS, opts.ValidateIPAddress), "dns", "DNS server to use")

+ 5 - 6
cmd/dockerd/daemon.go

@@ -40,6 +40,7 @@ import (
 	"github.com/docker/docker/pkg/pidfile"
 	"github.com/docker/docker/pkg/pidfile"
 	"github.com/docker/docker/pkg/plugingetter"
 	"github.com/docker/docker/pkg/plugingetter"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
+	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/plugin"
 	"github.com/docker/docker/plugin"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
@@ -210,6 +211,10 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
 		logrus.Fatalf("Error creating middlewares: %v", err)
 		logrus.Fatalf("Error creating middlewares: %v", err)
 	}
 	}
 
 
+	if system.LCOWSupported() {
+		logrus.Warnln("LCOW support is enabled - this feature is incomplete")
+	}
+
 	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore)
 	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("Error starting daemon: %v", err)
 		return fmt.Errorf("Error starting daemon: %v", err)
@@ -262,12 +267,6 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
 
 
 	logrus.Info("Daemon has completed initialization")
 	logrus.Info("Daemon has completed initialization")
 
 
-	logrus.WithFields(logrus.Fields{
-		"version":     dockerversion.Version,
-		"commit":      dockerversion.GitCommit,
-		"graphdriver": d.GraphDriverName(),
-	}).Info("Docker daemon")
-
 	cli.d = d
 	cli.d = d
 
 
 	initRouter(api, d, c)
 	initRouter(api, d, c)

+ 59 - 12
container/container.go

@@ -7,6 +7,7 @@ import (
 	"net"
 	"net"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
+	"runtime"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
@@ -33,6 +34,7 @@ import (
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/symlink"
 	"github.com/docker/docker/pkg/symlink"
+	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/restartmanager"
 	"github.com/docker/docker/restartmanager"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
@@ -58,9 +60,8 @@ var (
 	errInvalidNetwork  = fmt.Errorf("invalid network settings while building port map info")
 	errInvalidNetwork  = fmt.Errorf("invalid network settings while building port map info")
 )
 )
 
 
-// CommonContainer holds the fields for a container which are
-// applicable across all platforms supported by the daemon.
-type CommonContainer struct {
+// Container holds the structure defining a container object.
+type Container struct {
 	StreamConfig *stream.Config
 	StreamConfig *stream.Config
 	// embed for Container to support states directly.
 	// embed for Container to support states directly.
 	*State          `json:"State"` // Needed for Engine API version <= 1.11
 	*State          `json:"State"` // Needed for Engine API version <= 1.11
@@ -78,6 +79,7 @@ type CommonContainer struct {
 	LogPath         string
 	LogPath         string
 	Name            string
 	Name            string
 	Driver          string
 	Driver          string
+	Platform        string
 	// MountLabel contains the options for the 'mount' command
 	// MountLabel contains the options for the 'mount' command
 	MountLabel             string
 	MountLabel             string
 	ProcessLabel           string
 	ProcessLabel           string
@@ -95,21 +97,31 @@ type CommonContainer struct {
 	LogCopier      *logger.Copier `json:"-"`
 	LogCopier      *logger.Copier `json:"-"`
 	restartManager restartmanager.RestartManager
 	restartManager restartmanager.RestartManager
 	attachContext  *attachContext
 	attachContext  *attachContext
+
+	// Fields here are specific to Unix platforms
+	AppArmorProfile string
+	HostnamePath    string
+	HostsPath       string
+	ShmPath         string
+	ResolvConfPath  string
+	SeccompProfile  string
+	NoNewPrivileges bool
+
+	// Fields here are specific to Windows
+	NetworkSharedContainerID string
 }
 }
 
 
 // NewBaseContainer creates a new container with its
 // NewBaseContainer creates a new container with its
 // basic configuration.
 // basic configuration.
 func NewBaseContainer(id, root string) *Container {
 func NewBaseContainer(id, root string) *Container {
 	return &Container{
 	return &Container{
-		CommonContainer: CommonContainer{
-			ID:            id,
-			State:         NewState(),
-			ExecCommands:  exec.NewStore(),
-			Root:          root,
-			MountPoints:   make(map[string]*volume.MountPoint),
-			StreamConfig:  stream.NewConfig(),
-			attachContext: &attachContext{},
-		},
+		ID:            id,
+		State:         NewState(),
+		ExecCommands:  exec.NewStore(),
+		Root:          root,
+		MountPoints:   make(map[string]*volume.MountPoint),
+		StreamConfig:  stream.NewConfig(),
+		attachContext: &attachContext{},
 	}
 	}
 }
 }
 
 
@@ -133,6 +145,13 @@ func (container *Container) FromDisk() error {
 		return err
 		return err
 	}
 	}
 
 
+	// Ensure the platform is set if blank. Assume it is the platform of the
+	// host OS if not, to ensure containers created before multiple-platform
+	// support are migrated
+	if container.Platform == "" {
+		container.Platform = runtime.GOOS
+	}
+
 	if err := label.ReserveLabel(container.ProcessLabel); err != nil {
 	if err := label.ReserveLabel(container.ProcessLabel); err != nil {
 		return err
 		return err
 	}
 	}
@@ -986,3 +1005,31 @@ func (container *Container) ConfigsDirPath() string {
 func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference) string {
 func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference) string {
 	return filepath.Join(container.ConfigsDirPath(), configRef.ConfigID)
 	return filepath.Join(container.ConfigsDirPath(), configRef.ConfigID)
 }
 }
+
+// CreateDaemonEnvironment creates a new environment variable slice for this container.
+func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string) []string {
+	// Setup environment
+	// TODO @jhowardmsft LCOW Support. This will need revisiting later.
+	platform := container.Platform
+	if platform == "" {
+		platform = runtime.GOOS
+	}
+	env := []string{}
+	if runtime.GOOS != "windows" || (runtime.GOOS == "windows" && system.LCOWSupported() && platform == "linux") {
+		env = []string{
+			"PATH=" + system.DefaultPathEnv(platform),
+			"HOSTNAME=" + container.Config.Hostname,
+		}
+		if tty {
+			env = append(env, "TERM=xterm")
+		}
+		env = append(env, linkedEnv...)
+	}
+
+	// because the env on the container can override certain default values
+	// we need to replace the 'env' keys where they match and append anything
+	// else.
+	//return ReplaceOrAppendEnvValues(linkedEnv, container.Config.Env)
+	foo := ReplaceOrAppendEnvValues(env, container.Config.Env)
+	return foo
+}

+ 4 - 12
container/container_unit_test.go

@@ -11,9 +11,7 @@ import (
 
 
 func TestContainerStopSignal(t *testing.T) {
 func TestContainerStopSignal(t *testing.T) {
 	c := &Container{
 	c := &Container{
-		CommonContainer: CommonContainer{
-			Config: &container.Config{},
-		},
+		Config: &container.Config{},
 	}
 	}
 
 
 	def, err := signal.ParseSignal(signal.DefaultStopSignal)
 	def, err := signal.ParseSignal(signal.DefaultStopSignal)
@@ -27,9 +25,7 @@ func TestContainerStopSignal(t *testing.T) {
 	}
 	}
 
 
 	c = &Container{
 	c = &Container{
-		CommonContainer: CommonContainer{
-			Config: &container.Config{StopSignal: "SIGKILL"},
-		},
+		Config: &container.Config{StopSignal: "SIGKILL"},
 	}
 	}
 	s = c.StopSignal()
 	s = c.StopSignal()
 	if s != 9 {
 	if s != 9 {
@@ -39,9 +35,7 @@ func TestContainerStopSignal(t *testing.T) {
 
 
 func TestContainerStopTimeout(t *testing.T) {
 func TestContainerStopTimeout(t *testing.T) {
 	c := &Container{
 	c := &Container{
-		CommonContainer: CommonContainer{
-			Config: &container.Config{},
-		},
+		Config: &container.Config{},
 	}
 	}
 
 
 	s := c.StopTimeout()
 	s := c.StopTimeout()
@@ -51,9 +45,7 @@ func TestContainerStopTimeout(t *testing.T) {
 
 
 	stopTimeout := 15
 	stopTimeout := 15
 	c = &Container{
 	c = &Container{
-		CommonContainer: CommonContainer{
-			Config: &container.Config{StopTimeout: &stopTimeout},
-		},
+		Config: &container.Config{StopTimeout: &stopTimeout},
 	}
 	}
 	s = c.StopSignal()
 	s = c.StopSignal()
 	if s != 15 {
 	if s != 15 {

+ 0 - 36
container/container_unix.go

@@ -26,21 +26,6 @@ const (
 	containerSecretMountPath = "/run/secrets"
 	containerSecretMountPath = "/run/secrets"
 )
 )
 
 
-// Container holds the fields specific to unixen implementations.
-// See CommonContainer for standard fields common to all containers.
-type Container struct {
-	CommonContainer
-
-	// Fields below here are platform specific.
-	AppArmorProfile string
-	HostnamePath    string
-	HostsPath       string
-	ShmPath         string
-	ResolvConfPath  string
-	SeccompProfile  string
-	NoNewPrivileges bool
-}
-
 // ExitStatus provides exit reasons for a container.
 // ExitStatus provides exit reasons for a container.
 type ExitStatus struct {
 type ExitStatus struct {
 	// The exit code with which the container exited.
 	// The exit code with which the container exited.
@@ -50,27 +35,6 @@ type ExitStatus struct {
 	OOMKilled bool
 	OOMKilled bool
 }
 }
 
 
-// CreateDaemonEnvironment returns the list of all environment variables given the list of
-// environment variables related to links.
-// Sets PATH, HOSTNAME and if container.Config.Tty is set: TERM.
-// The defaults set here do not override the values in container.Config.Env
-func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string) []string {
-	// Setup environment
-	env := []string{
-		"PATH=" + system.DefaultPathEnv,
-		"HOSTNAME=" + container.Config.Hostname,
-	}
-	if tty {
-		env = append(env, "TERM=xterm")
-	}
-	env = append(env, linkedEnv...)
-	// because the env on the container can override certain default values
-	// we need to replace the 'env' keys where they match and append anything
-	// else.
-	env = ReplaceOrAppendEnvValues(env, container.Config.Env)
-	return env
-}
-
 // TrySetNetworkMount attempts to set the network mounts given a provided destination and
 // TrySetNetworkMount attempts to set the network mounts given a provided destination and
 // the path to use for it; return true if the given destination was a network mount file
 // the path to use for it; return true if the given destination was a network mount file
 func (container *Container) TrySetNetworkMount(destination string, path string) bool {
 func (container *Container) TrySetNetworkMount(destination string, path string) bool {

+ 2 - 19
container/container_windows.go

@@ -17,29 +17,12 @@ const (
 	containerInternalConfigsDirPath  = `C:\ProgramData\Docker\internal\configs`
 	containerInternalConfigsDirPath  = `C:\ProgramData\Docker\internal\configs`
 )
 )
 
 
-// Container holds fields specific to the Windows implementation. See
-// CommonContainer for standard fields common to all containers.
-type Container struct {
-	CommonContainer
-
-	// Fields below here are platform specific.
-	NetworkSharedContainerID string
-}
-
 // ExitStatus provides exit reasons for a container.
 // ExitStatus provides exit reasons for a container.
 type ExitStatus struct {
 type ExitStatus struct {
 	// The exit code with which the container exited.
 	// The exit code with which the container exited.
 	ExitCode int
 	ExitCode int
 }
 }
 
 
-// CreateDaemonEnvironment creates a new environment variable slice for this container.
-func (container *Container) CreateDaemonEnvironment(_ bool, linkedEnv []string) []string {
-	// because the env on the container can override certain default values
-	// we need to replace the 'env' keys where they match and append anything
-	// else.
-	return ReplaceOrAppendEnvValues(linkedEnv, container.Config.Env)
-}
-
 // UnmountIpcMounts unmounts Ipc related mounts.
 // UnmountIpcMounts unmounts Ipc related mounts.
 // This is a NOOP on windows.
 // This is a NOOP on windows.
 func (container *Container) UnmountIpcMounts(unmount func(pth string) error) {
 func (container *Container) UnmountIpcMounts(unmount func(pth string) error) {
@@ -60,7 +43,7 @@ func (container *Container) CreateSecretSymlinks() error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		if err := system.MkdirAll(filepath.Dir(resolvedPath), 0); err != nil {
+		if err := system.MkdirAll(filepath.Dir(resolvedPath), 0, ""); err != nil {
 			return err
 			return err
 		}
 		}
 		if err := os.Symlink(filepath.Join(containerInternalSecretMountPath, r.SecretID), resolvedPath); err != nil {
 		if err := os.Symlink(filepath.Join(containerInternalSecretMountPath, r.SecretID), resolvedPath); err != nil {
@@ -102,7 +85,7 @@ func (container *Container) CreateConfigSymlinks() error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		if err := system.MkdirAll(filepath.Dir(resolvedPath), 0); err != nil {
+		if err := system.MkdirAll(filepath.Dir(resolvedPath), 0, ""); err != nil {
 			return err
 			return err
 		}
 		}
 		if err := os.Symlink(filepath.Join(containerInternalConfigsDirPath, configRef.ConfigID), resolvedPath); err != nil {
 		if err := os.Symlink(filepath.Join(containerInternalConfigsDirPath, configRef.ConfigID), resolvedPath); err != nil {

+ 16 - 12
daemon/build.go

@@ -2,6 +2,7 @@ package daemon
 
 
 import (
 import (
 	"io"
 	"io"
+	"runtime"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
@@ -40,7 +41,7 @@ func (rl *releaseableLayer) Mount() (string, error) {
 	return rl.rwLayer.Mount("")
 	return rl.rwLayer.Mount("")
 }
 }
 
 
-func (rl *releaseableLayer) Commit() (builder.ReleaseableLayer, error) {
+func (rl *releaseableLayer) Commit(platform string) (builder.ReleaseableLayer, error) {
 	var chainID layer.ChainID
 	var chainID layer.ChainID
 	if rl.roLayer != nil {
 	if rl.roLayer != nil {
 		chainID = rl.roLayer.ChainID()
 		chainID = rl.roLayer.ChainID()
@@ -51,7 +52,7 @@ func (rl *releaseableLayer) Commit() (builder.ReleaseableLayer, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	newLayer, err := rl.layerStore.Register(stream, chainID)
+	newLayer, err := rl.layerStore.Register(stream, chainID, layer.Platform(platform))
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -114,7 +115,7 @@ func newReleasableLayerForImage(img *image.Image, layerStore layer.Store) (build
 }
 }
 
 
 // TODO: could this use the regular daemon PullImage ?
 // TODO: could this use the regular daemon PullImage ?
-func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (*image.Image, error) {
+func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer, platform string) (*image.Image, error) {
 	ref, err := reference.ParseNormalizedNamed(name)
 	ref, err := reference.ParseNormalizedNamed(name)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -133,7 +134,7 @@ func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfi
 		pullRegistryAuth = &resolvedConfig
 		pullRegistryAuth = &resolvedConfig
 	}
 	}
 
 
-	if err := daemon.pullImageWithReference(ctx, ref, nil, pullRegistryAuth, output); err != nil {
+	if err := daemon.pullImageWithReference(ctx, ref, platform, nil, pullRegistryAuth, output); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	return daemon.GetImage(name)
 	return daemon.GetImage(name)
@@ -144,7 +145,7 @@ func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfi
 // leaking of layers.
 // leaking of layers.
 func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
 func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
 	if refOrID == "" {
 	if refOrID == "" {
-		layer, err := newReleasableLayerForImage(nil, daemon.layerStore)
+		layer, err := newReleasableLayerForImage(nil, daemon.stores[opts.Platform].layerStore)
 		return nil, layer, err
 		return nil, layer, err
 	}
 	}
 
 
@@ -155,35 +156,38 @@ func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID st
 		}
 		}
 		// TODO: shouldn't we error out if error is different from "not found" ?
 		// TODO: shouldn't we error out if error is different from "not found" ?
 		if image != nil {
 		if image != nil {
-			layer, err := newReleasableLayerForImage(image, daemon.layerStore)
+			layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
 			return image, layer, err
 			return image, layer, err
 		}
 		}
 	}
 	}
 
 
-	image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output)
+	image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform)
 	if err != nil {
 	if err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
-	layer, err := newReleasableLayerForImage(image, daemon.layerStore)
+	layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
 	return image, layer, err
 	return image, layer, err
 }
 }
 
 
 // CreateImage creates a new image by adding a config and ID to the image store.
 // CreateImage creates a new image by adding a config and ID to the image store.
 // This is similar to LoadImage() except that it receives JSON encoded bytes of
 // This is similar to LoadImage() except that it receives JSON encoded bytes of
 // an image instead of a tar archive.
 // an image instead of a tar archive.
-func (daemon *Daemon) CreateImage(config []byte, parent string) (builder.Image, error) {
-	id, err := daemon.imageStore.Create(config)
+func (daemon *Daemon) CreateImage(config []byte, parent string, platform string) (builder.Image, error) {
+	if platform == "" {
+		platform = runtime.GOOS
+	}
+	id, err := daemon.stores[platform].imageStore.Create(config)
 	if err != nil {
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to create image")
 		return nil, errors.Wrapf(err, "failed to create image")
 	}
 	}
 
 
 	if parent != "" {
 	if parent != "" {
-		if err := daemon.imageStore.SetParent(id, image.ID(parent)); err != nil {
+		if err := daemon.stores[platform].imageStore.SetParent(id, image.ID(parent)); err != nil {
 			return nil, errors.Wrapf(err, "failed to set parent %s", parent)
 			return nil, errors.Wrapf(err, "failed to set parent %s", parent)
 		}
 		}
 	}
 	}
 
 
-	return daemon.imageStore.Get(id)
+	return daemon.stores[platform].imageStore.Get(id)
 }
 }
 
 
 // IDMappings returns uid/gid mappings for the builder
 // IDMappings returns uid/gid mappings for the builder

+ 3 - 3
daemon/cache.go

@@ -7,12 +7,12 @@ import (
 )
 )
 
 
 // MakeImageCache creates a stateful image cache.
 // MakeImageCache creates a stateful image cache.
-func (daemon *Daemon) MakeImageCache(sourceRefs []string) builder.ImageCache {
+func (daemon *Daemon) MakeImageCache(sourceRefs []string, platform string) builder.ImageCache {
 	if len(sourceRefs) == 0 {
 	if len(sourceRefs) == 0 {
-		return cache.NewLocal(daemon.imageStore)
+		return cache.NewLocal(daemon.stores[platform].imageStore)
 	}
 	}
 
 
-	cache := cache.New(daemon.imageStore)
+	cache := cache.New(daemon.stores[platform].imageStore)
 
 
 	for _, ref := range sourceRefs {
 	for _, ref := range sourceRefs {
 		img, err := daemon.GetImage(ref)
 		img, err := daemon.GetImage(ref)

+ 1 - 1
daemon/cluster/executor/backend.go

@@ -30,7 +30,7 @@ type Backend interface {
 	FindNetwork(idName string) (libnetwork.Network, error)
 	FindNetwork(idName string) (libnetwork.Network, error)
 	SetupIngress(clustertypes.NetworkCreateRequest, string) (<-chan struct{}, error)
 	SetupIngress(clustertypes.NetworkCreateRequest, string) (<-chan struct{}, error)
 	ReleaseIngress() (<-chan struct{}, error)
 	ReleaseIngress() (<-chan struct{}, error)
-	PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
+	PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
 	CreateManagedContainer(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
 	CreateManagedContainer(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
 	ContainerStart(name string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error
 	ContainerStart(name string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error
 	ContainerStop(name string, seconds *int) error
 	ContainerStop(name string, seconds *int) error

+ 9 - 1
daemon/cluster/executor/container/adapter.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"os"
 	"os"
+	"runtime"
 	"strings"
 	"strings"
 	"syscall"
 	"syscall"
 	"time"
 	"time"
@@ -20,6 +21,7 @@ import (
 	containerpkg "github.com/docker/docker/container"
 	containerpkg "github.com/docker/docker/container"
 	"github.com/docker/docker/daemon/cluster/convert"
 	"github.com/docker/docker/daemon/cluster/convert"
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
+	"github.com/docker/docker/pkg/system"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
 	"github.com/docker/swarmkit/agent/exec"
 	"github.com/docker/swarmkit/agent/exec"
 	"github.com/docker/swarmkit/api"
 	"github.com/docker/swarmkit/api"
@@ -88,7 +90,13 @@ func (c *containerAdapter) pullImage(ctx context.Context) error {
 	pr, pw := io.Pipe()
 	pr, pw := io.Pipe()
 	metaHeaders := map[string][]string{}
 	metaHeaders := map[string][]string{}
 	go func() {
 	go func() {
-		err := c.backend.PullImage(ctx, c.container.image(), "", metaHeaders, authConfig, pw)
+		// TODO @jhowardmsft LCOW Support: This will need revisiting as
+		// the stack is built up to include LCOW support for swarm.
+		platform := runtime.GOOS
+		if platform == "windows" && system.LCOWSupported() {
+			platform = "linux"
+		}
+		err := c.backend.PullImage(ctx, c.container.image(), "", platform, metaHeaders, authConfig, pw)
 		pw.CloseWithError(err)
 		pw.CloseWithError(err)
 	}()
 	}()
 
 

+ 6 - 8
daemon/cluster/executor/container/health_test.go

@@ -38,14 +38,12 @@ func TestHealthStates(t *testing.T) {
 	}
 	}
 
 
 	c := &container.Container{
 	c := &container.Container{
-		CommonContainer: container.CommonContainer{
-			ID:   "id",
-			Name: "name",
-			Config: &containertypes.Config{
-				Image: "image_name",
-				Labels: map[string]string{
-					"com.docker.swarm.task.id": "id",
-				},
+		ID:   "id",
+		Name: "name",
+		Config: &containertypes.Config{
+			Image: "image_name",
+			Labels: map[string]string{
+				"com.docker.swarm.task.id": "id",
 			},
 			},
 		},
 		},
 	}
 	}

+ 7 - 7
daemon/commit.go

@@ -164,17 +164,17 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
 		parent = new(image.Image)
 		parent = new(image.Image)
 		parent.RootFS = image.NewRootFS()
 		parent.RootFS = image.NewRootFS()
 	} else {
 	} else {
-		parent, err = daemon.imageStore.Get(container.ImageID)
+		parent, err = daemon.stores[container.Platform].imageStore.Get(container.ImageID)
 		if err != nil {
 		if err != nil {
 			return "", err
 			return "", err
 		}
 		}
 	}
 	}
 
 
-	l, err := daemon.layerStore.Register(rwTar, parent.RootFS.ChainID())
+	l, err := daemon.stores[container.Platform].layerStore.Register(rwTar, parent.RootFS.ChainID(), layer.Platform(container.Platform))
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
-	defer layer.ReleaseAndLog(daemon.layerStore, l)
+	defer layer.ReleaseAndLog(daemon.stores[container.Platform].layerStore, l)
 
 
 	containerConfig := c.ContainerConfig
 	containerConfig := c.ContainerConfig
 	if containerConfig == nil {
 	if containerConfig == nil {
@@ -188,18 +188,18 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
 		Config:          newConfig,
 		Config:          newConfig,
 		DiffID:          l.DiffID(),
 		DiffID:          l.DiffID(),
 	}
 	}
-	config, err := json.Marshal(image.NewChildImage(parent, cc))
+	config, err := json.Marshal(image.NewChildImage(parent, cc, container.Platform))
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
 
 
-	id, err := daemon.imageStore.Create(config)
+	id, err := daemon.stores[container.Platform].imageStore.Create(config)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
 
 
 	if container.ImageID != "" {
 	if container.ImageID != "" {
-		if err := daemon.imageStore.SetParent(id, container.ImageID); err != nil {
+		if err := daemon.stores[container.Platform].imageStore.SetParent(id, container.ImageID); err != nil {
 			return "", err
 			return "", err
 		}
 		}
 	}
 	}
@@ -218,7 +218,7 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
 				return "", err
 				return "", err
 			}
 			}
 		}
 		}
-		if err := daemon.TagImageWithReference(id, newTag); err != nil {
+		if err := daemon.TagImageWithReference(id, container.Platform, newTag); err != nil {
 			return "", err
 			return "", err
 		}
 		}
 		imageRef = reference.FamiliarString(newTag)
 		imageRef = reference.FamiliarString(newTag)

+ 3 - 3
daemon/container.go

@@ -111,7 +111,7 @@ func (daemon *Daemon) Register(c *container.Container) {
 	daemon.idIndex.Add(c.ID)
 	daemon.idIndex.Add(c.ID)
 }
 }
 
 
-func (daemon *Daemon) newContainer(name string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
+func (daemon *Daemon) newContainer(name string, platform string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
 	var (
 	var (
 		id             string
 		id             string
 		err            error
 		err            error
@@ -144,8 +144,8 @@ func (daemon *Daemon) newContainer(name string, config *containertypes.Config, h
 	base.ImageID = imgID
 	base.ImageID = imgID
 	base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName}
 	base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName}
 	base.Name = name
 	base.Name = name
-	base.Driver = daemon.GraphDriverName()
-
+	base.Driver = daemon.GraphDriverName(platform)
+	base.Platform = platform
 	return base, err
 	return base, err
 }
 }
 
 

+ 2 - 2
daemon/container_operations_windows.go

@@ -25,7 +25,7 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
 	logrus.Debugf("configs: setting up config dir: %s", localPath)
 	logrus.Debugf("configs: setting up config dir: %s", localPath)
 
 
 	// create local config root
 	// create local config root
-	if err := system.MkdirAllWithACL(localPath, 0); err != nil {
+	if err := system.MkdirAllWithACL(localPath, 0, system.SddlAdministratorsLocalSystem); err != nil {
 		return errors.Wrap(err, "error creating config dir")
 		return errors.Wrap(err, "error creating config dir")
 	}
 	}
 
 
@@ -98,7 +98,7 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
 	logrus.Debugf("secrets: setting up secret dir: %s", localMountPath)
 	logrus.Debugf("secrets: setting up secret dir: %s", localMountPath)
 
 
 	// create local secret root
 	// create local secret root
-	if err := system.MkdirAllWithACL(localMountPath, 0); err != nil {
+	if err := system.MkdirAllWithACL(localMountPath, 0, system.SddlAdministratorsLocalSystem); err != nil {
 		return errors.Wrap(err, "error creating secret local directory")
 		return errors.Wrap(err, "error creating secret local directory")
 	}
 	}
 
 

+ 29 - 4
daemon/create.go

@@ -19,6 +19,7 @@ import (
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/opencontainers/selinux/go-selinux/label"
 	"github.com/opencontainers/selinux/go-selinux/label"
 )
 )
@@ -75,6 +76,16 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
 		err       error
 		err       error
 	)
 	)
 
 
+	// TODO: @jhowardmsft LCOW support - at a later point, can remove the hard-coding
+	// to force the platform to be linux.
+	// Default the platform if not supplied
+	if params.Platform == "" {
+		params.Platform = runtime.GOOS
+	}
+	if params.Platform == "windows" && system.LCOWSupported() {
+		params.Platform = "linux"
+	}
+
 	if params.Config.Image != "" {
 	if params.Config.Image != "" {
 		img, err = daemon.GetImage(params.Config.Image)
 		img, err = daemon.GetImage(params.Config.Image)
 		if err != nil {
 		if err != nil {
@@ -82,9 +93,23 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
 		}
 		}
 
 
 		if runtime.GOOS == "solaris" && img.OS != "solaris " {
 		if runtime.GOOS == "solaris" && img.OS != "solaris " {
-			return nil, errors.New("Platform on which parent image was created is not Solaris")
+			return nil, errors.New("platform on which parent image was created is not Solaris")
 		}
 		}
 		imgID = img.ID()
 		imgID = img.ID()
+
+		if runtime.GOOS == "windows" && img.OS == "linux" && !system.LCOWSupported() {
+			return nil, errors.New("platform on which parent image was created is not Windows")
+		}
+	}
+
+	// Make sure the platform requested matches the image
+	if img != nil {
+		if params.Platform != img.Platform() {
+			// Ignore this in LCOW mode. @jhowardmsft TODO - This will need revisiting later.
+			if !(runtime.GOOS == "windows" && system.LCOWSupported()) {
+				return nil, fmt.Errorf("cannot create a %s container from a %s image", params.Platform, img.Platform())
+			}
+		}
 	}
 	}
 
 
 	if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
 	if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
@@ -95,7 +120,7 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	if container, err = daemon.newContainer(params.Name, params.Config, params.HostConfig, imgID, managed); err != nil {
+	if container, err = daemon.newContainer(params.Name, params.Platform, params.Config, params.HostConfig, imgID, managed); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	defer func() {
 	defer func() {
@@ -215,7 +240,7 @@ func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig)
 func (daemon *Daemon) setRWLayer(container *container.Container) error {
 func (daemon *Daemon) setRWLayer(container *container.Container) error {
 	var layerID layer.ChainID
 	var layerID layer.ChainID
 	if container.ImageID != "" {
 	if container.ImageID != "" {
-		img, err := daemon.imageStore.Get(container.ImageID)
+		img, err := daemon.stores[container.Platform].imageStore.Get(container.ImageID)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -228,7 +253,7 @@ func (daemon *Daemon) setRWLayer(container *container.Container) error {
 		StorageOpt: container.HostConfig.StorageOpt,
 		StorageOpt: container.HostConfig.StorageOpt,
 	}
 	}
 
 
-	rwLayer, err := daemon.layerStore.CreateRWLayer(container.ID, layerID, rwLayerOpts)
+	rwLayer, err := daemon.stores[container.Platform].layerStore.CreateRWLayer(container.ID, layerID, rwLayerOpts)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 150 - 91
daemon/daemon.go

@@ -69,43 +69,49 @@ var (
 	errSystemNotSupported = errors.New("The Docker daemon is not supported on this platform.")
 	errSystemNotSupported = errors.New("The Docker daemon is not supported on this platform.")
 )
 )
 
 
+type daemonStore struct {
+	graphDriver               string
+	imageRoot                 string
+	imageStore                image.Store
+	layerStore                layer.Store
+	distributionMetadataStore dmetadata.Store
+	referenceStore            refstore.Store
+}
+
 // Daemon holds information about the Docker daemon.
 // Daemon holds information about the Docker daemon.
 type Daemon struct {
 type Daemon struct {
-	ID                        string
-	repository                string
-	containers                container.Store
-	execCommands              *exec.Store
-	referenceStore            refstore.Store
-	downloadManager           *xfer.LayerDownloadManager
-	uploadManager             *xfer.LayerUploadManager
-	distributionMetadataStore dmetadata.Store
-	trustKey                  libtrust.PrivateKey
-	idIndex                   *truncindex.TruncIndex
-	configStore               *config.Config
-	statsCollector            *stats.Collector
-	defaultLogConfig          containertypes.LogConfig
-	RegistryService           registry.Service
-	EventsService             *events.Events
-	netController             libnetwork.NetworkController
-	volumes                   *store.VolumeStore
-	discoveryWatcher          discovery.Reloader
-	root                      string
-	seccompEnabled            bool
-	apparmorEnabled           bool
-	shutdown                  bool
-	idMappings                *idtools.IDMappings
-	layerStore                layer.Store
-	imageStore                image.Store
-	PluginStore               *plugin.Store // todo: remove
-	pluginManager             *plugin.Manager
-	nameIndex                 *registrar.Registrar
-	linkIndex                 *linkIndex
-	containerd                libcontainerd.Client
-	containerdRemote          libcontainerd.Remote
-	defaultIsolation          containertypes.Isolation // Default isolation mode on Windows
-	clusterProvider           cluster.Provider
-	cluster                   Cluster
-	metricsPluginListener     net.Listener
+	ID                    string
+	repository            string
+	containers            container.Store
+	execCommands          *exec.Store
+	downloadManager       *xfer.LayerDownloadManager
+	uploadManager         *xfer.LayerUploadManager
+	trustKey              libtrust.PrivateKey
+	idIndex               *truncindex.TruncIndex
+	configStore           *config.Config
+	statsCollector        *stats.Collector
+	defaultLogConfig      containertypes.LogConfig
+	RegistryService       registry.Service
+	EventsService         *events.Events
+	netController         libnetwork.NetworkController
+	volumes               *store.VolumeStore
+	discoveryWatcher      discovery.Reloader
+	root                  string
+	seccompEnabled        bool
+	apparmorEnabled       bool
+	shutdown              bool
+	idMappings            *idtools.IDMappings
+	stores                map[string]daemonStore // By container target platform
+	PluginStore           *plugin.Store          // todo: remove
+	pluginManager         *plugin.Manager
+	nameIndex             *registrar.Registrar
+	linkIndex             *linkIndex
+	containerd            libcontainerd.Client
+	containerdRemote      libcontainerd.Remote
+	defaultIsolation      containertypes.Isolation // Default isolation mode on Windows
+	clusterProvider       cluster.Provider
+	cluster               Cluster
+	metricsPluginListener net.Listener
 
 
 	machineMemory uint64
 	machineMemory uint64
 
 
@@ -137,10 +143,7 @@ func (daemon *Daemon) HasExperimental() bool {
 }
 }
 
 
 func (daemon *Daemon) restore() error {
 func (daemon *Daemon) restore() error {
-	var (
-		currentDriver = daemon.GraphDriverName()
-		containers    = make(map[string]*container.Container)
-	)
+	containers := make(map[string]*container.Container)
 
 
 	logrus.Info("Loading containers: start.")
 	logrus.Info("Loading containers: start.")
 
 
@@ -158,8 +161,9 @@ func (daemon *Daemon) restore() error {
 		}
 		}
 
 
 		// Ignore the container if it does not support the current driver being used by the graph
 		// Ignore the container if it does not support the current driver being used by the graph
-		if (container.Driver == "" && currentDriver == "aufs") || container.Driver == currentDriver {
-			rwlayer, err := daemon.layerStore.GetRWLayer(container.ID)
+		currentDriverForContainerPlatform := daemon.stores[container.Platform].graphDriver
+		if (container.Driver == "" && currentDriverForContainerPlatform == "aufs") || container.Driver == currentDriverForContainerPlatform {
+			rwlayer, err := daemon.stores[container.Platform].layerStore.GetRWLayer(container.ID)
 			if err != nil {
 			if err != nil {
 				logrus.Errorf("Failed to load container mount %v: %v", id, err)
 				logrus.Errorf("Failed to load container mount %v: %v", id, err)
 				continue
 				continue
@@ -590,14 +594,29 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
 	}
 	}
 
 
 	if runtime.GOOS == "windows" {
 	if runtime.GOOS == "windows" {
-		if err := system.MkdirAll(filepath.Join(config.Root, "credentialspecs"), 0); err != nil && !os.IsExist(err) {
+		if err := system.MkdirAll(filepath.Join(config.Root, "credentialspecs"), 0, ""); err != nil && !os.IsExist(err) {
 			return nil, err
 			return nil, err
 		}
 		}
 	}
 	}
 
 
-	driverName := os.Getenv("DOCKER_DRIVER")
-	if driverName == "" {
-		driverName = config.GraphDriver
+	// On Windows we don't support the environment variable, or a user supplied graphdriver
+	// as Windows has no choice in terms of which graphdrivers to use. It's a case of
+	// running Windows containers on Windows - windowsfilter, running Linux containers on Windows,
+	// lcow. Unix platforms however run a single graphdriver for all containers, and it can
+	// be set through an environment variable, a daemon start parameter, or chosen through
+	// initialization of the layerstore through driver priority order for example.
+	d.stores = make(map[string]daemonStore)
+	if runtime.GOOS == "windows" {
+		d.stores["windows"] = daemonStore{graphDriver: "windowsfilter"}
+		if system.LCOWSupported() {
+			d.stores["linux"] = daemonStore{graphDriver: "lcow"}
+		}
+	} else {
+		driverName := os.Getenv("DOCKER_DRIVER")
+		if driverName == "" {
+			driverName = config.GraphDriver
+		}
+		d.stores[runtime.GOOS] = daemonStore{graphDriver: driverName} // May still be empty. Layerstore init determines instead.
 	}
 	}
 
 
 	d.RegistryService = registryService
 	d.RegistryService = registryService
@@ -625,40 +644,56 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
 		return nil, errors.Wrap(err, "couldn't create plugin manager")
 		return nil, errors.Wrap(err, "couldn't create plugin manager")
 	}
 	}
 
 
-	d.layerStore, err = layer.NewStoreFromOptions(layer.StoreOptions{
-		StorePath:                 config.Root,
-		MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"),
-		GraphDriver:               driverName,
-		GraphDriverOptions:        config.GraphOptions,
-		IDMappings:                idMappings,
-		PluginGetter:              d.PluginStore,
-		ExperimentalEnabled:       config.Experimental,
-	})
-	if err != nil {
-		return nil, err
+	var graphDrivers []string
+	for platform, ds := range d.stores {
+		ls, err := layer.NewStoreFromOptions(layer.StoreOptions{
+			StorePath:                 config.Root,
+			MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"),
+			GraphDriver:               ds.graphDriver,
+			GraphDriverOptions:        config.GraphOptions,
+			IDMappings:                idMappings,
+			PluginGetter:              d.PluginStore,
+			ExperimentalEnabled:       config.Experimental,
+			Platform:                  platform,
+		})
+		if err != nil {
+			return nil, err
+		}
+		ds.graphDriver = ls.DriverName() // As layerstore may set the driver
+		ds.layerStore = ls
+		d.stores[platform] = ds
+		graphDrivers = append(graphDrivers, ls.DriverName())
 	}
 	}
 
 
-	graphDriver := d.layerStore.DriverName()
-	imageRoot := filepath.Join(config.Root, "image", graphDriver)
-
 	// Configure and validate the kernels security support
 	// Configure and validate the kernels security support
-	if err := configureKernelSecuritySupport(config, graphDriver); err != nil {
+	if err := configureKernelSecuritySupport(config, graphDrivers); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
 	logrus.Debugf("Max Concurrent Downloads: %d", *config.MaxConcurrentDownloads)
 	logrus.Debugf("Max Concurrent Downloads: %d", *config.MaxConcurrentDownloads)
-	d.downloadManager = xfer.NewLayerDownloadManager(d.layerStore, *config.MaxConcurrentDownloads)
+	lsMap := make(map[string]layer.Store)
+	for platform, ds := range d.stores {
+		lsMap[platform] = ds.layerStore
+	}
+	d.downloadManager = xfer.NewLayerDownloadManager(lsMap, *config.MaxConcurrentDownloads)
 	logrus.Debugf("Max Concurrent Uploads: %d", *config.MaxConcurrentUploads)
 	logrus.Debugf("Max Concurrent Uploads: %d", *config.MaxConcurrentUploads)
 	d.uploadManager = xfer.NewLayerUploadManager(*config.MaxConcurrentUploads)
 	d.uploadManager = xfer.NewLayerUploadManager(*config.MaxConcurrentUploads)
 
 
-	ifs, err := image.NewFSStoreBackend(filepath.Join(imageRoot, "imagedb"))
-	if err != nil {
-		return nil, err
-	}
+	for platform, ds := range d.stores {
+		imageRoot := filepath.Join(config.Root, "image", ds.graphDriver)
+		ifs, err := image.NewFSStoreBackend(filepath.Join(imageRoot, "imagedb"))
+		if err != nil {
+			return nil, err
+		}
 
 
-	d.imageStore, err = image.NewImageStore(ifs, d.layerStore)
-	if err != nil {
-		return nil, err
+		var is image.Store
+		is, err = image.NewImageStore(ifs, platform, ds.layerStore)
+		if err != nil {
+			return nil, err
+		}
+		ds.imageRoot = imageRoot
+		ds.imageStore = is
+		d.stores[platform] = ds
 	}
 	}
 
 
 	// Configure the volumes driver
 	// Configure the volumes driver
@@ -674,27 +709,35 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
 
 
 	trustDir := filepath.Join(config.Root, "trust")
 	trustDir := filepath.Join(config.Root, "trust")
 
 
-	if err := system.MkdirAll(trustDir, 0700); err != nil {
-		return nil, err
-	}
-
-	distributionMetadataStore, err := dmetadata.NewFSMetadataStore(filepath.Join(imageRoot, "distribution"))
-	if err != nil {
+	if err := system.MkdirAll(trustDir, 0700, ""); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
 	eventsService := events.New()
 	eventsService := events.New()
 
 
-	referenceStore, err := refstore.NewReferenceStore(filepath.Join(imageRoot, "repositories.json"))
-	if err != nil {
-		return nil, fmt.Errorf("Couldn't create Tag store repositories: %s", err)
-	}
+	for platform, ds := range d.stores {
+		dms, err := dmetadata.NewFSMetadataStore(filepath.Join(ds.imageRoot, "distribution"), platform)
+		if err != nil {
+			return nil, err
+		}
 
 
-	migrationStart := time.Now()
-	if err := v1.Migrate(config.Root, graphDriver, d.layerStore, d.imageStore, referenceStore, distributionMetadataStore); err != nil {
-		logrus.Errorf("Graph migration failed: %q. Your old graph data was found to be too inconsistent for upgrading to content-addressable storage. Some of the old data was probably not upgraded. We recommend starting over with a clean storage directory if possible.", err)
+		rs, err := refstore.NewReferenceStore(filepath.Join(ds.imageRoot, "repositories.json"), platform)
+		if err != nil {
+			return nil, fmt.Errorf("Couldn't create Tag store repositories: %s", err)
+		}
+		ds.distributionMetadataStore = dms
+		ds.referenceStore = rs
+		d.stores[platform] = ds
+
+		// No content-addressability migration on Windows as it never supported pre-CA
+		if runtime.GOOS != "windows" {
+			migrationStart := time.Now()
+			if err := v1.Migrate(config.Root, ds.graphDriver, ds.layerStore, ds.imageStore, rs, dms); err != nil {
+				logrus.Errorf("Graph migration failed: %q. Your old graph data was found to be too inconsistent for upgrading to content-addressable storage. Some of the old data was probably not upgraded. We recommend starting over with a clean storage directory if possible.", err)
+			}
+			logrus.Infof("Graph migration to content-addressability took %.2f seconds", time.Since(migrationStart).Seconds())
+		}
 	}
 	}
-	logrus.Infof("Graph migration to content-addressability took %.2f seconds", time.Since(migrationStart).Seconds())
 
 
 	// Discovery is only enabled when the daemon is launched with an address to advertise.  When
 	// Discovery is only enabled when the daemon is launched with an address to advertise.  When
 	// initialized, the daemon is registered and we can store the discovery backend as it's read-only
 	// initialized, the daemon is registered and we can store the discovery backend as it's read-only
@@ -713,8 +756,6 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
 	d.repository = daemonRepo
 	d.repository = daemonRepo
 	d.containers = container.NewMemoryStore()
 	d.containers = container.NewMemoryStore()
 	d.execCommands = exec.NewStore()
 	d.execCommands = exec.NewStore()
-	d.referenceStore = referenceStore
-	d.distributionMetadataStore = distributionMetadataStore
 	d.trustKey = trustKey
 	d.trustKey = trustKey
 	d.idIndex = truncindex.NewTruncIndex([]string{})
 	d.idIndex = truncindex.NewTruncIndex([]string{})
 	d.statsCollector = d.newStatsCollector(1 * time.Second)
 	d.statsCollector = d.newStatsCollector(1 * time.Second)
@@ -761,6 +802,22 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
 	engineCpus.Set(float64(info.NCPU))
 	engineCpus.Set(float64(info.NCPU))
 	engineMemory.Set(float64(info.MemTotal))
 	engineMemory.Set(float64(info.MemTotal))
 
 
+	gd := ""
+	for platform, ds := range d.stores {
+		if len(gd) > 0 {
+			gd += ", "
+		}
+		gd += ds.graphDriver
+		if len(d.stores) > 1 {
+			gd = fmt.Sprintf("%s (%s)", gd, platform)
+		}
+	}
+	logrus.WithFields(logrus.Fields{
+		"version":        dockerversion.Version,
+		"commit":         dockerversion.GitCommit,
+		"graphdriver(s)": gd,
+	}).Info("Docker daemon")
+
 	return d, nil
 	return d, nil
 }
 }
 
 
@@ -867,7 +924,7 @@ func (daemon *Daemon) Shutdown() error {
 				logrus.Errorf("Stop container error: %v", err)
 				logrus.Errorf("Stop container error: %v", err)
 				return
 				return
 			}
 			}
-			if mountid, err := daemon.layerStore.GetMountID(c.ID); err == nil {
+			if mountid, err := daemon.stores[c.Platform].layerStore.GetMountID(c.ID); err == nil {
 				daemon.cleanupMountsByID(mountid)
 				daemon.cleanupMountsByID(mountid)
 			}
 			}
 			logrus.Debugf("container stopped %s", c.ID)
 			logrus.Debugf("container stopped %s", c.ID)
@@ -880,9 +937,11 @@ func (daemon *Daemon) Shutdown() error {
 		}
 		}
 	}
 	}
 
 
-	if daemon.layerStore != nil {
-		if err := daemon.layerStore.Cleanup(); err != nil {
-			logrus.Errorf("Error during layer Store.Cleanup(): %v", err)
+	for platform, ds := range daemon.stores {
+		if ds.layerStore != nil {
+			if err := ds.layerStore.Cleanup(); err != nil {
+				logrus.Errorf("Error during layer Store.Cleanup(): %v %s", err, platform)
+			}
 		}
 		}
 	}
 	}
 
 
@@ -925,7 +984,7 @@ func (daemon *Daemon) Mount(container *container.Container) error {
 		if container.BaseFS != "" && runtime.GOOS != "windows" {
 		if container.BaseFS != "" && runtime.GOOS != "windows" {
 			daemon.Unmount(container)
 			daemon.Unmount(container)
 			return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
 			return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
-				daemon.GraphDriverName(), container.ID, container.BaseFS, dir)
+				daemon.GraphDriverName(container.Platform), container.ID, container.BaseFS, dir)
 		}
 		}
 	}
 	}
 	container.BaseFS = dir // TODO: combine these fields
 	container.BaseFS = dir // TODO: combine these fields
@@ -967,8 +1026,8 @@ func (daemon *Daemon) Subnets() ([]net.IPNet, []net.IPNet) {
 }
 }
 
 
 // GraphDriverName returns the name of the graph driver used by the layer.Store
 // GraphDriverName returns the name of the graph driver used by the layer.Store
-func (daemon *Daemon) GraphDriverName() string {
-	return daemon.layerStore.DriverName()
+func (daemon *Daemon) GraphDriverName(platform string) string {
+	return daemon.stores[platform].layerStore.DriverName()
 }
 }
 
 
 // prepareTempDir prepares and returns the default directory to use
 // prepareTempDir prepares and returns the default directory to use

+ 1 - 1
daemon/daemon_solaris.go

@@ -353,7 +353,7 @@ func configureMaxThreads(config *Config) error {
 }
 }
 
 
 // configureKernelSecuritySupport configures and validate security support for the kernel
 // configureKernelSecuritySupport configures and validate security support for the kernel
-func configureKernelSecuritySupport(config *Config, driverName string) error {
+func configureKernelSecuritySupport(config *config.Config, driverNames []string) error {
 	return nil
 	return nil
 }
 }
 
 

+ 11 - 21
daemon/daemon_test.go

@@ -27,38 +27,28 @@ import (
 
 
 func TestGetContainer(t *testing.T) {
 func TestGetContainer(t *testing.T) {
 	c1 := &container.Container{
 	c1 := &container.Container{
-		CommonContainer: container.CommonContainer{
-			ID:   "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
-			Name: "tender_bardeen",
-		},
+		ID:   "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
+		Name: "tender_bardeen",
 	}
 	}
 
 
 	c2 := &container.Container{
 	c2 := &container.Container{
-		CommonContainer: container.CommonContainer{
-			ID:   "3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de",
-			Name: "drunk_hawking",
-		},
+		ID:   "3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de",
+		Name: "drunk_hawking",
 	}
 	}
 
 
 	c3 := &container.Container{
 	c3 := &container.Container{
-		CommonContainer: container.CommonContainer{
-			ID:   "3cdbd1aa394fd68559fd1441d6eff2abfafdcba06e72d2febdba229008b0bf57",
-			Name: "3cdbd1aa",
-		},
+		ID:   "3cdbd1aa394fd68559fd1441d6eff2abfafdcba06e72d2febdba229008b0bf57",
+		Name: "3cdbd1aa",
 	}
 	}
 
 
 	c4 := &container.Container{
 	c4 := &container.Container{
-		CommonContainer: container.CommonContainer{
-			ID:   "75fb0b800922abdbef2d27e60abcdfaf7fb0698b2a96d22d3354da361a6ff4a5",
-			Name: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
-		},
+		ID:   "75fb0b800922abdbef2d27e60abcdfaf7fb0698b2a96d22d3354da361a6ff4a5",
+		Name: "5a4ff6a163ad4533d22d69a2b8960bf7fafdcba06e72d2febdba229008b0bf57",
 	}
 	}
 
 
 	c5 := &container.Container{
 	c5 := &container.Container{
-		CommonContainer: container.CommonContainer{
-			ID:   "d22d69a2b8960bf7fafdcba06e72d2febdba960bf7fafdcba06e72d2f9008b060b",
-			Name: "d22d69a2b896",
-		},
+		ID:   "d22d69a2b8960bf7fafdcba06e72d2febdba960bf7fafdcba06e72d2f9008b060b",
+		Name: "d22d69a2b896",
 	}
 	}
 
 
 	store := container.NewMemoryStore()
 	store := container.NewMemoryStore()
@@ -184,7 +174,7 @@ func TestContainerInitDNS(t *testing.T) {
 "UpdateDns":false,"Volumes":{},"VolumesRW":{},"AppliedVolumesFrom":null}`
 "UpdateDns":false,"Volumes":{},"VolumesRW":{},"AppliedVolumesFrom":null}`
 
 
 	// Container struct only used to retrieve path to config file
 	// Container struct only used to retrieve path to config file
-	container := &container.Container{CommonContainer: container.CommonContainer{Root: containerPath}}
+	container := &container.Container{Root: containerPath}
 	configPath, err := container.ConfigPath()
 	configPath, err := container.ConfigPath()
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)

+ 11 - 3
daemon/daemon_unix.go

@@ -702,14 +702,22 @@ func overlaySupportsSelinux() (bool, error) {
 }
 }
 
 
 // configureKernelSecuritySupport configures and validates security support for the kernel
 // configureKernelSecuritySupport configures and validates security support for the kernel
-func configureKernelSecuritySupport(config *config.Config, driverName string) error {
+func configureKernelSecuritySupport(config *config.Config, driverNames []string) error {
 	if config.EnableSelinuxSupport {
 	if config.EnableSelinuxSupport {
 		if !selinuxEnabled() {
 		if !selinuxEnabled() {
 			logrus.Warn("Docker could not enable SELinux on the host system")
 			logrus.Warn("Docker could not enable SELinux on the host system")
 			return nil
 			return nil
 		}
 		}
 
 
-		if driverName == "overlay" || driverName == "overlay2" {
+		overlayFound := false
+		for _, d := range driverNames {
+			if d == "overlay" || d == "overlay2" {
+				overlayFound = true
+				break
+			}
+		}
+
+		if overlayFound {
 			// If driver is overlay or overlay2, make sure kernel
 			// If driver is overlay or overlay2, make sure kernel
 			// supports selinux with overlay.
 			// supports selinux with overlay.
 			supported, err := overlaySupportsSelinux()
 			supported, err := overlaySupportsSelinux()
@@ -718,7 +726,7 @@ func configureKernelSecuritySupport(config *config.Config, driverName string) er
 			}
 			}
 
 
 			if !supported {
 			if !supported {
-				logrus.Warnf("SELinux is not supported with the %s graph driver on this kernel", driverName)
+				logrus.Warnf("SELinux is not supported with the %v graph driver on this kernel", driverNames)
 			}
 			}
 		}
 		}
 	} else {
 	} else {

+ 12 - 2
daemon/daemon_windows.go

@@ -260,7 +260,7 @@ func checkSystem() error {
 }
 }
 
 
 // configureKernelSecuritySupport configures and validate security support for the kernel
 // configureKernelSecuritySupport configures and validate security support for the kernel
-func configureKernelSecuritySupport(config *config.Config, driverName string) error {
+func configureKernelSecuritySupport(config *config.Config, driverNames []string) error {
 	return nil
 	return nil
 }
 }
 
 
@@ -465,7 +465,7 @@ func setupRemappedRoot(config *config.Config) (*idtools.IDMappings, error) {
 func setupDaemonRoot(config *config.Config, rootDir string, rootIDs idtools.IDPair) error {
 func setupDaemonRoot(config *config.Config, rootDir string, rootIDs idtools.IDPair) error {
 	config.Root = rootDir
 	config.Root = rootDir
 	// Create the root directory if it doesn't exists
 	// Create the root directory if it doesn't exists
-	if err := system.MkdirAllWithACL(config.Root, 0); err != nil && !os.IsExist(err) {
+	if err := system.MkdirAllWithACL(config.Root, 0, system.SddlAdministratorsLocalSystem); err != nil && !os.IsExist(err) {
 		return err
 		return err
 	}
 	}
 	return nil
 	return nil
@@ -486,6 +486,11 @@ func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig
 // conditionalMountOnStart is a platform specific helper function during the
 // conditionalMountOnStart is a platform specific helper function during the
 // container start to call mount.
 // container start to call mount.
 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
+	// Bail out now for Linux containers
+	if system.LCOWSupported() && container.Platform != "windows" {
+		return nil
+	}
+
 	// We do not mount if a Hyper-V container
 	// We do not mount if a Hyper-V container
 	if !daemon.runAsHyperVContainer(container.HostConfig) {
 	if !daemon.runAsHyperVContainer(container.HostConfig) {
 		return daemon.Mount(container)
 		return daemon.Mount(container)
@@ -496,6 +501,11 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
 // conditionalUnmountOnCleanup is a platform specific helper function called
 // conditionalUnmountOnCleanup is a platform specific helper function called
 // during the cleanup of a container to unmount.
 // during the cleanup of a container to unmount.
 func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
 func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
+	// Bail out now for Linux containers
+	if system.LCOWSupported() && container.Platform != "windows" {
+		return nil
+	}
+
 	// We do not unmount if a Hyper-V container
 	// We do not unmount if a Hyper-V container
 	if !daemon.runAsHyperVContainer(container.HostConfig) {
 	if !daemon.runAsHyperVContainer(container.HostConfig) {
 		return daemon.Unmount(container)
 		return daemon.Unmount(container)

+ 2 - 2
daemon/delete.go

@@ -115,10 +115,10 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
 	// When container creation fails and `RWLayer` has not been created yet, we
 	// When container creation fails and `RWLayer` has not been created yet, we
 	// do not call `ReleaseRWLayer`
 	// do not call `ReleaseRWLayer`
 	if container.RWLayer != nil {
 	if container.RWLayer != nil {
-		metadata, err := daemon.layerStore.ReleaseRWLayer(container.RWLayer)
+		metadata, err := daemon.stores[container.Platform].layerStore.ReleaseRWLayer(container.RWLayer)
 		layer.LogReleaseMetadata(metadata)
 		layer.LogReleaseMetadata(metadata)
 		if err != nil && err != layer.ErrMountDoesNotExist {
 		if err != nil && err != layer.ErrMountDoesNotExist {
-			return errors.Wrapf(err, "driver %q failed to remove root filesystem for %s", daemon.GraphDriverName(), container.ID)
+			return errors.Wrapf(err, "driver %q failed to remove root filesystem for %s", daemon.GraphDriverName(container.Platform), container.ID)
 		}
 		}
 	}
 	}
 
 

+ 3 - 5
daemon/delete_test.go

@@ -26,11 +26,9 @@ func newDaemonWithTmpRoot(t *testing.T) (*Daemon, func()) {
 
 
 func newContainerWithState(state *container.State) *container.Container {
 func newContainerWithState(state *container.State) *container.Container {
 	return &container.Container{
 	return &container.Container{
-		CommonContainer: container.CommonContainer{
-			ID:     "test",
-			State:  state,
-			Config: &containertypes.Config{},
-		},
+		ID:     "test",
+		State:  state,
+		Config: &containertypes.Config{},
 	}
 	}
 
 
 }
 }

+ 21 - 17
daemon/disk_usage.go

@@ -15,12 +15,12 @@ import (
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/go-digest"
 )
 )
 
 
-func (daemon *Daemon) getLayerRefs() map[layer.ChainID]int {
-	tmpImages := daemon.imageStore.Map()
+func (daemon *Daemon) getLayerRefs(platform string) map[layer.ChainID]int {
+	tmpImages := daemon.stores[platform].imageStore.Map()
 	layerRefs := map[layer.ChainID]int{}
 	layerRefs := map[layer.ChainID]int{}
 	for id, img := range tmpImages {
 	for id, img := range tmpImages {
 		dgst := digest.Digest(id)
 		dgst := digest.Digest(id)
-		if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
+		if len(daemon.stores[platform].referenceStore.References(dgst)) == 0 && len(daemon.stores[platform].imageStore.Children(id)) != 0 {
 			continue
 			continue
 		}
 		}
 
 
@@ -53,6 +53,7 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er
 	}
 	}
 
 
 	// Get all top images with extra attributes
 	// Get all top images with extra attributes
+	// TODO @jhowardmsft LCOW. This may need revisiting
 	allImages, err := daemon.Images(filters.NewArgs(), false, true)
 	allImages, err := daemon.Images(filters.NewArgs(), false, true)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("failed to retrieve image list: %v", err)
 		return nil, fmt.Errorf("failed to retrieve image list: %v", err)
@@ -94,23 +95,26 @@ func (daemon *Daemon) SystemDiskUsage(ctx context.Context) (*types.DiskUsage, er
 	}
 	}
 
 
 	// Get total layers size on disk
 	// Get total layers size on disk
-	layerRefs := daemon.getLayerRefs()
-	allLayers := daemon.layerStore.Map()
 	var allLayersSize int64
 	var allLayersSize int64
-	for _, l := range allLayers {
-		select {
-		case <-ctx.Done():
-			return nil, ctx.Err()
-		default:
-			size, err := l.DiffSize()
-			if err == nil {
-				if _, ok := layerRefs[l.ChainID()]; ok {
-					allLayersSize += size
+	for platform := range daemon.stores {
+		layerRefs := daemon.getLayerRefs(platform)
+		allLayers := daemon.stores[platform].layerStore.Map()
+		var allLayersSize int64
+		for _, l := range allLayers {
+			select {
+			case <-ctx.Done():
+				return nil, ctx.Err()
+			default:
+				size, err := l.DiffSize()
+				if err == nil {
+					if _, ok := layerRefs[l.ChainID()]; ok {
+						allLayersSize += size
+					} else {
+						logrus.Warnf("found leaked image layer %v platform %s", l.ChainID(), platform)
+					}
 				} else {
 				} else {
-					logrus.Warnf("found leaked image layer %v", l.ChainID())
+					logrus.Warnf("failed to get diff size for layer %v %s", l.ChainID(), platform)
 				}
 				}
-			} else {
-				logrus.Warnf("failed to get diff size for layer %v", l.ChainID())
 			}
 			}
 		}
 		}
 	}
 	}

+ 13 - 17
daemon/events_test.go

@@ -16,15 +16,13 @@ func TestLogContainerEventCopyLabels(t *testing.T) {
 	defer e.Evict(l)
 	defer e.Evict(l)
 
 
 	container := &container.Container{
 	container := &container.Container{
-		CommonContainer: container.CommonContainer{
-			ID:   "container_id",
-			Name: "container_name",
-			Config: &containertypes.Config{
-				Image: "image_name",
-				Labels: map[string]string{
-					"node": "1",
-					"os":   "alpine",
-				},
+		ID:   "container_id",
+		Name: "container_name",
+		Config: &containertypes.Config{
+			Image: "image_name",
+			Labels: map[string]string{
+				"node": "1",
+				"os":   "alpine",
 			},
 			},
 		},
 		},
 	}
 	}
@@ -49,14 +47,12 @@ func TestLogContainerEventWithAttributes(t *testing.T) {
 	defer e.Evict(l)
 	defer e.Evict(l)
 
 
 	container := &container.Container{
 	container := &container.Container{
-		CommonContainer: container.CommonContainer{
-			ID:   "container_id",
-			Name: "container_name",
-			Config: &containertypes.Config{
-				Labels: map[string]string{
-					"node": "1",
-					"os":   "alpine",
-				},
+		ID:   "container_id",
+		Name: "container_name",
+		Config: &containertypes.Config{
+			Labels: map[string]string{
+				"node": "1",
+				"os":   "alpine",
 			},
 			},
 		},
 		},
 	}
 	}

+ 5 - 3
daemon/getsize_unix.go

@@ -3,6 +3,8 @@
 package daemon
 package daemon
 
 
 import (
 import (
+	"runtime"
+
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 )
 )
 
 
@@ -13,17 +15,17 @@ func (daemon *Daemon) getSize(containerID string) (int64, int64) {
 		err                error
 		err                error
 	)
 	)
 
 
-	rwlayer, err := daemon.layerStore.GetRWLayer(containerID)
+	rwlayer, err := daemon.stores[runtime.GOOS].layerStore.GetRWLayer(containerID)
 	if err != nil {
 	if err != nil {
 		logrus.Errorf("Failed to compute size of container rootfs %v: %v", containerID, err)
 		logrus.Errorf("Failed to compute size of container rootfs %v: %v", containerID, err)
 		return sizeRw, sizeRootfs
 		return sizeRw, sizeRootfs
 	}
 	}
-	defer daemon.layerStore.ReleaseRWLayer(rwlayer)
+	defer daemon.stores[runtime.GOOS].layerStore.ReleaseRWLayer(rwlayer)
 
 
 	sizeRw, err = rwlayer.Size()
 	sizeRw, err = rwlayer.Size()
 	if err != nil {
 	if err != nil {
 		logrus.Errorf("Driver %s couldn't return diff size of container %s: %s",
 		logrus.Errorf("Driver %s couldn't return diff size of container %s: %s",
-			daemon.GraphDriverName(), containerID, err)
+			daemon.GraphDriverName(runtime.GOOS), containerID, err)
 		// FIXME: GetSize should return an error. Not changing it now in case
 		// FIXME: GetSize should return an error. Not changing it now in case
 		// there is a side-effect.
 		// there is a side-effect.
 		sizeRw = -1
 		sizeRw = -1

+ 497 - 0
daemon/graphdriver/lcow/lcow.go

@@ -0,0 +1,497 @@
+// +build windows
+
+package lcow
+
+// Maintainer: @jhowardmsft
+// Graph-driver for Linux Containers On Windows (LCOW)
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/daemon/graphdriver"
+	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/pkg/idtools"
+	"github.com/docker/docker/pkg/system"
+	"github.com/jhowardmsft/opengcs/gogcs/client"
+)
+
+// init registers the LCOW driver to the register.
+func init() {
+	graphdriver.Register("lcow", InitLCOW)
+}
+
+const (
+	// sandboxFilename is the name of the file containing a layers sandbox (read-write layer)
+	sandboxFilename = "sandbox.vhdx"
+)
+
+// cacheType is our internal structure representing an item in our local cache
+// of things that have been mounted.
+type cacheType struct {
+	uvmPath   string // Path in utility VM
+	hostPath  string // Path on host
+	refCount  int    // How many times its been mounted
+	isSandbox bool   // True if a sandbox
+}
+
+// Driver represents an LCOW graph driver.
+type Driver struct {
+	// homeDir is the hostpath where we're storing everything
+	homeDir string
+	// cachedSandboxFile is the location of the local default-sized cached sandbox
+	cachedSandboxFile string
+	// options are the graphdriver options we are initialised with
+	options []string
+	// JJH LIFETIME TODO - Remove this and move up to daemon. For now, a global service utility-VM
+	config client.Config
+
+	// it is safe for windows to use a cache here because it does not support
+	// restoring containers when the daemon dies.
+
+	// cacheMu is the mutex protection add/update/deletes to our cache
+	cacheMu sync.Mutex
+	// cache is the cache of all the IDs we've mounted/unmounted.
+	cache map[string]cacheType
+}
+
+// InitLCOW returns a new LCOW storage driver.
+func InitLCOW(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
+	title := "lcowdriver: init:"
+	logrus.Debugf("%s %s", title, home)
+
+	d := &Driver{
+		homeDir:           home,
+		options:           options,
+		cachedSandboxFile: filepath.Join(home, "cache", sandboxFilename),
+		cache:             make(map[string]cacheType),
+	}
+
+	if err := idtools.MkdirAllAs(home, 0700, 0, 0); err != nil {
+		return nil, fmt.Errorf("%s failed to create '%s': %v", title, home, err)
+	}
+
+	// Cache directory for blank sandbox so don't have to pull it from the service VM each time
+	if err := idtools.MkdirAllAs(filepath.Dir(d.cachedSandboxFile), 0700, 0, 0); err != nil {
+		return nil, fmt.Errorf("%s failed to create '%s': %v", title, home, err)
+	}
+
+	return d, nil
+}
+
+// startUvm starts the service utility VM if it isn't running.
+// TODO @jhowardmsft. This will change before RS3 ships as we move to a model of one
+// service VM globally to a service VM per container (or offline operation). However,
+// for the initial bring-up of LCOW, this is acceptable.
+func (d *Driver) startUvm(context string) error {
+	// Nothing to do if it's already running
+	if d.config.Uvm != nil {
+		return nil
+	}
+
+	// So we need to start it. Generate a default configuration
+	if err := d.config.GenerateDefault(d.options); err != nil {
+		return fmt.Errorf("failed to generate default gogcs configuration (%s): %s", context, err)
+	}
+
+	d.config.Name = "LinuxServiceVM" // TODO @jhowardmsft - This requires an in-flight platform change. Can't hard code it to this longer term
+	if err := d.config.Create(); err != nil {
+		return fmt.Errorf("failed to start utility VM (%s): %s", context, err)
+	}
+	return nil
+}
+
+// terminateUvm terminates the service utility VM if its running.
+func (d *Driver) terminateUvm(context string) error {
+	// Nothing to do if it's not running
+	if d.config.Uvm == nil {
+		return nil
+	}
+
+	// FIXME: @jhowardmsft
+	// This isn't thread-safe yet, but will change anyway with the lifetime
+	// changes and multiple instances. Defering that work for now.
+	uvm := d.config.Uvm
+	d.config.Uvm = nil
+
+	if err := uvm.Terminate(); err != nil {
+		return fmt.Errorf("failed to terminate utility VM (%s): %s", context, err)
+	}
+
+	if err := uvm.WaitTimeout(time.Duration(d.config.UvmTimeoutSeconds) * time.Second); err != nil {
+		return fmt.Errorf("failed waiting for utility VM to terminate (%s): %s", context, err)
+	}
+
+	return nil
+}
+
+// String returns the string representation of a driver. This should match
+// the name the graph driver has been registered with.
+func (d *Driver) String() string {
+	return "lcow"
+}
+
+// Status returns the status of the driver.
+func (d *Driver) Status() [][2]string {
+	return [][2]string{
+		{"LCOW", ""},
+	}
+}
+
+// Exists returns true if the given id is registered with this driver.
+func (d *Driver) Exists(id string) bool {
+	_, err := os.Lstat(d.dir(id))
+	logrus.Debugf("lcowdriver: exists: id %s %t", id, err == nil)
+	return err == nil
+}
+
+// CreateReadWrite creates a layer that is writable for use as a container
+// file system. That equates to creating a sandbox VHDx.
+func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
+	logrus.Debugf("lcowdriver: createreadwrite: id %s", id)
+
+	if err := d.startUvm("createreadwrite"); err != nil {
+		return err
+	}
+
+	if err := d.Create(id, parent, opts); err != nil {
+		return err
+	}
+
+	return d.config.CreateSandbox(filepath.Join(d.dir(id), sandboxFilename), client.DefaultSandboxSizeMB, d.cachedSandboxFile)
+}
+
+// Create creates a new read-only layer with the given id.
+func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
+	logrus.Debugf("lcowdriver: create: id %s parent: %s", id, parent)
+
+	parentChain, err := d.getLayerChain(parent)
+	if err != nil {
+		return err
+	}
+
+	var layerChain []string
+	if parent != "" {
+		if !d.Exists(parent) {
+			return fmt.Errorf("lcowdriver: cannot create read-only layer with missing parent %s", parent)
+		}
+		layerChain = []string{d.dir(parent)}
+	}
+	layerChain = append(layerChain, parentChain...)
+
+	// Make sure layers are created with the correct ACL so that VMs can access them.
+	layerPath := d.dir(id)
+	logrus.Debugf("lcowdriver: create: id %s: creating layerPath %s", id, layerPath)
+	// Make sure the layers are created with the correct ACL so that VMs can access them.
+	if err := system.MkdirAllWithACL(layerPath, 755, system.SddlNtvmAdministratorsLocalSystem); err != nil {
+		return err
+	}
+
+	if err := d.setLayerChain(id, layerChain); err != nil {
+		if err2 := os.RemoveAll(layerPath); err2 != nil {
+			logrus.Warnf("Failed to remove layer %s: %s", layerPath, err2)
+		}
+		return err
+	}
+	logrus.Debugf("lcowdriver: createreadwrite: id %s: success", id)
+
+	return nil
+}
+
+// Remove unmounts and removes the dir information.
+func (d *Driver) Remove(id string) error {
+	logrus.Debugf("lcowdriver: remove: id %s", id)
+	tmpID := fmt.Sprintf("%s-removing", id)
+	tmpLayerPath := d.dir(tmpID)
+	layerPath := d.dir(id)
+
+	logrus.Debugf("lcowdriver: remove: id %s: layerPath %s", id, layerPath)
+	if err := os.Rename(layerPath, tmpLayerPath); err != nil && !os.IsNotExist(err) {
+		return err
+	}
+
+	if err := os.RemoveAll(tmpLayerPath); err != nil {
+		return err
+	}
+
+	logrus.Debugf("lcowdriver: remove: id %s: layerPath %s succeeded", id, layerPath)
+	return nil
+}
+
+// Get returns the rootfs path for the id. It is reference counted and
+// effectively can be thought of as a "mount the layer into the utility
+// vm if it isn't already"
+func (d *Driver) Get(id, mountLabel string) (string, error) {
+	dir, _, _, err := d.getEx(id)
+	return dir, err
+}
+
+// getEx is Get, but also returns the cache-entry and the size of the VHD
+func (d *Driver) getEx(id string) (string, cacheType, int64, error) {
+	title := "lcowdriver: getEx"
+	logrus.Debugf("%s %s", title, id)
+
+	if err := d.startUvm(fmt.Sprintf("getex %s", id)); err != nil {
+		logrus.Debugf("%s failed to start utility vm: %s", title, err)
+		return "", cacheType{}, 0, err
+	}
+
+	// Work out what we are working on
+	vhdFilename, vhdSize, isSandbox, err := client.LayerVhdDetails(d.dir(id))
+	if err != nil {
+		logrus.Debugf("%s failed to get LayerVhdDetails from %s: %s", title, d.dir(id), err)
+		return "", cacheType{}, 0, fmt.Errorf("%s failed to open layer or sandbox VHD to open in %s: %s", title, d.dir(id), err)
+	}
+	logrus.Debugf("%s %s, size %d, isSandbox %t", title, vhdFilename, vhdSize, isSandbox)
+
+	hotAddRequired := false
+	d.cacheMu.Lock()
+	var cacheEntry cacheType
+	if _, ok := d.cache[id]; !ok {
+		// The item is not currently in the cache.
+		//
+		// Sandboxes need hot-adding in the case that there is a single global utility VM
+		// This will change for multiple instances with the lifetime changes.
+		if isSandbox {
+			hotAddRequired = true
+		}
+		d.cache[id] = cacheType{
+			uvmPath:   fmt.Sprintf("/mnt/%s", id),
+			refCount:  1,
+			isSandbox: isSandbox,
+			hostPath:  vhdFilename,
+		}
+	} else {
+		// Increment the reference counter in the cache.
+		cacheEntry = d.cache[id]
+		cacheEntry.refCount++
+		d.cache[id] = cacheEntry
+	}
+
+	cacheEntry = d.cache[id]
+	logrus.Debugf("%s %s: isSandbox %t, refCount %d", title, id, cacheEntry.isSandbox, cacheEntry.refCount)
+	d.cacheMu.Unlock()
+
+	if hotAddRequired {
+		logrus.Debugf("%s %s: Hot-Adding %s", title, id, vhdFilename)
+		if err := d.config.HotAddVhd(vhdFilename, cacheEntry.uvmPath); err != nil {
+			return "", cacheType{}, 0, fmt.Errorf("%s hot add %s failed: %s", title, vhdFilename, err)
+		}
+	}
+
+	logrus.Debugf("%s %s success. %s: %+v: size %d", title, id, d.dir(id), cacheEntry, vhdSize)
+	return d.dir(id), cacheEntry, vhdSize, nil
+}
+
+// Put does the reverse of get. If there are no more references to
+// the layer, it unmounts it from the utility VM.
+func (d *Driver) Put(id string) error {
+	title := "lcowdriver: put"
+	logrus.Debugf("%s %s", title, id)
+
+	if err := d.startUvm(fmt.Sprintf("put %s", id)); err != nil {
+		return err
+	}
+
+	d.cacheMu.Lock()
+	// Bad-news if unmounting something that isn't in the cache.
+	entry, ok := d.cache[id]
+	if !ok {
+		d.cacheMu.Unlock()
+		return fmt.Errorf("%s possible ref-count error, or invalid id was passed to the graphdriver. Cannot handle id %s as it's not in the cache", title, id)
+	}
+
+	// Are we just decrementing the reference count
+	if entry.refCount > 1 {
+		entry.refCount--
+		d.cache[id] = entry
+		logrus.Debugf("%s %s: refCount decremented to %d", title, id, entry.refCount)
+		d.cacheMu.Unlock()
+		return nil
+	}
+
+	// No more references, so tear it down if previously hot-added
+	if entry.isSandbox {
+		logrus.Debugf("%s %s: Hot-Removing %s", title, id, entry.hostPath)
+		if err := d.config.HotRemoveVhd(entry.hostPath); err != nil {
+			d.cacheMu.Unlock()
+			return fmt.Errorf("%s failed to hot-remove %s from service utility VM: %s", title, entry.hostPath, err)
+		}
+	}
+
+	// @jhowardmsft TEMPORARY FIX WHILE WAITING FOR HOT-REMOVE TO BE FIXED IN PLATFORM
+	//d.terminateUvm(fmt.Sprintf("put %s", id))
+
+	// Remove from the cache map.
+	delete(d.cache, id)
+	d.cacheMu.Unlock()
+
+	logrus.Debugf("%s %s: refCount 0. %s (%s) completed successfully", title, id, entry.hostPath, entry.uvmPath)
+	return nil
+}
+
+// Cleanup ensures the information the driver stores is properly removed.
+// We use this opportunity to cleanup any -removing folders which may be
+// still left if the daemon was killed while it was removing a layer.
+func (d *Driver) Cleanup() error {
+	title := "lcowdriver: cleanup"
+	logrus.Debugf(title)
+
+	d.cacheMu.Lock()
+	for k, v := range d.cache {
+		logrus.Debugf("%s cache entry: %s: %+v", title, k, v)
+		if v.refCount > 0 {
+			logrus.Warnf("%s leaked %s: %+v", title, k, v)
+		}
+	}
+	d.cacheMu.Unlock()
+
+	items, err := ioutil.ReadDir(d.homeDir)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return nil
+		}
+		return err
+	}
+
+	// Note we don't return an error below - it's possible the files
+	// are locked. However, next time around after the daemon exits,
+	// we likely will be able to to cleanup successfully. Instead we log
+	// warnings if there are errors.
+	for _, item := range items {
+		if item.IsDir() && strings.HasSuffix(item.Name(), "-removing") {
+			if err := os.RemoveAll(filepath.Join(d.homeDir, item.Name())); err != nil {
+				logrus.Warnf("%s failed to cleanup %s: %s", title, item.Name(), err)
+			} else {
+				logrus.Infof("%s cleaned up %s", title, item.Name())
+			}
+		}
+	}
+	return nil
+}
+
+// Diff takes a layer (and it's parent layer which may be null, but
+// is ignored by this implementation below) and returns a reader for
+// a tarstream representing the layers contents. The id could be
+// a read-only "layer.vhd" or a read-write "sandbox.vhdx". The semantics
+// of this function dictate that the layer is already mounted.
+func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
+	title := "lcowdriver: diff:"
+	logrus.Debugf("%s id %s", title, id)
+
+	if err := d.startUvm(fmt.Sprintf("diff %s", id)); err != nil {
+		return nil, err
+	}
+
+	d.cacheMu.Lock()
+	if _, ok := d.cache[id]; !ok {
+		d.cacheMu.Unlock()
+		return nil, fmt.Errorf("%s fail as %s is not in the cache", title, id)
+	}
+	cacheEntry := d.cache[id]
+	d.cacheMu.Unlock()
+
+	// Stat to get size
+	fileInfo, err := os.Stat(cacheEntry.hostPath)
+	if err != nil {
+		return nil, fmt.Errorf("%s failed to stat %s: %s", title, cacheEntry.hostPath, err)
+	}
+
+	// Then obtain the tar stream for it
+	logrus.Debugf("%s %s, size %d, isSandbox %t", title, cacheEntry.hostPath, fileInfo.Size(), cacheEntry.isSandbox)
+	tarReadCloser, err := d.config.VhdToTar(cacheEntry.hostPath, cacheEntry.uvmPath, cacheEntry.isSandbox, fileInfo.Size())
+	if err != nil {
+		return nil, fmt.Errorf("%s failed to export layer to tar stream for id: %s, parent: %s : %s", title, id, parent, err)
+	}
+	logrus.Debugf("%s id %s parent %s completed successfully", title, id, parent)
+	return tarReadCloser, nil
+}
+
+// ApplyDiff extracts the changeset from the given diff into the
+// layer with the specified id and parent, returning the size of the
+// new layer in bytes. The layer should not be mounted when calling
+// this function. Another way of describing this is that ApplyDiff writes
+// to a new layer (a VHD in LCOW) the contents of a tarstream it's given.
+func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
+	logrus.Debugf("lcowdriver: applydiff: id %s", id)
+
+	if err := d.startUvm(fmt.Sprintf("applydiff %s", id)); err != nil {
+		return 0, err
+	}
+
+	return d.config.TarToVhd(filepath.Join(d.homeDir, id, "layer.vhd"), diff)
+}
+
+// Changes produces a list of changes between the specified layer
+// and its parent layer. If parent is "", then all changes will be ADD changes.
+// The layer should not be mounted when calling this function.
+func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
+	logrus.Debugf("lcowdriver: changes: id %s parent %s", id, parent)
+	// TODO @gupta-ak. Needs implementation with assistance from service VM
+	return nil, nil
+}
+
+// DiffSize calculates the changes between the specified layer
+// and its parent and returns the size in bytes of the changes
+// relative to its base filesystem directory.
+func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
+	logrus.Debugf("lcowdriver: diffsize: id %s", id)
+	// TODO @gupta-ak. Needs implementation with assistance from service VM
+	return 0, nil
+}
+
+// GetMetadata returns custom driver information.
+func (d *Driver) GetMetadata(id string) (map[string]string, error) {
+	logrus.Debugf("lcowdriver: getmetadata: id %s", id)
+	m := make(map[string]string)
+	m["dir"] = d.dir(id)
+	return m, nil
+}
+
+// dir returns the absolute path to the layer.
+func (d *Driver) dir(id string) string {
+	return filepath.Join(d.homeDir, filepath.Base(id))
+}
+
+// getLayerChain returns the layer chain information.
+func (d *Driver) getLayerChain(id string) ([]string, error) {
+	jPath := filepath.Join(d.dir(id), "layerchain.json")
+	logrus.Debugf("lcowdriver: getlayerchain: id %s json %s", id, jPath)
+	content, err := ioutil.ReadFile(jPath)
+	if os.IsNotExist(err) {
+		return nil, nil
+	} else if err != nil {
+		return nil, fmt.Errorf("lcowdriver: getlayerchain: %s unable to read layerchain file %s: %s", id, jPath, err)
+	}
+
+	var layerChain []string
+	err = json.Unmarshal(content, &layerChain)
+	if err != nil {
+		return nil, fmt.Errorf("lcowdriver: getlayerchain: %s failed to unmarshall layerchain file %s: %s", id, jPath, err)
+	}
+	return layerChain, nil
+}
+
+// setLayerChain stores the layer chain information on disk.
+func (d *Driver) setLayerChain(id string, chain []string) error {
+	content, err := json.Marshal(&chain)
+	if err != nil {
+		return fmt.Errorf("lcowdriver: setlayerchain: %s failed to marshall layerchain json: %s", id, err)
+	}
+
+	jPath := filepath.Join(d.dir(id), "layerchain.json")
+	logrus.Debugf("lcowdriver: setlayerchain: id %s json %s", id, jPath)
+	err = ioutil.WriteFile(jPath, content, 0600)
+	if err != nil {
+		return fmt.Errorf("lcowdriver: setlayerchain: %s failed to write layerchain file: %s", id, err)
+	}
+	return nil
+}

+ 2 - 1
daemon/graphdriver/register/register_windows.go

@@ -1,6 +1,7 @@
 package register
 package register
 
 
 import (
 import (
-	// register the windows graph driver
+	// register the windows graph drivers
+	_ "github.com/docker/docker/daemon/graphdriver/lcow"
 	_ "github.com/docker/docker/daemon/graphdriver/windows"
 	_ "github.com/docker/docker/daemon/graphdriver/windows"
 )
 )

+ 26 - 1
daemon/graphdriver/windows/windows.go

@@ -94,6 +94,10 @@ func InitFilter(home string, options []string, uidMaps, gidMaps []idtools.IDMap)
 		return nil, fmt.Errorf("%s is on an ReFS volume - ReFS volumes are not supported", home)
 		return nil, fmt.Errorf("%s is on an ReFS volume - ReFS volumes are not supported", home)
 	}
 	}
 
 
+	if err := idtools.MkdirAllAs(home, 0700, 0, 0); err != nil {
+		return nil, fmt.Errorf("windowsfilter failed to create '%s': %v", home, err)
+	}
+
 	d := &Driver{
 	d := &Driver{
 		info: hcsshim.DriverInfo{
 		info: hcsshim.DriverInfo{
 			HomeDir: home,
 			HomeDir: home,
@@ -149,8 +153,19 @@ func (d *Driver) Status() [][2]string {
 	}
 	}
 }
 }
 
 
+// panicIfUsedByLcow does exactly what it says.
+// TODO @jhowardmsft - this is an temporary measure for the bring-up of
+// Linux containers on Windows. It is a failsafe to ensure that the right
+// graphdriver is used.
+func panicIfUsedByLcow() {
+	if system.LCOWSupported() {
+		panic("inconsistency - windowsfilter graphdriver should not be used when in LCOW mode")
+	}
+}
+
 // Exists returns true if the given id is registered with this driver.
 // Exists returns true if the given id is registered with this driver.
 func (d *Driver) Exists(id string) bool {
 func (d *Driver) Exists(id string) bool {
+	panicIfUsedByLcow()
 	rID, err := d.resolveID(id)
 	rID, err := d.resolveID(id)
 	if err != nil {
 	if err != nil {
 		return false
 		return false
@@ -165,6 +180,7 @@ func (d *Driver) Exists(id string) bool {
 // CreateReadWrite creates a layer that is writable for use as a container
 // CreateReadWrite creates a layer that is writable for use as a container
 // file system.
 // file system.
 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
+	panicIfUsedByLcow()
 	if opts != nil {
 	if opts != nil {
 		return d.create(id, parent, opts.MountLabel, false, opts.StorageOpt)
 		return d.create(id, parent, opts.MountLabel, false, opts.StorageOpt)
 	}
 	}
@@ -173,6 +189,7 @@ func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts
 
 
 // Create creates a new read-only layer with the given id.
 // Create creates a new read-only layer with the given id.
 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
+	panicIfUsedByLcow()
 	if opts != nil {
 	if opts != nil {
 		return d.create(id, parent, opts.MountLabel, true, opts.StorageOpt)
 		return d.create(id, parent, opts.MountLabel, true, opts.StorageOpt)
 	}
 	}
@@ -256,6 +273,7 @@ func (d *Driver) dir(id string) string {
 
 
 // Remove unmounts and removes the dir information.
 // Remove unmounts and removes the dir information.
 func (d *Driver) Remove(id string) error {
 func (d *Driver) Remove(id string) error {
+	panicIfUsedByLcow()
 	rID, err := d.resolveID(id)
 	rID, err := d.resolveID(id)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -337,6 +355,7 @@ func (d *Driver) Remove(id string) error {
 
 
 // Get returns the rootfs path for the id. This will mount the dir at its given path.
 // Get returns the rootfs path for the id. This will mount the dir at its given path.
 func (d *Driver) Get(id, mountLabel string) (string, error) {
 func (d *Driver) Get(id, mountLabel string) (string, error) {
+	panicIfUsedByLcow()
 	logrus.Debugf("WindowsGraphDriver Get() id %s mountLabel %s", id, mountLabel)
 	logrus.Debugf("WindowsGraphDriver Get() id %s mountLabel %s", id, mountLabel)
 	var dir string
 	var dir string
 
 
@@ -395,6 +414,7 @@ func (d *Driver) Get(id, mountLabel string) (string, error) {
 
 
 // Put adds a new layer to the driver.
 // Put adds a new layer to the driver.
 func (d *Driver) Put(id string) error {
 func (d *Driver) Put(id string) error {
+	panicIfUsedByLcow()
 	logrus.Debugf("WindowsGraphDriver Put() id %s", id)
 	logrus.Debugf("WindowsGraphDriver Put() id %s", id)
 
 
 	rID, err := d.resolveID(id)
 	rID, err := d.resolveID(id)
@@ -424,7 +444,6 @@ func (d *Driver) Put(id string) error {
 // We use this opportunity to cleanup any -removing folders which may be
 // We use this opportunity to cleanup any -removing folders which may be
 // still left if the daemon was killed while it was removing a layer.
 // still left if the daemon was killed while it was removing a layer.
 func (d *Driver) Cleanup() error {
 func (d *Driver) Cleanup() error {
-
 	items, err := ioutil.ReadDir(d.info.HomeDir)
 	items, err := ioutil.ReadDir(d.info.HomeDir)
 	if err != nil {
 	if err != nil {
 		if os.IsNotExist(err) {
 		if os.IsNotExist(err) {
@@ -454,6 +473,7 @@ func (d *Driver) Cleanup() error {
 // layer and its parent layer which may be "".
 // layer and its parent layer which may be "".
 // The layer should be mounted when calling this function
 // The layer should be mounted when calling this function
 func (d *Driver) Diff(id, parent string) (_ io.ReadCloser, err error) {
 func (d *Driver) Diff(id, parent string) (_ io.ReadCloser, err error) {
+	panicIfUsedByLcow()
 	rID, err := d.resolveID(id)
 	rID, err := d.resolveID(id)
 	if err != nil {
 	if err != nil {
 		return
 		return
@@ -490,6 +510,7 @@ func (d *Driver) Diff(id, parent string) (_ io.ReadCloser, err error) {
 // and its parent layer. If parent is "", then all changes will be ADD changes.
 // and its parent layer. If parent is "", then all changes will be ADD changes.
 // The layer should not be mounted when calling this function.
 // The layer should not be mounted when calling this function.
 func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
 func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
+	panicIfUsedByLcow()
 	rID, err := d.resolveID(id)
 	rID, err := d.resolveID(id)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -545,6 +566,7 @@ func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
 // new layer in bytes.
 // new layer in bytes.
 // The layer should not be mounted when calling this function
 // The layer should not be mounted when calling this function
 func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
 func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
+	panicIfUsedByLcow()
 	var layerChain []string
 	var layerChain []string
 	if parent != "" {
 	if parent != "" {
 		rPId, err := d.resolveID(parent)
 		rPId, err := d.resolveID(parent)
@@ -579,6 +601,7 @@ func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
 // and its parent and returns the size in bytes of the changes
 // and its parent and returns the size in bytes of the changes
 // relative to its base filesystem directory.
 // relative to its base filesystem directory.
 func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
 func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
+	panicIfUsedByLcow()
 	rPId, err := d.resolveID(parent)
 	rPId, err := d.resolveID(parent)
 	if err != nil {
 	if err != nil {
 		return
 		return
@@ -600,6 +623,7 @@ func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
 
 
 // GetMetadata returns custom driver information.
 // GetMetadata returns custom driver information.
 func (d *Driver) GetMetadata(id string) (map[string]string, error) {
 func (d *Driver) GetMetadata(id string) (map[string]string, error) {
+	panicIfUsedByLcow()
 	m := make(map[string]string)
 	m := make(map[string]string)
 	m["dir"] = d.dir(id)
 	m["dir"] = d.dir(id)
 	return m, nil
 	return m, nil
@@ -902,6 +926,7 @@ func (fg *fileGetCloserWithBackupPrivileges) Close() error {
 // DiffGetter returns a FileGetCloser that can read files from the directory that
 // DiffGetter returns a FileGetCloser that can read files from the directory that
 // contains files for the layer differences. Used for direct access for tar-split.
 // contains files for the layer differences. Used for direct access for tar-split.
 func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
 func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
+	panicIfUsedByLcow()
 	id, err := d.resolveID(id)
 	id, err := d.resolveID(id)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err

+ 11 - 15
daemon/health_test.go

@@ -19,17 +19,15 @@ func reset(c *container.Container) {
 
 
 func TestNoneHealthcheck(t *testing.T) {
 func TestNoneHealthcheck(t *testing.T) {
 	c := &container.Container{
 	c := &container.Container{
-		CommonContainer: container.CommonContainer{
-			ID:   "container_id",
-			Name: "container_name",
-			Config: &containertypes.Config{
-				Image: "image_name",
-				Healthcheck: &containertypes.HealthConfig{
-					Test: []string{"NONE"},
-				},
+		ID:   "container_id",
+		Name: "container_name",
+		Config: &containertypes.Config{
+			Image: "image_name",
+			Healthcheck: &containertypes.HealthConfig{
+				Test: []string{"NONE"},
 			},
 			},
-			State: &container.State{},
 		},
 		},
+		State: &container.State{},
 	}
 	}
 	daemon := &Daemon{}
 	daemon := &Daemon{}
 
 
@@ -58,12 +56,10 @@ func TestHealthStates(t *testing.T) {
 	}
 	}
 
 
 	c := &container.Container{
 	c := &container.Container{
-		CommonContainer: container.CommonContainer{
-			ID:   "container_id",
-			Name: "container_name",
-			Config: &containertypes.Config{
-				Image: "image_name",
-			},
+		ID:   "container_id",
+		Name: "container_name",
+		Config: &containertypes.Config{
+			Image: "image_name",
 		},
 		},
 	}
 	}
 	daemon := &Daemon{
 	daemon := &Daemon{

+ 26 - 18
daemon/image.go

@@ -21,37 +21,43 @@ func (e ErrImageDoesNotExist) Error() string {
 	return fmt.Sprintf("No such image: %s", reference.FamiliarString(ref))
 	return fmt.Sprintf("No such image: %s", reference.FamiliarString(ref))
 }
 }
 
 
-// GetImageID returns an image ID corresponding to the image referred to by
+// GetImageIDAndPlatform returns an image ID and platform corresponding to the image referred to by
 // refOrID.
 // refOrID.
-func (daemon *Daemon) GetImageID(refOrID string) (image.ID, error) {
+func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, error) {
 	ref, err := reference.ParseAnyReference(refOrID)
 	ref, err := reference.ParseAnyReference(refOrID)
 	if err != nil {
 	if err != nil {
-		return "", err
+		return "", "", err
 	}
 	}
 	namedRef, ok := ref.(reference.Named)
 	namedRef, ok := ref.(reference.Named)
 	if !ok {
 	if !ok {
 		digested, ok := ref.(reference.Digested)
 		digested, ok := ref.(reference.Digested)
 		if !ok {
 		if !ok {
-			return "", ErrImageDoesNotExist{ref}
+			return "", "", ErrImageDoesNotExist{ref}
 		}
 		}
 		id := image.IDFromDigest(digested.Digest())
 		id := image.IDFromDigest(digested.Digest())
-		if _, err := daemon.imageStore.Get(id); err != nil {
-			return "", ErrImageDoesNotExist{ref}
+		for platform := range daemon.stores {
+			if _, err = daemon.stores[platform].imageStore.Get(id); err == nil {
+				return id, platform, nil
+			}
 		}
 		}
-		return id, nil
+		return "", "", ErrImageDoesNotExist{ref}
 	}
 	}
 
 
-	if id, err := daemon.referenceStore.Get(namedRef); err == nil {
-		return image.IDFromDigest(id), nil
+	for platform := range daemon.stores {
+		if id, err := daemon.stores[platform].referenceStore.Get(namedRef); err == nil {
+			return image.IDFromDigest(id), platform, nil
+		}
 	}
 	}
 
 
 	// deprecated: repo:shortid https://github.com/docker/docker/pull/799
 	// deprecated: repo:shortid https://github.com/docker/docker/pull/799
 	if tagged, ok := namedRef.(reference.Tagged); ok {
 	if tagged, ok := namedRef.(reference.Tagged); ok {
 		if tag := tagged.Tag(); stringid.IsShortID(stringid.TruncateID(tag)) {
 		if tag := tagged.Tag(); stringid.IsShortID(stringid.TruncateID(tag)) {
-			if id, err := daemon.imageStore.Search(tag); err == nil {
-				for _, storeRef := range daemon.referenceStore.References(id.Digest()) {
-					if storeRef.Name() == namedRef.Name() {
-						return id, nil
+			for platform := range daemon.stores {
+				if id, err := daemon.stores[platform].imageStore.Search(tag); err == nil {
+					for _, storeRef := range daemon.stores[platform].referenceStore.References(id.Digest()) {
+						if storeRef.Name() == namedRef.Name() {
+							return id, platform, nil
+						}
 					}
 					}
 				}
 				}
 			}
 			}
@@ -59,18 +65,20 @@ func (daemon *Daemon) GetImageID(refOrID string) (image.ID, error) {
 	}
 	}
 
 
 	// Search based on ID
 	// Search based on ID
-	if id, err := daemon.imageStore.Search(refOrID); err == nil {
-		return id, nil
+	for platform := range daemon.stores {
+		if id, err := daemon.stores[platform].imageStore.Search(refOrID); err == nil {
+			return id, platform, nil
+		}
 	}
 	}
 
 
-	return "", ErrImageDoesNotExist{ref}
+	return "", "", ErrImageDoesNotExist{ref}
 }
 }
 
 
 // GetImage returns an image corresponding to the image referred to by refOrID.
 // GetImage returns an image corresponding to the image referred to by refOrID.
 func (daemon *Daemon) GetImage(refOrID string) (*image.Image, error) {
 func (daemon *Daemon) GetImage(refOrID string) (*image.Image, error) {
-	imgID, err := daemon.GetImageID(refOrID)
+	imgID, platform, err := daemon.GetImageIDAndPlatform(refOrID)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	return daemon.imageStore.Get(imgID)
+	return daemon.stores[platform].imageStore.Get(imgID)
 }
 }

+ 25 - 25
daemon/image_delete.go

@@ -65,12 +65,12 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 	start := time.Now()
 	start := time.Now()
 	records := []types.ImageDeleteResponseItem{}
 	records := []types.ImageDeleteResponseItem{}
 
 
-	imgID, err := daemon.GetImageID(imageRef)
+	imgID, platform, err := daemon.GetImageIDAndPlatform(imageRef)
 	if err != nil {
 	if err != nil {
 		return nil, daemon.imageNotExistToErrcode(err)
 		return nil, daemon.imageNotExistToErrcode(err)
 	}
 	}
 
 
-	repoRefs := daemon.referenceStore.References(imgID.Digest())
+	repoRefs := daemon.stores[platform].referenceStore.References(imgID.Digest())
 
 
 	var removedRepositoryRef bool
 	var removedRepositoryRef bool
 	if !isImageIDPrefix(imgID.String(), imageRef) {
 	if !isImageIDPrefix(imgID.String(), imageRef) {
@@ -94,7 +94,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 			return nil, err
 			return nil, err
 		}
 		}
 
 
-		parsedRef, err = daemon.removeImageRef(parsedRef)
+		parsedRef, err = daemon.removeImageRef(platform, parsedRef)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -104,7 +104,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 		daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
 		daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
 		records = append(records, untaggedRecord)
 		records = append(records, untaggedRecord)
 
 
-		repoRefs = daemon.referenceStore.References(imgID.Digest())
+		repoRefs = daemon.stores[platform].referenceStore.References(imgID.Digest())
 
 
 		// If a tag reference was removed and the only remaining
 		// If a tag reference was removed and the only remaining
 		// references to the same repository are digest references,
 		// references to the same repository are digest references,
@@ -122,7 +122,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 				remainingRefs := []reference.Named{}
 				remainingRefs := []reference.Named{}
 				for _, repoRef := range repoRefs {
 				for _, repoRef := range repoRefs {
 					if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
 					if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
-						if _, err := daemon.removeImageRef(repoRef); err != nil {
+						if _, err := daemon.removeImageRef(platform, repoRef); err != nil {
 							return records, err
 							return records, err
 						}
 						}
 
 
@@ -152,12 +152,12 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 			if !force {
 			if !force {
 				c |= conflictSoft &^ conflictActiveReference
 				c |= conflictSoft &^ conflictActiveReference
 			}
 			}
-			if conflict := daemon.checkImageDeleteConflict(imgID, c); conflict != nil {
+			if conflict := daemon.checkImageDeleteConflict(imgID, platform, c); conflict != nil {
 				return nil, conflict
 				return nil, conflict
 			}
 			}
 
 
 			for _, repoRef := range repoRefs {
 			for _, repoRef := range repoRefs {
-				parsedRef, err := daemon.removeImageRef(repoRef)
+				parsedRef, err := daemon.removeImageRef(platform, repoRef)
 				if err != nil {
 				if err != nil {
 					return nil, err
 					return nil, err
 				}
 				}
@@ -170,7 +170,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 		}
 		}
 	}
 	}
 
 
-	if err := daemon.imageDeleteHelper(imgID, &records, force, prune, removedRepositoryRef); err != nil {
+	if err := daemon.imageDeleteHelper(imgID, platform, &records, force, prune, removedRepositoryRef); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
@@ -231,13 +231,13 @@ func (daemon *Daemon) getContainerUsingImage(imageID image.ID) *container.Contai
 // repositoryRef must not be an image ID but a repository name followed by an
 // repositoryRef must not be an image ID but a repository name followed by an
 // optional tag or digest reference. If tag or digest is omitted, the default
 // optional tag or digest reference. If tag or digest is omitted, the default
 // tag is used. Returns the resolved image reference and an error.
 // tag is used. Returns the resolved image reference and an error.
-func (daemon *Daemon) removeImageRef(ref reference.Named) (reference.Named, error) {
+func (daemon *Daemon) removeImageRef(platform string, ref reference.Named) (reference.Named, error) {
 	ref = reference.TagNameOnly(ref)
 	ref = reference.TagNameOnly(ref)
 
 
 	// Ignore the boolean value returned, as far as we're concerned, this
 	// Ignore the boolean value returned, as far as we're concerned, this
 	// is an idempotent operation and it's okay if the reference didn't
 	// is an idempotent operation and it's okay if the reference didn't
 	// exist in the first place.
 	// exist in the first place.
-	_, err := daemon.referenceStore.Delete(ref)
+	_, err := daemon.stores[platform].referenceStore.Delete(ref)
 
 
 	return ref, err
 	return ref, err
 }
 }
@@ -247,11 +247,11 @@ func (daemon *Daemon) removeImageRef(ref reference.Named) (reference.Named, erro
 // on the first encountered error. Removed references are logged to this
 // on the first encountered error. Removed references are logged to this
 // daemon's event service. An "Untagged" types.ImageDeleteResponseItem is added to the
 // daemon's event service. An "Untagged" types.ImageDeleteResponseItem is added to the
 // given list of records.
 // given list of records.
-func (daemon *Daemon) removeAllReferencesToImageID(imgID image.ID, records *[]types.ImageDeleteResponseItem) error {
-	imageRefs := daemon.referenceStore.References(imgID.Digest())
+func (daemon *Daemon) removeAllReferencesToImageID(imgID image.ID, platform string, records *[]types.ImageDeleteResponseItem) error {
+	imageRefs := daemon.stores[platform].referenceStore.References(imgID.Digest())
 
 
 	for _, imageRef := range imageRefs {
 	for _, imageRef := range imageRefs {
-		parsedRef, err := daemon.removeImageRef(imageRef)
+		parsedRef, err := daemon.removeImageRef(platform, imageRef)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -296,15 +296,15 @@ func (idc *imageDeleteConflict) Error() string {
 // conflict is encountered, it will be returned immediately without deleting
 // conflict is encountered, it will be returned immediately without deleting
 // the image. If quiet is true, any encountered conflicts will be ignored and
 // the image. If quiet is true, any encountered conflicts will be ignored and
 // the function will return nil immediately without deleting the image.
 // the function will return nil immediately without deleting the image.
-func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDeleteResponseItem, force, prune, quiet bool) error {
+func (daemon *Daemon) imageDeleteHelper(imgID image.ID, platform string, records *[]types.ImageDeleteResponseItem, force, prune, quiet bool) error {
 	// First, determine if this image has any conflicts. Ignore soft conflicts
 	// First, determine if this image has any conflicts. Ignore soft conflicts
 	// if force is true.
 	// if force is true.
 	c := conflictHard
 	c := conflictHard
 	if !force {
 	if !force {
 		c |= conflictSoft
 		c |= conflictSoft
 	}
 	}
-	if conflict := daemon.checkImageDeleteConflict(imgID, c); conflict != nil {
-		if quiet && (!daemon.imageIsDangling(imgID) || conflict.used) {
+	if conflict := daemon.checkImageDeleteConflict(imgID, platform, c); conflict != nil {
+		if quiet && (!daemon.imageIsDangling(imgID, platform) || conflict.used) {
 			// Ignore conflicts UNLESS the image is "dangling" or not being used in
 			// Ignore conflicts UNLESS the image is "dangling" or not being used in
 			// which case we want the user to know.
 			// which case we want the user to know.
 			return nil
 			return nil
@@ -315,18 +315,18 @@ func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDe
 		return conflict
 		return conflict
 	}
 	}
 
 
-	parent, err := daemon.imageStore.GetParent(imgID)
+	parent, err := daemon.stores[platform].imageStore.GetParent(imgID)
 	if err != nil {
 	if err != nil {
 		// There may be no parent
 		// There may be no parent
 		parent = ""
 		parent = ""
 	}
 	}
 
 
 	// Delete all repository tag/digest references to this image.
 	// Delete all repository tag/digest references to this image.
-	if err := daemon.removeAllReferencesToImageID(imgID, records); err != nil {
+	if err := daemon.removeAllReferencesToImageID(imgID, platform, records); err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	removedLayers, err := daemon.imageStore.Delete(imgID)
+	removedLayers, err := daemon.stores[platform].imageStore.Delete(imgID)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -346,7 +346,7 @@ func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDe
 	// either running or stopped).
 	// either running or stopped).
 	// Do not force prunings, but do so quietly (stopping on any encountered
 	// Do not force prunings, but do so quietly (stopping on any encountered
 	// conflicts).
 	// conflicts).
-	return daemon.imageDeleteHelper(parent, records, false, true, true)
+	return daemon.imageDeleteHelper(parent, platform, records, false, true, true)
 }
 }
 
 
 // checkImageDeleteConflict determines whether there are any conflicts
 // checkImageDeleteConflict determines whether there are any conflicts
@@ -355,9 +355,9 @@ func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDe
 // using the image. A soft conflict is any tags/digest referencing the given
 // using the image. A soft conflict is any tags/digest referencing the given
 // image or any stopped container using the image. If ignoreSoftConflicts is
 // image or any stopped container using the image. If ignoreSoftConflicts is
 // true, this function will not check for soft conflict conditions.
 // true, this function will not check for soft conflict conditions.
-func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, mask conflictType) *imageDeleteConflict {
+func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, platform string, mask conflictType) *imageDeleteConflict {
 	// Check if the image has any descendant images.
 	// Check if the image has any descendant images.
-	if mask&conflictDependentChild != 0 && len(daemon.imageStore.Children(imgID)) > 0 {
+	if mask&conflictDependentChild != 0 && len(daemon.stores[platform].imageStore.Children(imgID)) > 0 {
 		return &imageDeleteConflict{
 		return &imageDeleteConflict{
 			hard:    true,
 			hard:    true,
 			imgID:   imgID,
 			imgID:   imgID,
@@ -381,7 +381,7 @@ func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, mask conflictType
 	}
 	}
 
 
 	// Check if any repository tags/digest reference this image.
 	// Check if any repository tags/digest reference this image.
-	if mask&conflictActiveReference != 0 && len(daemon.referenceStore.References(imgID.Digest())) > 0 {
+	if mask&conflictActiveReference != 0 && len(daemon.stores[platform].referenceStore.References(imgID.Digest())) > 0 {
 		return &imageDeleteConflict{
 		return &imageDeleteConflict{
 			imgID:   imgID,
 			imgID:   imgID,
 			message: "image is referenced in multiple repositories",
 			message: "image is referenced in multiple repositories",
@@ -408,6 +408,6 @@ func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, mask conflictType
 // imageIsDangling returns whether the given image is "dangling" which means
 // imageIsDangling returns whether the given image is "dangling" which means
 // that there are no repository references to the given image and it has no
 // that there are no repository references to the given image and it has no
 // child images.
 // child images.
-func (daemon *Daemon) imageIsDangling(imgID image.ID) bool {
-	return !(len(daemon.referenceStore.References(imgID.Digest())) > 0 || len(daemon.imageStore.Children(imgID)) > 0)
+func (daemon *Daemon) imageIsDangling(imgID image.ID, platform string) bool {
+	return !(len(daemon.stores[platform].referenceStore.References(imgID.Digest())) > 0 || len(daemon.stores[platform].imageStore.Children(imgID)) > 0)
 }
 }

+ 14 - 2
daemon/image_exporter.go

@@ -2,8 +2,10 @@ package daemon
 
 
 import (
 import (
 	"io"
 	"io"
+	"runtime"
 
 
 	"github.com/docker/docker/image/tarexport"
 	"github.com/docker/docker/image/tarexport"
+	"github.com/docker/docker/pkg/system"
 )
 )
 
 
 // ExportImage exports a list of images to the given output stream. The
 // ExportImage exports a list of images to the given output stream. The
@@ -12,7 +14,12 @@ import (
 // the same tag are exported. names is the set of tags to export, and
 // the same tag are exported. names is the set of tags to export, and
 // outStream is the writer which the images are written to.
 // outStream is the writer which the images are written to.
 func (daemon *Daemon) ExportImage(names []string, outStream io.Writer) error {
 func (daemon *Daemon) ExportImage(names []string, outStream io.Writer) error {
-	imageExporter := tarexport.NewTarExporter(daemon.imageStore, daemon.layerStore, daemon.referenceStore, daemon)
+	// TODO @jhowardmsft LCOW. This will need revisiting later.
+	platform := runtime.GOOS
+	if platform == "windows" && system.LCOWSupported() {
+		platform = "linux"
+	}
+	imageExporter := tarexport.NewTarExporter(daemon.stores[platform].imageStore, daemon.stores[platform].layerStore, daemon.stores[platform].referenceStore, daemon)
 	return imageExporter.Save(names, outStream)
 	return imageExporter.Save(names, outStream)
 }
 }
 
 
@@ -20,6 +27,11 @@ func (daemon *Daemon) ExportImage(names []string, outStream io.Writer) error {
 // complement of ImageExport.  The input stream is an uncompressed tar
 // complement of ImageExport.  The input stream is an uncompressed tar
 // ball containing images and metadata.
 // ball containing images and metadata.
 func (daemon *Daemon) LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error {
 func (daemon *Daemon) LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error {
-	imageExporter := tarexport.NewTarExporter(daemon.imageStore, daemon.layerStore, daemon.referenceStore, daemon)
+	// TODO @jhowardmsft LCOW. This will need revisiting later.
+	platform := runtime.GOOS
+	if platform == "windows" && system.LCOWSupported() {
+		platform = "linux"
+	}
+	imageExporter := tarexport.NewTarExporter(daemon.stores[platform].imageStore, daemon.stores[platform].layerStore, daemon.stores[platform].referenceStore, daemon)
 	return imageExporter.Load(inTar, outStream, quiet)
 	return imageExporter.Load(inTar, outStream, quiet)
 }
 }

+ 10 - 3
daemon/image_history.go

@@ -2,6 +2,7 @@ package daemon
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"runtime"
 	"time"
 	"time"
 
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
@@ -18,6 +19,12 @@ func (daemon *Daemon) ImageHistory(name string) ([]*image.HistoryResponseItem, e
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	// If the image OS isn't set, assume it's the host OS
+	platform := img.OS
+	if platform == "" {
+		platform = runtime.GOOS
+	}
+
 	history := []*image.HistoryResponseItem{}
 	history := []*image.HistoryResponseItem{}
 
 
 	layerCounter := 0
 	layerCounter := 0
@@ -33,12 +40,12 @@ func (daemon *Daemon) ImageHistory(name string) ([]*image.HistoryResponseItem, e
 			}
 			}
 
 
 			rootFS.Append(img.RootFS.DiffIDs[layerCounter])
 			rootFS.Append(img.RootFS.DiffIDs[layerCounter])
-			l, err := daemon.layerStore.Get(rootFS.ChainID())
+			l, err := daemon.stores[platform].layerStore.Get(rootFS.ChainID())
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
 			layerSize, err = l.DiffSize()
 			layerSize, err = l.DiffSize()
-			layer.ReleaseAndLog(daemon.layerStore, l)
+			layer.ReleaseAndLog(daemon.stores[platform].layerStore, l)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
@@ -62,7 +69,7 @@ func (daemon *Daemon) ImageHistory(name string) ([]*image.HistoryResponseItem, e
 		h.ID = id.String()
 		h.ID = id.String()
 
 
 		var tags []string
 		var tags []string
-		for _, r := range daemon.referenceStore.References(id.Digest()) {
+		for _, r := range daemon.stores[platform].referenceStore.References(id.Digest()) {
 			if _, ok := r.(reference.NamedTagged); ok {
 			if _, ok := r.(reference.NamedTagged); ok {
 				tags = append(tags, reference.FamiliarString(r))
 				tags = append(tags, reference.FamiliarString(r))
 			}
 			}

+ 12 - 6
daemon/image_inspect.go

@@ -1,6 +1,7 @@
 package daemon
 package daemon
 
 
 import (
 import (
+	"runtime"
 	"time"
 	"time"
 
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
@@ -17,7 +18,13 @@ func (daemon *Daemon) LookupImage(name string) (*types.ImageInspect, error) {
 		return nil, errors.Wrapf(err, "no such image: %s", name)
 		return nil, errors.Wrapf(err, "no such image: %s", name)
 	}
 	}
 
 
-	refs := daemon.referenceStore.References(img.ID().Digest())
+	// If the image OS isn't set, assume it's the host OS
+	platform := img.OS
+	if platform == "" {
+		platform = runtime.GOOS
+	}
+
+	refs := daemon.stores[platform].referenceStore.References(img.ID().Digest())
 	repoTags := []string{}
 	repoTags := []string{}
 	repoDigests := []string{}
 	repoDigests := []string{}
 	for _, ref := range refs {
 	for _, ref := range refs {
@@ -33,11 +40,11 @@ func (daemon *Daemon) LookupImage(name string) (*types.ImageInspect, error) {
 	var layerMetadata map[string]string
 	var layerMetadata map[string]string
 	layerID := img.RootFS.ChainID()
 	layerID := img.RootFS.ChainID()
 	if layerID != "" {
 	if layerID != "" {
-		l, err := daemon.layerStore.Get(layerID)
+		l, err := daemon.stores[platform].layerStore.Get(layerID)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
-		defer layer.ReleaseAndLog(daemon.layerStore, l)
+		defer layer.ReleaseAndLog(daemon.stores[platform].layerStore, l)
 		size, err = l.Size()
 		size, err = l.Size()
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
@@ -67,15 +74,14 @@ func (daemon *Daemon) LookupImage(name string) (*types.ImageInspect, error) {
 		Author:          img.Author,
 		Author:          img.Author,
 		Config:          img.Config,
 		Config:          img.Config,
 		Architecture:    img.Architecture,
 		Architecture:    img.Architecture,
-		Os:              img.OS,
+		Os:              platform,
 		OsVersion:       img.OSVersion,
 		OsVersion:       img.OSVersion,
 		Size:            size,
 		Size:            size,
 		VirtualSize:     size, // TODO: field unused, deprecate
 		VirtualSize:     size, // TODO: field unused, deprecate
 		RootFS:          rootFSToAPIType(img.RootFS),
 		RootFS:          rootFSToAPIType(img.RootFS),
 	}
 	}
 
 
-	imageInspect.GraphDriver.Name = daemon.GraphDriverName()
-
+	imageInspect.GraphDriver.Name = daemon.GraphDriverName(platform)
 	imageInspect.GraphDriver.Data = layerMetadata
 	imageInspect.GraphDriver.Data = layerMetadata
 
 
 	return imageInspect, nil
 	return imageInspect, nil

+ 13 - 6
daemon/image_pull.go

@@ -2,6 +2,7 @@ package daemon
 
 
 import (
 import (
 	"io"
 	"io"
+	"runtime"
 	"strings"
 	"strings"
 
 
 	dist "github.com/docker/distribution"
 	dist "github.com/docker/distribution"
@@ -17,7 +18,7 @@ import (
 
 
 // PullImage initiates a pull operation. image is the repository name to pull, and
 // PullImage initiates a pull operation. image is the repository name to pull, and
 // tag may be either empty, or indicate a specific tag to pull.
 // tag may be either empty, or indicate a specific tag to pull.
-func (daemon *Daemon) PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
+func (daemon *Daemon) PullImage(ctx context.Context, image, tag, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
 	// Special case: "pull -a" may send an image name with a
 	// Special case: "pull -a" may send an image name with a
 	// trailing :. This is ugly, but let's not break API
 	// trailing :. This is ugly, but let's not break API
 	// compatibility.
 	// compatibility.
@@ -42,10 +43,10 @@ func (daemon *Daemon) PullImage(ctx context.Context, image, tag string, metaHead
 		}
 		}
 	}
 	}
 
 
-	return daemon.pullImageWithReference(ctx, ref, metaHeaders, authConfig, outStream)
+	return daemon.pullImageWithReference(ctx, ref, platform, metaHeaders, authConfig, outStream)
 }
 }
 
 
-func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
+func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.Named, platform string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
 	// Include a buffer so that slow client connections don't affect
 	// Include a buffer so that slow client connections don't affect
 	// transfer performance.
 	// transfer performance.
 	progressChan := make(chan progress.Progress, 100)
 	progressChan := make(chan progress.Progress, 100)
@@ -59,6 +60,11 @@ func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.
 		close(writesDone)
 		close(writesDone)
 	}()
 	}()
 
 
+	// Default to the host OS platform in case it hasn't been populated with an explicit value.
+	if platform == "" {
+		platform = runtime.GOOS
+	}
+
 	imagePullConfig := &distribution.ImagePullConfig{
 	imagePullConfig := &distribution.ImagePullConfig{
 		Config: distribution.Config{
 		Config: distribution.Config{
 			MetaHeaders:      metaHeaders,
 			MetaHeaders:      metaHeaders,
@@ -66,12 +72,13 @@ func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.
 			ProgressOutput:   progress.ChanOutput(progressChan),
 			ProgressOutput:   progress.ChanOutput(progressChan),
 			RegistryService:  daemon.RegistryService,
 			RegistryService:  daemon.RegistryService,
 			ImageEventLogger: daemon.LogImageEvent,
 			ImageEventLogger: daemon.LogImageEvent,
-			MetadataStore:    daemon.distributionMetadataStore,
-			ImageStore:       distribution.NewImageConfigStoreFromStore(daemon.imageStore),
-			ReferenceStore:   daemon.referenceStore,
+			MetadataStore:    daemon.stores[platform].distributionMetadataStore,
+			ImageStore:       distribution.NewImageConfigStoreFromStore(daemon.stores[platform].imageStore),
+			ReferenceStore:   daemon.stores[platform].referenceStore,
 		},
 		},
 		DownloadManager: daemon.downloadManager,
 		DownloadManager: daemon.downloadManager,
 		Schema2Types:    distribution.ImageTypes,
 		Schema2Types:    distribution.ImageTypes,
+		Platform:        platform,
 	}
 	}
 
 
 	err := distribution.Pull(ctx, ref, imagePullConfig)
 	err := distribution.Pull(ctx, ref, imagePullConfig)

+ 12 - 4
daemon/image_push.go

@@ -2,6 +2,7 @@ package daemon
 
 
 import (
 import (
 	"io"
 	"io"
+	"runtime"
 
 
 	"github.com/docker/distribution/manifest/schema2"
 	"github.com/docker/distribution/manifest/schema2"
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
@@ -9,6 +10,7 @@ import (
 	"github.com/docker/docker/distribution"
 	"github.com/docker/docker/distribution"
 	progressutils "github.com/docker/docker/distribution/utils"
 	progressutils "github.com/docker/docker/distribution/utils"
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/progress"
+	"github.com/docker/docker/pkg/system"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
@@ -39,6 +41,12 @@ func (daemon *Daemon) PushImage(ctx context.Context, image, tag string, metaHead
 		close(writesDone)
 		close(writesDone)
 	}()
 	}()
 
 
+	// TODO @jhowardmsft LCOW Support. This will require revisiting. For now, hard-code.
+	platform := runtime.GOOS
+	if platform == "windows" && system.LCOWSupported() {
+		platform = "linux"
+	}
+
 	imagePushConfig := &distribution.ImagePushConfig{
 	imagePushConfig := &distribution.ImagePushConfig{
 		Config: distribution.Config{
 		Config: distribution.Config{
 			MetaHeaders:      metaHeaders,
 			MetaHeaders:      metaHeaders,
@@ -46,12 +54,12 @@ func (daemon *Daemon) PushImage(ctx context.Context, image, tag string, metaHead
 			ProgressOutput:   progress.ChanOutput(progressChan),
 			ProgressOutput:   progress.ChanOutput(progressChan),
 			RegistryService:  daemon.RegistryService,
 			RegistryService:  daemon.RegistryService,
 			ImageEventLogger: daemon.LogImageEvent,
 			ImageEventLogger: daemon.LogImageEvent,
-			MetadataStore:    daemon.distributionMetadataStore,
-			ImageStore:       distribution.NewImageConfigStoreFromStore(daemon.imageStore),
-			ReferenceStore:   daemon.referenceStore,
+			MetadataStore:    daemon.stores[platform].distributionMetadataStore,
+			ImageStore:       distribution.NewImageConfigStoreFromStore(daemon.stores[platform].imageStore),
+			ReferenceStore:   daemon.stores[platform].referenceStore,
 		},
 		},
 		ConfigMediaType: schema2.MediaTypeImageConfig,
 		ConfigMediaType: schema2.MediaTypeImageConfig,
-		LayerStore:      distribution.NewLayerProviderFromStore(daemon.layerStore),
+		LayerStore:      distribution.NewLayerProviderFromStore(daemon.stores[platform].layerStore),
 		TrustKey:        daemon.trustKey,
 		TrustKey:        daemon.trustKey,
 		UploadManager:   daemon.uploadManager,
 		UploadManager:   daemon.uploadManager,
 	}
 	}

+ 4 - 4
daemon/image_tag.go

@@ -8,7 +8,7 @@ import (
 // TagImage creates the tag specified by newTag, pointing to the image named
 // TagImage creates the tag specified by newTag, pointing to the image named
 // imageName (alternatively, imageName can also be an image ID).
 // imageName (alternatively, imageName can also be an image ID).
 func (daemon *Daemon) TagImage(imageName, repository, tag string) error {
 func (daemon *Daemon) TagImage(imageName, repository, tag string) error {
-	imageID, err := daemon.GetImageID(imageName)
+	imageID, platform, err := daemon.GetImageIDAndPlatform(imageName)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -23,12 +23,12 @@ func (daemon *Daemon) TagImage(imageName, repository, tag string) error {
 		}
 		}
 	}
 	}
 
 
-	return daemon.TagImageWithReference(imageID, newTag)
+	return daemon.TagImageWithReference(imageID, platform, newTag)
 }
 }
 
 
 // TagImageWithReference adds the given reference to the image ID provided.
 // TagImageWithReference adds the given reference to the image ID provided.
-func (daemon *Daemon) TagImageWithReference(imageID image.ID, newTag reference.Named) error {
-	if err := daemon.referenceStore.AddTag(newTag, imageID.Digest(), true); err != nil {
+func (daemon *Daemon) TagImageWithReference(imageID image.ID, platform string, newTag reference.Named) error {
+	if err := daemon.stores[platform].referenceStore.AddTag(newTag, imageID.Digest(), true); err != nil {
 		return err
 		return err
 	}
 	}
 
 

+ 38 - 15
daemon/images.go

@@ -3,6 +3,7 @@ package daemon
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
+	"runtime"
 	"sort"
 	"sort"
 	"time"
 	"time"
 
 
@@ -14,6 +15,7 @@ import (
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
+	"github.com/docker/docker/pkg/system"
 )
 )
 
 
 var acceptedImageFilterTags = map[string]bool{
 var acceptedImageFilterTags = map[string]bool{
@@ -34,7 +36,12 @@ func (r byCreated) Less(i, j int) bool { return r[i].Created < r[j].Created }
 
 
 // Map returns a map of all images in the ImageStore
 // Map returns a map of all images in the ImageStore
 func (daemon *Daemon) Map() map[image.ID]*image.Image {
 func (daemon *Daemon) Map() map[image.ID]*image.Image {
-	return daemon.imageStore.Map()
+	// TODO @jhowardmsft LCOW. This will need  work to enumerate the stores for all platforms.
+	platform := runtime.GOOS
+	if platform == "windows" && system.LCOWSupported() {
+		platform = "linux"
+	}
+	return daemon.stores[platform].imageStore.Map()
 }
 }
 
 
 // Images returns a filtered list of images. filterArgs is a JSON-encoded set
 // Images returns a filtered list of images. filterArgs is a JSON-encoded set
@@ -43,6 +50,13 @@ func (daemon *Daemon) Map() map[image.ID]*image.Image {
 // named all controls whether all images in the graph are filtered, or just
 // named all controls whether all images in the graph are filtered, or just
 // the heads.
 // the heads.
 func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error) {
 func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error) {
+
+	// TODO @jhowardmsft LCOW. This will need  work to enumerate the stores for all platforms.
+	platform := runtime.GOOS
+	if platform == "windows" && system.LCOWSupported() {
+		platform = "linux"
+	}
+
 	var (
 	var (
 		allImages    map[image.ID]*image.Image
 		allImages    map[image.ID]*image.Image
 		err          error
 		err          error
@@ -61,9 +75,9 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
 		}
 		}
 	}
 	}
 	if danglingOnly {
 	if danglingOnly {
-		allImages = daemon.imageStore.Heads()
+		allImages = daemon.stores[platform].imageStore.Heads()
 	} else {
 	} else {
-		allImages = daemon.imageStore.Map()
+		allImages = daemon.stores[platform].imageStore.Map()
 	}
 	}
 
 
 	var beforeFilter, sinceFilter *image.Image
 	var beforeFilter, sinceFilter *image.Image
@@ -116,7 +130,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
 		layerID := img.RootFS.ChainID()
 		layerID := img.RootFS.ChainID()
 		var size int64
 		var size int64
 		if layerID != "" {
 		if layerID != "" {
-			l, err := daemon.layerStore.Get(layerID)
+			l, err := daemon.stores[platform].layerStore.Get(layerID)
 			if err != nil {
 			if err != nil {
 				// The layer may have been deleted between the call to `Map()` or
 				// The layer may have been deleted between the call to `Map()` or
 				// `Heads()` and the call to `Get()`, so we just ignore this error
 				// `Heads()` and the call to `Get()`, so we just ignore this error
@@ -127,7 +141,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
 			}
 			}
 
 
 			size, err = l.Size()
 			size, err = l.Size()
-			layer.ReleaseAndLog(daemon.layerStore, l)
+			layer.ReleaseAndLog(daemon.stores[platform].layerStore, l)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
@@ -135,7 +149,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
 
 
 		newImage := newImage(img, size)
 		newImage := newImage(img, size)
 
 
-		for _, ref := range daemon.referenceStore.References(id.Digest()) {
+		for _, ref := range daemon.stores[platform].referenceStore.References(id.Digest()) {
 			if imageFilters.Include("reference") {
 			if imageFilters.Include("reference") {
 				var found bool
 				var found bool
 				var matchErr error
 				var matchErr error
@@ -157,7 +171,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
 			}
 			}
 		}
 		}
 		if newImage.RepoDigests == nil && newImage.RepoTags == nil {
 		if newImage.RepoDigests == nil && newImage.RepoTags == nil {
-			if all || len(daemon.imageStore.Children(id)) == 0 {
+			if all || len(daemon.stores[platform].imageStore.Children(id)) == 0 {
 
 
 				if imageFilters.Include("dangling") && !danglingOnly {
 				if imageFilters.Include("dangling") && !danglingOnly {
 					//dangling=false case, so dangling image is not needed
 					//dangling=false case, so dangling image is not needed
@@ -179,7 +193,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
 			// lazily init variables
 			// lazily init variables
 			if imagesMap == nil {
 			if imagesMap == nil {
 				allContainers = daemon.List()
 				allContainers = daemon.List()
-				allLayers = daemon.layerStore.Map()
+				allLayers = daemon.stores[platform].layerStore.Map()
 				imagesMap = make(map[*image.Image]*types.ImageSummary)
 				imagesMap = make(map[*image.Image]*types.ImageSummary)
 				layerRefs = make(map[layer.ChainID]int)
 				layerRefs = make(map[layer.ChainID]int)
 			}
 			}
@@ -242,7 +256,16 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
 // The existing image(s) is not destroyed.
 // The existing image(s) is not destroyed.
 // If no parent is specified, a new image with the diff of all the specified image's layers merged into a new layer that has no parents.
 // If no parent is specified, a new image with the diff of all the specified image's layers merged into a new layer that has no parents.
 func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
 func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
-	img, err := daemon.imageStore.Get(image.ID(id))
+
+	var (
+		img *image.Image
+		err error
+	)
+	for _, ds := range daemon.stores {
+		if img, err = ds.imageStore.Get(image.ID(id)); err == nil {
+			break
+		}
+	}
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -250,7 +273,7 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
 	var parentImg *image.Image
 	var parentImg *image.Image
 	var parentChainID layer.ChainID
 	var parentChainID layer.ChainID
 	if len(parent) != 0 {
 	if len(parent) != 0 {
-		parentImg, err = daemon.imageStore.Get(image.ID(parent))
+		parentImg, err = daemon.stores[img.Platform()].imageStore.Get(image.ID(parent))
 		if err != nil {
 		if err != nil {
 			return "", errors.Wrap(err, "error getting specified parent layer")
 			return "", errors.Wrap(err, "error getting specified parent layer")
 		}
 		}
@@ -260,11 +283,11 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
 		parentImg = &image.Image{RootFS: rootFS}
 		parentImg = &image.Image{RootFS: rootFS}
 	}
 	}
 
 
-	l, err := daemon.layerStore.Get(img.RootFS.ChainID())
+	l, err := daemon.stores[img.Platform()].layerStore.Get(img.RootFS.ChainID())
 	if err != nil {
 	if err != nil {
 		return "", errors.Wrap(err, "error getting image layer")
 		return "", errors.Wrap(err, "error getting image layer")
 	}
 	}
-	defer daemon.layerStore.Release(l)
+	defer daemon.stores[img.Platform()].layerStore.Release(l)
 
 
 	ts, err := l.TarStreamFrom(parentChainID)
 	ts, err := l.TarStreamFrom(parentChainID)
 	if err != nil {
 	if err != nil {
@@ -272,11 +295,11 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
 	}
 	}
 	defer ts.Close()
 	defer ts.Close()
 
 
-	newL, err := daemon.layerStore.Register(ts, parentChainID)
+	newL, err := daemon.stores[img.Platform()].layerStore.Register(ts, parentChainID, layer.Platform(img.Platform()))
 	if err != nil {
 	if err != nil {
 		return "", errors.Wrap(err, "error registering layer")
 		return "", errors.Wrap(err, "error registering layer")
 	}
 	}
-	defer daemon.layerStore.Release(newL)
+	defer daemon.stores[img.Platform()].layerStore.Release(newL)
 
 
 	var newImage image.Image
 	var newImage image.Image
 	newImage = *img
 	newImage = *img
@@ -313,7 +336,7 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
 		return "", errors.Wrap(err, "error marshalling image config")
 		return "", errors.Wrap(err, "error marshalling image config")
 	}
 	}
 
 
-	newImgID, err := daemon.imageStore.Create(b)
+	newImgID, err := daemon.stores[img.Platform()].imageStore.Create(b)
 	if err != nil {
 	if err != nil {
 		return "", errors.Wrap(err, "error creating new image after squash")
 		return "", errors.Wrap(err, "error creating new image after squash")
 	}
 	}

+ 11 - 7
daemon/import.go

@@ -26,13 +26,18 @@ import (
 // inConfig (if src is "-"), or from a URI specified in src. Progress output is
 // inConfig (if src is "-"), or from a URI specified in src. Progress output is
 // written to outStream. Repository and tag names can optionally be given in
 // written to outStream. Repository and tag names can optionally be given in
 // the repo and tag arguments, respectively.
 // the repo and tag arguments, respectively.
-func (daemon *Daemon) ImportImage(src string, repository, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error {
+func (daemon *Daemon) ImportImage(src string, repository, platform string, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error {
 	var (
 	var (
 		rc     io.ReadCloser
 		rc     io.ReadCloser
 		resp   *http.Response
 		resp   *http.Response
 		newRef reference.Named
 		newRef reference.Named
 	)
 	)
 
 
+	// Default the platform if not supplied.
+	if platform == "" {
+		platform = runtime.GOOS
+	}
+
 	if repository != "" {
 	if repository != "" {
 		var err error
 		var err error
 		newRef, err = reference.ParseNormalizedNamed(repository)
 		newRef, err = reference.ParseNormalizedNamed(repository)
@@ -85,12 +90,11 @@ func (daemon *Daemon) ImportImage(src string, repository, tag string, msg string
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	// TODO: support windows baselayer?
-	l, err := daemon.layerStore.Register(inflatedLayerData, "")
+	l, err := daemon.stores[platform].layerStore.Register(inflatedLayerData, "", layer.Platform(platform))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	defer layer.ReleaseAndLog(daemon.layerStore, l)
+	defer layer.ReleaseAndLog(daemon.stores[platform].layerStore, l)
 
 
 	created := time.Now().UTC()
 	created := time.Now().UTC()
 	imgConfig, err := json.Marshal(&image.Image{
 	imgConfig, err := json.Marshal(&image.Image{
@@ -98,7 +102,7 @@ func (daemon *Daemon) ImportImage(src string, repository, tag string, msg string
 			DockerVersion: dockerversion.Version,
 			DockerVersion: dockerversion.Version,
 			Config:        config,
 			Config:        config,
 			Architecture:  runtime.GOARCH,
 			Architecture:  runtime.GOARCH,
-			OS:            runtime.GOOS,
+			OS:            platform,
 			Created:       created,
 			Created:       created,
 			Comment:       msg,
 			Comment:       msg,
 		},
 		},
@@ -115,14 +119,14 @@ func (daemon *Daemon) ImportImage(src string, repository, tag string, msg string
 		return err
 		return err
 	}
 	}
 
 
-	id, err := daemon.imageStore.Create(imgConfig)
+	id, err := daemon.stores[platform].imageStore.Create(imgConfig)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	// FIXME: connect with commit code and call refstore directly
 	// FIXME: connect with commit code and call refstore directly
 	if newRef != nil {
 	if newRef != nil {
-		if err := daemon.TagImageWithReference(id, newRef); err != nil {
+		if err := daemon.TagImageWithReference(id, platform, newRef); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}

+ 21 - 3
daemon/info.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"fmt"
 	"os"
 	"os"
 	"runtime"
 	"runtime"
+	"strings"
 	"time"
 	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
@@ -77,15 +78,32 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
 		securityOptions = append(securityOptions, "name=userns")
 		securityOptions = append(securityOptions, "name=userns")
 	}
 	}
 
 
+	imageCount := 0
+	drivers := ""
+	for p, ds := range daemon.stores {
+		imageCount += len(ds.imageStore.Map())
+		drivers += daemon.GraphDriverName(p)
+		if len(daemon.stores) > 1 {
+			drivers += fmt.Sprintf(" (%s) ", p)
+		}
+	}
+
+	// TODO @jhowardmsft LCOW support. For now, hard-code the platform shown for the driver status
+	p := runtime.GOOS
+	if p == "windows" && system.LCOWSupported() {
+		p = "linux"
+	}
+
+	drivers = strings.TrimSpace(drivers)
 	v := &types.Info{
 	v := &types.Info{
 		ID:                 daemon.ID,
 		ID:                 daemon.ID,
 		Containers:         int(cRunning + cPaused + cStopped),
 		Containers:         int(cRunning + cPaused + cStopped),
 		ContainersRunning:  int(cRunning),
 		ContainersRunning:  int(cRunning),
 		ContainersPaused:   int(cPaused),
 		ContainersPaused:   int(cPaused),
 		ContainersStopped:  int(cStopped),
 		ContainersStopped:  int(cStopped),
-		Images:             len(daemon.imageStore.Map()),
-		Driver:             daemon.GraphDriverName(),
-		DriverStatus:       daemon.layerStore.DriverStatus(),
+		Images:             imageCount,
+		Driver:             drivers,
+		DriverStatus:       daemon.stores[p].layerStore.DriverStatus(),
 		Plugins:            daemon.showPluginsInfo(),
 		Plugins:            daemon.showPluginsInfo(),
 		IPv4Forwarding:     !sysInfo.IPv4ForwardingDisabled,
 		IPv4Forwarding:     !sysInfo.IPv4ForwardingDisabled,
 		BridgeNfIptables:   !sysInfo.BridgeNFCallIPTablesDisabled,
 		BridgeNfIptables:   !sysInfo.BridgeNFCallIPTablesDisabled,

+ 1 - 0
daemon/inspect.go

@@ -170,6 +170,7 @@ func (daemon *Daemon) getInspectData(container *container.Container) (*types.Con
 		Name:         container.Name,
 		Name:         container.Name,
 		RestartCount: container.RestartCount,
 		RestartCount: container.RestartCount,
 		Driver:       container.Driver,
 		Driver:       container.Driver,
+		Platform:     container.Platform,
 		MountLabel:   container.MountLabel,
 		MountLabel:   container.MountLabel,
 		ProcessLabel: container.ProcessLabel,
 		ProcessLabel: container.ProcessLabel,
 		ExecIDs:      container.GetExecIDs(),
 		ExecIDs:      container.GetExecIDs(),

+ 3 - 3
daemon/list.go

@@ -317,7 +317,7 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte
 	if psFilters.Include("ancestor") {
 	if psFilters.Include("ancestor") {
 		ancestorFilter = true
 		ancestorFilter = true
 		psFilters.WalkValues("ancestor", func(ancestor string) error {
 		psFilters.WalkValues("ancestor", func(ancestor string) error {
-			id, err := daemon.GetImageID(ancestor)
+			id, platform, err := daemon.GetImageIDAndPlatform(ancestor)
 			if err != nil {
 			if err != nil {
 				logrus.Warnf("Error while looking up for image %v", ancestor)
 				logrus.Warnf("Error while looking up for image %v", ancestor)
 				return nil
 				return nil
@@ -327,7 +327,7 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte
 				return nil
 				return nil
 			}
 			}
 			// Then walk down the graph and put the imageIds in imagesFilter
 			// Then walk down the graph and put the imageIds in imagesFilter
-			populateImageFilterByParents(imagesFilter, id, daemon.imageStore.Children)
+			populateImageFilterByParents(imagesFilter, id, daemon.stores[platform].imageStore.Children)
 			return nil
 			return nil
 		})
 		})
 	}
 	}
@@ -558,7 +558,7 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li
 
 
 	image := container.Config.Image // if possible keep the original ref
 	image := container.Config.Image // if possible keep the original ref
 	if image != container.ImageID.String() {
 	if image != container.ImageID.String() {
-		id, err := daemon.GetImageID(image)
+		id, _, err := daemon.GetImageIDAndPlatform(image)
 		if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE {
 		if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE {
 			return nil, err
 			return nil, err
 		}
 		}

+ 43 - 10
daemon/oci_windows.go

@@ -7,11 +7,17 @@ import (
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/oci"
 	"github.com/docker/docker/oci"
 	"github.com/docker/docker/pkg/sysinfo"
 	"github.com/docker/docker/pkg/sysinfo"
+	"github.com/docker/docker/pkg/system"
 	"github.com/opencontainers/runtime-spec/specs-go"
 	"github.com/opencontainers/runtime-spec/specs-go"
 )
 )
 
 
 func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
-	s := oci.DefaultSpec()
+	img, err := daemon.GetImage(string(c.ImageID))
+	if err != nil {
+		return nil, err
+	}
+
+	s := oci.DefaultOSSpec(img.OS)
 
 
 	linkedEnv, err := daemon.setupLinkedContainers(c)
 	linkedEnv, err := daemon.setupLinkedContainers(c)
 	if err != nil {
 	if err != nil {
@@ -95,7 +101,30 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 	if !c.Config.ArgsEscaped {
 	if !c.Config.ArgsEscaped {
 		s.Process.Args = escapeArgs(s.Process.Args)
 		s.Process.Args = escapeArgs(s.Process.Args)
 	}
 	}
+
 	s.Process.Cwd = c.Config.WorkingDir
 	s.Process.Cwd = c.Config.WorkingDir
+	s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv)
+	if c.Config.Tty {
+		s.Process.Terminal = c.Config.Tty
+		s.Process.ConsoleSize.Height = c.HostConfig.ConsoleSize[0]
+		s.Process.ConsoleSize.Width = c.HostConfig.ConsoleSize[1]
+	}
+	s.Process.User.Username = c.Config.User
+
+	if img.OS == "windows" {
+		daemon.createSpecWindowsFields(c, &s, isHyperV)
+	} 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
+func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) {
 	if len(s.Process.Cwd) == 0 {
 	if len(s.Process.Cwd) == 0 {
 		// We default to C:\ to workaround the oddity of the case that the
 		// We default to C:\ to workaround the oddity of the case that the
 		// default directory for cmd running as LocalSystem (or
 		// default directory for cmd running as LocalSystem (or
@@ -106,17 +135,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 		// as c:\. Hence, setting it to default of c:\ makes for consistency.
 		// as c:\. Hence, setting it to default of c:\ makes for consistency.
 		s.Process.Cwd = `C:\`
 		s.Process.Cwd = `C:\`
 	}
 	}
-	s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv)
-	s.Process.ConsoleSize.Height = c.HostConfig.ConsoleSize[0]
-	s.Process.ConsoleSize.Width = c.HostConfig.ConsoleSize[1]
-	s.Process.Terminal = c.Config.Tty
-	s.Process.User.Username = c.Config.User
 
 
-	// In spec.Root. This is not set for Hyper-V containers
+	s.Root.Readonly = false // Windows does not support a read-only root filesystem
 	if !isHyperV {
 	if !isHyperV {
-		s.Root.Path = c.BaseFS
+		s.Root.Path = c.BaseFS // This is not set for Hyper-V containers
 	}
 	}
-	s.Root.Readonly = false // Windows does not support a read-only root filesystem
 
 
 	// In s.Windows.Resources
 	// In s.Windows.Resources
 	cpuShares := uint16(c.HostConfig.CPUShares)
 	cpuShares := uint16(c.HostConfig.CPUShares)
@@ -157,7 +180,17 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 			Iops: &c.HostConfig.IOMaximumIOps,
 			Iops: &c.HostConfig.IOMaximumIOps,
 		},
 		},
 	}
 	}
-	return (*specs.Spec)(&s), nil
+}
+
+// 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
 }
 }
 
 
 func escapeArgs(args []string) []string {
 func escapeArgs(args []string) []string {

+ 13 - 5
daemon/prune.go

@@ -3,6 +3,7 @@ package daemon
 import (
 import (
 	"fmt"
 	"fmt"
 	"regexp"
 	"regexp"
+	"runtime"
 	"sync/atomic"
 	"sync/atomic"
 	"time"
 	"time"
 
 
@@ -14,6 +15,7 @@ import (
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/pkg/directory"
 	"github.com/docker/docker/pkg/directory"
+	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
@@ -157,6 +159,12 @@ func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Arg
 
 
 // ImagesPrune removes unused images
 // ImagesPrune removes unused images
 func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
 func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
+	// TODO @jhowardmsft LCOW Support: This will need revisiting later.
+	platform := runtime.GOOS
+	if platform == "windows" && system.LCOWSupported() {
+		platform = "linux"
+	}
+
 	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
 	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
 		return nil, errPruneRunning
 		return nil, errPruneRunning
 	}
 	}
@@ -186,9 +194,9 @@ func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args
 
 
 	var allImages map[image.ID]*image.Image
 	var allImages map[image.ID]*image.Image
 	if danglingOnly {
 	if danglingOnly {
-		allImages = daemon.imageStore.Heads()
+		allImages = daemon.stores[platform].imageStore.Heads()
 	} else {
 	} else {
-		allImages = daemon.imageStore.Map()
+		allImages = daemon.stores[platform].imageStore.Map()
 	}
 	}
 	allContainers := daemon.List()
 	allContainers := daemon.List()
 	imageRefs := map[string]bool{}
 	imageRefs := map[string]bool{}
@@ -202,7 +210,7 @@ func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args
 	}
 	}
 
 
 	// Filter intermediary images and get their unique size
 	// Filter intermediary images and get their unique size
-	allLayers := daemon.layerStore.Map()
+	allLayers := daemon.stores[platform].layerStore.Map()
 	topImages := map[image.ID]*image.Image{}
 	topImages := map[image.ID]*image.Image{}
 	for id, img := range allImages {
 	for id, img := range allImages {
 		select {
 		select {
@@ -210,7 +218,7 @@ func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args
 			return nil, ctx.Err()
 			return nil, ctx.Err()
 		default:
 		default:
 			dgst := digest.Digest(id)
 			dgst := digest.Digest(id)
-			if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
+			if len(daemon.stores[platform].referenceStore.References(dgst)) == 0 && len(daemon.stores[platform].imageStore.Children(id)) != 0 {
 				continue
 				continue
 			}
 			}
 			if !until.IsZero() && img.Created.After(until) {
 			if !until.IsZero() && img.Created.After(until) {
@@ -241,7 +249,7 @@ deleteImagesLoop:
 		}
 		}
 
 
 		deletedImages := []types.ImageDeleteResponseItem{}
 		deletedImages := []types.ImageDeleteResponseItem{}
-		refs := daemon.referenceStore.References(dgst)
+		refs := daemon.stores[platform].referenceStore.References(dgst)
 		if len(refs) > 0 {
 		if len(refs) > 0 {
 			shouldDelete := !danglingOnly
 			shouldDelete := !danglingOnly
 			if !shouldDelete {
 			if !shouldDelete {

+ 1 - 1
daemon/start.go

@@ -207,7 +207,7 @@ func (daemon *Daemon) Cleanup(container *container.Container) {
 	if err := daemon.conditionalUnmountOnCleanup(container); err != nil {
 	if err := daemon.conditionalUnmountOnCleanup(container); err != nil {
 		// FIXME: remove once reference counting for graphdrivers has been refactored
 		// FIXME: remove once reference counting for graphdrivers has been refactored
 		// Ensure that all the mounts are gone
 		// Ensure that all the mounts are gone
-		if mountid, err := daemon.layerStore.GetMountID(container.ID); err == nil {
+		if mountid, err := daemon.stores[container.Platform].layerStore.GetMountID(container.ID); err == nil {
 			daemon.cleanupMountsByID(mountid)
 			daemon.cleanupMountsByID(mountid)
 		}
 		}
 	}
 	}

+ 3 - 3
daemon/start_windows.go

@@ -41,7 +41,7 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
 	layerOpts.LayerFolderPath = m["dir"]
 	layerOpts.LayerFolderPath = m["dir"]
 
 
 	// Generate the layer paths of the layer options
 	// Generate the layer paths of the layer options
-	img, err := daemon.imageStore.Get(container.ImageID)
+	img, err := daemon.stores[container.Platform].imageStore.Get(container.ImageID)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("failed to graph.Get on ImageID %s - %s", container.ImageID, err)
 		return nil, fmt.Errorf("failed to graph.Get on ImageID %s - %s", container.ImageID, err)
 	}
 	}
@@ -49,9 +49,9 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
 	max := len(img.RootFS.DiffIDs)
 	max := len(img.RootFS.DiffIDs)
 	for i := 1; i <= max; i++ {
 	for i := 1; i <= max; i++ {
 		img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
 		img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
-		layerPath, err := layer.GetLayerPath(daemon.layerStore, img.RootFS.ChainID())
+		layerPath, err := layer.GetLayerPath(daemon.stores[container.Platform].layerStore, img.RootFS.ChainID())
 		if err != nil {
 		if err != nil {
-			return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.layerStore, img.RootFS.ChainID(), err)
+			return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[container.Platform].layerStore, img.RootFS.ChainID(), err)
 		}
 		}
 		// Reverse order, expecting parent most first
 		// Reverse order, expecting parent most first
 		layerOpts.LayerPaths = append([]string{layerPath}, layerOpts.LayerPaths...)
 		layerOpts.LayerPaths = append([]string{layerPath}, layerOpts.LayerPaths...)

+ 55 - 58
daemon/volumes_unix_test.go

@@ -18,70 +18,67 @@ func TestBackportMountSpec(t *testing.T) {
 	d := Daemon{containers: container.NewMemoryStore()}
 	d := Daemon{containers: container.NewMemoryStore()}
 
 
 	c := &container.Container{
 	c := &container.Container{
-		CommonContainer: container.CommonContainer{
-			State: &container.State{},
-			MountPoints: map[string]*volume.MountPoint{
-				"/apple":      {Destination: "/apple", Source: "/var/lib/docker/volumes/12345678", Name: "12345678", RW: true, CopyData: true}, // anonymous volume
-				"/banana":     {Destination: "/banana", Source: "/var/lib/docker/volumes/data", Name: "data", RW: true, CopyData: true},        // named volume
-				"/cherry":     {Destination: "/cherry", Source: "/var/lib/docker/volumes/data", Name: "data", CopyData: true},                  // RO named volume
-				"/dates":      {Destination: "/dates", Source: "/var/lib/docker/volumes/data", Name: "data"},                                   // named volume nocopy
-				"/elderberry": {Destination: "/elderberry", Source: "/var/lib/docker/volumes/data", Name: "data"},                              // masks anon vol
-				"/fig":        {Destination: "/fig", Source: "/data", RW: true},                                                                // RW bind
-				"/guava":      {Destination: "/guava", Source: "/data", RW: false, Propagation: "shared"},                                      // RO bind + propagation
-				"/kumquat":    {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},                                              // volumes-from
+		State: &container.State{},
+		MountPoints: map[string]*volume.MountPoint{
+			"/apple":      {Destination: "/apple", Source: "/var/lib/docker/volumes/12345678", Name: "12345678", RW: true, CopyData: true}, // anonymous volume
+			"/banana":     {Destination: "/banana", Source: "/var/lib/docker/volumes/data", Name: "data", RW: true, CopyData: true},        // named volume
+			"/cherry":     {Destination: "/cherry", Source: "/var/lib/docker/volumes/data", Name: "data", CopyData: true},                  // RO named volume
+			"/dates":      {Destination: "/dates", Source: "/var/lib/docker/volumes/data", Name: "data"},                                   // named volume nocopy
+			"/elderberry": {Destination: "/elderberry", Source: "/var/lib/docker/volumes/data", Name: "data"},                              // masks anon vol
+			"/fig":        {Destination: "/fig", Source: "/data", RW: true},                                                                // RW bind
+			"/guava":      {Destination: "/guava", Source: "/data", RW: false, Propagation: "shared"},                                      // RO bind + propagation
+			"/kumquat":    {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},                                              // volumes-from
 
 
-				// partially configured mountpoint due to #32613
-				// specifically, `mp.Spec.Source` is not set
-				"/honeydew": {
-					Type:        mounttypes.TypeVolume,
-					Destination: "/honeydew",
-					Name:        "data",
-					Source:      "/var/lib/docker/volumes/data",
-					Spec:        mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/honeydew", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}},
-				},
+			// partially configured mountpoint due to #32613
+			// specifically, `mp.Spec.Source` is not set
+			"/honeydew": {
+				Type:        mounttypes.TypeVolume,
+				Destination: "/honeydew",
+				Name:        "data",
+				Source:      "/var/lib/docker/volumes/data",
+				Spec:        mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/honeydew", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}},
+			},
 
 
-				// from hostconfig.Mounts
-				"/jambolan": {
-					Type:        mounttypes.TypeVolume,
-					Destination: "/jambolan",
-					Source:      "/var/lib/docker/volumes/data",
-					RW:          true,
-					Name:        "data",
-					Spec:        mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/jambolan", Source: "data"},
-				},
+			// from hostconfig.Mounts
+			"/jambolan": {
+				Type:        mounttypes.TypeVolume,
+				Destination: "/jambolan",
+				Source:      "/var/lib/docker/volumes/data",
+				RW:          true,
+				Name:        "data",
+				Spec:        mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/jambolan", Source: "data"},
 			},
 			},
-			HostConfig: &containertypes.HostConfig{
-				Binds: []string{
-					"data:/banana",
-					"data:/cherry:ro",
-					"data:/dates:ro,nocopy",
-					"data:/elderberry:ro,nocopy",
-					"/data:/fig",
-					"/data:/guava:ro,shared",
-					"data:/honeydew:nocopy",
-				},
-				VolumesFrom: []string{"1:ro"},
-				Mounts: []mounttypes.Mount{
-					{Type: mounttypes.TypeVolume, Target: "/jambolan"},
-				},
+		},
+		HostConfig: &containertypes.HostConfig{
+			Binds: []string{
+				"data:/banana",
+				"data:/cherry:ro",
+				"data:/dates:ro,nocopy",
+				"data:/elderberry:ro,nocopy",
+				"/data:/fig",
+				"/data:/guava:ro,shared",
+				"data:/honeydew:nocopy",
+			},
+			VolumesFrom: []string{"1:ro"},
+			Mounts: []mounttypes.Mount{
+				{Type: mounttypes.TypeVolume, Target: "/jambolan"},
 			},
 			},
-			Config: &containertypes.Config{Volumes: map[string]struct{}{
-				"/apple":      {},
-				"/elderberry": {},
-			}},
-		}}
+		},
+		Config: &containertypes.Config{Volumes: map[string]struct{}{
+			"/apple":      {},
+			"/elderberry": {},
+		}},
+	}
 
 
 	d.containers.Add("1", &container.Container{
 	d.containers.Add("1", &container.Container{
-		CommonContainer: container.CommonContainer{
-			State: &container.State{},
-			ID:    "1",
-			MountPoints: map[string]*volume.MountPoint{
-				"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},
-			},
-			HostConfig: &containertypes.HostConfig{
-				Binds: []string{
-					"data:/kumquat:ro",
-				},
+		State: &container.State{},
+		ID:    "1",
+		MountPoints: map[string]*volume.MountPoint{
+			"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},
+		},
+		HostConfig: &containertypes.HostConfig{
+			Binds: []string{
+				"data:/kumquat:ro",
 			},
 			},
 		},
 		},
 	})
 	})

+ 17 - 9
distribution/config.go

@@ -14,6 +14,7 @@ import (
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/progress"
+	"github.com/docker/docker/pkg/system"
 	refstore "github.com/docker/docker/reference"
 	refstore "github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
 	"github.com/docker/libtrust"
 	"github.com/docker/libtrust"
@@ -58,6 +59,9 @@ type ImagePullConfig struct {
 	// Schema2Types is the valid schema2 configuration types allowed
 	// Schema2Types is the valid schema2 configuration types allowed
 	// by the pull operation.
 	// by the pull operation.
 	Schema2Types []string
 	Schema2Types []string
+	// Platform is the requested platform of the image being pulled to ensure it can be validated
+	// when the host platform supports multiple image operating systems.
+	Platform string
 }
 }
 
 
 // ImagePushConfig stores push configuration.
 // ImagePushConfig stores push configuration.
@@ -82,7 +86,7 @@ type ImagePushConfig struct {
 type ImageConfigStore interface {
 type ImageConfigStore interface {
 	Put([]byte) (digest.Digest, error)
 	Put([]byte) (digest.Digest, error)
 	Get(digest.Digest) ([]byte, error)
 	Get(digest.Digest) ([]byte, error)
-	RootFSFromConfig([]byte) (*image.RootFS, error)
+	RootFSAndPlatformFromConfig([]byte) (*image.RootFS, layer.Platform, error)
 }
 }
 
 
 // PushLayerProvider provides layers to be pushed by ChainID.
 // PushLayerProvider provides layers to be pushed by ChainID.
@@ -108,7 +112,7 @@ type RootFSDownloadManager interface {
 	// returns the final rootfs.
 	// returns the final rootfs.
 	// Given progress output to track download progress
 	// Given progress output to track download progress
 	// Returns function to release download resources
 	// Returns function to release download resources
-	Download(ctx context.Context, initialRootFS image.RootFS, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error)
+	Download(ctx context.Context, initialRootFS image.RootFS, platform layer.Platform, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error)
 }
 }
 
 
 type imageConfigStore struct {
 type imageConfigStore struct {
@@ -136,21 +140,25 @@ func (s *imageConfigStore) Get(d digest.Digest) ([]byte, error) {
 	return img.RawJSON(), nil
 	return img.RawJSON(), nil
 }
 }
 
 
-func (s *imageConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
+func (s *imageConfigStore) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS, layer.Platform, error) {
 	var unmarshalledConfig image.Image
 	var unmarshalledConfig image.Image
 	if err := json.Unmarshal(c, &unmarshalledConfig); err != nil {
 	if err := json.Unmarshal(c, &unmarshalledConfig); err != nil {
-		return nil, err
+		return nil, "", err
 	}
 	}
 
 
 	// fail immediately on Windows when downloading a non-Windows image
 	// fail immediately on Windows when downloading a non-Windows image
-	// and vice versa
-	if runtime.GOOS == "windows" && unmarshalledConfig.OS == "linux" {
-		return nil, fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
+	// and vice versa. Exception on Windows if Linux Containers are enabled.
+	if runtime.GOOS == "windows" && unmarshalledConfig.OS == "linux" && !system.LCOWSupported() {
+		return nil, "", fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
 	} else if runtime.GOOS != "windows" && unmarshalledConfig.OS == "windows" {
 	} else if runtime.GOOS != "windows" && unmarshalledConfig.OS == "windows" {
-		return nil, fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
+		return nil, "", fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
 	}
 	}
 
 
-	return unmarshalledConfig.RootFS, nil
+	platform := ""
+	if runtime.GOOS == "windows" {
+		platform = unmarshalledConfig.OS
+	}
+	return unmarshalledConfig.RootFS, layer.Platform(platform), nil
 }
 }
 
 
 type storeLayerProvider struct {
 type storeLayerProvider struct {

+ 3 - 1
distribution/metadata/metadata.go

@@ -26,15 +26,17 @@ type Store interface {
 type FSMetadataStore struct {
 type FSMetadataStore struct {
 	sync.RWMutex
 	sync.RWMutex
 	basePath string
 	basePath string
+	platform string
 }
 }
 
 
 // NewFSMetadataStore creates a new filesystem-based metadata store.
 // NewFSMetadataStore creates a new filesystem-based metadata store.
-func NewFSMetadataStore(basePath string) (*FSMetadataStore, error) {
+func NewFSMetadataStore(basePath, platform string) (*FSMetadataStore, error) {
 	if err := os.MkdirAll(basePath, 0700); err != nil {
 	if err := os.MkdirAll(basePath, 0700); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	return &FSMetadataStore{
 	return &FSMetadataStore{
 		basePath: basePath,
 		basePath: basePath,
+		platform: platform,
 	}, nil
 	}, nil
 }
 }
 
 

+ 2 - 1
distribution/metadata/v1_id_service_test.go

@@ -3,6 +3,7 @@ package metadata
 import (
 import (
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
+	"runtime"
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
@@ -15,7 +16,7 @@ func TestV1IDService(t *testing.T) {
 	}
 	}
 	defer os.RemoveAll(tmpDir)
 	defer os.RemoveAll(tmpDir)
 
 
-	metadataStore, err := NewFSMetadataStore(tmpDir)
+	metadataStore, err := NewFSMetadataStore(tmpDir, runtime.GOOS)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("could not create metadata store: %v", err)
 		t.Fatalf("could not create metadata store: %v", err)
 	}
 	}

+ 2 - 1
distribution/metadata/v2_metadata_service_test.go

@@ -6,6 +6,7 @@ import (
 	"math/rand"
 	"math/rand"
 	"os"
 	"os"
 	"reflect"
 	"reflect"
+	"runtime"
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
@@ -19,7 +20,7 @@ func TestV2MetadataService(t *testing.T) {
 	}
 	}
 	defer os.RemoveAll(tmpDir)
 	defer os.RemoveAll(tmpDir)
 
 
-	metadataStore, err := NewFSMetadataStore(tmpDir)
+	metadataStore, err := NewFSMetadataStore(tmpDir, runtime.GOOS)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("could not create metadata store: %v", err)
 		t.Fatalf("could not create metadata store: %v", err)
 	}
 	}

+ 1 - 1
distribution/pull_v1.go

@@ -232,7 +232,7 @@ func (p *v1Puller) pullImage(ctx context.Context, v1ID, endpoint string, localNa
 	}
 	}
 
 
 	rootFS := image.NewRootFS()
 	rootFS := image.NewRootFS()
-	resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, descriptors, p.config.ProgressOutput)
+	resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, "", descriptors, p.config.ProgressOutput)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 34 - 13
distribution/pull_v2.go

@@ -27,6 +27,7 @@ import (
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/docker/pkg/system"
 	refstore "github.com/docker/docker/reference"
 	refstore "github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/go-digest"
@@ -486,7 +487,26 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Named, unverif
 		descriptors = append(descriptors, layerDescriptor)
 		descriptors = append(descriptors, layerDescriptor)
 	}
 	}
 
 
-	resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, descriptors, p.config.ProgressOutput)
+	// The v1 manifest itself doesn't directly contain a platform. However,
+	// the history does, but unfortunately that's a string, so search through
+	// all the history until hopefully we find one which indicates the os.
+	platform := runtime.GOOS
+	if runtime.GOOS == "windows" && system.LCOWSupported() {
+		type config struct {
+			Os string `json:"os,omitempty"`
+		}
+		for _, v := range verifiedManifest.History {
+			var c config
+			if err := json.Unmarshal([]byte(v.V1Compatibility), &c); err == nil {
+				if c.Os != "" {
+					platform = c.Os
+					break
+				}
+			}
+		}
+	}
+
+	resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, layer.Platform(platform), descriptors, p.config.ProgressOutput)
 	if err != nil {
 	if err != nil {
 		return "", "", err
 		return "", "", err
 	}
 	}
@@ -556,10 +576,11 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 	}()
 	}()
 
 
 	var (
 	var (
-		configJSON       []byte        // raw serialized image config
-		downloadedRootFS *image.RootFS // rootFS from registered layers
-		configRootFS     *image.RootFS // rootFS from configuration
-		release          func()        // release resources from rootFS download
+		configJSON       []byte         // raw serialized image config
+		downloadedRootFS *image.RootFS  // rootFS from registered layers
+		configRootFS     *image.RootFS  // rootFS from configuration
+		release          func()         // release resources from rootFS download
+		platform         layer.Platform // for LCOW when registering downloaded layers
 	)
 	)
 
 
 	// https://github.com/docker/docker/issues/24766 - Err on the side of caution,
 	// https://github.com/docker/docker/issues/24766 - Err on the side of caution,
@@ -571,7 +592,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 	// check to block Windows images being pulled on Linux is implemented, it
 	// check to block Windows images being pulled on Linux is implemented, it
 	// may be necessary to perform the same type of serialisation.
 	// may be necessary to perform the same type of serialisation.
 	if runtime.GOOS == "windows" {
 	if runtime.GOOS == "windows" {
-		configJSON, configRootFS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
+		configJSON, configRootFS, platform, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
 		if err != nil {
 		if err != nil {
 			return "", "", err
 			return "", "", err
 		}
 		}
@@ -598,7 +619,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 				rootFS image.RootFS
 				rootFS image.RootFS
 			)
 			)
 			downloadRootFS := *image.NewRootFS()
 			downloadRootFS := *image.NewRootFS()
-			rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, descriptors, p.config.ProgressOutput)
+			rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, platform, descriptors, p.config.ProgressOutput)
 			if err != nil {
 			if err != nil {
 				// Intentionally do not cancel the config download here
 				// Intentionally do not cancel the config download here
 				// as the error from config download (if there is one)
 				// as the error from config download (if there is one)
@@ -616,7 +637,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 	}
 	}
 
 
 	if configJSON == nil {
 	if configJSON == nil {
-		configJSON, configRootFS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
+		configJSON, configRootFS, _, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
 		if err == nil && configRootFS == nil {
 		if err == nil && configRootFS == nil {
 			err = errRootFSInvalid
 			err = errRootFSInvalid
 		}
 		}
@@ -663,16 +684,16 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 	return imageID, manifestDigest, nil
 	return imageID, manifestDigest, nil
 }
 }
 
 
-func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, error) {
+func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, layer.Platform, error) {
 	select {
 	select {
 	case configJSON := <-configChan:
 	case configJSON := <-configChan:
-		rootfs, err := s.RootFSFromConfig(configJSON)
+		rootfs, platform, err := s.RootFSAndPlatformFromConfig(configJSON)
 		if err != nil {
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, "", err
 		}
 		}
-		return configJSON, rootfs, nil
+		return configJSON, rootfs, platform, nil
 	case err := <-errChan:
 	case err := <-errChan:
-		return nil, nil, err
+		return nil, nil, "", err
 		// Don't need a case for ctx.Done in the select because cancellation
 		// Don't need a case for ctx.Done in the select because cancellation
 		// will trigger an error in p.pullSchema2ImageConfig.
 		// will trigger an error in p.pullSchema2ImageConfig.
 	}
 	}

+ 1 - 1
distribution/push_v2.go

@@ -118,7 +118,7 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, id
 		return fmt.Errorf("could not find image from tag %s: %v", reference.FamiliarString(ref), err)
 		return fmt.Errorf("could not find image from tag %s: %v", reference.FamiliarString(ref), err)
 	}
 	}
 
 
-	rootfs, err := p.config.ImageStore.RootFSFromConfig(imgConfig)
+	rootfs, _, err := p.config.ImageStore.RootFSAndPlatformFromConfig(imgConfig)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("unable to get rootfs for image %s: %s", reference.FamiliarString(ref), err)
 		return fmt.Errorf("unable to get rootfs for image %s: %s", reference.FamiliarString(ref), err)
 	}
 	}

+ 25 - 19
distribution/xfer/download.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
+	"runtime"
 	"time"
 	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
@@ -22,7 +23,7 @@ const maxDownloadAttempts = 5
 // registers and downloads those, taking into account dependencies between
 // registers and downloads those, taking into account dependencies between
 // layers.
 // layers.
 type LayerDownloadManager struct {
 type LayerDownloadManager struct {
-	layerStore   layer.Store
+	layerStores  map[string]layer.Store
 	tm           TransferManager
 	tm           TransferManager
 	waitDuration time.Duration
 	waitDuration time.Duration
 }
 }
@@ -33,9 +34,9 @@ func (ldm *LayerDownloadManager) SetConcurrency(concurrency int) {
 }
 }
 
 
 // NewLayerDownloadManager returns a new LayerDownloadManager.
 // NewLayerDownloadManager returns a new LayerDownloadManager.
-func NewLayerDownloadManager(layerStore layer.Store, concurrencyLimit int, options ...func(*LayerDownloadManager)) *LayerDownloadManager {
+func NewLayerDownloadManager(layerStores map[string]layer.Store, concurrencyLimit int, options ...func(*LayerDownloadManager)) *LayerDownloadManager {
 	manager := LayerDownloadManager{
 	manager := LayerDownloadManager{
-		layerStore:   layerStore,
+		layerStores:  layerStores,
 		tm:           NewTransferManager(concurrencyLimit),
 		tm:           NewTransferManager(concurrencyLimit),
 		waitDuration: time.Second,
 		waitDuration: time.Second,
 	}
 	}
@@ -94,7 +95,7 @@ type DownloadDescriptorWithRegistered interface {
 // Download method is called to get the layer tar data. Layers are then
 // Download method is called to get the layer tar data. Layers are then
 // registered in the appropriate order.  The caller must call the returned
 // registered in the appropriate order.  The caller must call the returned
 // release function once it is done with the returned RootFS object.
 // release function once it is done with the returned RootFS object.
-func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS image.RootFS, layers []DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
+func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS image.RootFS, platform layer.Platform, layers []DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
 	var (
 	var (
 		topLayer       layer.Layer
 		topLayer       layer.Layer
 		topDownload    *downloadTransfer
 		topDownload    *downloadTransfer
@@ -104,6 +105,11 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 		downloadsByKey = make(map[string]*downloadTransfer)
 		downloadsByKey = make(map[string]*downloadTransfer)
 	)
 	)
 
 
+	// Assume that the platform is the host OS if blank
+	if platform == "" {
+		platform = layer.Platform(runtime.GOOS)
+	}
+
 	rootFS := initialRootFS
 	rootFS := initialRootFS
 	for _, descriptor := range layers {
 	for _, descriptor := range layers {
 		key := descriptor.Key()
 		key := descriptor.Key()
@@ -115,13 +121,13 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 			if err == nil {
 			if err == nil {
 				getRootFS := rootFS
 				getRootFS := rootFS
 				getRootFS.Append(diffID)
 				getRootFS.Append(diffID)
-				l, err := ldm.layerStore.Get(getRootFS.ChainID())
+				l, err := ldm.layerStores[string(platform)].Get(getRootFS.ChainID())
 				if err == nil {
 				if err == nil {
 					// Layer already exists.
 					// Layer already exists.
 					logrus.Debugf("Layer already exists: %s", descriptor.ID())
 					logrus.Debugf("Layer already exists: %s", descriptor.ID())
 					progress.Update(progressOutput, descriptor.ID(), "Already exists")
 					progress.Update(progressOutput, descriptor.ID(), "Already exists")
 					if topLayer != nil {
 					if topLayer != nil {
-						layer.ReleaseAndLog(ldm.layerStore, topLayer)
+						layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
 					}
 					}
 					topLayer = l
 					topLayer = l
 					missingLayer = false
 					missingLayer = false
@@ -140,7 +146,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 		// the stack? If so, avoid downloading it more than once.
 		// the stack? If so, avoid downloading it more than once.
 		var topDownloadUncasted Transfer
 		var topDownloadUncasted Transfer
 		if existingDownload, ok := downloadsByKey[key]; ok {
 		if existingDownload, ok := downloadsByKey[key]; ok {
-			xferFunc := ldm.makeDownloadFuncFromDownload(descriptor, existingDownload, topDownload)
+			xferFunc := ldm.makeDownloadFuncFromDownload(descriptor, existingDownload, topDownload, platform)
 			defer topDownload.Transfer.Release(watcher)
 			defer topDownload.Transfer.Release(watcher)
 			topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
 			topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
 			topDownload = topDownloadUncasted.(*downloadTransfer)
 			topDownload = topDownloadUncasted.(*downloadTransfer)
@@ -152,10 +158,10 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 
 
 		var xferFunc DoFunc
 		var xferFunc DoFunc
 		if topDownload != nil {
 		if topDownload != nil {
-			xferFunc = ldm.makeDownloadFunc(descriptor, "", topDownload)
+			xferFunc = ldm.makeDownloadFunc(descriptor, "", topDownload, platform)
 			defer topDownload.Transfer.Release(watcher)
 			defer topDownload.Transfer.Release(watcher)
 		} else {
 		} else {
-			xferFunc = ldm.makeDownloadFunc(descriptor, rootFS.ChainID(), nil)
+			xferFunc = ldm.makeDownloadFunc(descriptor, rootFS.ChainID(), nil, platform)
 		}
 		}
 		topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
 		topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
 		topDownload = topDownloadUncasted.(*downloadTransfer)
 		topDownload = topDownloadUncasted.(*downloadTransfer)
@@ -165,7 +171,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 	if topDownload == nil {
 	if topDownload == nil {
 		return rootFS, func() {
 		return rootFS, func() {
 			if topLayer != nil {
 			if topLayer != nil {
-				layer.ReleaseAndLog(ldm.layerStore, topLayer)
+				layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
 			}
 			}
 		}, nil
 		}, nil
 	}
 	}
@@ -176,7 +182,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 
 
 	defer func() {
 	defer func() {
 		if topLayer != nil {
 		if topLayer != nil {
-			layer.ReleaseAndLog(ldm.layerStore, topLayer)
+			layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
 		}
 		}
 	}()
 	}()
 
 
@@ -212,11 +218,11 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 // complete before the registration step, and registers the downloaded data
 // complete before the registration step, and registers the downloaded data
 // on top of parentDownload's resulting layer. Otherwise, it registers the
 // on top of parentDownload's resulting layer. Otherwise, it registers the
 // layer on top of the ChainID given by parentLayer.
 // layer on top of the ChainID given by parentLayer.
-func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor, parentLayer layer.ChainID, parentDownload *downloadTransfer) DoFunc {
+func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor, parentLayer layer.ChainID, parentDownload *downloadTransfer, platform layer.Platform) DoFunc {
 	return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
 	return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
 		d := &downloadTransfer{
 		d := &downloadTransfer{
 			Transfer:   NewTransfer(),
 			Transfer:   NewTransfer(),
-			layerStore: ldm.layerStore,
+			layerStore: ldm.layerStores[string(platform)],
 		}
 		}
 
 
 		go func() {
 		go func() {
@@ -335,9 +341,9 @@ func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor,
 				src = fs.Descriptor()
 				src = fs.Descriptor()
 			}
 			}
 			if ds, ok := d.layerStore.(layer.DescribableStore); ok {
 			if ds, ok := d.layerStore.(layer.DescribableStore); ok {
-				d.layer, err = ds.RegisterWithDescriptor(inflatedLayerData, parentLayer, src)
+				d.layer, err = ds.RegisterWithDescriptor(inflatedLayerData, parentLayer, platform, src)
 			} else {
 			} else {
-				d.layer, err = d.layerStore.Register(inflatedLayerData, parentLayer)
+				d.layer, err = d.layerStore.Register(inflatedLayerData, parentLayer, platform)
 			}
 			}
 			if err != nil {
 			if err != nil {
 				select {
 				select {
@@ -376,11 +382,11 @@ func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor,
 // parentDownload. This function does not log progress output because it would
 // parentDownload. This function does not log progress output because it would
 // interfere with the progress reporting for sourceDownload, which has the same
 // interfere with the progress reporting for sourceDownload, which has the same
 // Key.
 // Key.
-func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor DownloadDescriptor, sourceDownload *downloadTransfer, parentDownload *downloadTransfer) DoFunc {
+func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor DownloadDescriptor, sourceDownload *downloadTransfer, parentDownload *downloadTransfer, platform layer.Platform) DoFunc {
 	return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
 	return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
 		d := &downloadTransfer{
 		d := &downloadTransfer{
 			Transfer:   NewTransfer(),
 			Transfer:   NewTransfer(),
-			layerStore: ldm.layerStore,
+			layerStore: ldm.layerStores[string(platform)],
 		}
 		}
 
 
 		go func() {
 		go func() {
@@ -434,9 +440,9 @@ func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor Downloa
 				src = fs.Descriptor()
 				src = fs.Descriptor()
 			}
 			}
 			if ds, ok := d.layerStore.(layer.DescribableStore); ok {
 			if ds, ok := d.layerStore.(layer.DescribableStore); ok {
-				d.layer, err = ds.RegisterWithDescriptor(layerReader, parentLayer, src)
+				d.layer, err = ds.RegisterWithDescriptor(layerReader, parentLayer, platform, src)
 			} else {
 			} else {
-				d.layer, err = d.layerStore.Register(layerReader, parentLayer)
+				d.layer, err = d.layerStore.Register(layerReader, parentLayer, platform)
 			}
 			}
 			if err != nil {
 			if err != nil {
 				d.err = fmt.Errorf("failed to register layer: %v", err)
 				d.err = fmt.Errorf("failed to register layer: %v", err)

+ 16 - 6
distribution/xfer/download_test.go

@@ -26,6 +26,7 @@ type mockLayer struct {
 	diffID    layer.DiffID
 	diffID    layer.DiffID
 	chainID   layer.ChainID
 	chainID   layer.ChainID
 	parent    layer.Layer
 	parent    layer.Layer
+	platform  layer.Platform
 }
 }
 
 
 func (ml *mockLayer) TarStream() (io.ReadCloser, error) {
 func (ml *mockLayer) TarStream() (io.ReadCloser, error) {
@@ -56,6 +57,10 @@ func (ml *mockLayer) DiffSize() (size int64, err error) {
 	return 0, nil
 	return 0, nil
 }
 }
 
 
+func (ml *mockLayer) Platform() layer.Platform {
+	return ml.platform
+}
+
 func (ml *mockLayer) Metadata() (map[string]string, error) {
 func (ml *mockLayer) Metadata() (map[string]string, error) {
 	return make(map[string]string), nil
 	return make(map[string]string), nil
 }
 }
@@ -86,7 +91,7 @@ func (ls *mockLayerStore) Map() map[layer.ChainID]layer.Layer {
 	return layers
 	return layers
 }
 }
 
 
-func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID) (layer.Layer, error) {
+func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID, platform layer.Platform) (layer.Layer, error) {
 	return ls.RegisterWithDescriptor(reader, parentID, distribution.Descriptor{})
 	return ls.RegisterWithDescriptor(reader, parentID, distribution.Descriptor{})
 }
 }
 
 
@@ -267,7 +272,9 @@ func TestSuccessfulDownload(t *testing.T) {
 	}
 	}
 
 
 	layerStore := &mockLayerStore{make(map[layer.ChainID]*mockLayer)}
 	layerStore := &mockLayerStore{make(map[layer.ChainID]*mockLayer)}
-	ldm := NewLayerDownloadManager(layerStore, maxDownloadConcurrency, func(m *LayerDownloadManager) { m.waitDuration = time.Millisecond })
+	lsMap := make(map[string]layer.Store)
+	lsMap[runtime.GOOS] = layerStore
+	ldm := NewLayerDownloadManager(lsMap, maxDownloadConcurrency, func(m *LayerDownloadManager) { m.waitDuration = time.Millisecond })
 
 
 	progressChan := make(chan progress.Progress)
 	progressChan := make(chan progress.Progress)
 	progressDone := make(chan struct{})
 	progressDone := make(chan struct{})
@@ -286,13 +293,13 @@ func TestSuccessfulDownload(t *testing.T) {
 	firstDescriptor := descriptors[0].(*mockDownloadDescriptor)
 	firstDescriptor := descriptors[0].(*mockDownloadDescriptor)
 
 
 	// Pre-register the first layer to simulate an already-existing layer
 	// Pre-register the first layer to simulate an already-existing layer
-	l, err := layerStore.Register(firstDescriptor.mockTarStream(), "")
+	l, err := layerStore.Register(firstDescriptor.mockTarStream(), "", layer.Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	firstDescriptor.diffID = l.DiffID()
 	firstDescriptor.diffID = l.DiffID()
 
 
-	rootFS, releaseFunc, err := ldm.Download(context.Background(), *image.NewRootFS(), descriptors, progress.ChanOutput(progressChan))
+	rootFS, releaseFunc, err := ldm.Download(context.Background(), *image.NewRootFS(), layer.Platform(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
 	if err != nil {
 	if err != nil {
 		t.Fatalf("download error: %v", err)
 		t.Fatalf("download error: %v", err)
 	}
 	}
@@ -328,7 +335,10 @@ func TestSuccessfulDownload(t *testing.T) {
 }
 }
 
 
 func TestCancelledDownload(t *testing.T) {
 func TestCancelledDownload(t *testing.T) {
-	ldm := NewLayerDownloadManager(&mockLayerStore{make(map[layer.ChainID]*mockLayer)}, maxDownloadConcurrency, func(m *LayerDownloadManager) { m.waitDuration = time.Millisecond })
+	layerStore := &mockLayerStore{make(map[layer.ChainID]*mockLayer)}
+	lsMap := make(map[string]layer.Store)
+	lsMap[runtime.GOOS] = layerStore
+	ldm := NewLayerDownloadManager(lsMap, maxDownloadConcurrency, func(m *LayerDownloadManager) { m.waitDuration = time.Millisecond })
 
 
 	progressChan := make(chan progress.Progress)
 	progressChan := make(chan progress.Progress)
 	progressDone := make(chan struct{})
 	progressDone := make(chan struct{})
@@ -347,7 +357,7 @@ func TestCancelledDownload(t *testing.T) {
 	}()
 	}()
 
 
 	descriptors := downloadDescriptors(nil)
 	descriptors := downloadDescriptors(nil)
-	_, _, err := ldm.Download(ctx, *image.NewRootFS(), descriptors, progress.ChanOutput(progressChan))
+	_, _, err := ldm.Download(ctx, *image.NewRootFS(), layer.Platform(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
 	if err != context.Canceled {
 	if err != context.Canceled {
 		t.Fatal("expected download to be cancelled")
 		t.Fatal("expected download to be cancelled")
 	}
 	}

+ 11 - 2
image/image.go

@@ -96,6 +96,15 @@ func (img *Image) RunConfig() *container.Config {
 	return img.Config
 	return img.Config
 }
 }
 
 
+// Platform returns the image's operating system. If not populated, defaults to the host runtime OS.
+func (img *Image) Platform() string {
+	os := img.OS
+	if os == "" {
+		os = runtime.GOOS
+	}
+	return os
+}
+
 // MarshalJSON serializes the image to JSON. It sorts the top-level keys so
 // MarshalJSON serializes the image to JSON. It sorts the top-level keys so
 // that JSON that's been manipulated by a push/pull cycle with a legacy
 // that JSON that's been manipulated by a push/pull cycle with a legacy
 // registry won't end up with a different key order.
 // registry won't end up with a different key order.
@@ -126,7 +135,7 @@ type ChildConfig struct {
 }
 }
 
 
 // NewChildImage creates a new Image as a child of this image.
 // NewChildImage creates a new Image as a child of this image.
-func NewChildImage(img *Image, child ChildConfig) *Image {
+func NewChildImage(img *Image, child ChildConfig, platform string) *Image {
 	isEmptyLayer := layer.IsEmpty(child.DiffID)
 	isEmptyLayer := layer.IsEmpty(child.DiffID)
 	rootFS := img.RootFS
 	rootFS := img.RootFS
 	if rootFS == nil {
 	if rootFS == nil {
@@ -146,7 +155,7 @@ func NewChildImage(img *Image, child ChildConfig) *Image {
 			DockerVersion:   dockerversion.Version,
 			DockerVersion:   dockerversion.Version,
 			Config:          child.Config,
 			Config:          child.Config,
 			Architecture:    runtime.GOARCH,
 			Architecture:    runtime.GOARCH,
-			OS:              runtime.GOOS,
+			OS:              platform,
 			Container:       child.ContainerID,
 			Container:       child.ContainerID,
 			ContainerConfig: *child.ContainerConfig,
 			ContainerConfig: *child.ContainerConfig,
 			Author:          child.Author,
 			Author:          child.Author,

+ 13 - 1
image/store.go

@@ -3,11 +3,14 @@ package image
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
+	"runtime"
+	"strings"
 	"sync"
 	"sync"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/distribution/digestset"
 	"github.com/docker/distribution/digestset"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
+	"github.com/docker/docker/pkg/system"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/go-digest"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 )
 )
@@ -42,15 +45,17 @@ type store struct {
 	images    map[ID]*imageMeta
 	images    map[ID]*imageMeta
 	fs        StoreBackend
 	fs        StoreBackend
 	digestSet *digestset.Set
 	digestSet *digestset.Set
+	platform  string
 }
 }
 
 
 // NewImageStore returns new store object for given layer store
 // NewImageStore returns new store object for given layer store
-func NewImageStore(fs StoreBackend, ls LayerGetReleaser) (Store, error) {
+func NewImageStore(fs StoreBackend, platform string, ls LayerGetReleaser) (Store, error) {
 	is := &store{
 	is := &store{
 		ls:        ls,
 		ls:        ls,
 		images:    make(map[ID]*imageMeta),
 		images:    make(map[ID]*imageMeta),
 		fs:        fs,
 		fs:        fs,
 		digestSet: digestset.NewSet(),
 		digestSet: digestset.NewSet(),
+		platform:  platform,
 	}
 	}
 
 
 	// load all current images and retain layers
 	// load all current images and retain layers
@@ -111,6 +116,13 @@ func (is *store) Create(config []byte) (ID, error) {
 		return "", err
 		return "", err
 	}
 	}
 
 
+	// Integrity check - ensure we are creating something for the correct platform
+	if runtime.GOOS == "windows" && system.LCOWSupported() {
+		if strings.ToLower(img.Platform()) != strings.ToLower(is.platform) {
+			return "", fmt.Errorf("cannot create entry for platform %q in image store for platform %q", img.Platform(), is.platform)
+		}
+	}
+
 	// Must reject any config that references diffIDs from the history
 	// Must reject any config that references diffIDs from the history
 	// which aren't among the rootfs layers.
 	// which aren't among the rootfs layers.
 	rootFSLayers := make(map[layer.DiffID]struct{})
 	rootFSLayers := make(map[layer.DiffID]struct{})

+ 3 - 2
image/store_test.go

@@ -1,6 +1,7 @@
 package image
 package image
 
 
 import (
 import (
+	"runtime"
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
@@ -25,7 +26,7 @@ func TestRestore(t *testing.T) {
 	err = fs.SetMetadata(id2, "parent", []byte(id1))
 	err = fs.SetMetadata(id2, "parent", []byte(id1))
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
-	is, err := NewImageStore(fs, &mockLayerGetReleaser{})
+	is, err := NewImageStore(fs, runtime.GOOS, &mockLayerGetReleaser{})
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
 	assert.Len(t, is.Map(), 2)
 	assert.Len(t, is.Map(), 2)
@@ -142,7 +143,7 @@ func TestParentReset(t *testing.T) {
 func defaultImageStore(t *testing.T) (Store, func()) {
 func defaultImageStore(t *testing.T) (Store, func()) {
 	fsBackend, cleanup := defaultFSStoreBackend(t)
 	fsBackend, cleanup := defaultFSStoreBackend(t)
 
 
-	store, err := NewImageStore(fsBackend, &mockLayerGetReleaser{})
+	store, err := NewImageStore(fsBackend, runtime.GOOS, &mockLayerGetReleaser{})
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
 	return store, cleanup
 	return store, cleanup

+ 22 - 5
image/tarexport/load.go

@@ -2,12 +2,14 @@ package tarexport
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 	"reflect"
 	"reflect"
+	"runtime"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/distribution"
 	"github.com/docker/distribution"
@@ -85,6 +87,17 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
 			return fmt.Errorf("invalid manifest, layers length mismatch: expected %d, got %d", expected, actual)
 			return fmt.Errorf("invalid manifest, layers length mismatch: expected %d, got %d", expected, actual)
 		}
 		}
 
 
+		// On Windows, validate the platform, defaulting to windows if not present.
+		platform := layer.Platform(img.OS)
+		if runtime.GOOS == "windows" {
+			if platform == "" {
+				platform = "windows"
+			}
+			if (platform != "windows") && (platform != "linux") {
+				return fmt.Errorf("configuration for this image has an unsupported platform: %s", platform)
+			}
+		}
+
 		for i, diffID := range img.RootFS.DiffIDs {
 		for i, diffID := range img.RootFS.DiffIDs {
 			layerPath, err := safePath(tmpDir, m.Layers[i])
 			layerPath, err := safePath(tmpDir, m.Layers[i])
 			if err != nil {
 			if err != nil {
@@ -94,7 +107,7 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
 			r.Append(diffID)
 			r.Append(diffID)
 			newLayer, err := l.ls.Get(r.ChainID())
 			newLayer, err := l.ls.Get(r.ChainID())
 			if err != nil {
 			if err != nil {
-				newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), m.LayerSources[diffID], progressOutput)
+				newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), platform, m.LayerSources[diffID], progressOutput)
 				if err != nil {
 				if err != nil {
 					return err
 					return err
 				}
 				}
@@ -161,7 +174,7 @@ func (l *tarexporter) setParentID(id, parentID image.ID) error {
 	return l.is.SetParent(id, parentID)
 	return l.is.SetParent(id, parentID)
 }
 }
 
 
-func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) {
+func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, platform layer.Platform, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) {
 	// We use system.OpenSequential to use sequential file access on Windows, avoiding
 	// We use system.OpenSequential to use sequential file access on Windows, avoiding
 	// depleting the standby list. On Linux, this equates to a regular os.Open.
 	// depleting the standby list. On Linux, this equates to a regular os.Open.
 	rawTar, err := system.OpenSequential(filename)
 	rawTar, err := system.OpenSequential(filename)
@@ -191,9 +204,9 @@ func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string,
 	defer inflatedLayerData.Close()
 	defer inflatedLayerData.Close()
 
 
 	if ds, ok := l.ls.(layer.DescribableStore); ok {
 	if ds, ok := l.ls.(layer.DescribableStore); ok {
-		return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), foreignSrc)
+		return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), platform, foreignSrc)
 	}
 	}
-	return l.ls.Register(inflatedLayerData, rootFS.ChainID())
+	return l.ls.Register(inflatedLayerData, rootFS.ChainID(), platform)
 }
 }
 
 
 func (l *tarexporter) setLoadedTag(ref reference.NamedTagged, imgID digest.Digest, outStream io.Writer) error {
 func (l *tarexporter) setLoadedTag(ref reference.NamedTagged, imgID digest.Digest, outStream io.Writer) error {
@@ -208,6 +221,10 @@ func (l *tarexporter) setLoadedTag(ref reference.NamedTagged, imgID digest.Diges
 }
 }
 
 
 func (l *tarexporter) legacyLoad(tmpDir string, outStream io.Writer, progressOutput progress.Output) error {
 func (l *tarexporter) legacyLoad(tmpDir string, outStream io.Writer, progressOutput progress.Output) error {
+	if runtime.GOOS == "windows" {
+		return errors.New("Windows does not support legacy loading of images")
+	}
+
 	legacyLoadedMap := make(map[string]image.ID)
 	legacyLoadedMap := make(map[string]image.ID)
 
 
 	dirs, err := ioutil.ReadDir(tmpDir)
 	dirs, err := ioutil.ReadDir(tmpDir)
@@ -312,7 +329,7 @@ func (l *tarexporter) legacyLoadImage(oldID, sourceDir string, loadedMap map[str
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	newLayer, err := l.loadLayer(layerPath, *rootFS, oldID, distribution.Descriptor{}, progressOutput)
+	newLayer, err := l.loadLayer(layerPath, *rootFS, oldID, "", distribution.Descriptor{}, progressOutput)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 4 - 0
layer/empty.go

@@ -55,6 +55,10 @@ func (el *emptyLayer) Metadata() (map[string]string, error) {
 	return make(map[string]string), nil
 	return make(map[string]string), nil
 }
 }
 
 
+func (el *emptyLayer) Platform() Platform {
+	return ""
+}
+
 // IsEmpty returns true if the layer is an EmptyLayer
 // IsEmpty returns true if the layer is an EmptyLayer
 func IsEmpty(diffID DiffID) bool {
 func IsEmpty(diffID DiffID) bool {
 	return diffID == DigestSHA256EmptyTar
 	return diffID == DigestSHA256EmptyTar

+ 13 - 0
layer/filestore_unix.go

@@ -0,0 +1,13 @@
+// +build !windows
+
+package layer
+
+// SetPlatform writes the "platform" file to the layer filestore
+func (fm *fileMetadataTransaction) SetPlatform(platform Platform) error {
+	return nil
+}
+
+// GetPlatform reads the "platform" file from the layer filestore
+func (fms *fileMetadataStore) GetPlatform(layer ChainID) (Platform, error) {
+	return "", nil
+}

+ 35 - 0
layer/filestore_windows.go

@@ -0,0 +1,35 @@
+package layer
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+)
+
+// SetPlatform writes the "platform" file to the layer filestore
+func (fm *fileMetadataTransaction) SetPlatform(platform Platform) error {
+	if platform == "" {
+		return nil
+	}
+	return fm.ws.WriteFile("platform", []byte(platform), 0644)
+}
+
+// GetPlatform reads the "platform" file from the layer filestore
+func (fms *fileMetadataStore) GetPlatform(layer ChainID) (Platform, error) {
+	contentBytes, err := ioutil.ReadFile(fms.getLayerFilename(layer, "platform"))
+	if err != nil {
+		// For backwards compatibility, the platform file may not exist. Default to "windows" if missing.
+		if os.IsNotExist(err) {
+			return "windows", nil
+		}
+		return "", err
+	}
+	content := strings.TrimSpace(string(contentBytes))
+
+	if content != "windows" && content != "linux" {
+		return "", fmt.Errorf("invalid platform value: %s", content)
+	}
+
+	return Platform(content), nil
+}

+ 15 - 2
layer/layer.go

@@ -64,6 +64,14 @@ func (id ChainID) String() string {
 	return string(id)
 	return string(id)
 }
 }
 
 
+// Platform is the platform of a layer
+type Platform string
+
+// String returns a string rendition of layers target platform
+func (id Platform) String() string {
+	return string(id)
+}
+
 // DiffID is the hash of an individual layer tar.
 // DiffID is the hash of an individual layer tar.
 type DiffID digest.Digest
 type DiffID digest.Digest
 
 
@@ -99,6 +107,9 @@ type Layer interface {
 	// Parent returns the next layer in the layer chain.
 	// Parent returns the next layer in the layer chain.
 	Parent() Layer
 	Parent() Layer
 
 
+	// Platform returns the platform of the layer
+	Platform() Platform
+
 	// Size returns the size of the entire layer chain. The size
 	// Size returns the size of the entire layer chain. The size
 	// is calculated from the total size of all files in the layers.
 	// is calculated from the total size of all files in the layers.
 	Size() (int64, error)
 	Size() (int64, error)
@@ -179,7 +190,7 @@ type CreateRWLayerOpts struct {
 // Store represents a backend for managing both
 // Store represents a backend for managing both
 // read-only and read-write layers.
 // read-only and read-write layers.
 type Store interface {
 type Store interface {
-	Register(io.Reader, ChainID) (Layer, error)
+	Register(io.Reader, ChainID, Platform) (Layer, error)
 	Get(ChainID) (Layer, error)
 	Get(ChainID) (Layer, error)
 	Map() map[ChainID]Layer
 	Map() map[ChainID]Layer
 	Release(Layer) ([]Metadata, error)
 	Release(Layer) ([]Metadata, error)
@@ -197,7 +208,7 @@ type Store interface {
 // DescribableStore represents a layer store capable of storing
 // DescribableStore represents a layer store capable of storing
 // descriptors for layers.
 // descriptors for layers.
 type DescribableStore interface {
 type DescribableStore interface {
-	RegisterWithDescriptor(io.Reader, ChainID, distribution.Descriptor) (Layer, error)
+	RegisterWithDescriptor(io.Reader, ChainID, Platform, distribution.Descriptor) (Layer, error)
 }
 }
 
 
 // MetadataTransaction represents functions for setting layer metadata
 // MetadataTransaction represents functions for setting layer metadata
@@ -208,6 +219,7 @@ type MetadataTransaction interface {
 	SetDiffID(DiffID) error
 	SetDiffID(DiffID) error
 	SetCacheID(string) error
 	SetCacheID(string) error
 	SetDescriptor(distribution.Descriptor) error
 	SetDescriptor(distribution.Descriptor) error
+	SetPlatform(Platform) error
 	TarSplitWriter(compressInput bool) (io.WriteCloser, error)
 	TarSplitWriter(compressInput bool) (io.WriteCloser, error)
 
 
 	Commit(ChainID) error
 	Commit(ChainID) error
@@ -228,6 +240,7 @@ type MetadataStore interface {
 	GetDiffID(ChainID) (DiffID, error)
 	GetDiffID(ChainID) (DiffID, error)
 	GetCacheID(ChainID) (string, error)
 	GetCacheID(ChainID) (string, error)
 	GetDescriptor(ChainID) (distribution.Descriptor, error)
 	GetDescriptor(ChainID) (distribution.Descriptor, error)
+	GetPlatform(ChainID) (Platform, error)
 	TarSplitReader(ChainID) (io.ReadCloser, error)
 	TarSplitReader(ChainID) (io.ReadCloser, error)
 
 
 	SetMountID(string, string) error
 	SetMountID(string, string) error

+ 27 - 7
layer/layer_store.go

@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
+	"runtime"
+	"strings"
 	"sync"
 	"sync"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
@@ -13,6 +15,7 @@ import (
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/plugingetter"
 	"github.com/docker/docker/pkg/plugingetter"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/docker/pkg/system"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/go-digest"
 	"github.com/vbatts/tar-split/tar/asm"
 	"github.com/vbatts/tar-split/tar/asm"
 	"github.com/vbatts/tar-split/tar/storage"
 	"github.com/vbatts/tar-split/tar/storage"
@@ -36,6 +39,8 @@ type layerStore struct {
 	mountL sync.Mutex
 	mountL sync.Mutex
 
 
 	useTarSplit bool
 	useTarSplit bool
+
+	platform string
 }
 }
 
 
 // StoreOptions are the options used to create a new Store instance
 // StoreOptions are the options used to create a new Store instance
@@ -47,6 +52,7 @@ type StoreOptions struct {
 	IDMappings                *idtools.IDMappings
 	IDMappings                *idtools.IDMappings
 	PluginGetter              plugingetter.PluginGetter
 	PluginGetter              plugingetter.PluginGetter
 	ExperimentalEnabled       bool
 	ExperimentalEnabled       bool
+	Platform                  string
 }
 }
 
 
 // NewStoreFromOptions creates a new Store instance
 // NewStoreFromOptions creates a new Store instance
@@ -68,13 +74,13 @@ func NewStoreFromOptions(options StoreOptions) (Store, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	return NewStoreFromGraphDriver(fms, driver)
+	return NewStoreFromGraphDriver(fms, driver, options.Platform)
 }
 }
 
 
 // NewStoreFromGraphDriver creates a new Store instance using the provided
 // NewStoreFromGraphDriver creates a new Store instance using the provided
 // metadata store and graph driver. The metadata store will be used to restore
 // metadata store and graph driver. The metadata store will be used to restore
 // the Store.
 // the Store.
-func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver) (Store, error) {
+func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver, platform string) (Store, error) {
 	caps := graphdriver.Capabilities{}
 	caps := graphdriver.Capabilities{}
 	if capDriver, ok := driver.(graphdriver.CapabilityDriver); ok {
 	if capDriver, ok := driver.(graphdriver.CapabilityDriver); ok {
 		caps = capDriver.Capabilities()
 		caps = capDriver.Capabilities()
@@ -86,6 +92,7 @@ func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver) (St
 		layerMap:    map[ChainID]*roLayer{},
 		layerMap:    map[ChainID]*roLayer{},
 		mounts:      map[string]*mountedLayer{},
 		mounts:      map[string]*mountedLayer{},
 		useTarSplit: !caps.ReproducesExactDiffs,
 		useTarSplit: !caps.ReproducesExactDiffs,
+		platform:    platform,
 	}
 	}
 
 
 	ids, mounts, err := store.List()
 	ids, mounts, err := store.List()
@@ -144,6 +151,11 @@ func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
 		return nil, fmt.Errorf("failed to get descriptor for %s: %s", layer, err)
 		return nil, fmt.Errorf("failed to get descriptor for %s: %s", layer, err)
 	}
 	}
 
 
+	platform, err := ls.store.GetPlatform(layer)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get platform for %s: %s", layer, err)
+	}
+
 	cl = &roLayer{
 	cl = &roLayer{
 		chainID:    layer,
 		chainID:    layer,
 		diffID:     diff,
 		diffID:     diff,
@@ -152,6 +164,7 @@ func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
 		layerStore: ls,
 		layerStore: ls,
 		references: map[Layer]struct{}{},
 		references: map[Layer]struct{}{},
 		descriptor: descriptor,
 		descriptor: descriptor,
+		platform:   platform,
 	}
 	}
 
 
 	if parent != "" {
 	if parent != "" {
@@ -247,17 +260,25 @@ func (ls *layerStore) applyTar(tx MetadataTransaction, ts io.Reader, parent stri
 	return nil
 	return nil
 }
 }
 
 
-func (ls *layerStore) Register(ts io.Reader, parent ChainID) (Layer, error) {
-	return ls.registerWithDescriptor(ts, parent, distribution.Descriptor{})
+func (ls *layerStore) Register(ts io.Reader, parent ChainID, platform Platform) (Layer, error) {
+	return ls.registerWithDescriptor(ts, parent, platform, distribution.Descriptor{})
 }
 }
 
 
-func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descriptor distribution.Descriptor) (Layer, error) {
+func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, platform Platform, descriptor distribution.Descriptor) (Layer, error) {
 	// err is used to hold the error which will always trigger
 	// err is used to hold the error which will always trigger
 	// cleanup of creates sources but may not be an error returned
 	// cleanup of creates sources but may not be an error returned
 	// to the caller (already exists).
 	// to the caller (already exists).
 	var err error
 	var err error
 	var pid string
 	var pid string
 	var p *roLayer
 	var p *roLayer
+
+	// Integrity check - ensure we are creating something for the correct platform
+	if runtime.GOOS == "windows" && system.LCOWSupported() {
+		if strings.ToLower(ls.platform) != strings.ToLower(string(platform)) {
+			return nil, fmt.Errorf("cannot create entry for platform %q in layer store for platform %q", platform, ls.platform)
+		}
+	}
+
 	if string(parent) != "" {
 	if string(parent) != "" {
 		p = ls.get(parent)
 		p = ls.get(parent)
 		if p == nil {
 		if p == nil {
@@ -286,6 +307,7 @@ func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descr
 		layerStore:     ls,
 		layerStore:     ls,
 		references:     map[Layer]struct{}{},
 		references:     map[Layer]struct{}{},
 		descriptor:     descriptor,
 		descriptor:     descriptor,
+		platform:       platform,
 	}
 	}
 
 
 	if err = ls.driver.Create(layer.cacheID, pid, nil); err != nil {
 	if err = ls.driver.Create(layer.cacheID, pid, nil); err != nil {
@@ -388,7 +410,6 @@ func (ls *layerStore) deleteLayer(layer *roLayer, metadata *Metadata) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-
 	err = ls.store.Remove(layer.chainID)
 	err = ls.store.Remove(layer.chainID)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -518,7 +539,6 @@ func (ls *layerStore) CreateRWLayer(name string, parent ChainID, opts *CreateRWL
 	if err = ls.driver.CreateReadWrite(m.mountID, pid, createOpts); err != nil {
 	if err = ls.driver.CreateReadWrite(m.mountID, pid, createOpts); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-
 	if err = ls.saveMount(m); err != nil {
 	if err = ls.saveMount(m); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 2 - 2
layer/layer_store_windows.go

@@ -6,6 +6,6 @@ import (
 	"github.com/docker/distribution"
 	"github.com/docker/distribution"
 )
 )
 
 
-func (ls *layerStore) RegisterWithDescriptor(ts io.Reader, parent ChainID, descriptor distribution.Descriptor) (Layer, error) {
-	return ls.registerWithDescriptor(ts, parent, descriptor)
+func (ls *layerStore) RegisterWithDescriptor(ts io.Reader, parent ChainID, platform Platform, descriptor distribution.Descriptor) (Layer, error) {
+	return ls.registerWithDescriptor(ts, parent, platform, descriptor)
 }
 }

+ 9 - 9
layer/layer_test.go

@@ -71,7 +71,7 @@ func newTestStore(t *testing.T) (Store, string, func()) {
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	ls, err := NewStoreFromGraphDriver(fms, graph)
+	ls, err := NewStoreFromGraphDriver(fms, graph, runtime.GOOS)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -106,7 +106,7 @@ func createLayer(ls Store, parent ChainID, layerFunc layerInit) (Layer, error) {
 	}
 	}
 	defer ts.Close()
 	defer ts.Close()
 
 
-	layer, err := ls.Register(ts, parent)
+	layer, err := ls.Register(ts, parent, Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -404,7 +404,7 @@ func TestStoreRestore(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	ls2, err := NewStoreFromGraphDriver(ls.(*layerStore).store, ls.(*layerStore).driver)
+	ls2, err := NewStoreFromGraphDriver(ls.(*layerStore).store, ls.(*layerStore).driver, runtime.GOOS)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -499,7 +499,7 @@ func TestTarStreamStability(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer1, err := ls.Register(bytes.NewReader(tar1), "")
+	layer1, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -518,7 +518,7 @@ func TestTarStreamStability(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID())
+	layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID(), Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -686,12 +686,12 @@ func TestRegisterExistingLayer(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID())
+	layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID())
+	layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -726,12 +726,12 @@ func TestTarStreamVerification(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer1, err := ls.Register(bytes.NewReader(tar1), "")
+	layer1, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer2, err := ls.Register(bytes.NewReader(tar2), "")
+	layer2, err := ls.Register(bytes.NewReader(tar2), "", Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}

+ 6 - 6
layer/migration_test.go

@@ -94,7 +94,7 @@ func TestLayerMigration(t *testing.T) {
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	ls, err := NewStoreFromGraphDriver(fms, graph)
+	ls, err := NewStoreFromGraphDriver(fms, graph, runtime.GOOS)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -110,14 +110,14 @@ func TestLayerMigration(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer1b, err := ls.Register(bytes.NewReader(tar1), "")
+	layer1b, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
 	assertReferences(t, layer1a, layer1b)
 	assertReferences(t, layer1a, layer1b)
 	// Attempt register, should be same
 	// Attempt register, should be same
-	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID())
+	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -222,7 +222,7 @@ func TestLayerMigrationNoTarsplit(t *testing.T) {
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	ls, err := NewStoreFromGraphDriver(fms, graph)
+	ls, err := NewStoreFromGraphDriver(fms, graph, runtime.GOOS)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -238,7 +238,7 @@ func TestLayerMigrationNoTarsplit(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer1b, err := ls.Register(bytes.NewReader(tar1), "")
+	layer1b, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -246,7 +246,7 @@ func TestLayerMigrationNoTarsplit(t *testing.T) {
 	assertReferences(t, layer1a, layer1b)
 	assertReferences(t, layer1a, layer1b)
 
 
 	// Attempt register, should be same
 	// Attempt register, should be same
-	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID())
+	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), Platform(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}

+ 4 - 0
layer/ro_layer.go

@@ -16,6 +16,7 @@ type roLayer struct {
 	size       int64
 	size       int64
 	layerStore *layerStore
 	layerStore *layerStore
 	descriptor distribution.Descriptor
 	descriptor distribution.Descriptor
+	platform   Platform
 
 
 	referenceCount int
 	referenceCount int
 	references     map[Layer]struct{}
 	references     map[Layer]struct{}
@@ -142,6 +143,9 @@ func storeLayer(tx MetadataTransaction, layer *roLayer) error {
 			return err
 			return err
 		}
 		}
 	}
 	}
+	if err := tx.SetPlatform(layer.platform); err != nil {
+		return err
+	}
 
 
 	return nil
 	return nil
 }
 }

+ 7 - 0
layer/ro_layer_unix.go

@@ -0,0 +1,7 @@
+// +build !windows
+
+package layer
+
+func (rl *roLayer) Platform() Platform {
+	return ""
+}

+ 7 - 0
layer/ro_layer_windows.go

@@ -7,3 +7,10 @@ var _ distribution.Describable = &roLayer{}
 func (rl *roLayer) Descriptor() distribution.Descriptor {
 func (rl *roLayer) Descriptor() distribution.Descriptor {
 	return rl.descriptor
 	return rl.descriptor
 }
 }
+
+func (rl *roLayer) Platform() Platform {
+	if rl.platform == "" {
+		return "windows"
+	}
+	return rl.platform
+}

+ 101 - 6
libcontainerd/client_windows.go

@@ -1,6 +1,7 @@
 package libcontainerd
 package libcontainerd
 
 
 import (
 import (
+	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
@@ -96,8 +97,17 @@ const defaultOwner = "docker"
 func (clnt *client) Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
 func (clnt *client) Create(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
 	clnt.lock(containerID)
 	clnt.lock(containerID)
 	defer clnt.unlock(containerID)
 	defer clnt.unlock(containerID)
-	logrus.Debugln("libcontainerd: client.Create() with spec", spec)
+	if b, err := json.Marshal(spec); err == nil {
+		logrus.Debugln("libcontainerd: client.Create() with spec", string(b))
+	}
+	osName := spec.Platform.OS
+	if osName == "windows" {
+		return clnt.createWindows(containerID, checkpoint, checkpointDir, spec, attachStdio, options...)
+	}
+	return clnt.createLinux(containerID, checkpoint, checkpointDir, spec, attachStdio, options...)
+}
 
 
+func (clnt *client) createWindows(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
 	configuration := &hcsshim.ContainerConfig{
 	configuration := &hcsshim.ContainerConfig{
 		SystemType: "Container",
 		SystemType: "Container",
 		Name:       containerID,
 		Name:       containerID,
@@ -265,17 +275,100 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir
 	// Call start, and if it fails, delete the container from our
 	// Call start, and if it fails, delete the container from our
 	// internal structure, start will keep HCS in sync by deleting the
 	// internal structure, start will keep HCS in sync by deleting the
 	// container there.
 	// container there.
-	logrus.Debugf("libcontainerd: Create() id=%s, Calling start()", containerID)
+	logrus.Debugf("libcontainerd: createWindows() id=%s, Calling start()", containerID)
 	if err := container.start(attachStdio); err != nil {
 	if err := container.start(attachStdio); err != nil {
 		clnt.deleteContainer(containerID)
 		clnt.deleteContainer(containerID)
 		return err
 		return err
 	}
 	}
 
 
-	logrus.Debugf("libcontainerd: Create() id=%s completed successfully", containerID)
+	logrus.Debugf("libcontainerd: createWindows() id=%s completed successfully", containerID)
 	return nil
 	return nil
 
 
 }
 }
 
 
+func (clnt *client) createLinux(containerID string, checkpoint string, checkpointDir string, spec specs.Spec, attachStdio StdioCallback, options ...CreateOption) error {
+	logrus.Debugf("libcontainerd: createLinux(): containerId %s ", containerID)
+
+	// TODO @jhowardmsft LCOW Support: This needs to be configurable, not hard-coded.
+	// However, good-enough for the LCOW bring-up.
+	configuration := &hcsshim.ContainerConfig{
+		HvPartition:                 true,
+		Name:                        containerID,
+		SystemType:                  "container",
+		ContainerType:               "linux",
+		TerminateOnLastHandleClosed: true,
+		HvRuntime: &hcsshim.HvRuntime{
+			ImagePath: `c:\program files\lcow`,
+		},
+	}
+
+	var layerOpt *LayerOption
+	for _, option := range options {
+		if l, ok := option.(*LayerOption); ok {
+			layerOpt = l
+		}
+	}
+
+	// We must have a layer option with at least one path
+	if layerOpt == nil || layerOpt.LayerPaths == nil {
+		return fmt.Errorf("no layer option or paths were supplied to the runtime")
+	}
+
+	// LayerFolderPath (writeable layer) + Layers (Guid + path)
+	configuration.LayerFolderPath = layerOpt.LayerFolderPath
+	for _, layerPath := range layerOpt.LayerPaths {
+		_, filename := filepath.Split(layerPath)
+		g, err := hcsshim.NameToGuid(filename)
+		if err != nil {
+			return err
+		}
+		configuration.Layers = append(configuration.Layers, hcsshim.Layer{
+			ID:   g.ToString(),
+			Path: filepath.Join(layerPath, "layer.vhd"),
+		})
+	}
+
+	hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
+	if err != nil {
+		return err
+	}
+
+	// Construct a container object for calling start on it.
+	container := &container{
+		containerCommon: containerCommon{
+			process: process{
+				processCommon: processCommon{
+					containerID:  containerID,
+					client:       clnt,
+					friendlyName: InitFriendlyName,
+				},
+			},
+			processes: make(map[string]*process),
+		},
+		ociSpec:      spec,
+		hcsContainer: hcsContainer,
+	}
+
+	container.options = options
+	for _, option := range options {
+		if err := option.Apply(container); err != nil {
+			logrus.Errorf("libcontainerd: createLinux() %v", err)
+		}
+	}
+
+	// Call start, and if it fails, delete the container from our
+	// internal structure, start will keep HCS in sync by deleting the
+	// container there.
+	logrus.Debugf("libcontainerd: createLinux() id=%s, Calling start()", containerID)
+	if err := container.start(attachStdio); err != nil {
+		clnt.deleteContainer(containerID)
+		return err
+	}
+
+	logrus.Debugf("libcontainerd: createLinux() id=%s completed successfully", containerID)
+	return nil
+}
+
 // AddProcess is the handler for adding a process to an already running
 // AddProcess is the handler for adding a process to an already running
 // container. It's called through docker exec. It returns the system pid of the
 // container. It's called through docker exec. It returns the system pid of the
 // exec'd process.
 // exec'd process.
@@ -292,13 +385,15 @@ func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendly
 	// create stdin, even if it's not used - it will be closed shortly. Stderr
 	// create stdin, even if it's not used - it will be closed shortly. Stderr
 	// is only created if it we're not -t.
 	// is only created if it we're not -t.
 	createProcessParms := hcsshim.ProcessConfig{
 	createProcessParms := hcsshim.ProcessConfig{
-		EmulateConsole:   procToAdd.Terminal,
 		CreateStdInPipe:  true,
 		CreateStdInPipe:  true,
 		CreateStdOutPipe: true,
 		CreateStdOutPipe: true,
 		CreateStdErrPipe: !procToAdd.Terminal,
 		CreateStdErrPipe: !procToAdd.Terminal,
 	}
 	}
-	createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height)
-	createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width)
+	if procToAdd.Terminal {
+		createProcessParms.EmulateConsole = true
+		createProcessParms.ConsoleSize[0] = uint(procToAdd.ConsoleSize.Height)
+		createProcessParms.ConsoleSize[1] = uint(procToAdd.ConsoleSize.Width)
+	}
 
 
 	// Take working directory from the process to add if it is defined,
 	// Take working directory from the process to add if it is defined,
 	// otherwise take from the first process.
 	// otherwise take from the first process.

+ 20 - 5
libcontainerd/container_windows.go

@@ -1,6 +1,7 @@
 package libcontainerd
 package libcontainerd
 
 
 import (
 import (
+	"encoding/json"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
@@ -10,6 +11,7 @@ import (
 
 
 	"github.com/Microsoft/hcsshim"
 	"github.com/Microsoft/hcsshim"
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/system"
 	"github.com/opencontainers/runtime-spec/specs-go"
 	"github.com/opencontainers/runtime-spec/specs-go"
 )
 )
 
 
@@ -83,6 +85,16 @@ func (ctr *container) start(attachStdio StdioCallback) error {
 	createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
 	createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
 	createProcessParms.User = ctr.ociSpec.Process.User.Username
 	createProcessParms.User = ctr.ociSpec.Process.User.Username
 
 
+	// LCOW requires the raw OCI spec passed through HCS and onwards to GCS for the utility VM.
+	if system.LCOWSupported() && ctr.ociSpec.Platform.OS == "linux" {
+		ociBuf, err := json.Marshal(ctr.ociSpec)
+		if err != nil {
+			return err
+		}
+		ociRaw := json.RawMessage(ociBuf)
+		createProcessParms.OCISpecification = &ociRaw
+	}
+
 	// Start the command running in the container.
 	// Start the command running in the container.
 	newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms)
 	newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms)
 	if err != nil {
 	if err != nil {
@@ -228,11 +240,14 @@ func (ctr *container) waitExit(process *process, isFirstProcessToStart bool) err
 	if !isFirstProcessToStart {
 	if !isFirstProcessToStart {
 		si.State = StateExitProcess
 		si.State = StateExitProcess
 	} else {
 	} else {
-		updatePending, err := ctr.hcsContainer.HasPendingUpdates()
-		if err != nil {
-			logrus.Warnf("libcontainerd: HasPendingUpdates() failed (container may have been killed): %s", err)
-		} else {
-			si.UpdatePending = updatePending
+		// Pending updates is only applicable for WCOW
+		if ctr.ociSpec.Platform.OS == "windows" {
+			updatePending, err := ctr.hcsContainer.HasPendingUpdates()
+			if err != nil {
+				logrus.Warnf("libcontainerd: HasPendingUpdates() failed (container may have been killed): %s", err)
+			} else {
+				si.UpdatePending = updatePending
+			}
 		}
 		}
 
 
 		logrus.Debugf("libcontainerd: shutting down container %s", ctr.containerID)
 		logrus.Debugf("libcontainerd: shutting down container %s", ctr.containerID)

+ 1 - 1
libcontainerd/remote_unix.go

@@ -80,7 +80,7 @@ func New(stateDir string, options ...RemoteOption) (_ Remote, err error) {
 		}
 		}
 	}
 	}
 
 
-	if err := system.MkdirAll(stateDir, 0700); err != nil {
+	if err := system.MkdirAll(stateDir, 0700, ""); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 

+ 7 - 3
migrate/v1/migratev1_test.go

@@ -94,7 +94,7 @@ func TestMigrateContainers(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	is, err := image.NewImageStore(ifs, ls)
+	is, err := image.NewImageStore(ifs, runtime.GOOS, ls)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -172,12 +172,12 @@ func TestMigrateImages(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	is, err := image.NewImageStore(ifs, ls)
+	is, err := image.NewImageStore(ifs, runtime.GOOS, ls)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	ms, err := metadata.NewFSMetadataStore(filepath.Join(tmpdir, "distribution"))
+	ms, err := metadata.NewFSMetadataStore(filepath.Join(tmpdir, "distribution"), runtime.GOOS)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -433,6 +433,10 @@ func (l *mockLayer) DiffSize() (int64, error) {
 	return 0, nil
 	return 0, nil
 }
 }
 
 
+func (l *mockLayer) Platform() layer.Platform {
+	return ""
+}
+
 func (l *mockLayer) Metadata() (map[string]string, error) {
 func (l *mockLayer) Metadata() (map[string]string, error) {
 	return nil, nil
 	return nil, nil
 }
 }

+ 48 - 3
oci/defaults_linux.go → oci/defaults.go

@@ -30,14 +30,55 @@ func defaultCapabilities() []string {
 	}
 	}
 }
 }
 
 
-// DefaultSpec returns default oci spec used by docker.
+// DefaultSpec returns the default spec used by docker for the current Platform
 func DefaultSpec() specs.Spec {
 func DefaultSpec() specs.Spec {
-	s := specs.Spec{
+	return DefaultOSSpec(runtime.GOOS)
+}
+
+// DefaultOSSpec returns the spec for a given OS
+func DefaultOSSpec(osName string) specs.Spec {
+	if osName == "windows" {
+		return DefaultWindowsSpec()
+	} else if osName == "solaris" {
+		return DefaultSolarisSpec()
+	} else {
+		return DefaultLinuxSpec()
+	}
+}
+
+// DefaultWindowsSpec create a default spec for running Windows containers
+func DefaultWindowsSpec() specs.Spec {
+	return specs.Spec{
 		Version: specs.Version,
 		Version: specs.Version,
 		Platform: specs.Platform{
 		Platform: specs.Platform{
 			OS:   runtime.GOOS,
 			OS:   runtime.GOOS,
 			Arch: runtime.GOARCH,
 			Arch: runtime.GOARCH,
 		},
 		},
+		Windows: &specs.Windows{},
+	}
+}
+
+// DefaultSolarisSpec create a default spec for running Solaris containers
+func DefaultSolarisSpec() specs.Spec {
+	s := specs.Spec{
+		Version: "0.6.0",
+		Platform: specs.Platform{
+			OS:   "SunOS",
+			Arch: runtime.GOARCH,
+		},
+	}
+	s.Solaris = &specs.Solaris{}
+	return s
+}
+
+// DefaultLinuxSpec create a default spec for running Linux containers
+func DefaultLinuxSpec() specs.Spec {
+	s := specs.Spec{
+		Version: specs.Version,
+		Platform: specs.Platform{
+			OS:   "linux",
+			Arch: runtime.GOARCH,
+		},
 	}
 	}
 	s.Mounts = []specs.Mount{
 	s.Mounts = []specs.Mount{
 		{
 		{
@@ -91,7 +132,6 @@ func DefaultSpec() specs.Spec {
 			"/proc/timer_list",
 			"/proc/timer_list",
 			"/proc/timer_stats",
 			"/proc/timer_stats",
 			"/proc/sched_debug",
 			"/proc/sched_debug",
-			"/sys/firmware",
 		},
 		},
 		ReadonlyPaths: []string{
 		ReadonlyPaths: []string{
 			"/proc/asound",
 			"/proc/asound",
@@ -172,5 +212,10 @@ func DefaultSpec() specs.Spec {
 		},
 		},
 	}
 	}
 
 
+	// For LCOW support, don't mask /sys/firmware
+	if runtime.GOOS != "windows" {
+		s.Linux.MaskedPaths = append(s.Linux.MaskedPaths, "/sys/firmware")
+	}
+
 	return s
 	return s
 }
 }

+ 0 - 20
oci/defaults_solaris.go

@@ -1,20 +0,0 @@
-package oci
-
-import (
-	"runtime"
-
-	"github.com/opencontainers/runtime-spec/specs-go"
-)
-
-// DefaultSpec returns default oci spec used by docker.
-func DefaultSpec() specs.Spec {
-	s := specs.Spec{
-		Version: "0.6.0",
-		Platform: specs.Platform{
-			OS:   "SunOS",
-			Arch: runtime.GOARCH,
-		},
-	}
-	s.Solaris = &specs.Solaris{}
-	return s
-}

Some files were not shown because too many files changed in this diff