Kaynağa Gözat

Merge pull request #43366 from corhere/finish-identitymapping-refactor

Finish refactor of UID/GID usage to a new struct
Sebastiaan van Stijn 3 yıl önce
ebeveyn
işleme
0a3336fd7d
51 değiştirilmiş dosya ile 301 ekleme ve 340 silme
  1. 14 4
      builder/builder-next/adapters/snapshot/snapshot.go
  2. 1 5
      builder/builder-next/builder.go
  3. 9 2
      builder/builder-next/executor_unix.go
  4. 1 1
      builder/builder-next/executor_windows.go
  5. 4 4
      builder/dockerfile/builder.go
  6. 1 1
      builder/dockerfile/internals.go
  7. 1 1
      builder/dockerfile/internals_linux.go
  8. 4 4
      builder/dockerfile/internals_linux_test.go
  9. 1 1
      builder/dockerfile/internals_windows.go
  10. 2 1
      contrib/docker-device-tool/device_tool.go
  11. 1 2
      daemon/archive_tarcopyoptions.go
  12. 2 2
      daemon/daemon.go
  13. 8 8
      daemon/daemon_unix.go
  14. 3 3
      daemon/daemon_windows.go
  15. 1 2
      daemon/export.go
  16. 8 20
      daemon/graphdriver/aufs/aufs.go
  17. 2 1
      daemon/graphdriver/aufs/aufs_test.go
  18. 9 15
      daemon/graphdriver/btrfs/btrfs.go
  19. 5 15
      daemon/graphdriver/devmapper/deviceset.go
  20. 2 1
      daemon/graphdriver/devmapper/devmapper_test.go
  21. 10 18
      daemon/graphdriver/devmapper/driver.go
  22. 8 9
      daemon/graphdriver/driver.go
  23. 5 8
      daemon/graphdriver/fsdiff.go
  24. 14 32
      daemon/graphdriver/fuse-overlayfs/fuseoverlayfs.go
  25. 13 27
      daemon/graphdriver/overlay/overlay.go
  26. 12 28
      daemon/graphdriver/overlay2/overlay.go
  27. 1 1
      daemon/graphdriver/plugin.go
  28. 7 9
      daemon/graphdriver/proxy.go
  29. 43 0
      daemon/graphdriver/proxy_test.go
  30. 5 2
      daemon/graphdriver/vfs/copy_unsupported.go
  31. 5 9
      daemon/graphdriver/vfs/driver.go
  32. 1 1
      daemon/graphdriver/windows/windows.go
  33. 8 18
      daemon/graphdriver/zfs/zfs.go
  34. 3 3
      daemon/oci_linux.go
  35. 0 2
      daemon/oci_linux_test.go
  36. 1 1
      integration/image/remove_unix_test.go
  37. 3 2
      integration/plugin/graphdriver/external_test.go
  38. 2 3
      layer/layer_store.go
  39. 1 1
      layer/layer_test.go
  40. 12 16
      pkg/archive/archive.go
  41. 1 1
      pkg/archive/archive_test.go
  42. 2 2
      pkg/archive/changes.go
  43. 4 2
      pkg/archive/changes_posix_test.go
  44. 2 1
      pkg/archive/changes_test.go
  45. 1 3
      pkg/archive/diff.go
  46. 2 6
      pkg/chrootarchive/archive.go
  47. 2 1
      pkg/chrootarchive/archive_test.go
  48. 5 7
      pkg/containerfs/archiver.go
  49. 28 26
      pkg/idtools/idtools.go
  50. 19 6
      pkg/idtools/idtools_unix.go
  51. 2 2
      pkg/idtools/idtools_unix_test.go

+ 14 - 4
builder/builder-next/adapters/snapshot/snapshot.go

@@ -32,7 +32,7 @@ type Opt struct {
 	GraphDriver     graphdriver.Driver
 	LayerStore      layer.Store
 	Root            string
-	IdentityMapping *idtools.IdentityMapping
+	IdentityMapping idtools.IdentityMapping
 }
 
 type graphIDRegistrar interface {
@@ -100,7 +100,12 @@ func (s *snapshotter) Name() string {
 }
 
 func (s *snapshotter) IdentityMapping() *idtools.IdentityMapping {
-	return s.opt.IdentityMapping
+	// Returning a non-nil but empty *IdentityMapping breaks BuildKit:
+	// https://github.com/moby/moby/pull/39444
+	if s.opt.IdentityMapping.Empty() {
+		return nil
+	}
+	return &s.opt.IdentityMapping
 }
 
 func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) error {
@@ -482,7 +487,7 @@ type mountable struct {
 	acquire  func() ([]mount.Mount, func() error, error)
 	release  func() error
 	refCount int
-	idmap    *idtools.IdentityMapping
+	idmap    idtools.IdentityMapping
 }
 
 func (m *mountable) Mount() ([]mount.Mount, func() error, error) {
@@ -527,5 +532,10 @@ func (m *mountable) releaseMount() error {
 }
 
 func (m *mountable) IdentityMapping() *idtools.IdentityMapping {
-	return m.idmap
+	// Returning a non-nil but empty *IdentityMapping breaks BuildKit:
+	// https://github.com/moby/moby/pull/39444
+	if m.idmap.Empty() {
+		return nil
+	}
+	return &m.idmap
 }

+ 1 - 5
builder/builder-next/builder.go

@@ -73,7 +73,7 @@ type Opt struct {
 	RegistryHosts       docker.RegistryHosts
 	BuilderConfig       config.BuilderConfig
 	Rootless            bool
-	IdentityMapping     *idtools.IdentityMapping
+	IdentityMapping     idtools.IdentityMapping
 	DNSConfig           config.DNSConfig
 	ApparmorProfile     string
 }
@@ -91,10 +91,6 @@ type Builder struct {
 func New(opt Opt) (*Builder, error) {
 	reqHandler := newReqBodyHandler(tracing.DefaultTransport)
 
-	if opt.IdentityMapping != nil && opt.IdentityMapping.Empty() {
-		opt.IdentityMapping = nil
-	}
-
 	c, err := newController(reqHandler, opt)
 	if err != nil {
 		return nil, err

+ 9 - 2
builder/builder-next/executor_unix.go

@@ -25,7 +25,7 @@ import (
 
 const networkName = "bridge"
 
-func newExecutor(root, cgroupParent string, net libnetwork.NetworkController, dnsConfig *oci.DNSConfig, rootless bool, idmap *idtools.IdentityMapping, apparmorProfile string) (executor.Executor, error) {
+func newExecutor(root, cgroupParent string, net libnetwork.NetworkController, dnsConfig *oci.DNSConfig, rootless bool, idmap idtools.IdentityMapping, apparmorProfile string) (executor.Executor, error) {
 	netRoot := filepath.Join(root, "net")
 	networkProviders := map[pb.NetMode]network.Provider{
 		pb.NetMode_UNSET: &bridgeProvider{NetworkController: net, Root: netRoot},
@@ -44,13 +44,20 @@ func newExecutor(root, cgroupParent string, net libnetwork.NetworkController, dn
 		}
 	}
 
+	// Returning a non-nil but empty *IdentityMapping breaks BuildKit:
+	// https://github.com/moby/moby/pull/39444
+	pidmap := &idmap
+	if idmap.Empty() {
+		pidmap = nil
+	}
+
 	return runcexecutor.New(runcexecutor.Opt{
 		Root:                filepath.Join(root, "executor"),
 		CommandCandidates:   []string{"runc"},
 		DefaultCgroupParent: cgroupParent,
 		Rootless:            rootless,
 		NoPivot:             os.Getenv("DOCKER_RAMDISK") != "",
-		IdentityMapping:     idmap,
+		IdentityMapping:     pidmap,
 		DNS:                 dnsConfig,
 		ApparmorProfile:     apparmorProfile,
 	}, networkProviders)

+ 1 - 1
builder/builder-next/executor_windows.go

@@ -11,7 +11,7 @@ import (
 	"github.com/moby/buildkit/executor/oci"
 )
 
-func newExecutor(_, _ string, _ libnetwork.NetworkController, _ *oci.DNSConfig, _ bool, _ *idtools.IdentityMapping, _ string) (executor.Executor, error) {
+func newExecutor(_, _ string, _ libnetwork.NetworkController, _ *oci.DNSConfig, _ bool, _ idtools.IdentityMapping, _ string) (executor.Executor, error) {
 	return &winExecutor{}, nil
 }
 

+ 4 - 4
builder/dockerfile/builder.go

@@ -46,13 +46,13 @@ const (
 
 // BuildManager is shared across all Builder objects
 type BuildManager struct {
-	idMapping *idtools.IdentityMapping
+	idMapping idtools.IdentityMapping
 	backend   builder.Backend
 	pathCache pathCache // TODO: make this persistent
 }
 
 // NewBuildManager creates a BuildManager
-func NewBuildManager(b builder.Backend, identityMapping *idtools.IdentityMapping) (*BuildManager, error) {
+func NewBuildManager(b builder.Backend, identityMapping idtools.IdentityMapping) (*BuildManager, error) {
 	bm := &BuildManager{
 		backend:   b,
 		pathCache: &syncmap.Map{},
@@ -103,7 +103,7 @@ type builderOptions struct {
 	Backend        builder.Backend
 	ProgressWriter backend.ProgressWriter
 	PathCache      pathCache
-	IDMapping      *idtools.IdentityMapping
+	IDMapping      idtools.IdentityMapping
 }
 
 // Builder is a Dockerfile builder
@@ -119,7 +119,7 @@ type Builder struct {
 	docker    builder.Backend
 	clientCtx context.Context
 
-	idMapping        *idtools.IdentityMapping
+	idMapping        idtools.IdentityMapping
 	disableCommit    bool
 	imageSources     *imageSources
 	pathCache        pathCache

+ 1 - 1
builder/dockerfile/internals.go

@@ -33,7 +33,7 @@ type Archiver interface {
 	UntarPath(src, dst string) error
 	CopyWithTar(src, dst string) error
 	CopyFileWithTar(src, dst string) error
-	IdentityMapping() *idtools.IdentityMapping
+	IdentityMapping() idtools.IdentityMapping
 }
 
 // The builder will use the following interfaces if the container fs implements

+ 1 - 1
builder/dockerfile/internals_linux.go

@@ -11,7 +11,7 @@ import (
 	"github.com/pkg/errors"
 )
 
-func parseChownFlag(builder *Builder, state *dispatchState, chown, ctrRootPath string, identityMapping *idtools.IdentityMapping) (idtools.Identity, error) {
+func parseChownFlag(builder *Builder, state *dispatchState, chown, ctrRootPath string, identityMapping idtools.IdentityMapping) (idtools.Identity, error) {
 	var userStr, grpStr string
 	parts := strings.Split(chown, ":")
 	if len(parts) > 2 {

+ 4 - 4
builder/dockerfile/internals_linux_test.go

@@ -34,8 +34,8 @@ othergrp:x:6666:
 			Size:        65536,
 		},
 	}
-	remapped := idtools.NewIDMappingsFromMaps(idMaps, idMaps)
-	unmapped := &idtools.IdentityMapping{}
+	remapped := idtools.IdentityMapping{UIDMaps: idMaps, GIDMaps: idMaps}
+	unmapped := idtools.IdentityMapping{}
 
 	contextDir, cleanup := createTestTempDir(t, "", "builder-chown-parse-test")
 	defer cleanup()
@@ -53,7 +53,7 @@ othergrp:x:6666:
 		builder   *Builder
 		name      string
 		chownStr  string
-		idMapping *idtools.IdentityMapping
+		idMapping idtools.IdentityMapping
 		state     *dispatchState
 		expected  idtools.Identity
 	}{
@@ -126,7 +126,7 @@ othergrp:x:6666:
 		builder   *Builder
 		name      string
 		chownStr  string
-		idMapping *idtools.IdentityMapping
+		idMapping idtools.IdentityMapping
 		state     *dispatchState
 		descr     string
 	}{

+ 1 - 1
builder/dockerfile/internals_windows.go

@@ -14,7 +14,7 @@ import (
 	"golang.org/x/sys/windows"
 )
 
-func parseChownFlag(builder *Builder, state *dispatchState, chown, ctrRootPath string, identityMapping *idtools.IdentityMapping) (idtools.Identity, error) {
+func parseChownFlag(builder *Builder, state *dispatchState, chown, ctrRootPath string, identityMapping idtools.IdentityMapping) (idtools.Identity, error) {
 	if builder.options.Platform == "windows" {
 		return getAccountIdentity(builder, chown, ctrRootPath, state)
 	}

+ 2 - 1
contrib/docker-device-tool/device_tool.go

@@ -14,6 +14,7 @@ import (
 
 	"github.com/docker/docker/daemon/graphdriver/devmapper"
 	"github.com/docker/docker/pkg/devicemapper"
+	"github.com/docker/docker/pkg/idtools"
 	"github.com/sirupsen/logrus"
 )
 
@@ -76,7 +77,7 @@ func main() {
 	args := flag.Args()
 
 	home := path.Join(*root, "devicemapper")
-	devices, err := devmapper.NewDeviceSet(home, false, nil, nil, nil)
+	devices, err := devmapper.NewDeviceSet(home, false, nil, idtools.IdentityMapping{})
 	if err != nil {
 		fmt.Println("Can't initialize device mapper: ", err)
 		os.Exit(1)

+ 1 - 2
daemon/archive_tarcopyoptions.go

@@ -9,7 +9,6 @@ import (
 func (daemon *Daemon) defaultTarCopyOptions(noOverwriteDirNonDir bool) *archive.TarOptions {
 	return &archive.TarOptions{
 		NoOverwriteDirNonDir: noOverwriteDirNonDir,
-		UIDMaps:              daemon.idMapping.UIDs(),
-		GIDMaps:              daemon.idMapping.GIDs(),
+		IDMap:                daemon.idMapping,
 	}
 }

+ 2 - 2
daemon/daemon.go

@@ -98,7 +98,7 @@ type Daemon struct {
 	sysInfoOnce           sync.Once
 	sysInfo               *sysinfo.SysInfo
 	shutdown              bool
-	idMapping             *idtools.IdentityMapping
+	idMapping             idtools.IdentityMapping
 	graphDriver           string        // TODO: move graphDriver field to an InfoService
 	PluginStore           *plugin.Store // TODO: remove
 	pluginManager         *plugin.Manager
@@ -1459,7 +1459,7 @@ func (daemon *Daemon) GetAttachmentStore() *network.AttachmentStore {
 }
 
 // IdentityMapping returns uid/gid mapping or a SID (in the case of Windows) for the builder
-func (daemon *Daemon) IdentityMapping() *idtools.IdentityMapping {
+func (daemon *Daemon) IdentityMapping() idtools.IdentityMapping {
 	return daemon.idMapping
 }
 

+ 8 - 8
daemon/daemon_unix.go

@@ -1062,7 +1062,7 @@ func removeDefaultBridgeInterface() {
 	}
 }
 
-func setupInitLayer(idMapping *idtools.IdentityMapping) func(containerfs.ContainerFS) error {
+func setupInitLayer(idMapping idtools.IdentityMapping) func(containerfs.ContainerFS) error {
 	return func(initPath containerfs.ContainerFS) error {
 		return initlayer.Setup(initPath, idMapping.RootPair())
 	}
@@ -1161,9 +1161,9 @@ func parseRemappedRoot(usergrp string) (string, string, error) {
 	return username, groupname, nil
 }
 
-func setupRemappedRoot(config *config.Config) (*idtools.IdentityMapping, error) {
+func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
 	if runtime.GOOS != "linux" && config.RemappedRoot != "" {
-		return nil, fmt.Errorf("User namespaces are only supported on Linux")
+		return idtools.IdentityMapping{}, fmt.Errorf("User namespaces are only supported on Linux")
 	}
 
 	// if the daemon was started with remapped root option, parse
@@ -1171,25 +1171,25 @@ func setupRemappedRoot(config *config.Config) (*idtools.IdentityMapping, error)
 	if config.RemappedRoot != "" {
 		username, groupname, err := parseRemappedRoot(config.RemappedRoot)
 		if err != nil {
-			return nil, err
+			return idtools.IdentityMapping{}, err
 		}
 		if username == "root" {
 			// Cannot setup user namespaces with a 1-to-1 mapping; "--root=0:0" is a no-op
 			// effectively
 			logrus.Warn("User namespaces: root cannot be remapped with itself; user namespaces are OFF")
-			return &idtools.IdentityMapping{}, nil
+			return idtools.IdentityMapping{}, nil
 		}
 		logrus.Infof("User namespaces: ID ranges will be mapped to subuid/subgid ranges of: %s", username)
 		// update remapped root setting now that we have resolved them to actual names
 		config.RemappedRoot = fmt.Sprintf("%s:%s", username, groupname)
 
-		mappings, err := idtools.NewIdentityMapping(username)
+		mappings, err := idtools.LoadIdentityMapping(username)
 		if err != nil {
-			return nil, errors.Wrap(err, "Can't create ID mappings")
+			return idtools.IdentityMapping{}, errors.Wrap(err, "Can't create ID mappings")
 		}
 		return mappings, nil
 	}
-	return &idtools.IdentityMapping{}, nil
+	return idtools.IdentityMapping{}, nil
 }
 
 func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools.Identity) error {

+ 3 - 3
daemon/daemon_windows.go

@@ -63,7 +63,7 @@ func (daemon *Daemon) parseSecurityOpt(container *container.Container, hostConfi
 	return nil
 }
 
-func setupInitLayer(idMapping *idtools.IdentityMapping) func(containerfs.ContainerFS) error {
+func setupInitLayer(idMapping idtools.IdentityMapping) func(containerfs.ContainerFS) error {
 	return nil
 }
 
@@ -437,8 +437,8 @@ func recursiveUnmount(_ string) error {
 	return nil
 }
 
-func setupRemappedRoot(config *config.Config) (*idtools.IdentityMapping, error) {
-	return &idtools.IdentityMapping{}, nil
+func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
+	return idtools.IdentityMapping{}, nil
 }
 
 func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools.Identity) error {

+ 1 - 2
daemon/export.go

@@ -63,8 +63,7 @@ func (daemon *Daemon) containerExport(container *container.Container) (arch io.R
 
 	archv, err := archivePath(basefs, basefs.Path(), &archive.TarOptions{
 		Compression: archive.Uncompressed,
-		UIDMaps:     daemon.idMapping.UIDs(),
-		GIDMaps:     daemon.idMapping.GIDs(),
+		IDMap:       daemon.idMapping,
 	}, basefs.Path())
 	if err != nil {
 		rwlayer.Unmount()

+ 8 - 20
daemon/graphdriver/aufs/aufs.go

@@ -71,8 +71,7 @@ func init() {
 // Driver contains information about the filesystem mounted.
 type Driver struct {
 	root          string
-	uidMaps       []idtools.IDMap
-	gidMaps       []idtools.IDMap
+	idMap         idtools.IdentityMapping
 	ctr           *graphdriver.RefCounter
 	pathCacheLock sync.Mutex
 	pathCache     map[string]string
@@ -83,7 +82,7 @@ type Driver struct {
 
 // Init returns a new AUFS driver.
 // An error is returned if AUFS is not supported.
-func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
+func Init(root string, options []string, idMap idtools.IdentityMapping) (graphdriver.Driver, error) {
 	// Try to load the aufs kernel module
 	if err := supportsAufs(); err != nil {
 		logger.Error(err)
@@ -121,21 +120,16 @@ func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 
 	a := &Driver{
 		root:      root,
-		uidMaps:   uidMaps,
-		gidMaps:   gidMaps,
+		idMap:     idMap,
 		pathCache: make(map[string]string),
 		ctr:       graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicAufs)),
 		locker:    locker.New(),
 	}
 
 	currentID := idtools.CurrentIdentity()
-	_, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
-	if err != nil {
-		return nil, err
-	}
 	dirID := idtools.Identity{
 		UID: currentID.UID,
-		GID: rootGID,
+		GID: a.idMap.RootPair().GID,
 	}
 
 	// Create the root aufs driver dir
@@ -170,7 +164,7 @@ func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 		}
 	}
 
-	a.naiveDiff = graphdriver.NewNaiveDiffDriver(a, uidMaps, gidMaps)
+	a.naiveDiff = graphdriver.NewNaiveDiffDriver(a, a.idMap)
 	return a, nil
 }
 
@@ -285,15 +279,11 @@ func (a *Driver) createDirsFor(id string) error {
 		"diff",
 	}
 
-	rootUID, rootGID, err := idtools.GetRootUIDGID(a.uidMaps, a.gidMaps)
-	if err != nil {
-		return err
-	}
 	// Directory permission is 0755.
 	// The path of directories are <aufs_root_path>/mnt/<image_id>
 	// and <aufs_root_path>/diff/<image_id>
 	for _, p := range paths {
-		if err := idtools.MkdirAllAndChown(path.Join(a.rootPath(), p, id), 0755, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
+		if err := idtools.MkdirAllAndChown(path.Join(a.rootPath(), p, id), 0755, a.idMap.RootPair()); err != nil {
 			return err
 		}
 	}
@@ -439,8 +429,7 @@ func (a *Driver) Diff(id, parent string) (io.ReadCloser, error) {
 	return archive.TarWithOptions(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
 		Compression:     archive.Uncompressed,
 		ExcludePatterns: []string{archive.WhiteoutMetaPrefix + "*", "!" + archive.WhiteoutOpaqueDir},
-		UIDMaps:         a.uidMaps,
-		GIDMaps:         a.gidMaps,
+		IDMap:           a.idMap,
 	})
 }
 
@@ -461,8 +450,7 @@ func (a *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
 
 func (a *Driver) applyDiff(id string, diff io.Reader) error {
 	return chrootarchive.UntarUncompressed(diff, path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
-		UIDMaps: a.uidMaps,
-		GIDMaps: a.gidMaps,
+		IDMap: a.idMap,
 	})
 }
 

+ 2 - 1
daemon/graphdriver/aufs/aufs_test.go

@@ -15,6 +15,7 @@ import (
 
 	"github.com/docker/docker/daemon/graphdriver"
 	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/reexec"
 	"github.com/docker/docker/pkg/stringid"
 	"gotest.tools/v3/assert"
@@ -31,7 +32,7 @@ func init() {
 }
 
 func testInit(dir string, t testing.TB) graphdriver.Driver {
-	d, err := Init(dir, nil, nil, nil)
+	d, err := Init(dir, nil, idtools.IdentityMapping{})
 	if err != nil {
 		if err == graphdriver.ErrNotSupported {
 			t.Skip(err)

+ 9 - 15
daemon/graphdriver/btrfs/btrfs.go

@@ -50,7 +50,7 @@ type btrfsOptions struct {
 
 // Init returns a new BTRFS driver.
 // An error is returned if BTRFS is not supported.
-func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
+func Init(home string, options []string, idMap idtools.IdentityMapping) (graphdriver.Driver, error) {
 
 	// Perform feature detection on /var/lib/docker/btrfs if it's an existing directory.
 	// This covers situations where /var/lib/docker/btrfs is a mount, and on a different
@@ -70,11 +70,10 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 		return nil, graphdriver.ErrPrerequisites
 	}
 
-	remappedRoot := idtools.NewIDMappingsFromMaps(uidMaps, gidMaps)
 	currentID := idtools.CurrentIdentity()
 	dirID := idtools.Identity{
 		UID: currentID.UID,
-		GID: remappedRoot.RootPair().GID,
+		GID: idMap.RootPair().GID,
 	}
 
 	if err := idtools.MkdirAllAndChown(home, 0710, dirID); err != nil {
@@ -97,8 +96,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 
 	driver := &Driver{
 		home:    home,
-		uidMaps: uidMaps,
-		gidMaps: gidMaps,
+		idMap:   idMap,
 		options: opt,
 	}
 
@@ -108,7 +106,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 		}
 	}
 
-	return graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps), nil
+	return graphdriver.NewNaiveDiffDriver(driver, driver.idMap), nil
 }
 
 func parseOptions(opt []string) (btrfsOptions, bool, error) {
@@ -139,8 +137,7 @@ func parseOptions(opt []string) (btrfsOptions, bool, error) {
 type Driver struct {
 	// root of the file system
 	home         string
-	uidMaps      []idtools.IDMap
-	gidMaps      []idtools.IDMap
+	idMap        idtools.IdentityMapping
 	options      btrfsOptions
 	quotaEnabled bool
 	once         sync.Once
@@ -490,15 +487,12 @@ func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts
 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
 	quotas := path.Join(d.home, "quotas")
 	subvolumes := path.Join(d.home, "subvolumes")
-	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
-	if err != nil {
-		return err
-	}
+	root := d.idMap.RootPair()
 
 	currentID := idtools.CurrentIdentity()
 	dirID := idtools.Identity{
 		UID: currentID.UID,
-		GID: rootGID,
+		GID: root.GID,
 	}
 
 	if err := idtools.MkdirAllAndChown(subvolumes, 0710, dirID); err != nil {
@@ -546,8 +540,8 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
 
 	// if we have a remapped root (user namespaces enabled), change the created snapshot
 	// dir ownership to match
-	if rootUID != 0 || rootGID != 0 {
-		if err := os.Chown(path.Join(subvolumes, id), rootUID, rootGID); err != nil {
+	if root.UID != 0 || root.GID != 0 {
+		if err := root.Chown(path.Join(subvolumes, id)); err != nil {
 			return err
 		}
 	}

+ 5 - 15
daemon/graphdriver/devmapper/deviceset.go

@@ -117,8 +117,7 @@ type DeviceSet struct {
 	BaseDeviceFilesystem  string // save filesystem of base device
 	nrDeletedDevices      uint   // number of deleted devices
 	deletionWorkerTicker  *time.Ticker
-	uidMaps               []idtools.IDMap
-	gidMaps               []idtools.IDMap
+	idMap                 idtools.IdentityMapping
 	minFreeSpacePercent   uint32 // min free space percentage in thinpool
 	xfsNospaceRetries     string // max retries when xfs receives ENOSPC
 	lvmSetupConfig        directLVMConfig
@@ -264,11 +263,7 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
 	dirname := devices.loopbackDir()
 	filename := path.Join(dirname, name)
 
-	uid, gid, err := idtools.GetRootUIDGID(devices.uidMaps, devices.gidMaps)
-	if err != nil {
-		return "", err
-	}
-	if err := idtools.MkdirAllAndChown(dirname, 0700, idtools.Identity{UID: uid, GID: gid}); err != nil {
+	if err := idtools.MkdirAllAndChown(dirname, 0700, devices.idMap.RootPair()); err != nil {
 		return "", err
 	}
 
@@ -1694,11 +1689,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
 
 	// create the root dir of the devmapper driver ownership to match this
 	// daemon's remapped root uid/gid so containers can start properly
-	uid, gid, err := idtools.GetRootUIDGID(devices.uidMaps, devices.gidMaps)
-	if err != nil {
-		return err
-	}
-	if err := idtools.MkdirAndChown(devices.root, 0700, idtools.Identity{UID: uid, GID: gid}); err != nil {
+	if err := idtools.MkdirAndChown(devices.root, 0700, devices.idMap.RootPair()); err != nil {
 		return err
 	}
 	if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil {
@@ -2622,7 +2613,7 @@ func (devices *DeviceSet) exportDeviceMetadata(hash string) (*deviceMetadata, er
 }
 
 // NewDeviceSet creates the device set based on the options provided.
-func NewDeviceSet(root string, doInit bool, options []string, uidMaps, gidMaps []idtools.IDMap) (*DeviceSet, error) {
+func NewDeviceSet(root string, doInit bool, options []string, idMap idtools.IdentityMapping) (*DeviceSet, error) {
 	devicemapper.SetDevDir("/dev")
 
 	devices := &DeviceSet{
@@ -2636,8 +2627,7 @@ func NewDeviceSet(root string, doInit bool, options []string, uidMaps, gidMaps [
 		thinpBlockSize:        defaultThinpBlockSize,
 		deviceIDMap:           make([]byte, deviceIDMapSz),
 		deletionWorkerTicker:  time.NewTicker(time.Second * 30),
-		uidMaps:               uidMaps,
-		gidMaps:               gidMaps,
+		idMap:                 idMap,
 		minFreeSpacePercent:   defaultMinFreeSpacePercent,
 	}
 

+ 2 - 1
daemon/graphdriver/devmapper/devmapper_test.go

@@ -13,6 +13,7 @@ import (
 
 	"github.com/docker/docker/daemon/graphdriver"
 	"github.com/docker/docker/daemon/graphdriver/graphtest"
+	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/parsers/kernel"
 	"golang.org/x/sys/unix"
 )
@@ -115,7 +116,7 @@ func testChangeLoopBackSize(t *testing.T, delta, expectDataSize, expectMetaDataS
 	d, err := Init(driver.home, []string{
 		fmt.Sprintf("dm.loopdatasize=%d", defaultDataLoopbackSize+delta),
 		fmt.Sprintf("dm.loopmetadatasize=%d", defaultMetaDataLoopbackSize+delta),
-	}, nil, nil)
+	}, idtools.IdentityMapping{})
 	if err != nil {
 		t.Fatalf("error creating devicemapper driver: %v", err)
 	}

+ 10 - 18
daemon/graphdriver/devmapper/driver.go

@@ -27,16 +27,14 @@ func init() {
 // Driver contains the device set mounted and the home directory
 type Driver struct {
 	*DeviceSet
-	home    string
-	uidMaps []idtools.IDMap
-	gidMaps []idtools.IDMap
-	ctr     *graphdriver.RefCounter
-	locker  *locker.Locker
+	home   string
+	ctr    *graphdriver.RefCounter
+	locker *locker.Locker
 }
 
 // Init creates a driver with the given home and the set of options.
-func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
-	deviceSet, err := NewDeviceSet(home, true, options, uidMaps, gidMaps)
+func Init(home string, options []string, idMap idtools.IdentityMapping) (graphdriver.Driver, error) {
+	deviceSet, err := NewDeviceSet(home, true, options, idMap)
 	if err != nil {
 		return nil, err
 	}
@@ -44,13 +42,11 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 	d := &Driver{
 		DeviceSet: deviceSet,
 		home:      home,
-		uidMaps:   uidMaps,
-		gidMaps:   gidMaps,
 		ctr:       graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()),
 		locker:    locker.New(),
 	}
 
-	return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
+	return graphdriver.NewNaiveDiffDriver(d, d.idMap), nil
 }
 
 func (d *Driver) String() string {
@@ -188,18 +184,14 @@ func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) {
 		return containerfs.NewLocalContainerFS(rootFs), nil
 	}
 
-	uid, gid, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
-	if err != nil {
-		d.ctr.Decrement(mp)
-		return nil, err
-	}
+	root := d.idMap.RootPair()
 
 	// Create the target directories if they don't exist
-	if err := idtools.MkdirAllAndChown(path.Join(d.home, "mnt"), 0755, idtools.Identity{UID: uid, GID: gid}); err != nil {
+	if err := idtools.MkdirAllAndChown(path.Join(d.home, "mnt"), 0755, root); err != nil {
 		d.ctr.Decrement(mp)
 		return nil, err
 	}
-	if err := idtools.MkdirAndChown(mp, 0755, idtools.Identity{UID: uid, GID: gid}); err != nil && !os.IsExist(err) {
+	if err := idtools.MkdirAndChown(mp, 0755, root); err != nil && !os.IsExist(err) {
 		d.ctr.Decrement(mp)
 		return nil, err
 	}
@@ -210,7 +202,7 @@ func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) {
 		return nil, err
 	}
 
-	if err := idtools.MkdirAllAndChown(rootFs, 0755, idtools.Identity{UID: uid, GID: gid}); err != nil {
+	if err := idtools.MkdirAllAndChown(rootFs, 0755, root); err != nil {
 		d.ctr.Decrement(mp)
 		d.DeviceSet.UnmountDevice(id, mp)
 		return nil, err

+ 8 - 9
daemon/graphdriver/driver.go

@@ -37,7 +37,7 @@ type CreateOpts struct {
 }
 
 // InitFunc initializes the storage driver.
-type InitFunc func(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error)
+type InitFunc func(root string, options []string, idMap idtools.IdentityMapping) (Driver, error)
 
 // ProtoDriver defines the basic capabilities of a driver.
 // This interface exists solely to be a minimum set of methods
@@ -162,7 +162,7 @@ func Register(name string, initFunc InitFunc) error {
 // GetDriver initializes and returns the registered driver
 func GetDriver(name string, pg plugingetter.PluginGetter, config Options) (Driver, error) {
 	if initFunc, exists := drivers[name]; exists {
-		return initFunc(filepath.Join(config.Root, name), config.DriverOptions, config.UIDMaps, config.GIDMaps)
+		return initFunc(filepath.Join(config.Root, name), config.DriverOptions, config.IDMap)
 	}
 
 	pluginDriver, err := lookupPlugin(name, pg, config)
@@ -174,9 +174,9 @@ func GetDriver(name string, pg plugingetter.PluginGetter, config Options) (Drive
 }
 
 // getBuiltinDriver initializes and returns the registered driver, but does not try to load from plugins
-func getBuiltinDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {
+func getBuiltinDriver(name, home string, options []string, idMap idtools.IdentityMapping) (Driver, error) {
 	if initFunc, exists := drivers[name]; exists {
-		return initFunc(filepath.Join(home, name), options, uidMaps, gidMaps)
+		return initFunc(filepath.Join(home, name), options, idMap)
 	}
 	logrus.Errorf("Failed to built-in GetDriver graph %s %s", name, home)
 	return nil, ErrNotSupported
@@ -186,8 +186,7 @@ func getBuiltinDriver(name, home string, options []string, uidMaps, gidMaps []id
 type Options struct {
 	Root                string
 	DriverOptions       []string
-	UIDMaps             []idtools.IDMap
-	GIDMaps             []idtools.IDMap
+	IDMap               idtools.IdentityMapping
 	ExperimentalEnabled bool
 }
 
@@ -211,7 +210,7 @@ func New(name string, pg plugingetter.PluginGetter, config Options) (Driver, err
 		if _, prior := driversMap[name]; prior {
 			// of the state found from prior drivers, check in order of our priority
 			// which we would prefer
-			driver, err := getBuiltinDriver(name, config.Root, config.DriverOptions, config.UIDMaps, config.GIDMaps)
+			driver, err := getBuiltinDriver(name, config.Root, config.DriverOptions, config.IDMap)
 			if err != nil {
 				// unlike below, we will return error here, because there is prior
 				// state, and now it is no longer supported/prereq/compatible, so
@@ -240,7 +239,7 @@ func New(name string, pg plugingetter.PluginGetter, config Options) (Driver, err
 
 	// Check for priority drivers first
 	for _, name := range list {
-		driver, err := getBuiltinDriver(name, config.Root, config.DriverOptions, config.UIDMaps, config.GIDMaps)
+		driver, err := getBuiltinDriver(name, config.Root, config.DriverOptions, config.IDMap)
 		if err != nil {
 			if IsDriverNotSupported(err) {
 				continue
@@ -258,7 +257,7 @@ func New(name string, pg plugingetter.PluginGetter, config Options) (Driver, err
 			// can be selected through configuration.
 			continue
 		}
-		driver, err := initFunc(filepath.Join(config.Root, name), config.DriverOptions, config.UIDMaps, config.GIDMaps)
+		driver, err := initFunc(filepath.Join(config.Root, name), config.DriverOptions, config.IDMap)
 		if err != nil {
 			if IsDriverNotSupported(err) {
 				continue

+ 5 - 8
daemon/graphdriver/fsdiff.go

@@ -24,8 +24,7 @@ var (
 // Notably, the AUFS driver doesn't need to be wrapped like this.
 type NaiveDiffDriver struct {
 	ProtoDriver
-	uidMaps []idtools.IDMap
-	gidMaps []idtools.IDMap
+	idMap idtools.IdentityMapping
 }
 
 // NewNaiveDiffDriver returns a fully functional driver that wraps the
@@ -35,10 +34,9 @@ type NaiveDiffDriver struct {
 //     Changes(id, parent string) ([]archive.Change, error)
 //     ApplyDiff(id, parent string, diff archive.Reader) (size int64, err error)
 //     DiffSize(id, parent string) (size int64, err error)
-func NewNaiveDiffDriver(driver ProtoDriver, uidMaps, gidMaps []idtools.IDMap) Driver {
+func NewNaiveDiffDriver(driver ProtoDriver, idMap idtools.IdentityMapping) Driver {
 	return &NaiveDiffDriver{ProtoDriver: driver,
-		uidMaps: uidMaps,
-		gidMaps: gidMaps}
+		idMap: idMap}
 }
 
 // Diff produces an archive of the changes between the specified
@@ -84,7 +82,7 @@ func (gdw *NaiveDiffDriver) Diff(id, parent string) (arch io.ReadCloser, err err
 		return nil, err
 	}
 
-	archive, err := archive.ExportChanges(layerFs, changes, gdw.uidMaps, gdw.gidMaps)
+	archive, err := archive.ExportChanges(layerFs, changes, gdw.idMap)
 	if err != nil {
 		return nil, err
 	}
@@ -142,8 +140,7 @@ func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, diff io.Reader) (size i
 	defer driver.Put(id)
 
 	layerFs := layerRootFs.Path()
-	options := &archive.TarOptions{UIDMaps: gdw.uidMaps,
-		GIDMaps: gdw.gidMaps}
+	options := &archive.TarOptions{IDMap: gdw.idMap}
 	start := time.Now().UTC()
 	logrus.WithField("id", id).Debug("Start untar layer")
 	if size, err = ApplyUncompressedLayer(layerFs, diff, options); err != nil {

+ 14 - 32
daemon/graphdriver/fuse-overlayfs/fuseoverlayfs.go

@@ -60,8 +60,7 @@ const (
 // mounts that are created using this driver.
 type Driver struct {
 	home      string
-	uidMaps   []idtools.IDMap
-	gidMaps   []idtools.IDMap
+	idMap     idtools.IdentityMapping
 	ctr       *graphdriver.RefCounter
 	naiveDiff graphdriver.DiffDriver
 	locker    *locker.Locker
@@ -78,7 +77,7 @@ func init() {
 // Init returns the naive diff driver for fuse-overlayfs.
 // If fuse-overlayfs is not supported on the host, the error
 // graphdriver.ErrNotSupported is returned.
-func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
+func Init(home string, options []string, idMap idtools.IdentityMapping) (graphdriver.Driver, error) {
 	if _, err := exec.LookPath(binary); err != nil {
 		logger.Error(err)
 		return nil, graphdriver.ErrNotSupported
@@ -87,11 +86,10 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 		return nil, graphdriver.ErrNotSupported
 	}
 
-	remappedRoot := idtools.NewIDMappingsFromMaps(uidMaps, gidMaps)
 	currentID := idtools.CurrentIdentity()
 	dirID := idtools.Identity{
 		UID: currentID.UID,
-		GID: remappedRoot.RootPair().GID,
+		GID: idMap.RootPair().GID,
 	}
 
 	if err := idtools.MkdirAllAndChown(home, 0710, dirID); err != nil {
@@ -102,14 +100,13 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 	}
 
 	d := &Driver{
-		home:    home,
-		uidMaps: uidMaps,
-		gidMaps: gidMaps,
-		ctr:     graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicFUSE)),
-		locker:  locker.New(),
+		home:   home,
+		idMap:  idMap,
+		ctr:    graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicFUSE)),
+		locker: locker.New(),
 	}
 
-	d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps)
+	d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, idMap)
 
 	return d, nil
 }
@@ -175,22 +172,12 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
 
 func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) {
 	dir := d.dir(id)
+	root := d.idMap.RootPair()
 
-	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
-	if err != nil {
-		return err
-	}
-	root := idtools.Identity{UID: rootUID, GID: rootGID}
-
-	dirID := idtools.Identity{
-		UID: rootUID,
-		GID: rootGID,
-	}
-
-	if err := idtools.MkdirAllAndChown(path.Dir(dir), 0710, dirID); err != nil {
+	if err := idtools.MkdirAllAndChown(path.Dir(dir), 0710, root); err != nil {
 		return err
 	}
-	if err := idtools.MkdirAndChown(dir, 0710, dirID); err != nil {
+	if err := idtools.MkdirAndChown(dir, 0710, root); err != nil {
 		return err
 	}
 
@@ -224,7 +211,7 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr
 		return nil
 	}
 
-	if err := idtools.MkdirAndChown(path.Join(dir, workDirName), 0710, dirID); err != nil {
+	if err := idtools.MkdirAndChown(path.Join(dir, workDirName), 0710, root); err != nil {
 		return err
 	}
 
@@ -377,11 +364,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
 	mountData := label.FormatMountLabel(opts, mountLabel)
 	mountTarget := mergedDir
 
-	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
-	if err != nil {
-		return nil, err
-	}
-	if err := idtools.MkdirAndChown(mergedDir, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
+	if err := idtools.MkdirAndChown(mergedDir, 0700, d.idMap.RootPair()); err != nil {
 		return nil, err
 	}
 
@@ -477,8 +460,7 @@ func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64
 	logger.Debugf("Applying tar in %s", applyDir)
 	// Overlay doesn't need the parent id to apply the diff
 	if err := untar(diff, applyDir, &archive.TarOptions{
-		UIDMaps: d.uidMaps,
-		GIDMaps: d.gidMaps,
+		IDMap: d.idMap,
 		// Use AUFS whiteout format: https://github.com/containers/storage/blob/39a8d5ed9843844eafb5d2ba6e6a7510e0126f40/drivers/overlay/overlay.go#L1084-L1089
 		WhiteoutFormat: archive.AUFSWhiteoutFormat,
 		InUserNS:       userns.RunningInUserNS(),

+ 13 - 27
daemon/graphdriver/overlay/overlay.go

@@ -50,9 +50,9 @@ type naiveDiffDriverWithApply struct {
 }
 
 // NaiveDiffDriverWithApply returns a NaiveDiff driver with custom ApplyDiff.
-func NaiveDiffDriverWithApply(driver ApplyDiffProtoDriver, uidMaps, gidMaps []idtools.IDMap) graphdriver.Driver {
+func NaiveDiffDriverWithApply(driver ApplyDiffProtoDriver, idMap idtools.IdentityMapping) graphdriver.Driver {
 	return &naiveDiffDriverWithApply{
-		Driver:    graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps),
+		Driver:    graphdriver.NewNaiveDiffDriver(driver, idMap),
 		applyDiff: driver,
 	}
 }
@@ -99,8 +99,7 @@ type overlayOptions struct{}
 // Driver contains information about the home directory and the list of active mounts that are created using this driver.
 type Driver struct {
 	home          string
-	uidMaps       []idtools.IDMap
-	gidMaps       []idtools.IDMap
+	idMap         idtools.IdentityMapping
 	ctr           *graphdriver.RefCounter
 	supportsDType bool
 	locker        *locker.Locker
@@ -115,7 +114,7 @@ func init() {
 // graphdriver.ErrNotSupported is returned.
 // If an overlay filesystem is not supported over an existing filesystem then
 // error graphdriver.ErrIncompatibleFS is returned.
-func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
+func Init(home string, options []string, idMap idtools.IdentityMapping) (graphdriver.Driver, error) {
 	_, err := parseOptions(options)
 	if err != nil {
 		return nil, err
@@ -156,13 +155,9 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 	}
 
 	currentID := idtools.CurrentIdentity()
-	_, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
-	if err != nil {
-		return nil, err
-	}
 	dirID := idtools.Identity{
 		UID: currentID.UID,
-		GID: rootGID,
+		GID: idMap.RootPair().GID,
 	}
 
 	// Create the driver home dir
@@ -171,14 +166,13 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 	}
 	d := &Driver{
 		home:          home,
-		uidMaps:       uidMaps,
-		gidMaps:       gidMaps,
+		idMap:         idMap,
 		ctr:           graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
 		supportsDType: supportsDType,
 		locker:        locker.New(),
 	}
 
-	return NaiveDiffDriverWithApply(d, uidMaps, gidMaps), nil
+	return NaiveDiffDriverWithApply(d, d.idMap), nil
 }
 
 func parseOptions(options []string) (*overlayOptions, error) {
@@ -262,17 +256,12 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
 	}
 
 	dir := d.dir(id)
-
-	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
-	if err != nil {
-		return err
-	}
-	root := idtools.Identity{UID: rootUID, GID: rootGID}
+	root := d.idMap.RootPair()
 
 	currentID := idtools.CurrentIdentity()
 	dirID := idtools.Identity{
 		UID: currentID.UID,
-		GID: rootGID,
+		GID: root.GID,
 	}
 	if err := idtools.MkdirAndChown(dir, 0710, dirID); err != nil {
 		return err
@@ -388,11 +377,8 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, err erro
 	if err != nil {
 		return nil, err
 	}
-	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
-	if err != nil {
-		return nil, err
-	}
-	if err := idtools.MkdirAndChown(mergedDir, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
+	root := d.idMap.RootPair()
+	if err := idtools.MkdirAndChown(mergedDir, 0700, root); err != nil {
 		return nil, err
 	}
 	var (
@@ -406,7 +392,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, err erro
 	}
 	// chown "workdir/work" to the remapped root UID/GID. Overlay fs inside a
 	// user namespace requires this to move a directory from lower to upper.
-	if err := os.Chown(path.Join(workDir, "work"), rootUID, rootGID); err != nil {
+	if err := root.Chown(path.Join(workDir, "work")); err != nil {
 		return nil, err
 	}
 	return containerfs.NewLocalContainerFS(mergedDir), nil
@@ -483,7 +469,7 @@ func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64
 		return 0, err
 	}
 
-	options := &archive.TarOptions{UIDMaps: d.uidMaps, GIDMaps: d.gidMaps}
+	options := &archive.TarOptions{IDMap: d.idMap}
 	if size, err = graphdriver.ApplyUncompressedLayer(tmpRootDir, diff, options); err != nil {
 		return 0, err
 	}

+ 12 - 28
daemon/graphdriver/overlay2/overlay.go

@@ -93,8 +93,7 @@ type overlayOptions struct {
 // mounts that are created using this driver.
 type Driver struct {
 	home          string
-	uidMaps       []idtools.IDMap
-	gidMaps       []idtools.IDMap
+	idMap         idtools.IdentityMapping
 	ctr           *graphdriver.RefCounter
 	quotaCtl      *quota.Control
 	options       overlayOptions
@@ -124,7 +123,7 @@ func init() {
 // graphdriver.ErrNotSupported is returned.
 // If an overlay filesystem is not supported over an existing filesystem then
 // the error graphdriver.ErrIncompatibleFS is returned.
-func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
+func Init(home string, options []string, idMap idtools.IdentityMapping) (graphdriver.Driver, error) {
 	opts, err := parseOptions(options)
 	if err != nil {
 		return nil, err
@@ -164,15 +163,10 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 		logger.Warn(overlayutils.ErrDTypeNotSupported("overlay2", backingFs))
 	}
 
-	_, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
-	if err != nil {
-		return nil, err
-	}
-
 	cur := idtools.CurrentIdentity()
 	dirID := idtools.Identity{
 		UID: cur.UID,
-		GID: rootGID,
+		GID: idMap.RootPair().GID,
 	}
 	if err := idtools.MkdirAllAndChown(home, 0710, dirID); err != nil {
 		return nil, err
@@ -183,15 +177,14 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 
 	d := &Driver{
 		home:          home,
-		uidMaps:       uidMaps,
-		gidMaps:       gidMaps,
+		idMap:         idMap,
 		ctr:           graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
 		supportsDType: supportsDType,
 		locker:        locker.New(),
 		options:       *opts,
 	}
 
-	d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps)
+	d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, idMap)
 
 	if backingFs == "xfs" {
 		// Try to enable project quota support over xfs.
@@ -351,14 +344,10 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
 func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) {
 	dir := d.dir(id)
 
-	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
-	if err != nil {
-		return err
-	}
-	root := idtools.Identity{UID: rootUID, GID: rootGID}
+	root := d.idMap.RootPair()
 	dirID := idtools.Identity{
 		UID: idtools.CurrentIdentity().UID,
-		GID: rootGID,
+		GID: root.GID,
 	}
 
 	if err := idtools.MkdirAllAndChown(path.Dir(dir), 0710, dirID); err != nil {
@@ -580,11 +569,8 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
 	mount := unix.Mount
 	mountTarget := mergedDir
 
-	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
-	if err != nil {
-		return nil, err
-	}
-	if err := idtools.MkdirAndChown(mergedDir, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
+	root := d.idMap.RootPair()
+	if err := idtools.MkdirAndChown(mergedDir, 0700, root); err != nil {
 		return nil, err
 	}
 
@@ -618,7 +604,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
 	if !readonly {
 		// chown "workdir/work" to the remapped root UID/GID. Overlay fs inside a
 		// user namespace requires this to move a directory from lower to upper.
-		if err := os.Chown(path.Join(workDir, workDirName), rootUID, rootGID); err != nil {
+		if err := root.Chown(path.Join(workDir, workDirName)); err != nil {
 			return nil, err
 		}
 	}
@@ -702,8 +688,7 @@ func (d *Driver) ApplyDiff(id string, parent string, diff io.Reader) (size int64
 	logger.Debugf("Applying tar in %s", applyDir)
 	// Overlay doesn't need the parent id to apply the diff
 	if err := untar(diff, applyDir, &archive.TarOptions{
-		UIDMaps:        d.uidMaps,
-		GIDMaps:        d.gidMaps,
+		IDMap:          d.idMap,
 		WhiteoutFormat: archive.OverlayWhiteoutFormat,
 	}); err != nil {
 		return 0, err
@@ -740,8 +725,7 @@ func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
 	logger.Debugf("Tar with options on %s", diffPath)
 	return archive.TarWithOptions(diffPath, &archive.TarOptions{
 		Compression:    archive.Uncompressed,
-		UIDMaps:        d.uidMaps,
-		GIDMaps:        d.gidMaps,
+		IDMap:          d.idMap,
 		WhiteoutFormat: archive.OverlayWhiteoutFormat,
 	})
 }

+ 1 - 1
daemon/graphdriver/plugin.go

@@ -51,5 +51,5 @@ func newPluginDriver(name string, pl plugingetter.CompatPlugin, config Options)
 		return nil, errdefs.System(errors.Errorf("got unknown plugin type %T", pt))
 	}
 
-	return proxy, proxy.Init(filepath.Join(home, name), config.DriverOptions, config.UIDMaps, config.GIDMaps)
+	return proxy, proxy.Init(filepath.Join(home, name), config.DriverOptions, config.IDMap)
 }

+ 7 - 9
daemon/graphdriver/proxy.go

@@ -38,13 +38,12 @@ type graphDriverResponse struct {
 }
 
 type graphDriverInitRequest struct {
-	Home    string
-	Opts    []string        `json:"Opts"`
-	UIDMaps []idtools.IDMap `json:"UIDMaps"`
-	GIDMaps []idtools.IDMap `json:"GIDMaps"`
+	Home string
+	Opts []string `json:"Opts"`
+	idtools.IdentityMapping
 }
 
-func (d *graphDriverProxy) Init(home string, opts []string, uidMaps, gidMaps []idtools.IDMap) error {
+func (d *graphDriverProxy) Init(home string, opts []string, idMap idtools.IdentityMapping) error {
 	if !d.p.IsV1() {
 		if cp, ok := d.p.(plugingetter.CountedPlugin); ok {
 			// always acquire here, it will be cleaned up on daemon shutdown
@@ -52,10 +51,9 @@ func (d *graphDriverProxy) Init(home string, opts []string, uidMaps, gidMaps []i
 		}
 	}
 	args := &graphDriverInitRequest{
-		Home:    home,
-		Opts:    opts,
-		UIDMaps: uidMaps,
-		GIDMaps: gidMaps,
+		Home:            home,
+		Opts:            opts,
+		IdentityMapping: idMap,
 	}
 	var ret graphDriverResponse
 	if err := d.client.Call("GraphDriver.Init", args, &ret); err != nil {

+ 43 - 0
daemon/graphdriver/proxy_test.go

@@ -0,0 +1,43 @@
+package graphdriver // import "github.com/docker/docker/daemon/graphdriver"
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/docker/docker/pkg/idtools"
+	"gotest.tools/v3/assert"
+)
+
+func TestGraphDriverInitRequestIsCompatible(t *testing.T) {
+	// Graph driver plugins may unmarshal into this version of the init
+	// request struct. Verify that the serialization of
+	// graphDriverInitRequest is fully backwards compatible.
+
+	type graphDriverInitRequestV1 struct {
+		Home    string
+		Opts    []string        `json:"Opts"`
+		UIDMaps []idtools.IDMap `json:"UIDMaps"`
+		GIDMaps []idtools.IDMap `json:"GIDMaps"`
+	}
+
+	args := graphDriverInitRequest{
+		Home: "homedir",
+		Opts: []string{"option1", "option2"},
+		IdentityMapping: idtools.IdentityMapping{
+			UIDMaps: []idtools.IDMap{{ContainerID: 123, HostID: 456, Size: 42}},
+			GIDMaps: []idtools.IDMap{{ContainerID: 789, HostID: 1011, Size: 16}},
+		},
+	}
+	v, err := json.Marshal(&args)
+	assert.NilError(t, err)
+
+	var got graphDriverInitRequestV1
+	assert.NilError(t, json.Unmarshal(v, &got))
+	want := graphDriverInitRequestV1{
+		Home:    args.Home,
+		Opts:    args.Opts,
+		UIDMaps: args.UIDMaps,
+		GIDMaps: args.GIDMaps,
+	}
+	assert.DeepEqual(t, got, want)
+}

+ 5 - 2
daemon/graphdriver/vfs/copy_unsupported.go

@@ -3,8 +3,11 @@
 
 package vfs // import "github.com/docker/docker/daemon/graphdriver/vfs"
 
-import "github.com/docker/docker/pkg/chrootarchive"
+import (
+	"github.com/docker/docker/pkg/chrootarchive"
+	"github.com/docker/docker/pkg/idtools"
+)
 
 func dirCopy(srcDir, dstDir string) error {
-	return chrootarchive.NewArchiver(nil).CopyWithTar(srcDir, dstDir)
+	return chrootarchive.NewArchiver(idtools.IdentityMapping{}).CopyWithTar(srcDir, dstDir)
 }

+ 5 - 9
daemon/graphdriver/vfs/driver.go

@@ -27,23 +27,19 @@ func init() {
 
 // Init returns a new VFS driver.
 // This sets the home directory for the driver and returns NaiveDiffDriver.
-func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
+func Init(home string, options []string, idMap idtools.IdentityMapping) (graphdriver.Driver, error) {
 	d := &Driver{
 		home:      home,
-		idMapping: idtools.NewIDMappingsFromMaps(uidMaps, gidMaps),
+		idMapping: idMap,
 	}
 
 	if err := d.parseOptions(options); err != nil {
 		return nil, err
 	}
-	_, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
-	if err != nil {
-		return nil, err
-	}
 
 	dirID := idtools.Identity{
 		UID: idtools.CurrentIdentity().UID,
-		GID: rootGID,
+		GID: d.idMapping.RootPair().GID,
 	}
 	if err := idtools.MkdirAllAndChown(home, 0710, dirID); err != nil {
 		return nil, err
@@ -55,7 +51,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 		return nil, quota.ErrQuotaNotSupported
 	}
 
-	return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
+	return graphdriver.NewNaiveDiffDriver(d, d.idMapping), nil
 }
 
 // Driver holds information about the driver, home directory of the driver.
@@ -65,7 +61,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 type Driver struct {
 	driverQuota
 	home      string
-	idMapping *idtools.IdentityMapping
+	idMapping idtools.IdentityMapping
 }
 
 func (d *Driver) String() string {

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

@@ -97,7 +97,7 @@ type Driver struct {
 }
 
 // InitFilter returns a new Windows storage filter driver.
-func InitFilter(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
+func InitFilter(home string, options []string, _ idtools.IdentityMapping) (graphdriver.Driver, error) {
 	logrus.Debugf("WindowsGraphDriver InitFilter at %s", home)
 
 	fsType, err := getFileSystemType(string(home[0]))

+ 8 - 18
daemon/graphdriver/zfs/zfs.go

@@ -47,7 +47,7 @@ func (*Logger) Log(cmd []string) {
 // Init returns a new ZFS driver.
 // It takes base mount path and an array of options which are represented as key value pairs.
 // Each option is in the for key=value. 'zfs.fsname' is expected to be a valid key in the options.
-func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
+func Init(base string, opt []string, idMap idtools.IdentityMapping) (graphdriver.Driver, error) {
 	var err error
 
 	logger := logrus.WithField("storage-driver", "zfs")
@@ -106,14 +106,9 @@ func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdri
 		return nil, fmt.Errorf("BUG: zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName)
 	}
 
-	_, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
-	if err != nil {
-		return nil, err
-	}
-
 	dirID := idtools.Identity{
 		UID: idtools.CurrentIdentity().UID,
-		GID: rootGID,
+		GID: idMap.RootPair().GID,
 	}
 	if err := idtools.MkdirAllAndChown(base, 0710, dirID); err != nil {
 		return nil, fmt.Errorf("Failed to create '%s': %v", base, err)
@@ -123,12 +118,11 @@ func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdri
 		dataset:          rootDataset,
 		options:          options,
 		filesystemsCache: filesystemsCache,
-		uidMaps:          uidMaps,
-		gidMaps:          gidMaps,
+		idMap:            idMap,
 		ctr:              graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()),
 		locker:           locker.New(),
 	}
-	return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
+	return graphdriver.NewNaiveDiffDriver(d, idMap), nil
 }
 
 func parseOptions(opt []string) (zfsOptions, error) {
@@ -181,8 +175,7 @@ type Driver struct {
 	options          zfsOptions
 	sync.Mutex       // protects filesystem cache against concurrent access
 	filesystemsCache map[string]bool
-	uidMaps          []idtools.IDMap
-	gidMaps          []idtools.IDMap
+	idMap            idtools.IdentityMapping
 	ctr              *graphdriver.RefCounter
 	locker           *locker.Locker
 }
@@ -395,12 +388,9 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
 	options := label.FormatMountLabel("", mountLabel)
 	logrus.WithField("storage-driver", "zfs").Debugf(`mount("%s", "%s", "%s")`, filesystem, mountpoint, options)
 
-	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
-	if err != nil {
-		return nil, err
-	}
+	root := d.idMap.RootPair()
 	// Create the target directories if they don't exist
-	if err := idtools.MkdirAllAndChown(mountpoint, 0755, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil {
+	if err := idtools.MkdirAllAndChown(mountpoint, 0755, root); err != nil {
 		return nil, err
 	}
 
@@ -410,7 +400,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
 
 	// this could be our first mount after creation of the filesystem, and the root dir may still have root
 	// permissions instead of the remapped root uid:gid (if user namespaces are enabled):
-	if err := os.Chown(mountpoint, rootUID, rootGID); err != nil {
+	if err := root.Chown(mountpoint); err != nil {
 		return nil, fmt.Errorf("error modifying zfs mountpoint (%s) directory ownership: %v", mountpoint, err)
 	}
 

+ 3 - 3
daemon/oci_linux.go

@@ -227,13 +227,13 @@ func WithNamespaces(daemon *Daemon, c *container.Container) coci.SpecOpts {
 		userNS := false
 		// user
 		if c.HostConfig.UsernsMode.IsPrivate() {
-			uidMap := daemon.idMapping.UIDs()
+			uidMap := daemon.idMapping.UIDMaps
 			if uidMap != nil {
 				userNS = true
 				ns := specs.LinuxNamespace{Type: "user"}
 				setNamespace(s, ns)
 				s.Linux.UIDMappings = specMapping(uidMap)
-				s.Linux.GIDMappings = specMapping(daemon.idMapping.GIDs())
+				s.Linux.GIDMappings = specMapping(daemon.idMapping.GIDMaps)
 			}
 		}
 		// network
@@ -689,7 +689,7 @@ func WithMounts(daemon *Daemon, c *container.Container) coci.SpecOpts {
 
 		// TODO: until a kernel/mount solution exists for handling remount in a user namespace,
 		// we must clear the readonly flag for the cgroups mount (@mrunalp concurs)
-		if uidMap := daemon.idMapping.UIDs(); uidMap != nil || c.HostConfig.Privileged {
+		if uidMap := daemon.idMapping.UIDMaps; uidMap != nil || c.HostConfig.Privileged {
 			for i, m := range s.Mounts {
 				if m.Type == "cgroup" {
 					clearReadOnly(&s.Mounts[i])

+ 0 - 2
daemon/oci_linux_test.go

@@ -11,7 +11,6 @@ import (
 	"github.com/docker/docker/daemon/network"
 	"github.com/docker/docker/libnetwork"
 	"github.com/docker/docker/pkg/containerfs"
-	"github.com/docker/docker/pkg/idtools"
 	"gotest.tools/v3/assert"
 	is "gotest.tools/v3/assert/cmp"
 	"gotest.tools/v3/skip"
@@ -31,7 +30,6 @@ func setupFakeDaemon(t *testing.T, c *container.Container) *Daemon {
 	d := &Daemon{
 		// some empty structs to avoid getting a panic
 		// caused by a null pointer dereference
-		idMapping:     &idtools.IdentityMapping{},
 		configStore:   &config.Config{},
 		linkIndex:     newLinkIndex(),
 		netController: netController,

+ 1 - 1
integration/image/remove_unix_test.go

@@ -49,7 +49,7 @@ func TestRemoveImageGarbageCollector(t *testing.T) {
 		MetadataStorePathTemplate: filepath.Join(d.RootDir(), "image", "%s", "layerdb"),
 		GraphDriver:               d.StorageDriver(),
 		GraphDriverOptions:        nil,
-		IDMapping:                 &idtools.IdentityMapping{},
+		IDMapping:                 idtools.IdentityMapping{},
 		PluginGetter:              nil,
 		ExperimentalEnabled:       false,
 	})

+ 3 - 2
integration/plugin/graphdriver/external_test.go

@@ -19,6 +19,7 @@ import (
 	"github.com/docker/docker/integration/internal/container"
 	"github.com/docker/docker/integration/internal/requirement"
 	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/plugins"
 	"github.com/docker/docker/testutil/daemon"
 	"gotest.tools/v3/assert"
@@ -146,9 +147,9 @@ func setupPlugin(t *testing.T, ec map[string]*graphEventsCounter, ext string, mu
 
 	base, err := os.MkdirTemp("", name)
 	assert.NilError(t, err)
-	vfsProto, err := vfs.Init(base, []string{}, nil, nil)
+	vfsProto, err := vfs.Init(base, []string{}, idtools.IdentityMapping{})
 	assert.NilError(t, err, "error initializing graph driver")
-	driver := graphdriver.NewNaiveDiffDriver(vfsProto, nil, nil)
+	driver := graphdriver.NewNaiveDiffDriver(vfsProto, idtools.IdentityMapping{})
 
 	ec[ext] = &graphEventsCounter{}
 	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {

+ 2 - 3
layer/layer_store.go

@@ -48,7 +48,7 @@ type StoreOptions struct {
 	MetadataStorePathTemplate string
 	GraphDriver               string
 	GraphDriverOptions        []string
-	IDMapping                 *idtools.IdentityMapping
+	IDMapping                 idtools.IdentityMapping
 	PluginGetter              plugingetter.PluginGetter
 	ExperimentalEnabled       bool
 }
@@ -58,8 +58,7 @@ func NewStoreFromOptions(options StoreOptions) (Store, error) {
 	driver, err := graphdriver.New(options.GraphDriver, options.PluginGetter, graphdriver.Options{
 		Root:                options.Root,
 		DriverOptions:       options.GraphDriverOptions,
-		UIDMaps:             options.IDMapping.UIDs(),
-		GIDMaps:             options.IDMapping.GIDs(),
+		IDMap:               options.IDMapping,
 		ExperimentalEnabled: options.ExperimentalEnabled,
 	})
 	if err != nil {

+ 1 - 1
layer/layer_test.go

@@ -41,7 +41,7 @@ func newVFSGraphDriver(td string) (graphdriver.Driver, error) {
 		},
 	}
 
-	options := graphdriver.Options{Root: td, UIDMaps: uidMap, GIDMaps: gidMap}
+	options := graphdriver.Options{Root: td, IDMap: idtools.IdentityMapping{UIDMaps: uidMap, GIDMaps: gidMap}}
 	return graphdriver.GetDriver("vfs", nil, options)
 }
 

+ 12 - 16
pkg/archive/archive.go

@@ -40,8 +40,7 @@ type (
 		ExcludePatterns  []string
 		Compression      Compression
 		NoLchown         bool
-		UIDMaps          []idtools.IDMap
-		GIDMaps          []idtools.IDMap
+		IDMap            idtools.IdentityMapping
 		ChownOpts        *idtools.Identity
 		IncludeSourceDir bool
 		// WhiteoutFormat is the expected on disk format for whiteout files.
@@ -63,12 +62,12 @@ type (
 // mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations.
 type Archiver struct {
 	Untar     func(io.Reader, string, *TarOptions) error
-	IDMapping *idtools.IdentityMapping
+	IDMapping idtools.IdentityMapping
 }
 
 // NewDefaultArchiver returns a new Archiver without any IdentityMapping
 func NewDefaultArchiver() *Archiver {
-	return &Archiver{Untar: Untar, IDMapping: &idtools.IdentityMapping{}}
+	return &Archiver{Untar: Untar}
 }
 
 // breakoutError is used to differentiate errors related to breaking out
@@ -534,7 +533,7 @@ type tarAppender struct {
 
 	// for hardlink mapping
 	SeenFiles       map[uint64]string
-	IdentityMapping *idtools.IdentityMapping
+	IdentityMapping idtools.IdentityMapping
 	ChownOpts       *idtools.Identity
 
 	// For packing and unpacking whiteout files in the
@@ -544,7 +543,7 @@ type tarAppender struct {
 	WhiteoutConverter tarWhiteoutConverter
 }
 
-func newTarAppender(idMapping *idtools.IdentityMapping, writer io.Writer, chownOpts *idtools.Identity) *tarAppender {
+func newTarAppender(idMapping idtools.IdentityMapping, writer io.Writer, chownOpts *idtools.Identity) *tarAppender {
 	return &tarAppender{
 		SeenFiles:       make(map[uint64]string),
 		TarWriter:       tar.NewWriter(writer),
@@ -860,7 +859,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
 
 	go func() {
 		ta := newTarAppender(
-			idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps),
+			options.IDMap,
 			compressWriter,
 			options.ChownOpts,
 		)
@@ -1044,8 +1043,7 @@ func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) err
 	defer pools.BufioReader32KPool.Put(trBuf)
 
 	var dirs []*tar.Header
-	idMapping := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)
-	rootIDs := idMapping.RootPair()
+	rootIDs := options.IDMap.RootPair()
 	whiteoutConverter, err := getWhiteoutConverter(options.WhiteoutFormat, options.InUserNS)
 	if err != nil {
 		return err
@@ -1134,7 +1132,7 @@ loop:
 		}
 		trBuf.Reset(tr)
 
-		if err := remapIDs(idMapping, hdr); err != nil {
+		if err := remapIDs(options.IDMap, hdr); err != nil {
 			return err
 		}
 
@@ -1221,8 +1219,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error {
 	}
 	defer archive.Close()
 	options := &TarOptions{
-		UIDMaps: archiver.IDMapping.UIDs(),
-		GIDMaps: archiver.IDMapping.GIDs(),
+		IDMap: archiver.IDMapping,
 	}
 	return archiver.Untar(archive, dst, options)
 }
@@ -1235,8 +1232,7 @@ func (archiver *Archiver) UntarPath(src, dst string) error {
 	}
 	defer archive.Close()
 	options := &TarOptions{
-		UIDMaps: archiver.IDMapping.UIDs(),
-		GIDMaps: archiver.IDMapping.GIDs(),
+		IDMap: archiver.IDMapping,
 	}
 	return archiver.Untar(archive, dst, options)
 }
@@ -1343,11 +1339,11 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
 }
 
 // IdentityMapping returns the IdentityMapping of the archiver.
-func (archiver *Archiver) IdentityMapping() *idtools.IdentityMapping {
+func (archiver *Archiver) IdentityMapping() idtools.IdentityMapping {
 	return archiver.IDMapping
 }
 
-func remapIDs(idMapping *idtools.IdentityMapping, hdr *tar.Header) error {
+func remapIDs(idMapping idtools.IdentityMapping, hdr *tar.Header) error {
 	ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid})
 	hdr.Uid, hdr.Gid = ids.UID, ids.GID
 	return err

+ 1 - 1
pkg/archive/archive_test.go

@@ -791,7 +791,7 @@ func TestTarWithOptionsChownOptsAlwaysOverridesIdPair(t *testing.T) {
 		expectedGID int
 	}{
 		{&TarOptions{ChownOpts: &idtools.Identity{UID: 1337, GID: 42}}, 1337, 42},
-		{&TarOptions{ChownOpts: &idtools.Identity{UID: 100001, GID: 100001}, UIDMaps: idMaps, GIDMaps: idMaps}, 100001, 100001},
+		{&TarOptions{ChownOpts: &idtools.Identity{UID: 100001, GID: 100001}, IDMap: idtools.IdentityMapping{UIDMaps: idMaps, GIDMaps: idMaps}}, 100001, 100001},
 		{&TarOptions{ChownOpts: &idtools.Identity{UID: 0, GID: 0}, NoLchown: false}, 0, 0},
 		{&TarOptions{ChownOpts: &idtools.Identity{UID: 1, GID: 1}, NoLchown: true}, 1, 1},
 		{&TarOptions{ChownOpts: &idtools.Identity{UID: 1000, GID: 1000}, NoLchown: true}, 1000, 1000},

+ 2 - 2
pkg/archive/changes.go

@@ -394,10 +394,10 @@ func ChangesSize(newDir string, changes []Change) int64 {
 }
 
 // ExportChanges produces an Archive from the provided changes, relative to dir.
-func ExportChanges(dir string, changes []Change, uidMaps, gidMaps []idtools.IDMap) (io.ReadCloser, error) {
+func ExportChanges(dir string, changes []Change, idMap idtools.IdentityMapping) (io.ReadCloser, error) {
 	reader, writer := io.Pipe()
 	go func() {
-		ta := newTarAppender(idtools.NewIDMappingsFromMaps(uidMaps, gidMaps), writer, nil)
+		ta := newTarAppender(idMap, writer, nil)
 
 		// this buffer is needed for the duration of this piped stream
 		defer pools.BufioWriter32KPool.Put(ta.Buffer)

+ 4 - 2
pkg/archive/changes_posix_test.go

@@ -8,6 +8,8 @@ import (
 	"path"
 	"sort"
 	"testing"
+
+	"github.com/docker/docker/pkg/idtools"
 )
 
 func TestHardLinkOrder(t *testing.T) {
@@ -60,7 +62,7 @@ func TestHardLinkOrder(t *testing.T) {
 	sort.Sort(changesByPath(changes))
 
 	// ExportChanges
-	ar, err := ExportChanges(dest, changes, nil, nil)
+	ar, err := ExportChanges(dest, changes, idtools.IdentityMapping{})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -72,7 +74,7 @@ func TestHardLinkOrder(t *testing.T) {
 	// reverse sort
 	sort.Sort(sort.Reverse(changesByPath(changes)))
 	// ExportChanges
-	arRev, err := ExportChanges(dest, changes, nil, nil)
+	arRev, err := ExportChanges(dest, changes, idtools.IdentityMapping{})
 	if err != nil {
 		t.Fatal(err)
 	}

+ 2 - 1
pkg/archive/changes_test.go

@@ -14,6 +14,7 @@ import (
 	"time"
 
 	"github.com/Microsoft/hcsshim/osversion"
+	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/parsers/kernel"
 	"github.com/docker/docker/pkg/system"
 	"gotest.tools/v3/assert"
@@ -444,7 +445,7 @@ func TestApplyLayer(t *testing.T) {
 	changes, err := ChangesDirs(dst, src)
 	assert.NilError(t, err)
 
-	layer, err := ExportChanges(dst, changes, nil, nil)
+	layer, err := ExportChanges(dst, changes, idtools.IdentityMapping{})
 	assert.NilError(t, err)
 
 	layerCopy, err := NewTempArchive(layer, "")

+ 1 - 3
pkg/archive/diff.go

@@ -9,7 +9,6 @@ import (
 	"runtime"
 	"strings"
 
-	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/pools"
 	"github.com/docker/docker/pkg/system"
 	"github.com/sirupsen/logrus"
@@ -32,7 +31,6 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
 	if options.ExcludePatterns == nil {
 		options.ExcludePatterns = []string{}
 	}
-	idMapping := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)
 
 	aufsTempdir := ""
 	aufsHardlinks := make(map[string]*tar.Header)
@@ -192,7 +190,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
 				srcData = tmpFile
 			}
 
-			if err := remapIDs(idMapping, srcHdr); err != nil {
+			if err := remapIDs(options.IDMap, srcHdr); err != nil {
 				return 0, err
 			}
 

+ 2 - 6
pkg/chrootarchive/archive.go

@@ -20,10 +20,7 @@ func init() {
 }
 
 // NewArchiver returns a new Archiver which uses chrootarchive.Untar
-func NewArchiver(idMapping *idtools.IdentityMapping) *archive.Archiver {
-	if idMapping == nil {
-		idMapping = &idtools.IdentityMapping{}
-	}
+func NewArchiver(idMapping idtools.IdentityMapping) *archive.Archiver {
 	return &archive.Archiver{
 		Untar:     Untar,
 		IDMapping: idMapping,
@@ -76,8 +73,7 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions
 	// If dest is inside a root then directory is created within chroot by extractor.
 	// This case is only currently used by cp.
 	if dest == root {
-		idMapping := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)
-		rootIDs := idMapping.RootPair()
+		rootIDs := options.IDMap.RootPair()
 
 		dest = filepath.Clean(dest)
 		if _, err := os.Stat(dest); os.IsNotExist(err) {

+ 2 - 1
pkg/chrootarchive/archive_test.go

@@ -13,6 +13,7 @@ import (
 	"time"
 
 	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/reexec"
 	"github.com/docker/docker/pkg/system"
 	"gotest.tools/v3/skip"
@@ -22,7 +23,7 @@ func init() {
 	reexec.Init()
 }
 
-var chrootArchiver = NewArchiver(nil)
+var chrootArchiver = NewArchiver(idtools.IdentityMapping{})
 
 func TarUntar(src, dst string) error {
 	return chrootArchiver.TarUntar(src, dst)

+ 5 - 7
pkg/containerfs/archiver.go

@@ -26,7 +26,7 @@ type Archiver struct {
 	DstDriver Driver
 	Tar       TarFunc
 	Untar     UntarFunc
-	IDMapping *idtools.IdentityMapping
+	IDMapping idtools.IdentityMapping
 }
 
 // TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other.
@@ -39,8 +39,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error {
 	}
 	defer tarArchive.Close()
 	options := &archive.TarOptions{
-		UIDMaps: archiver.IDMapping.UIDs(),
-		GIDMaps: archiver.IDMapping.GIDs(),
+		IDMap: archiver.IDMapping,
 	}
 	return archiver.Untar(tarArchive, dst, options)
 }
@@ -53,8 +52,7 @@ func (archiver *Archiver) UntarPath(src, dst string) error {
 	}
 	defer tarArchive.Close()
 	options := &archive.TarOptions{
-		UIDMaps: archiver.IDMapping.UIDs(),
-		GIDMaps: archiver.IDMapping.GIDs(),
+		IDMap: archiver.IDMapping,
 	}
 	return archiver.Untar(tarArchive, dst, options)
 }
@@ -181,11 +179,11 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (retErr error) {
 }
 
 // IdentityMapping returns the IdentityMapping of the archiver.
-func (archiver *Archiver) IdentityMapping() *idtools.IdentityMapping {
+func (archiver *Archiver) IdentityMapping() idtools.IdentityMapping {
 	return archiver.IDMapping
 }
 
-func remapIDs(idMapping *idtools.IdentityMapping, hdr *tar.Header) error {
+func remapIDs(idMapping idtools.IdentityMapping, hdr *tar.Header) error {
 	ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid})
 	hdr.Uid, hdr.Gid = ids.UID, ids.GID
 	return err

+ 28 - 26
pkg/idtools/idtools.go

@@ -108,70 +108,72 @@ type Identity struct {
 	SID string
 }
 
-// IdentityMapping contains a mappings of UIDs and GIDs
-type IdentityMapping struct {
-	uids []IDMap
-	gids []IDMap
+// Chown changes the numeric uid and gid of the named file to id.UID and id.GID.
+func (id Identity) Chown(name string) error {
+	return os.Chown(name, id.UID, id.GID)
 }
 
-// NewIDMappingsFromMaps creates a new mapping from two slices
-// Deprecated: this is a temporary shim while transitioning to IDMapping
-func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IdentityMapping {
-	return &IdentityMapping{uids: uids, gids: gids}
+// IdentityMapping contains a mappings of UIDs and GIDs.
+// The zero value represents an empty mapping.
+type IdentityMapping struct {
+	UIDMaps []IDMap `json:"UIDMaps"`
+	GIDMaps []IDMap `json:"GIDMaps"`
 }
 
 // RootPair returns a uid and gid pair for the root user. The error is ignored
 // because a root user always exists, and the defaults are correct when the uid
 // and gid maps are empty.
-func (i *IdentityMapping) RootPair() Identity {
-	uid, gid, _ := GetRootUIDGID(i.uids, i.gids)
+func (i IdentityMapping) RootPair() Identity {
+	uid, gid, _ := GetRootUIDGID(i.UIDMaps, i.GIDMaps)
 	return Identity{UID: uid, GID: gid}
 }
 
 // ToHost returns the host UID and GID for the container uid, gid.
 // Remapping is only performed if the ids aren't already the remapped root ids
-func (i *IdentityMapping) ToHost(pair Identity) (Identity, error) {
+func (i IdentityMapping) ToHost(pair Identity) (Identity, error) {
 	var err error
 	target := i.RootPair()
 
 	if pair.UID != target.UID {
-		target.UID, err = toHost(pair.UID, i.uids)
+		target.UID, err = toHost(pair.UID, i.UIDMaps)
 		if err != nil {
 			return target, err
 		}
 	}
 
 	if pair.GID != target.GID {
-		target.GID, err = toHost(pair.GID, i.gids)
+		target.GID, err = toHost(pair.GID, i.GIDMaps)
 	}
 	return target, err
 }
 
 // ToContainer returns the container UID and GID for the host uid and gid
-func (i *IdentityMapping) ToContainer(pair Identity) (int, int, error) {
-	uid, err := toContainer(pair.UID, i.uids)
+func (i IdentityMapping) ToContainer(pair Identity) (int, int, error) {
+	uid, err := toContainer(pair.UID, i.UIDMaps)
 	if err != nil {
 		return -1, -1, err
 	}
-	gid, err := toContainer(pair.GID, i.gids)
+	gid, err := toContainer(pair.GID, i.GIDMaps)
 	return uid, gid, err
 }
 
 // Empty returns true if there are no id mappings
-func (i *IdentityMapping) Empty() bool {
-	return len(i.uids) == 0 && len(i.gids) == 0
+func (i IdentityMapping) Empty() bool {
+	return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0
 }
 
-// UIDs return the UID mapping
-// TODO: remove this once everything has been refactored to use pairs
-func (i *IdentityMapping) UIDs() []IDMap {
-	return i.uids
+// UIDs returns the mapping for UID.
+//
+// Deprecated: reference the UIDMaps field directly.
+func (i IdentityMapping) UIDs() []IDMap {
+	return i.UIDMaps
 }
 
-// GIDs return the UID mapping
-// TODO: remove this once everything has been refactored to use pairs
-func (i *IdentityMapping) GIDs() []IDMap {
-	return i.gids
+// GIDs returns the mapping for GID.
+//
+// Deprecated: reference the GIDMaps field directly.
+func (i IdentityMapping) GIDs() []IDMap {
+	return i.GIDMaps
 }
 
 func createIDMap(subidRanges ranges) []IDMap {

+ 19 - 6
pkg/idtools/idtools_unix.go

@@ -240,24 +240,37 @@ func setPermissions(p string, mode os.FileMode, uid, gid int, stat *system.StatT
 // NewIdentityMapping takes a requested username and
 // using the data from /etc/sub{uid,gid} ranges, creates the
 // proper uid and gid remapping ranges for that user/group pair
+//
+// Deprecated: Use LoadIdentityMapping.
 func NewIdentityMapping(name string) (*IdentityMapping, error) {
+	m, err := LoadIdentityMapping(name)
+	if err != nil {
+		return nil, err
+	}
+	return &m, err
+}
+
+// LoadIdentityMapping takes a requested username and
+// using the data from /etc/sub{uid,gid} ranges, creates the
+// proper uid and gid remapping ranges for that user/group pair
+func LoadIdentityMapping(name string) (IdentityMapping, error) {
 	usr, err := LookupUser(name)
 	if err != nil {
-		return nil, fmt.Errorf("Could not get user for username %s: %v", name, err)
+		return IdentityMapping{}, fmt.Errorf("Could not get user for username %s: %v", name, err)
 	}
 
 	subuidRanges, err := lookupSubUIDRanges(usr)
 	if err != nil {
-		return nil, err
+		return IdentityMapping{}, err
 	}
 	subgidRanges, err := lookupSubGIDRanges(usr)
 	if err != nil {
-		return nil, err
+		return IdentityMapping{}, err
 	}
 
-	return &IdentityMapping{
-		uids: subuidRanges,
-		gids: subgidRanges,
+	return IdentityMapping{
+		UIDMaps: subuidRanges,
+		GIDMaps: subgidRanges,
 	}, nil
 }
 

+ 2 - 2
pkg/idtools/idtools_unix_test.go

@@ -321,10 +321,10 @@ func TestNewIDMappings(t *testing.T) {
 	tempUser, err := user.Lookup(tempUser)
 	assert.Check(t, err)
 
-	idMapping, err := NewIdentityMapping(tempUser.Username)
+	idMapping, err := LoadIdentityMapping(tempUser.Username)
 	assert.Check(t, err)
 
-	rootUID, rootGID, err := GetRootUIDGID(idMapping.UIDs(), idMapping.GIDs())
+	rootUID, rootGID, err := GetRootUIDGID(idMapping.UIDMaps, idMapping.GIDMaps)
 	assert.Check(t, err)
 
 	dirName, err := os.MkdirTemp("", "mkdirall")