Add user namespace (mapping) support to the Docker engine

Adds support for the daemon to handle user namespace maps as a
per-daemon setting.

Support for handling uid/gid mapping is added to the builder,
archive/unarchive packages and functions, all graphdrivers (except
Windows), and the test suite is updated to handle user namespace daemon
rootgraph changes.

Docker-DCO-1.1-Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com> (github: estesp)
This commit is contained in:
Phil Estes 2015-10-08 11:51:41 -04:00
parent 9a3ab0358e
commit 442b45628e
56 changed files with 878 additions and 203 deletions

View file

@ -18,6 +18,8 @@ import (
"github.com/docker/docker/daemon/daemonbuilder"
"github.com/docker/docker/graph"
"github.com/docker/docker/graph/tags"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/progressreader"
@ -393,7 +395,13 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R
}
}()
docker := daemonbuilder.Docker{s.daemon, output, authConfigs}
uidMaps, gidMaps := s.daemon.GetUIDGIDMaps()
defaultArchiver := &archive.Archiver{
Untar: chrootarchive.Untar,
UIDMaps: uidMaps,
GIDMaps: gidMaps,
}
docker := daemonbuilder.Docker{s.daemon, output, authConfigs, defaultArchiver}
b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{context}, nil)
if err != nil {

View file

@ -30,6 +30,7 @@ type CommonConfig struct {
LogConfig runconfig.LogConfig
Mtu int
Pidfile string
RemappedRoot string
Root string
TrustKeyPath string
DefaultNetwork string

View file

@ -27,6 +27,7 @@ type Config struct {
CorsHeaders string
EnableCors bool
EnableSelinuxSupport bool
RemappedRoot string
SocketGroup string
Ulimits map[string]*ulimit.Ulimit
}
@ -77,4 +78,6 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin
cmd.BoolVar(&config.Bridge.EnableUserlandProxy, []string{"-userland-proxy"}, true, usageFn("Use userland proxy for loopback traffic"))
cmd.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, usageFn("Enable CORS headers in the remote API, this is deprecated by --api-cors-header"))
cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API"))
config.attachExperimentalFlags(cmd, usageFn)
}

View file

@ -553,7 +553,12 @@ func (container *Container) export() (archive.Archive, error) {
return nil, err
}
archive, err := archive.Tar(container.basefs, archive.Uncompressed)
uidMaps, gidMaps := container.daemon.GetUIDGIDMaps()
archive, err := archive.TarWithOptions(container.basefs, &archive.TarOptions{
Compression: archive.Uncompressed,
UIDMaps: uidMaps,
GIDMaps: gidMaps,
})
if err != nil {
container.Unmount()
return nil, err

View file

@ -20,6 +20,7 @@ import (
"github.com/docker/docker/daemon/network"
derr "github.com/docker/docker/errors"
"github.com/docker/docker/pkg/directory"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/system"
@ -302,6 +303,14 @@ func populateCommand(c *Container, env []string) error {
processConfig.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
processConfig.Env = env
remappedRoot := &execdriver.User{}
rootUID, rootGID := c.daemon.GetRemappedUIDGID()
if rootUID != 0 {
remappedRoot.UID = rootUID
remappedRoot.GID = rootGID
}
uidMap, gidMap := c.daemon.GetUIDGIDMaps()
c.command = &execdriver.Command{
ID: c.ID,
Rootfs: c.rootfsPath(),
@ -310,6 +319,9 @@ func populateCommand(c *Container, env []string) error {
WorkingDir: c.Config.WorkingDir,
Network: en,
Ipc: ipc,
UIDMapping: uidMap,
GIDMapping: gidMap,
RemappedRoot: remappedRoot,
Pid: pid,
UTS: uts,
Resources: resources,
@ -1343,19 +1355,23 @@ func (container *Container) hasMountFor(path string) bool {
}
func (container *Container) setupIpcDirs() error {
rootUID, rootGID := container.daemon.GetRemappedUIDGID()
if !container.hasMountFor("/dev/shm") {
shmPath, err := container.shmPath()
if err != nil {
return err
}
if err := os.MkdirAll(shmPath, 0700); err != nil {
if err := idtools.MkdirAllAs(shmPath, 0700, rootUID, rootGID); err != nil {
return err
}
if err := syscall.Mount("shm", shmPath, "tmpfs", uintptr(syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV), label.FormatMountLabel("mode=1777,size=65536k", container.getMountLabel())); err != nil {
return fmt.Errorf("mounting shm tmpfs: %s", err)
}
if err := os.Chown(shmPath, rootUID, rootGID); err != nil {
return err
}
}
if !container.hasMountFor("/dev/mqueue") {
@ -1364,13 +1380,16 @@ func (container *Container) setupIpcDirs() error {
return err
}
if err := os.MkdirAll(mqueuePath, 0700); err != nil {
if err := idtools.MkdirAllAs(mqueuePath, 0700, rootUID, rootGID); err != nil {
return err
}
if err := syscall.Mount("mqueue", mqueuePath, "mqueue", uintptr(syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV), ""); err != nil {
return fmt.Errorf("mounting mqueue mqueue : %s", err)
}
if err := os.Chown(mqueuePath, rootUID, rootGID); err != nil {
return err
}
}
return nil

View file

@ -37,6 +37,7 @@ import (
"github.com/docker/docker/pkg/discovery"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/graphdb"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/namesgenerator"
"github.com/docker/docker/pkg/nat"
@ -121,6 +122,8 @@ type Daemon struct {
discoveryWatcher discovery.Watcher
root string
shutdown bool
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
}
// Get looks for a container using the provided information, which could be
@ -632,6 +635,15 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
// on Windows to dump Go routine stacks
setupDumpStackTrap()
uidMaps, gidMaps, err := setupRemappedRoot(config)
if err != nil {
return nil, err
}
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
if err != nil {
return nil, err
}
// get the canonical path to the Docker root directory
var realRoot string
if _, err := os.Stat(config.Root); err != nil && os.IsNotExist(err) {
@ -642,14 +654,13 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
return nil, fmt.Errorf("Unable to get the full path to root (%s): %s", config.Root, err)
}
}
config.Root = realRoot
// Create the root directory if it doesn't exists
if err := system.MkdirAll(config.Root, 0700); err != nil {
if err = setupDaemonRoot(config, realRoot, rootUID, rootGID); err != nil {
return nil, err
}
// set up the tmpDir to use a canonical path
tmp, err := tempDir(config.Root)
tmp, err := tempDir(config.Root, rootUID, rootGID)
if err != nil {
return nil, fmt.Errorf("Unable to get the TempDir under %s: %s", config.Root, err)
}
@ -663,7 +674,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
graphdriver.DefaultDriver = config.GraphDriver
// Load storage driver
driver, err := graphdriver.New(config.Root, config.GraphOptions)
driver, err := graphdriver.New(config.Root, config.GraphOptions, uidMaps, gidMaps)
if err != nil {
return nil, fmt.Errorf("error initializing graphdriver: %v", err)
}
@ -696,7 +707,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
daemonRepo := filepath.Join(config.Root, "containers")
if err := system.MkdirAll(daemonRepo, 0700); err != nil {
if err := idtools.MkdirAllAs(daemonRepo, 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
return nil, err
}
@ -706,13 +717,13 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
}
logrus.Debug("Creating images graph")
g, err := graph.NewGraph(filepath.Join(config.Root, "graph"), d.driver)
g, err := graph.NewGraph(filepath.Join(config.Root, "graph"), d.driver, uidMaps, gidMaps)
if err != nil {
return nil, err
}
// Configure the volumes driver
volStore, err := configureVolumes(config)
volStore, err := configureVolumes(config, rootUID, rootGID)
if err != nil {
return nil, err
}
@ -777,7 +788,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
var sysInitPath string
if config.ExecDriver == "lxc" {
initPath, err := configureSysInit(config)
initPath, err := configureSysInit(config, rootUID, rootGID)
if err != nil {
return nil, err
}
@ -812,6 +823,8 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
d.EventsService = eventsService
d.volumes = volStore
d.root = config.Root
d.uidMaps = uidMaps
d.gidMaps = gidMaps
if err := d.cleanupMounts(); err != nil {
return nil, err
@ -974,7 +987,11 @@ func (daemon *Daemon) diff(container *Container) (archive.Archive, error) {
func (daemon *Daemon) createRootfs(container *Container) error {
// Step 1: create the container directory.
// This doubles as a barrier to avoid race conditions.
if err := os.Mkdir(container.root, 0700); err != nil {
rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps)
if err != nil {
return err
}
if err := idtools.MkdirAs(container.root, 0700, rootUID, rootGID); err != nil {
return err
}
initID := fmt.Sprintf("%s-init", container.ID)
@ -986,7 +1003,7 @@ func (daemon *Daemon) createRootfs(container *Container) error {
return err
}
if err := setupInitLayer(initPath); err != nil {
if err := setupInitLayer(initPath, rootUID, rootGID); err != nil {
daemon.driver.Put(initID)
return err
}
@ -1105,6 +1122,21 @@ func (daemon *Daemon) containerGraph() *graphdb.Database {
return daemon.containerGraphDB
}
// GetUIDGIDMaps returns the current daemon's user namespace settings
// for the full uid and gid maps which will be applied to containers
// started in this instance.
func (daemon *Daemon) GetUIDGIDMaps() ([]idtools.IDMap, []idtools.IDMap) {
return daemon.uidMaps, daemon.gidMaps
}
// GetRemappedUIDGID returns the current daemon's uid and gid values
// if user namespaces are in use for this daemon instance. If not
// this function will return "real" root values of 0, 0.
func (daemon *Daemon) GetRemappedUIDGID() (int, int) {
uid, gid, _ := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps)
return uid, gid
}
// ImageGetCached returns the earliest created image that is a child
// of the image with imgID, that had the same config when it was
// created. nil is returned if a child cannot be found. An error is
@ -1139,12 +1171,12 @@ func (daemon *Daemon) ImageGetCached(imgID string, config *runconfig.Config) (*i
}
// tempDir returns the default directory to use for temporary files.
func tempDir(rootDir string) (string, error) {
func tempDir(rootDir string, rootUID, rootGID int) (string, error) {
var tmpDir string
if tmpDir = os.Getenv("DOCKER_TMPDIR"); tmpDir == "" {
tmpDir = filepath.Join(rootDir, "tmp")
}
return tmpDir, system.MkdirAll(tmpDir, 0700)
return tmpDir, idtools.MkdirAllAs(tmpDir, 0700, rootUID, rootGID)
}
func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error {
@ -1228,8 +1260,8 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
return verifyPlatformContainerSettings(daemon, hostConfig, config)
}
func configureVolumes(config *Config) (*store.VolumeStore, error) {
volumesDriver, err := local.New(config.Root)
func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore, error) {
volumesDriver, err := local.New(config.Root, rootUID, rootGID)
if err != nil {
return nil, err
}

View file

@ -509,7 +509,7 @@ func initDaemonForVolumesTest(tmp string) (*Daemon, error) {
volumes: store.New(),
}
volumesDriver, err := local.New(tmp)
volumesDriver, err := local.New(tmp, 0, 0)
if err != nil {
return nil, err
}

View file

@ -15,10 +15,10 @@ import (
"github.com/docker/docker/daemon/graphdriver"
derr "github.com/docker/docker/errors"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
"github.com/docker/libnetwork"
@ -121,6 +121,11 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *runconfig.HostC
warnings := []string{}
sysInfo := sysinfo.New(true)
warnings, err := daemon.verifyExperimentalContainerSettings(hostConfig, config)
if err != nil {
return warnings, err
}
if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") {
return warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name())
}
@ -275,7 +280,7 @@ func migrateIfDownlevel(driver graphdriver.Driver, root string) error {
return migrateIfAufs(driver, root)
}
func configureSysInit(config *Config) (string, error) {
func configureSysInit(config *Config, rootUID, rootGID int) (string, error) {
localCopy := filepath.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", dockerversion.VERSION))
sysInitPath := utils.DockerInitPath(localCopy)
if sysInitPath == "" {
@ -284,7 +289,7 @@ func configureSysInit(config *Config) (string, error) {
if sysInitPath != localCopy {
// When we find a suitable dockerinit binary (even if it's our local binary), we copy it into config.Root at localCopy for future use (so that the original can go away without that being a problem, for example during a package upgrade).
if err := os.Mkdir(filepath.Dir(localCopy), 0700); err != nil && !os.IsExist(err) {
if err := idtools.MkdirAs(filepath.Dir(localCopy), 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
return "", err
}
if _, err := fileutils.CopyFile(sysInitPath, localCopy); err != nil {
@ -455,7 +460,7 @@ func initBridgeDriver(controller libnetwork.NetworkController, config *Config) e
//
// This extra layer is used by all containers as the top-most ro layer. It protects
// the container from unwanted side-effects on the rw layer.
func setupInitLayer(initLayer string) error {
func setupInitLayer(initLayer string, rootUID, rootGID int) error {
for pth, typ := range map[string]string{
"/dev/pts": "dir",
"/dev/shm": "dir",
@ -478,12 +483,12 @@ func setupInitLayer(initLayer string) error {
if _, err := os.Stat(filepath.Join(initLayer, pth)); err != nil {
if os.IsNotExist(err) {
if err := system.MkdirAll(filepath.Join(initLayer, filepath.Dir(pth)), 0755); err != nil {
if err := idtools.MkdirAllAs(filepath.Join(initLayer, filepath.Dir(pth)), 0755, rootUID, rootGID); err != nil {
return err
}
switch typ {
case "dir":
if err := system.MkdirAll(filepath.Join(initLayer, pth), 0755); err != nil {
if err := idtools.MkdirAllAs(filepath.Join(initLayer, pth), 0755, rootUID, rootGID); err != nil {
return err
}
case "file":
@ -492,6 +497,7 @@ func setupInitLayer(initLayer string) error {
return err
}
f.Close()
f.Chown(rootUID, rootGID)
default:
if err := os.Symlink(typ, filepath.Join(initLayer, pth)); err != nil {
return err

View file

@ -25,7 +25,7 @@ func parseSecurityOpt(container *Container, config *runconfig.HostConfig) error
return nil
}
func setupInitLayer(initLayer string) error {
func setupInitLayer(initLayer string, rootUID, rootGID int) error {
return nil
}
@ -89,7 +89,7 @@ func migrateIfDownlevel(driver graphdriver.Driver, root string) error {
return nil
}
func configureSysInit(config *Config) (string, error) {
func configureSysInit(config *Config, rootUID, rootGID int) (string, error) {
// TODO Windows.
return os.Getenv("TEMP"), nil
}

View file

@ -16,7 +16,6 @@ import (
"github.com/docker/docker/graph"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/parsers"
@ -32,6 +31,7 @@ type Docker struct {
Daemon *daemon.Daemon
OutOld io.Writer
AuthConfigs map[string]cliconfig.AuthConfig
Archiver *archive.Archiver
}
// ensure Docker implements builder.Docker
@ -121,6 +121,7 @@ func (d Docker) Release(sessionID string, activeImages []string) {
func (d Docker) Copy(c *daemon.Container, destPath string, src builder.FileInfo, decompress bool) error {
srcPath := src.Path()
destExists := true
rootUID, rootGID := d.Daemon.GetRemappedUIDGID()
// Work in daemon-local OS specific file paths
destPath = filepath.FromSlash(destPath)
@ -149,10 +150,10 @@ func (d Docker) Copy(c *daemon.Container, destPath string, src builder.FileInfo,
if src.IsDir() {
// copy as directory
if err := chrootarchive.CopyWithTar(srcPath, destPath); err != nil {
if err := d.Archiver.CopyWithTar(srcPath, destPath); err != nil {
return err
}
return fixPermissions(srcPath, destPath, 0, 0, destExists)
return fixPermissions(srcPath, destPath, rootUID, rootGID, destExists)
}
if decompress {
// Only try to untar if it is a file and that we've been told to decompress (when ADD-ing a remote file)
@ -167,7 +168,7 @@ func (d Docker) Copy(c *daemon.Container, destPath string, src builder.FileInfo,
}
// try to successfully untar the orig
if err := chrootarchive.UntarPath(srcPath, tarDest); err == nil {
if err := d.Archiver.UntarPath(srcPath, tarDest); err == nil {
return nil
} else if err != io.EOF {
logrus.Debugf("Couldn't untar to %s: %v", tarDest, err)
@ -182,11 +183,11 @@ func (d Docker) Copy(c *daemon.Container, destPath string, src builder.FileInfo,
if err := system.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
return err
}
if err := chrootarchive.CopyFileWithTar(srcPath, destPath); err != nil {
if err := d.Archiver.CopyFileWithTar(srcPath, destPath); err != nil {
return err
}
return fixPermissions(srcPath, destPath, 0, 0, destExists)
return fixPermissions(srcPath, destPath, rootUID, rootGID, destExists)
}
// GetCachedImage returns a reference to a cached image whose parent equals `parent`

View file

@ -6,6 +6,7 @@ import (
"os/exec"
"time"
"github.com/docker/docker/pkg/idtools"
// TODO Windows: Factor out ulimit
"github.com/docker/docker/pkg/ulimit"
"github.com/opencontainers/runc/libcontainer"
@ -173,6 +174,12 @@ type Mount struct {
Slave bool `json:"slave"`
}
// User contains the uid and gid representing a Unix user
type User struct {
UID int `json:"root_uid"`
GID int `json:"root_gid"`
}
// ProcessConfig describes a process that will be run inside a container.
type ProcessConfig struct {
exec.Cmd `json:"-"`
@ -202,6 +209,9 @@ type Command struct {
Ipc *Ipc `json:"ipc"`
Pid *Pid `json:"pid"`
UTS *UTS `json:"uts"`
RemappedRoot *User `json:"remap_root"`
UIDMapping []idtools.IDMap `json:"uidmapping"`
GIDMapping []idtools.IDMap `json:"gidmapping"`
Resources *Resources `json:"resources"`
Mounts []Mount `json:"mounts"`
AllowedDevices []*configs.Device `json:"allowed_devices"`

View file

@ -8,6 +8,7 @@ import (
"syscall"
"github.com/docker/docker/daemon/execdriver"
"github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/devices"
@ -30,6 +31,10 @@ func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks)
return nil, err
}
if err := d.setupRemappedRoot(container, c); err != nil {
return nil, err
}
if err := d.createNetwork(container, c, hooks); err != nil {
return nil, err
}
@ -193,6 +198,40 @@ func (d *Driver) createUTS(container *configs.Config, c *execdriver.Command) err
return nil
}
func (d *Driver) setupRemappedRoot(container *configs.Config, c *execdriver.Command) error {
if c.RemappedRoot.UID == 0 {
container.Namespaces.Remove(configs.NEWUSER)
return nil
}
// convert the Docker daemon id map to the libcontainer variant of the same struct
// this keeps us from having to import libcontainer code across Docker client + daemon packages
cuidMaps := []configs.IDMap{}
cgidMaps := []configs.IDMap{}
for _, idMap := range c.UIDMapping {
cuidMaps = append(cuidMaps, configs.IDMap(idMap))
}
for _, idMap := range c.GIDMapping {
cgidMaps = append(cgidMaps, configs.IDMap(idMap))
}
container.UidMappings = cuidMaps
container.GidMappings = cgidMaps
for _, node := range container.Devices {
node.Uid = uint32(c.RemappedRoot.UID)
node.Gid = uint32(c.RemappedRoot.GID)
}
// TODO: until a kernel/mount solution exists for handling remount in a user namespace,
// we must clear the readonly flag for the cgroups mount (@mrunalp concurs)
for i := range container.Mounts {
if container.Mounts[i].Device == "cgroup" {
container.Mounts[i].Flags &= ^syscall.MS_RDONLY
}
}
return nil
}
func (d *Driver) setPrivileged(container *configs.Config) (err error) {
container.Capabilities = execdriver.GetAllCapabilities()
container.Cgroups.AllowAllDevices = true
@ -255,6 +294,7 @@ func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) e
if m.Slave {
flags |= syscall.MS_SLAVE
}
container.Mounts = append(container.Mounts, &configs.Mount{
Source: m.Source,
Destination: m.Destination,

View file

@ -443,22 +443,35 @@ func (t *TtyConsole) Close() error {
}
func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes) error {
var term execdriver.Terminal
var err error
rootuid, err := container.HostUID()
if err != nil {
return err
}
if processConfig.Tty {
rootuid, err := container.HostUID()
if err != nil {
return err
}
cons, err := p.NewConsole(rootuid)
if err != nil {
return err
}
term, err = NewTtyConsole(cons, pipes)
} else {
term, err := NewTtyConsole(cons, pipes)
if err != nil {
return err
}
processConfig.Terminal = term
return nil
}
// not a tty--set up stdio pipes
term := &execdriver.StdConsole{}
processConfig.Terminal = term
// if we are not in a user namespace, there is no reason to go through
// the hassle of setting up os-level pipes with proper (remapped) ownership
// so we will do the prior shortcut for non-userns containers
if rootuid == 0 {
p.Stdout = pipes.Stdout
p.Stderr = pipes.Stderr
r, w, err := os.Pipe()
if err != nil {
return err
@ -470,12 +483,57 @@ func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConf
}()
p.Stdin = r
}
term = &execdriver.StdConsole{}
return nil
}
// if we have user namespaces enabled (rootuid != 0), we will set
// up os pipes for stderr, stdout, stdin so we can chown them to
// the proper ownership to allow for proper access to the underlying
// fds
var fds []int
//setup stdout
r, w, err := os.Pipe()
if err != nil {
return err
}
processConfig.Terminal = term
fds = append(fds, int(r.Fd()), int(w.Fd()))
if pipes.Stdout != nil {
go io.Copy(pipes.Stdout, r)
}
term.Closers = append(term.Closers, r)
p.Stdout = w
//setup stderr
r, w, err = os.Pipe()
if err != nil {
return err
}
fds = append(fds, int(r.Fd()), int(w.Fd()))
if pipes.Stderr != nil {
go io.Copy(pipes.Stderr, r)
}
term.Closers = append(term.Closers, r)
p.Stderr = w
//setup stdin
r, w, err = os.Pipe()
if err != nil {
return err
}
fds = append(fds, int(r.Fd()), int(w.Fd()))
if pipes.Stdin != nil {
go func() {
io.Copy(w, pipes.Stdin)
w.Close()
}()
p.Stdin = r
}
for _, fd := range fds {
if err := syscall.Fchown(fd, rootuid, rootuid); err != nil {
return fmt.Errorf("Failed to chown pipes fd: %v", err)
}
}
return nil
}

View file

@ -26,11 +26,18 @@ func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
return -1, fmt.Errorf("No active container exists with ID %s", c.ID)
}
user := processConfig.User
if c.RemappedRoot.UID != 0 && user == "" {
//if user namespaces are enabled, set user explicitly so uid/gid is set to 0
//otherwise we end up with the overflow id and no permissions (65534)
user = "0"
}
p := &libcontainer.Process{
Args: append([]string{processConfig.Entrypoint}, processConfig.Arguments...),
Env: c.ProcessConfig.Env,
Cwd: c.WorkingDir,
User: processConfig.User,
User: user,
}
if processConfig.Privileged {

View file

@ -34,6 +34,7 @@ func New() *configs.Config {
{Type: "NEWIPC"},
{Type: "NEWPID"},
{Type: "NEWNET"},
{Type: "NEWUSER"},
}),
Cgroups: &configs.Cgroup{
Parent: "docker",

View file

@ -7,6 +7,8 @@ import (
// StdConsole defines standard console operations for execdriver
type StdConsole struct {
// Closers holds io.Closer references for closing at terminal close time
Closers []io.Closer
}
// NewStdConsole returns a new StdConsole struct
@ -46,6 +48,8 @@ func (s *StdConsole) Resize(h, w int) error {
// Close implements Close method of Terminal interface
func (s *StdConsole) Close() error {
// nothing to close here
for _, c := range s.Closers {
c.Close()
}
return nil
}

View file

@ -34,12 +34,15 @@ import (
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/directory"
"github.com/docker/docker/pkg/idtools"
mountpk "github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/stringid"
"github.com/opencontainers/runc/libcontainer/label"
)
@ -71,13 +74,15 @@ type data struct {
// active maps mount id to the count
type Driver struct {
root string
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
sync.Mutex // Protects concurrent modification to active
active map[string]*data
}
// Init returns a new AUFS driver.
// An error is returned if AUFS is not supported.
func Init(root string, options []string) (graphdriver.Driver, error) {
func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
// Try to load the aufs kernel module
if err := supportsAufs(); err != nil {
@ -105,12 +110,23 @@ func Init(root string, options []string) (graphdriver.Driver, error) {
}
a := &Driver{
root: root,
active: make(map[string]*data),
root: root,
active: make(map[string]*data),
uidMaps: uidMaps,
gidMaps: gidMaps,
}
// Create the root aufs driver dir
if err := os.MkdirAll(root, 0755); err != nil {
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
if err != nil {
return nil, err
}
// Create the root aufs driver dir and return
// if it already exists
// If not populate the dir structure
if err := idtools.MkdirAllAs(root, 0755, rootUID, rootGID); err != nil {
if os.IsExist(err) {
return a, nil
}
return nil, err
}
@ -120,7 +136,7 @@ func Init(root string, options []string) (graphdriver.Driver, error) {
// Populate the dir structure
for _, p := range paths {
if err := os.MkdirAll(path.Join(root, p), 0755); err != nil {
if err := idtools.MkdirAllAs(path.Join(root, p), 0755, rootUID, rootGID); err != nil {
return nil, err
}
}
@ -221,8 +237,12 @@ func (a *Driver) createDirsFor(id string) error {
"diff",
}
rootUID, rootGID, err := idtools.GetRootUIDGID(a.uidMaps, a.gidMaps)
if err != nil {
return err
}
for _, p := range paths {
if err := os.MkdirAll(path.Join(a.rootPath(), p, id), 0755); err != nil {
if err := idtools.MkdirAllAs(path.Join(a.rootPath(), p, id), 0755, rootUID, rootGID); err != nil {
return err
}
}
@ -334,11 +354,16 @@ func (a *Driver) Diff(id, parent string) (archive.Archive, 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,
})
}
func (a *Driver) applyDiff(id string, diff archive.Reader) error {
return chrootarchive.UntarUncompressed(diff, path.Join(a.rootPath(), "diff", id), nil)
return chrootarchive.UntarUncompressed(diff, path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
UIDMaps: a.uidMaps,
GIDMaps: a.gidMaps,
})
}
// DiffSize calculates the changes between the specified id

View file

@ -26,7 +26,7 @@ func init() {
}
func testInit(dir string, t *testing.T) graphdriver.Driver {
d, err := Init(dir, nil)
d, err := Init(dir, nil, nil, nil)
if err != nil {
if err == graphdriver.ErrNotSupported {
t.Skip(err)

View file

@ -8,6 +8,8 @@ import (
"io/ioutil"
"os"
"path"
"github.com/docker/docker/pkg/idtools"
)
type metadata struct {
@ -38,7 +40,7 @@ func pathExists(pth string) bool {
// For the migration we try to move the folder containing the layer files, if that
// fails because the data is currently mounted we will fallback to creating a
// symlink.
func (a *Driver) Migrate(pth string, setupInit func(p string) error) error {
func (a *Driver) Migrate(pth string, setupInit func(p string, rootUID, rootGID int) error) error {
if pathExists(path.Join(pth, "graph")) {
if err := a.migrateRepositories(pth); err != nil {
return err
@ -59,12 +61,17 @@ func (a *Driver) migrateRepositories(pth string) error {
return nil
}
func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) error {
func (a *Driver) migrateContainers(pth string, setupInit func(p string, rootUID, rootGID int) error) error {
fis, err := ioutil.ReadDir(pth)
if err != nil {
return err
}
rootUID, rootGID, err := idtools.GetRootUIDGID(a.uidMaps, a.gidMaps)
if err != nil {
return err
}
for _, fi := range fis {
if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "rw")) {
if err := tryRelocate(path.Join(pth, id, "rw"), path.Join(a.rootPath(), "diff", id)); err != nil {
@ -88,7 +95,7 @@ func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) e
return err
}
// setup init layer
if err := setupInit(initPath); err != nil {
if err := setupInit(initPath, rootUID, rootGID); err != nil {
return err
}

View file

@ -19,6 +19,7 @@ import (
"unsafe"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/mount"
)
@ -28,7 +29,7 @@ func init() {
// Init returns a new BTRFS driver.
// An error is returned if BTRFS is not supported.
func Init(home string, options []string) (graphdriver.Driver, error) {
func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
rootdir := path.Dir(home)
var buf syscall.Statfs_t
@ -40,7 +41,11 @@ func Init(home string, options []string) (graphdriver.Driver, error) {
return nil, graphdriver.ErrPrerequisites
}
if err := os.MkdirAll(home, 0700); err != nil {
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
if err != nil {
return nil, err
}
if err := idtools.MkdirAllAs(home, 0700, rootUID, rootGID); err != nil {
return nil, err
}
@ -49,16 +54,20 @@ func Init(home string, options []string) (graphdriver.Driver, error) {
}
driver := &Driver{
home: home,
home: home,
uidMaps: uidMaps,
gidMaps: gidMaps,
}
return graphdriver.NewNaiveDiffDriver(driver), nil
return graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps), nil
}
// Driver contains information about the filesystem mounted.
type Driver struct {
//root of the file system
home string
home string
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
}
// String prints the name of the driver (btrfs).
@ -226,7 +235,11 @@ func (d *Driver) subvolumesDirID(id string) string {
// Create the filesystem with given id.
func (d *Driver) Create(id string, parent string) error {
subvolumes := path.Join(d.home, "subvolumes")
if err := os.MkdirAll(subvolumes, 0700); err != nil {
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
if err != nil {
return err
}
if err := idtools.MkdirAllAs(subvolumes, 0700, rootUID, rootGID); err != nil {
return err
}
if parent == "" {

View file

@ -19,11 +19,14 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/devicemapper"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/units"
"github.com/opencontainers/runc/libcontainer/label"
)
@ -113,6 +116,8 @@ type DeviceSet struct {
BaseDeviceUUID string //save UUID of base device
nrDeletedDevices uint //number of deleted devices
deletionWorkerTicker *time.Ticker
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
}
// DiskUsage contains information about disk usage and is used when reporting Status of a device.
@ -250,7 +255,11 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
if err := os.MkdirAll(dirname, 0700); err != nil {
uid, gid, err := idtools.GetRootUIDGID(devices.uidMaps, devices.gidMaps)
if err != nil {
return "", err
}
if err := idtools.MkdirAllAs(dirname, 0700, uid, gid); err != nil && !os.IsExist(err) {
return "", err
}
@ -1448,7 +1457,16 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
logrus.Warn("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors. For more information, see https://docs.docker.com/reference/commandline/daemon/#daemon-storage-driver-option")
}
if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil {
//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.MkdirAs(devices.root, 0700, uid, gid); err != nil && !os.IsExist(err) {
return err
}
if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) {
return err
}
@ -2230,7 +2248,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) (*DeviceSet, error) {
func NewDeviceSet(root string, doInit bool, options []string, uidMaps, gidMaps []idtools.IDMap) (*DeviceSet, error) {
devicemapper.SetDevDir("/dev")
devices := &DeviceSet{
@ -2245,6 +2263,8 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error
thinpBlockSize: defaultThinpBlockSize,
deviceIDMap: make([]byte, deviceIDMapSz),
deletionWorkerTicker: time.NewTicker(time.Second * 30),
uidMaps: uidMaps,
gidMaps: gidMaps,
}
foundBlkDiscard := false

View file

@ -67,7 +67,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)
if err != nil {
t.Fatalf("error creating devicemapper driver: %v", err)
}

View file

@ -10,8 +10,10 @@ import (
"strconv"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/devicemapper"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/units"
)
@ -28,13 +30,15 @@ func init() {
// Driver contains the device set mounted and the home directory
type Driver struct {
*DeviceSet
home string
home string
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
}
var backingFs = "<unknown>"
// Init creates a driver with the given home and the set of options.
func Init(home string, options []string) (graphdriver.Driver, error) {
func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
fsMagic, err := graphdriver.GetFSMagic(home)
if err != nil {
return nil, err
@ -43,7 +47,7 @@ func Init(home string, options []string) (graphdriver.Driver, error) {
backingFs = fsName
}
deviceSet, err := NewDeviceSet(home, true, options)
deviceSet, err := NewDeviceSet(home, true, options, uidMaps, gidMaps)
if err != nil {
return nil, err
}
@ -55,9 +59,11 @@ func Init(home string, options []string) (graphdriver.Driver, error) {
d := &Driver{
DeviceSet: deviceSet,
home: home,
uidMaps: uidMaps,
gidMaps: gidMaps,
}
return graphdriver.NewNaiveDiffDriver(d), nil
return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
}
func (d *Driver) String() string {
@ -160,8 +166,15 @@ func (d *Driver) Remove(id string) error {
func (d *Driver) Get(id, mountLabel string) (string, error) {
mp := path.Join(d.home, "mnt", id)
uid, gid, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
if err != nil {
return "", err
}
// Create the target directories if they don't exist
if err := os.MkdirAll(mp, 0755); err != nil {
if err := idtools.MkdirAllAs(path.Join(d.home, "mnt"), 0755, uid, gid); err != nil && !os.IsExist(err) {
return "", err
}
if err := idtools.MkdirAs(mp, 0755, uid, gid); err != nil && !os.IsExist(err) {
return "", err
}
@ -171,7 +184,7 @@ func (d *Driver) Get(id, mountLabel string) (string, error) {
}
rootFs := path.Join(mp, "rootfs")
if err := os.MkdirAll(rootFs, 0755); err != nil {
if err := idtools.MkdirAllAs(rootFs, 0755, uid, gid); err != nil && !os.IsExist(err) {
d.DeviceSet.UnmountDevice(id)
return "", err
}

View file

@ -8,7 +8,9 @@ import (
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools"
)
// FsMagic unsigned id of the filesystem in use.
@ -34,7 +36,7 @@ var (
)
// InitFunc initializes the storage driver.
type InitFunc func(root string, options []string) (Driver, error)
type InitFunc func(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error)
// ProtoDriver defines the basic capabilities of a driver.
// This interface exists solely to be a minimum set of methods
@ -46,7 +48,6 @@ type ProtoDriver interface {
// String returns a string representation of this driver.
String() string
// Create creates a new, empty, filesystem layer with the
// specified id and parent. Parent may be "".
Create(id, parent string) error
// Remove attempts to remove the filesystem layer with this id.
Remove(id string) error
@ -107,9 +108,9 @@ func Register(name string, initFunc InitFunc) error {
}
// GetDriver initializes and returns the registered driver
func GetDriver(name, home string, options []string) (Driver, error) {
func GetDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {
if initFunc, exists := drivers[name]; exists {
return initFunc(filepath.Join(home, name), options)
return initFunc(filepath.Join(home, name), options, uidMaps, gidMaps)
}
if pluginDriver, err := lookupPlugin(name, home, options); err == nil {
return pluginDriver, nil
@ -119,20 +120,20 @@ func GetDriver(name, home string, options []string) (Driver, error) {
}
// getBuiltinDriver initalizes and returns the registered driver, but does not try to load from plugins
func getBuiltinDriver(name, home string, options []string) (Driver, error) {
func getBuiltinDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {
if initFunc, exists := drivers[name]; exists {
return initFunc(filepath.Join(home, name), options)
return initFunc(filepath.Join(home, name), options, uidMaps, gidMaps)
}
logrus.Errorf("Failed to built-in GetDriver graph %s %s", name, home)
return nil, ErrNotSupported
}
// New creates the driver and initializes it at the specified root.
func New(root string, options []string) (driver Driver, err error) {
func New(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (driver Driver, err error) {
for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} {
if name != "" {
logrus.Debugf("[graphdriver] trying provided driver %q", name) // so the logs show specified driver
return GetDriver(name, root, options)
return GetDriver(name, root, options, uidMaps, gidMaps)
}
}
@ -147,7 +148,7 @@ func New(root string, options []string) (driver Driver, err error) {
// of the state found from prior drivers, check in order of our priority
// which we would prefer
if prior == name {
driver, err = getBuiltinDriver(name, root, options)
driver, err = getBuiltinDriver(name, root, options, uidMaps, gidMaps)
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
@ -167,7 +168,7 @@ func New(root string, options []string) (driver Driver, err error) {
// Check for priority drivers first
for _, name := range priority {
driver, err = getBuiltinDriver(name, root, options)
driver, err = getBuiltinDriver(name, root, options, uidMaps, gidMaps)
if err != nil {
if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
continue
@ -179,7 +180,7 @@ func New(root string, options []string) (driver Driver, err error) {
// Check all registered drivers if no priority driver is found
for _, initFunc := range drivers {
if driver, err = initFunc(root, options); err != nil {
if driver, err = initFunc(root, options, uidMaps, gidMaps); err != nil {
if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
continue
}

View file

@ -6,8 +6,10 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/ioutils"
)
@ -18,6 +20,8 @@ import (
// Notably, the AUFS driver doesn't need to be wrapped like this.
type NaiveDiffDriver struct {
ProtoDriver
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
}
// NewNaiveDiffDriver returns a fully functional driver that wraps the
@ -27,8 +31,10 @@ 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) Driver {
return &NaiveDiffDriver{ProtoDriver: driver}
func NewNaiveDiffDriver(driver ProtoDriver, uidMaps, gidMaps []idtools.IDMap) Driver {
return &NaiveDiffDriver{ProtoDriver: driver,
uidMaps: uidMaps,
gidMaps: gidMaps}
}
// Diff produces an archive of the changes between the specified
@ -70,7 +76,7 @@ func (gdw *NaiveDiffDriver) Diff(id, parent string) (arch archive.Archive, err e
return nil, err
}
archive, err := archive.ExportChanges(layerFs, changes)
archive, err := archive.ExportChanges(layerFs, changes, gdw.uidMaps, gdw.gidMaps)
if err != nil {
return nil, err
}
@ -119,9 +125,11 @@ func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, diff archive.Reader) (s
}
defer driver.Put(id)
options := &archive.TarOptions{UIDMaps: gdw.uidMaps,
GIDMaps: gdw.gidMaps}
start := time.Now().UTC()
logrus.Debugf("Start untar layer")
if size, err = chrootarchive.ApplyUncompressedLayer(layerFs, diff); err != nil {
if size, err = chrootarchive.ApplyUncompressedLayer(layerFs, diff, options); err != nil {
return
}
logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())

View file

@ -74,7 +74,7 @@ func newDriver(t *testing.T, name string) *Driver {
t.Fatal(err)
}
d, err := graphdriver.GetDriver(name, root, nil)
d, err := graphdriver.GetDriver(name, root, nil, nil, nil)
if err != nil {
t.Logf("graphdriver: %v\n", err)
if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites || err == graphdriver.ErrIncompatibleFS {

View file

@ -13,9 +13,12 @@ import (
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/idtools"
"github.com/opencontainers/runc/libcontainer/label"
)
@ -41,9 +44,9 @@ type naiveDiffDriverWithApply struct {
}
// NaiveDiffDriverWithApply returns a NaiveDiff driver with custom ApplyDiff.
func NaiveDiffDriverWithApply(driver ApplyDiffProtoDriver) graphdriver.Driver {
func NaiveDiffDriverWithApply(driver ApplyDiffProtoDriver, uidMaps, gidMaps []idtools.IDMap) graphdriver.Driver {
return &naiveDiffDriverWithApply{
Driver: graphdriver.NewNaiveDiffDriver(driver),
Driver: graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps),
applyDiff: driver,
}
}
@ -98,6 +101,8 @@ type Driver struct {
home string
sync.Mutex // Protects concurrent modification to active
active map[string]*ActiveMount
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
}
var backingFs = "<unknown>"
@ -109,7 +114,7 @@ func init() {
// Init returns the NaiveDiffDriver, a native diff driver for overlay filesystem.
// If overlay filesystem is not supported on the host, graphdriver.ErrNotSupported is returned as error.
// If a overlay filesystem is not supported over a existing filesystem then error graphdriver.ErrIncompatibleFS is returned.
func Init(home string, options []string) (graphdriver.Driver, error) {
func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
if err := supportsOverlay(); err != nil {
return nil, graphdriver.ErrNotSupported
@ -136,17 +141,23 @@ func Init(home string, options []string) (graphdriver.Driver, error) {
return nil, graphdriver.ErrIncompatibleFS
}
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
if err != nil {
return nil, err
}
// Create the driver home dir
if err := os.MkdirAll(home, 0755); err != nil {
if err := idtools.MkdirAllAs(home, 0755, rootUID, rootGID); err != nil && !os.IsExist(err) {
return nil, err
}
d := &Driver{
home: home,
active: make(map[string]*ActiveMount),
home: home,
active: make(map[string]*ActiveMount),
uidMaps: uidMaps,
gidMaps: gidMaps,
}
return NaiveDiffDriverWithApply(d), nil
return NaiveDiffDriverWithApply(d, uidMaps, gidMaps), nil
}
func supportsOverlay() error {
@ -221,10 +232,15 @@ func (d *Driver) Cleanup() error {
// The parent filesystem is used to configure these directories for the overlay.
func (d *Driver) Create(id string, parent string) (retErr error) {
dir := d.dir(id)
if err := os.MkdirAll(path.Dir(dir), 0700); err != nil {
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
if err != nil {
return err
}
if err := os.Mkdir(dir, 0700); err != nil {
if err := idtools.MkdirAllAs(path.Dir(dir), 0700, rootUID, rootGID); err != nil {
return err
}
if err := idtools.MkdirAs(dir, 0700, rootUID, rootGID); err != nil {
return err
}
@ -237,7 +253,7 @@ func (d *Driver) Create(id string, parent string) (retErr error) {
// Toplevel images are just a "root" dir
if parent == "" {
if err := os.Mkdir(path.Join(dir, "root"), 0755); err != nil {
if err := idtools.MkdirAs(path.Join(dir, "root"), 0755, rootUID, rootGID); err != nil {
return err
}
return nil
@ -260,7 +276,7 @@ func (d *Driver) Create(id string, parent string) (retErr error) {
if err := os.Mkdir(path.Join(dir, "work"), 0700); err != nil {
return err
}
if err := os.Mkdir(path.Join(dir, "merged"), 0700); err != nil {
if err := idtools.MkdirAs(path.Join(dir, "merged"), 0700, rootUID, rootGID); err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(dir, "lower-id"), []byte(parent), 0666); err != nil {
@ -293,7 +309,7 @@ func (d *Driver) Create(id string, parent string) (retErr error) {
if err := os.Mkdir(path.Join(dir, "work"), 0700); err != nil {
return err
}
if err := os.Mkdir(path.Join(dir, "merged"), 0700); err != nil {
if err := idtools.MkdirAs(path.Join(dir, "merged"), 0700, rootUID, rootGID); err != nil {
return err
}
@ -349,6 +365,12 @@ func (d *Driver) Get(id string, mountLabel string) (string, error) {
if err := syscall.Mount("overlay", mergedDir, "overlay", 0, label.FormatMountLabel(opts, mountLabel)); err != nil {
return "", fmt.Errorf("error creating overlay mount to %s: %v", mergedDir, err)
}
// 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.
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
if err := os.Chown(path.Join(workDir, "work"), rootUID, rootGID); err != nil {
return "", err
}
mount.path = mergedDir
mount.mounted = true
d.active[id] = mount
@ -431,7 +453,8 @@ func (d *Driver) ApplyDiff(id string, parent string, diff archive.Reader) (size
return 0, err
}
if size, err = chrootarchive.ApplyUncompressedLayer(tmpRootDir, diff); err != nil {
options := &archive.TarOptions{UIDMaps: d.uidMaps, GIDMaps: d.gidMaps}
if size, err = chrootarchive.ApplyUncompressedLayer(tmpRootDir, diff, options); err != nil {
return 0, err
}

View file

@ -9,7 +9,8 @@ import (
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/idtools"
"github.com/opencontainers/runc/libcontainer/label"
)
@ -19,11 +20,20 @@ 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) (graphdriver.Driver, error) {
func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
d := &Driver{
home: home,
home: home,
uidMaps: uidMaps,
gidMaps: gidMaps,
}
return graphdriver.NewNaiveDiffDriver(d), nil
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
if err != nil {
return nil, err
}
if err := idtools.MkdirAllAs(home, 0700, rootUID, rootGID); err != nil {
return nil, err
}
return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
}
// Driver holds information about the driver, home directory of the driver.
@ -31,7 +41,9 @@ func Init(home string, options []string) (graphdriver.Driver, error) {
// In order to support layering, files are copied from the parent layer into the new layer. There is no copy-on-write support.
// Driver must be wrapped in NaiveDiffDriver to be used as a graphdriver.Driver
type Driver struct {
home string
home string
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
}
func (d *Driver) String() string {
@ -56,10 +68,14 @@ func (d *Driver) Cleanup() error {
// Create prepares the filesystem for the VFS driver and copies the directory for the given id under the parent.
func (d *Driver) Create(id, parent string) error {
dir := d.dir(id)
if err := system.MkdirAll(filepath.Dir(dir), 0700); err != nil {
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
if err != nil {
return err
}
if err := os.Mkdir(dir, 0755); err != nil {
if err := idtools.MkdirAllAs(filepath.Dir(dir), 0700, rootUID, rootGID); err != nil {
return err
}
if err := idtools.MkdirAs(dir, 0755, rootUID, rootGID); err != nil {
return err
}
opts := []string{"level:s0"}

View file

@ -21,6 +21,7 @@ import (
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/random"
"github.com/microsoft/hcsshim"
@ -50,7 +51,7 @@ type Driver struct {
}
// InitFilter returns a new Windows storage filter driver.
func InitFilter(home string, options []string) (graphdriver.Driver, error) {
func InitFilter(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
logrus.Debugf("WindowsGraphDriver InitFilter at %s", home)
d := &Driver{
info: hcsshim.DriverInfo{
@ -63,7 +64,7 @@ func InitFilter(home string, options []string) (graphdriver.Driver, error) {
}
// InitDiff returns a new Windows differencing disk driver.
func InitDiff(home string, options []string) (graphdriver.Driver, error) {
func InitDiff(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
logrus.Debugf("WindowsGraphDriver InitDiff at %s", home)
d := &Driver{
info: hcsshim.DriverInfo{
@ -328,7 +329,7 @@ func (d *Driver) ApplyDiff(id, parent string, diff archive.Reader) (size int64,
logrus.Debugf("WindowsGraphDriver ApplyDiff: Start untar layer")
destination := d.dir(id)
destination = filepath.Dir(destination)
if size, err = chrootarchive.ApplyUncompressedLayer(destination, diff); err != nil {
if size, err = chrootarchive.ApplyUncompressedLayer(destination, diff, nil); err != nil {
return
}
logrus.Debugf("WindowsGraphDriver ApplyDiff: Untar time: %vs", time.Now().UTC().Sub(start).Seconds())

View file

@ -15,6 +15,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/parsers"
zfs "github.com/mistifyio/go-zfs"
@ -41,7 +42,7 @@ func (*Logger) Log(cmd []string) {
// Init returns a new ZFS driver.
// It takes base mount path and a 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) (graphdriver.Driver, error) {
func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
var err error
if _, err := exec.LookPath("zfs"); err != nil {
@ -102,8 +103,10 @@ func Init(base string, opt []string) (graphdriver.Driver, error) {
dataset: rootDataset,
options: options,
filesystemsCache: filesystemsCache,
uidMaps: uidMaps,
gidMaps: gidMaps,
}
return graphdriver.NewNaiveDiffDriver(d), nil
return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
}
func parseOptions(opt []string) (zfsOptions, error) {
@ -156,6 +159,8 @@ type Driver struct {
options zfsOptions
sync.Mutex // protects filesystem cache against concurrent access
filesystemsCache map[string]bool
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
}
func (d *Driver) String() string {
@ -294,12 +299,16 @@ func (d *Driver) Get(id, mountLabel string) (string, error) {
options := label.FormatMountLabel("", mountLabel)
logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options)
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
if err != nil {
return "", err
}
// Create the target directories if they don't exist
if err := os.MkdirAll(mountpoint, 0755); err != nil {
if err := idtools.MkdirAllAs(mountpoint, 0755, rootUID, rootGID); err != nil {
return "", err
}
err := mount.Mount(filesystem, mountpoint, "zfs", options)
err = mount.Mount(filesystem, mountpoint, "zfs", options)
if err != nil {
return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
}

View file

@ -55,7 +55,17 @@ func (container *Container) setupMounts() ([]execdriver.Mount, error) {
}
mounts = sortMounts(mounts)
return append(mounts, container.networkMounts()...), nil
netMounts := container.networkMounts()
// if we are going to mount any of the network files from container
// metadata, the ownership must be set properly for potential container
// remapped root (user namespaces)
rootUID, rootGID := container.daemon.GetRemappedUIDGID()
for _, mount := range netMounts {
if err := os.Chown(mount.Source, rootUID, rootGID); err != nil {
return nil, err
}
}
return append(mounts, netMounts...), nil
}
// parseBindMount validates the configuration of mount information in runconfig is valid.

View file

@ -21,10 +21,10 @@ import (
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/progressreader"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/truncindex"
"github.com/docker/docker/runconfig"
"github.com/vbatts/tar-split/tar/asm"
@ -82,6 +82,8 @@ type Graph struct {
imageMutex imageMutex // protect images in driver.
retained *retainedLayers
tarSplitDisabled bool
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
}
// file names for ./graph/<ID>/
@ -101,13 +103,18 @@ var (
// NewGraph instantiates a new graph at the given root path in the filesystem.
// `root` will be created if it doesn't exist.
func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) {
func NewGraph(root string, driver graphdriver.Driver, uidMaps, gidMaps []idtools.IDMap) (*Graph, error) {
abspath, err := filepath.Abs(root)
if err != nil {
return nil, err
}
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
if err != nil {
return nil, err
}
// Create the root directory if it doesn't exists
if err := system.MkdirAll(root, 0700); err != nil {
if err := idtools.MkdirAllAs(root, 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
return nil, err
}
@ -116,6 +123,8 @@ func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) {
idIndex: truncindex.NewTruncIndex([]string{}),
driver: driver,
retained: &retainedLayers{layerHolders: make(map[string]map[string]struct{})},
uidMaps: uidMaps,
gidMaps: gidMaps,
}
// Windows does not currently support tarsplit functionality.
@ -325,7 +334,11 @@ func (graph *Graph) TempLayerArchive(id string, sf *streamformatter.StreamFormat
// mktemp creates a temporary sub-directory inside the graph's filesystem.
func (graph *Graph) mktemp() (string, error) {
dir := filepath.Join(graph.root, "_tmp", stringid.GenerateNonCryptoID())
if err := system.MkdirAll(dir, 0700); err != nil {
rootUID, rootGID, err := idtools.GetRootUIDGID(graph.uidMaps, graph.gidMaps)
if err != nil {
return "", err
}
if err := idtools.MkdirAllAs(dir, 0700, rootUID, rootGID); err != nil {
return "", err
}
return dir, nil

View file

@ -281,11 +281,11 @@ func tempGraph(t *testing.T) (*Graph, graphdriver.Driver) {
if err != nil {
t.Fatal(err)
}
driver, err := graphdriver.New(tmp, nil)
driver, err := graphdriver.New(tmp, nil, nil, nil)
if err != nil {
t.Fatal(err)
}
graph, err := NewGraph(tmp, driver)
graph, err := NewGraph(tmp, driver, nil, nil)
if err != nil {
t.Fatal(err)
}

View file

@ -54,11 +54,11 @@ func fakeTar() (io.Reader, error) {
}
func mkTestTagStore(root string, t *testing.T) *TagStore {
driver, err := graphdriver.New(root, nil)
driver, err := graphdriver.New(root, nil, nil, nil)
if err != nil {
t.Fatal(err)
}
graph, err := NewGraph(root, driver)
graph, err := NewGraph(root, driver, nil, nil)
if err != nil {
t.Fatal(err)
}

View file

@ -2248,7 +2248,7 @@ func (s *DockerSuite) TestBuildContextCleanup(c *check.C) {
testRequires(c, SameHostDaemon)
name := "testbuildcontextcleanup"
entries, err := ioutil.ReadDir("/var/lib/docker/tmp")
entries, err := ioutil.ReadDir(filepath.Join(dockerBasePath, "tmp"))
if err != nil {
c.Fatalf("failed to list contents of tmp dir: %s", err)
}
@ -2259,7 +2259,7 @@ func (s *DockerSuite) TestBuildContextCleanup(c *check.C) {
if err != nil {
c.Fatal(err)
}
entriesFinal, err := ioutil.ReadDir("/var/lib/docker/tmp")
entriesFinal, err := ioutil.ReadDir(filepath.Join(dockerBasePath, "tmp"))
if err != nil {
c.Fatalf("failed to list contents of tmp dir: %s", err)
}
@ -2274,7 +2274,7 @@ func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) {
testRequires(c, SameHostDaemon)
name := "testbuildcontextcleanup"
entries, err := ioutil.ReadDir("/var/lib/docker/tmp")
entries, err := ioutil.ReadDir(filepath.Join(dockerBasePath, "tmp"))
if err != nil {
c.Fatalf("failed to list contents of tmp dir: %s", err)
}
@ -2285,7 +2285,7 @@ func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) {
if err == nil {
c.Fatalf("expected build to fail, but it didn't")
}
entriesFinal, err := ioutil.ReadDir("/var/lib/docker/tmp")
entriesFinal, err := ioutil.ReadDir(filepath.Join(dockerBasePath, "tmp"))
if err != nil {
c.Fatalf("failed to list contents of tmp dir: %s", err)
}

View file

@ -559,7 +559,7 @@ func (s *DockerSuite) TestCpSpecialFiles(c *check.C) {
// Copy actual /etc/resolv.conf
dockerCmd(c, "cp", cleanedContainerID+":/etc/resolv.conf", outDir)
expected, err := ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/resolv.conf")
expected, err := readContainerFile(cleanedContainerID, "resolv.conf")
actual, err := ioutil.ReadFile(outDir + "/resolv.conf")
if !bytes.Equal(actual, expected) {
@ -569,7 +569,7 @@ func (s *DockerSuite) TestCpSpecialFiles(c *check.C) {
// Copy actual /etc/hosts
dockerCmd(c, "cp", cleanedContainerID+":/etc/hosts", outDir)
expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hosts")
expected, err = readContainerFile(cleanedContainerID, "hosts")
actual, err = ioutil.ReadFile(outDir + "/hosts")
if !bytes.Equal(actual, expected) {
@ -579,7 +579,7 @@ func (s *DockerSuite) TestCpSpecialFiles(c *check.C) {
// Copy actual /etc/resolv.conf
dockerCmd(c, "cp", cleanedContainerID+":/etc/hostname", outDir)
expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hostname")
expected, err = readContainerFile(cleanedContainerID, "hostname")
actual, err = ioutil.ReadFile(outDir + "/hostname")
if !bytes.Equal(actual, expected) {

View file

@ -1075,7 +1075,7 @@ func (s *DockerDaemonSuite) TestDaemonLoggingDriverDefault(c *check.C) {
if out, err := s.d.Cmd("wait", id); err != nil {
c.Fatal(out, err)
}
logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log")
logPath := filepath.Join(s.d.root, "containers", id, id+"-json.log")
if _, err := os.Stat(logPath); err != nil {
c.Fatal(err)
@ -1117,7 +1117,7 @@ func (s *DockerDaemonSuite) TestDaemonLoggingDriverDefaultOverride(c *check.C) {
if out, err := s.d.Cmd("wait", id); err != nil {
c.Fatal(out, err)
}
logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log")
logPath := filepath.Join(s.d.root, "containers", id, id+"-json.log")
if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) {
c.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err)
@ -1159,7 +1159,7 @@ func (s *DockerDaemonSuite) TestDaemonLoggingDriverNoneOverride(c *check.C) {
if out, err := s.d.Cmd("wait", id); err != nil {
c.Fatal(out, err)
}
logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log")
logPath := filepath.Join(s.d.root, "containers", id, id+"-json.log")
if _, err := os.Stat(logPath); err != nil {
c.Fatal(err)

View file

@ -83,7 +83,7 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
switch t := data.(type) {
case error:
fmt.Fprintln(w, fmt.Sprintf(`{"Err": %s}`, t.Error()))
fmt.Fprintln(w, fmt.Sprintf(`{"Err": %q}`, t.Error()))
case string:
fmt.Fprintln(w, t)
default:
@ -91,13 +91,21 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
}
}
decReq := func(b io.ReadCloser, out interface{}, w http.ResponseWriter) error {
defer b.Close()
if err := json.NewDecoder(b).Decode(&out); err != nil {
http.Error(w, fmt.Sprintf("error decoding json: %s", err.Error()), 500)
}
return nil
}
base, err := ioutil.TempDir("", "external-graph-test")
c.Assert(err, check.IsNil)
vfsProto, err := vfs.Init(base, []string{})
vfsProto, err := vfs.Init(base, []string{}, nil, nil)
if err != nil {
c.Fatalf("error initializing graph driver: %v", err)
}
driver := graphdriver.NewNaiveDiffDriver(vfsProto)
driver := graphdriver.NewNaiveDiffDriver(vfsProto, nil, nil)
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
s.ec.activations++
@ -113,8 +121,7 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
s.ec.creations++
var req graphDriverRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), 500)
if err := decReq(r.Body, &req, w); err != nil {
return
}
if err := driver.Create(req.ID, req.Parent); err != nil {
@ -128,8 +135,7 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
s.ec.removals++
var req graphDriverRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), 500)
if err := decReq(r.Body, &req, w); err != nil {
return
}
@ -144,8 +150,8 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
s.ec.gets++
var req graphDriverRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), 500)
if err := decReq(r.Body, &req, w); err != nil {
return
}
dir, err := driver.Get(req.ID, req.MountLabel)
@ -160,8 +166,7 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
s.ec.puts++
var req graphDriverRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), 500)
if err := decReq(r.Body, &req, w); err != nil {
return
}
@ -176,8 +181,7 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
s.ec.exists++
var req graphDriverRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), 500)
if err := decReq(r.Body, &req, w); err != nil {
return
}
respond(w, &graphDriverResponse{Exists: driver.Exists(req.ID)})
@ -185,7 +189,7 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
mux.HandleFunc("/GraphDriver.Status", func(w http.ResponseWriter, r *http.Request) {
s.ec.stats++
respond(w, `{"Status":{}}`)
respond(w, &graphDriverResponse{Status: driver.Status()})
})
mux.HandleFunc("/GraphDriver.Cleanup", func(w http.ResponseWriter, r *http.Request) {
@ -202,8 +206,7 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
s.ec.metadata++
var req graphDriverRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), 500)
if err := decReq(r.Body, &req, w); err != nil {
return
}
@ -219,8 +222,7 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
s.ec.diff++
var req graphDriverRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), 500)
if err := decReq(r.Body, &req, w); err != nil {
return
}
@ -235,8 +237,7 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
mux.HandleFunc("/GraphDriver.Changes", func(w http.ResponseWriter, r *http.Request) {
s.ec.changes++
var req graphDriverRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), 500)
if err := decReq(r.Body, &req, w); err != nil {
return
}
@ -250,10 +251,17 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
mux.HandleFunc("/GraphDriver.ApplyDiff", func(w http.ResponseWriter, r *http.Request) {
s.ec.applydiff++
var diff archive.Reader = r.Body
defer r.Body.Close()
id := r.URL.Query().Get("id")
parent := r.URL.Query().Get("parent")
size, err := driver.ApplyDiff(id, parent, r.Body)
if id == "" {
http.Error(w, fmt.Sprintf("missing id"), 409)
}
size, err := driver.ApplyDiff(id, parent, diff)
if err != nil {
respond(w, err)
return
@ -265,8 +273,7 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) {
s.ec.diffsize++
var req graphDriverRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), 500)
if err := decReq(r.Body, &req, w); err != nil {
return
}
@ -296,7 +303,10 @@ func (s *DockerExternalGraphdriverSuite) TearDownSuite(c *check.C) {
}
func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriver(c *check.C) {
c.Assert(s.d.StartWithBusybox("-s", "test-external-graph-driver"), check.IsNil)
if err := s.d.StartWithBusybox("-s", "test-external-graph-driver"); err != nil {
b, _ := ioutil.ReadFile(s.d.LogfileName())
c.Assert(err, check.IsNil, check.Commentf("\n%s", string(b)))
}
out, err := s.d.Cmd("run", "-d", "--name=graphtest", "busybox", "sh", "-c", "echo hello > /hello")
c.Assert(err, check.IsNil, check.Commentf(out))
@ -326,7 +336,7 @@ func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriver(c *check.C) {
c.Assert(s.ec.removals >= 1, check.Equals, true)
c.Assert(s.ec.gets >= 1, check.Equals, true)
c.Assert(s.ec.puts >= 1, check.Equals, true)
c.Assert(s.ec.stats, check.Equals, 1)
c.Assert(s.ec.stats, check.Equals, 3)
c.Assert(s.ec.cleanups, check.Equals, 2)
c.Assert(s.ec.exists >= 1, check.Equals, true)
c.Assert(s.ec.applydiff >= 1, check.Equals, true)

View file

@ -1,9 +1,12 @@
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"github.com/docker/docker/pkg/reexec"
)
var (
@ -16,10 +19,6 @@ var (
// the private registry to use for tests
privateRegistryURL = "127.0.0.1:5000"
dockerBasePath = "/var/lib/docker"
volumesConfigPath = dockerBasePath + "/volumes"
containerStoragePath = dockerBasePath + "/containers"
runtimePath = "/var/run/docker"
execDriverPath = runtimePath + "/execdriver/native"
@ -38,6 +37,13 @@ var (
// daemonDefaultImage is the name of the default image to use when running
// tests. This is platform dependent.
daemonDefaultImage string
// For a local daemon on Linux, these values will be used for testing
// user namespace support as the standard graph path(s) will be
// appended with the root remapped uid.gid prefix
dockerBasePath string
volumesConfigPath string
containerStoragePath string
)
const (
@ -50,6 +56,7 @@ const (
)
func init() {
reexec.Init()
if dockerBin := os.Getenv("DOCKER_BINARY"); dockerBin != "" {
dockerBinary = dockerBin
}
@ -85,4 +92,21 @@ func init() {
} else {
isLocalDaemon = true
}
// This is only used for a tests with local daemon true (Linux-only today)
// default is "/var/lib/docker", but we'll try and ask the
// /info endpoint for the specific root dir
dockerBasePath = "/var/lib/docker"
type Info struct {
DockerRootDir string
}
var i Info
status, b, err := sockRequest("GET", "/info", nil)
if err == nil && status == 200 {
if err = json.Unmarshal(b, &i); err == nil {
dockerBasePath = i.DockerRootDir
}
}
volumesConfigPath = dockerBasePath + "/volumes"
containerStoragePath = dockerBasePath + "/containers"
}

View file

@ -41,6 +41,7 @@ type Daemon struct {
c *check.C
logFile *os.File
folder string
root string
stdin io.WriteCloser
stdout, stderr io.ReadCloser
cmd *exec.Cmd
@ -65,9 +66,10 @@ func NewDaemon(c *check.C) *Daemon {
if err != nil {
c.Fatalf("Could not make %q an absolute path: %v", dir, err)
}
daemonRoot := filepath.Join(daemonFolder, "root")
if err := os.MkdirAll(filepath.Join(daemonFolder, "graph"), 0600); err != nil {
c.Fatalf("Could not create %s/graph directory", daemonFolder)
if err := os.MkdirAll(daemonRoot, 0755); err != nil {
c.Fatalf("Could not create daemon root %q: %v", dir, err)
}
userlandProxy := true
@ -82,6 +84,7 @@ func NewDaemon(c *check.C) *Daemon {
id: id,
c: c,
folder: daemonFolder,
root: daemonRoot,
storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"),
execDriver: os.Getenv("DOCKER_EXECDRIVER"),
userlandProxy: userlandProxy,
@ -99,7 +102,7 @@ func (d *Daemon) Start(arg ...string) error {
args := append(d.GlobalFlags,
d.Command,
"--host", d.sock(),
"--graph", fmt.Sprintf("%s/graph", d.folder),
"--graph", d.root,
"--pidfile", fmt.Sprintf("%s/docker.pid", d.folder),
fmt.Sprintf("--userland-proxy=%t", d.userlandProxy),
)
@ -181,8 +184,11 @@ func (d *Daemon) Start(arg ...string) error {
if resp.StatusCode != http.StatusOK {
d.c.Logf("[%s] received status != 200 OK: %s", d.id, resp.Status)
}
d.c.Logf("[%s] daemon started", d.id)
d.root, err = d.queryRootDir()
if err != nil {
return fmt.Errorf("[%s] error querying daemon for root directory: %v", d.id, err)
}
return nil
}
}
@ -278,6 +284,47 @@ func (d *Daemon) Restart(arg ...string) error {
return d.Start(arg...)
}
func (d *Daemon) queryRootDir() (string, error) {
// update daemon root by asking /info endpoint (to support user
// namespaced daemon with root remapped uid.gid directory)
conn, err := net.Dial("unix", filepath.Join(d.folder, "docker.sock"))
if err != nil {
return "", err
}
client := httputil.NewClientConn(conn, nil)
req, err := http.NewRequest("GET", "/info", nil)
if err != nil {
client.Close()
return "", err
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
client.Close()
return "", err
}
body := ioutils.NewReadCloserWrapper(resp.Body, func() error {
defer client.Close()
return resp.Body.Close()
})
type Info struct {
DockerRootDir string
}
var b []byte
var i Info
b, err = readBody(body)
if err == nil && resp.StatusCode == 200 {
// read the docker root dir
if err = json.Unmarshal(b, &i); err == nil {
return i.DockerRootDir, nil
}
}
return "", err
}
func (d *Daemon) sock() string {
return fmt.Sprintf("unix://%s/docker.sock", d.folder)
}
@ -1236,7 +1283,7 @@ func readFile(src string, c *check.C) (content string) {
}
func containerStorageFile(containerID, basename string) string {
return filepath.Join("/var/lib/docker/containers", containerID, basename)
return filepath.Join(containerStoragePath, containerID, basename)
}
// docker commands that use this function must be run with the '-d' switch.

View file

@ -19,6 +19,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/system"
@ -41,6 +42,8 @@ type (
ExcludePatterns []string
Compression Compression
NoLchown bool
UIDMaps []idtools.IDMap
GIDMaps []idtools.IDMap
ChownOpts *TarChownOptions
IncludeSourceDir bool
// When unpacking, specifies whether overwriting a directory with a
@ -52,9 +55,13 @@ type (
}
// Archiver allows the reuse of most utility functions of this package
// with a pluggable Untar function.
// with a pluggable Untar function. Also, to facilitate the passing of
// specific id mappings for untar, an archiver can be created with maps
// which will then be passed to Untar operations
Archiver struct {
Untar func(io.Reader, string, *TarOptions) error
Untar func(io.Reader, string, *TarOptions) error
UIDMaps []idtools.IDMap
GIDMaps []idtools.IDMap
}
// breakoutError is used to differentiate errors related to breaking out
@ -66,7 +73,7 @@ type (
var (
// ErrNotImplemented is the error message of function not implemented.
ErrNotImplemented = errors.New("Function not implemented")
defaultArchiver = &Archiver{Untar}
defaultArchiver = &Archiver{Untar: Untar, UIDMaps: nil, GIDMaps: nil}
)
const (
@ -194,6 +201,8 @@ type tarAppender struct {
// for hardlink mapping
SeenFiles map[uint64]string
UIDMaps []idtools.IDMap
GIDMaps []idtools.IDMap
}
// canonicalTarName provides a platform-independent and consistent posix-style
@ -261,6 +270,25 @@ func (ta *tarAppender) addTarFile(path, name string) error {
hdr.Xattrs["security.capability"] = string(capability)
}
//handle re-mapping container ID mappings back to host ID mappings before
//writing tar headers/files
if ta.UIDMaps != nil || ta.GIDMaps != nil {
uid, gid, err := getFileUIDGID(fi.Sys())
if err != nil {
return err
}
xUID, err := idtools.ToContainer(uid, ta.UIDMaps)
if err != nil {
return err
}
xGID, err := idtools.ToContainer(gid, ta.GIDMaps)
if err != nil {
return err
}
hdr.Uid = xUID
hdr.Gid = xGID
}
if err := ta.TarWriter.WriteHeader(hdr); err != nil {
return err
}
@ -427,6 +455,8 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
TarWriter: tar.NewWriter(compressWriter),
Buffer: pools.BufioWriter32KPool.Get(nil),
SeenFiles: make(map[uint64]string),
UIDMaps: options.UIDMaps,
GIDMaps: options.GIDMaps,
}
defer func() {
@ -554,6 +584,10 @@ func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) err
defer pools.BufioReader32KPool.Put(trBuf)
var dirs []*tar.Header
remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps)
if err != nil {
return err
}
// Iterate through the files in the archive.
loop:
@ -631,6 +665,28 @@ loop:
}
trBuf.Reset(tr)
// if the options contain a uid & gid maps, convert header uid/gid
// entries using the maps such that lchown sets the proper mapped
// uid/gid after writing the file. We only perform this mapping if
// the file isn't already owned by the remapped root UID or GID, as
// that specific uid/gid has no mapping from container -> host, and
// those files already have the proper ownership for inside the
// container.
if hdr.Uid != remappedRootUID {
xUID, err := idtools.ToHost(hdr.Uid, options.UIDMaps)
if err != nil {
return err
}
hdr.Uid = xUID
}
if hdr.Gid != remappedRootGID {
xGID, err := idtools.ToHost(hdr.Gid, options.GIDMaps)
if err != nil {
return err
}
hdr.Gid = xGID
}
if err := createTarFile(path, dest, hdr, trBuf, !options.NoLchown, options.ChownOpts); err != nil {
return err
}
@ -703,7 +759,15 @@ func (archiver *Archiver) TarUntar(src, dst string) error {
return err
}
defer archive.Close()
return archiver.Untar(archive, dst, nil)
var options *TarOptions
if archiver.UIDMaps != nil || archiver.GIDMaps != nil {
options = &TarOptions{
UIDMaps: archiver.UIDMaps,
GIDMaps: archiver.GIDMaps,
}
}
return archiver.Untar(archive, dst, options)
}
// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other.
@ -719,7 +783,14 @@ func (archiver *Archiver) UntarPath(src, dst string) error {
return err
}
defer archive.Close()
if err := archiver.Untar(archive, dst, nil); err != nil {
var options *TarOptions
if archiver.UIDMaps != nil || archiver.GIDMaps != nil {
options = &TarOptions{
UIDMaps: archiver.UIDMaps,
GIDMaps: archiver.GIDMaps,
}
}
if err := archiver.Untar(archive, dst, options); err != nil {
return err
}
return nil
@ -801,6 +872,28 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
hdr.Name = filepath.Base(dst)
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(archiver.UIDMaps, archiver.GIDMaps)
if err != nil {
return err
}
// only perform mapping if the file being copied isn't already owned by the
// uid or gid of the remapped root in the container
if remappedRootUID != hdr.Uid {
xUID, err := idtools.ToHost(hdr.Uid, archiver.UIDMaps)
if err != nil {
return err
}
hdr.Uid = xUID
}
if remappedRootGID != hdr.Gid {
xGID, err := idtools.ToHost(hdr.Gid, archiver.GIDMaps)
if err != nil {
return err
}
hdr.Gid = xGID
}
tw := tar.NewWriter(w)
defer tw.Close()
if err := tw.WriteHeader(hdr); err != nil {
@ -816,6 +909,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
err = er
}
}()
return archiver.Untar(r, filepath.Dir(dst), nil)
}

View file

@ -61,6 +61,15 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st
return
}
func getFileUIDGID(stat interface{}) (int, int, error) {
s, ok := stat.(*syscall.Stat_t)
if !ok {
return -1, -1, errors.New("cannot convert stat value to syscall.Stat_t")
}
return int(s.Uid), int(s.Gid), nil
}
func major(device uint64) uint64 {
return (device >> 8) & 0xfff
}

View file

@ -63,3 +63,8 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
return nil
}
func getFileUIDGID(stat interface{}) (int, int, error) {
// no notion of file ownership mapping yet on Windows
return 0, 0, nil
}

View file

@ -14,6 +14,7 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/system"
)
@ -341,13 +342,15 @@ func ChangesSize(newDir string, changes []Change) int64 {
}
// ExportChanges produces an Archive from the provided changes, relative to dir.
func ExportChanges(dir string, changes []Change) (Archive, error) {
func ExportChanges(dir string, changes []Change, uidMaps, gidMaps []idtools.IDMap) (Archive, error) {
reader, writer := io.Pipe()
go func() {
ta := &tarAppender{
TarWriter: tar.NewWriter(writer),
Buffer: pools.BufioWriter32KPool.Get(nil),
SeenFiles: make(map[uint64]string),
UIDMaps: uidMaps,
GIDMaps: gidMaps,
}
// this buffer is needed for the duration of this piped stream
defer pools.BufioWriter32KPool.Put(ta.Buffer)

View file

@ -61,7 +61,7 @@ func TestHardLinkOrder(t *testing.T) {
sort.Sort(changesByPath(changes))
// ExportChanges
ar, err := ExportChanges(dest, changes)
ar, err := ExportChanges(dest, changes, nil, nil)
if err != nil {
t.Fatal(err)
}
@ -73,7 +73,7 @@ func TestHardLinkOrder(t *testing.T) {
// reverse sort
sort.Sort(sort.Reverse(changesByPath(changes)))
// ExportChanges
arRev, err := ExportChanges(dest, changes)
arRev, err := ExportChanges(dest, changes, nil, nil)
if err != nil {
t.Fatal(err)
}

View file

@ -410,7 +410,7 @@ func TestApplyLayer(t *testing.T) {
t.Fatal(err)
}
layer, err := ExportChanges(dst, changes)
layer, err := ExportChanges(dst, changes, nil, nil)
if err != nil {
t.Fatal(err)
}

View file

@ -11,6 +11,7 @@ import (
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/system"
)
@ -18,16 +19,23 @@ import (
// UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be
// compressed or uncompressed.
// Returns the size in bytes of the contents of the layer.
func UnpackLayer(dest string, layer Reader) (size int64, err error) {
func UnpackLayer(dest string, layer Reader, options *TarOptions) (size int64, err error) {
tr := tar.NewReader(layer)
trBuf := pools.BufioReader32KPool.Get(tr)
defer pools.BufioReader32KPool.Put(trBuf)
var dirs []*tar.Header
remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps)
if err != nil {
return 0, err
}
aufsTempdir := ""
aufsHardlinks := make(map[string]*tar.Header)
if options == nil {
options = &TarOptions{}
}
// Iterate through the files in the archive.
for {
hdr, err := tr.Next()
@ -169,6 +177,27 @@ func UnpackLayer(dest string, layer Reader) (size int64, err error) {
srcData = tmpFile
}
// if the options contain a uid & gid maps, convert header uid/gid
// entries using the maps such that lchown sets the proper mapped
// uid/gid after writing the file. We only perform this mapping if
// the file isn't already owned by the remapped root UID or GID, as
// that specific uid/gid has no mapping from container -> host, and
// those files already have the proper ownership for inside the
// container.
if srcHdr.Uid != remappedRootUID {
xUID, err := idtools.ToHost(srcHdr.Uid, options.UIDMaps)
if err != nil {
return 0, err
}
srcHdr.Uid = xUID
}
if srcHdr.Gid != remappedRootGID {
xGID, err := idtools.ToHost(srcHdr.Gid, options.GIDMaps)
if err != nil {
return 0, err
}
srcHdr.Gid = xGID
}
if err := createTarFile(path, dest, srcHdr, srcData, true, nil); err != nil {
return 0, err
}
@ -196,19 +225,19 @@ func UnpackLayer(dest string, layer Reader) (size int64, err error) {
// compressed or uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyLayer(dest string, layer Reader) (int64, error) {
return applyLayerHandler(dest, layer, true)
return applyLayerHandler(dest, layer, &TarOptions{}, true)
}
// ApplyUncompressedLayer parses a diff in the standard layer format from
// `layer`, and applies it to the directory `dest`. The stream `layer`
// can only be uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyUncompressedLayer(dest string, layer Reader) (int64, error) {
return applyLayerHandler(dest, layer, false)
func ApplyUncompressedLayer(dest string, layer Reader, options *TarOptions) (int64, error) {
return applyLayerHandler(dest, layer, options, false)
}
// do the bulk load of ApplyLayer, but allow for not calling DecompressStream
func applyLayerHandler(dest string, layer Reader, decompress bool) (int64, error) {
func applyLayerHandler(dest string, layer Reader, options *TarOptions, decompress bool) (int64, error) {
dest = filepath.Clean(dest)
// We need to be able to set any perms
@ -224,5 +253,5 @@ func applyLayerHandler(dest string, layer Reader, decompress bool) (int64, error
return 0, err
}
}
return UnpackLayer(dest, layer)
return UnpackLayer(dest, layer, options)
}

View file

@ -7,13 +7,13 @@ import "github.com/docker/docker/pkg/archive"
// uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyLayer(dest string, layer archive.Reader) (size int64, err error) {
return applyLayerHandler(dest, layer, true)
return applyLayerHandler(dest, layer, &archive.TarOptions{}, true)
}
// ApplyUncompressedLayer parses a diff in the standard layer format from
// `layer`, and applies it to the directory `dest`. The stream `layer`
// can only be uncompressed.
// Returns the size in bytes of the contents of the layer.
func ApplyUncompressedLayer(dest string, layer archive.Reader) (int64, error) {
return applyLayerHandler(dest, layer, false)
func ApplyUncompressedLayer(dest string, layer archive.Reader, options *archive.TarOptions) (int64, error) {
return applyLayerHandler(dest, layer, options, false)
}

View file

@ -27,8 +27,9 @@ type applyLayerResponse struct {
func applyLayer() {
var (
tmpDir = ""
err error
tmpDir = ""
err error
options *archive.TarOptions
)
runtime.LockOSThread()
flag.Parse()
@ -44,12 +45,16 @@ func applyLayer() {
fatal(err)
}
if err := json.Unmarshal([]byte(os.Getenv("OPT")), &options); err != nil {
fatal(err)
}
if tmpDir, err = ioutil.TempDir("/", "temp-docker-extract"); err != nil {
fatal(err)
}
os.Setenv("TMPDIR", tmpDir)
size, err := archive.UnpackLayer("/", os.Stdin)
size, err := archive.UnpackLayer("/", os.Stdin, options)
os.RemoveAll(tmpDir)
if err != nil {
fatal(err)
@ -68,7 +73,7 @@ func applyLayer() {
// applyLayerHandler parses a diff in the standard layer format from `layer`, and
// applies it to the directory `dest`. Returns the size in bytes of the
// contents of the layer.
func applyLayerHandler(dest string, layer archive.Reader, decompress bool) (size int64, err error) {
func applyLayerHandler(dest string, layer archive.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) {
dest = filepath.Clean(dest)
if decompress {
decompressed, err := archive.DecompressStream(layer)
@ -79,9 +84,21 @@ func applyLayerHandler(dest string, layer archive.Reader, decompress bool) (size
layer = decompressed
}
if options == nil {
options = &archive.TarOptions{}
}
if options.ExcludePatterns == nil {
options.ExcludePatterns = []string{}
}
data, err := json.Marshal(options)
if err != nil {
return 0, fmt.Errorf("ApplyLayer json encode: %v", err)
}
cmd := reexec.Command("docker-applyLayer", dest)
cmd.Stdin = layer
cmd.Env = append(cmd.Env, fmt.Sprintf("OPT=%s", data))
outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer)
cmd.Stdout, cmd.Stderr = outBuf, errBuf

View file

@ -13,7 +13,7 @@ import (
// applyLayerHandler parses a diff in the standard layer format from `layer`, and
// applies it to the directory `dest`. Returns the size in bytes of the
// contents of the layer.
func applyLayerHandler(dest string, layer archive.Reader, decompress bool) (size int64, err error) {
func applyLayerHandler(dest string, layer archive.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) {
dest = filepath.Clean(dest)
// Ensure it is a Windows-style volume path
@ -34,7 +34,7 @@ func applyLayerHandler(dest string, layer archive.Reader, decompress bool) (size
return 0, fmt.Errorf("ApplyLayer failed to create temp-docker-extract under %s. %s", dest, err)
}
s, err := archive.UnpackLayer(dest, layer)
s, err := archive.UnpackLayer(dest, layer, nil)
os.RemoveAll(tmpDir)
if err != nil {
return 0, fmt.Errorf("ApplyLayer %s failed UnpackLayer to %s", err, dest)

View file

@ -0,0 +1,26 @@
package directory
import (
"io/ioutil"
"os"
"path/filepath"
)
// MoveToSubdir moves all contents of a directory to a subdirectory underneath the original path
func MoveToSubdir(oldpath, subdir string) error {
infos, err := ioutil.ReadDir(oldpath)
if err != nil {
return err
}
for _, info := range infos {
if info.Name() != subdir {
oldName := filepath.Join(oldpath, info.Name())
newName := filepath.Join(oldpath, subdir, info.Name())
if err := os.Rename(oldName, newName); err != nil {
return err
}
}
}
return nil
}

View file

@ -3,6 +3,9 @@ package directory
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"testing"
)
@ -135,3 +138,45 @@ func TestSizeFileAndNestedDirectoryNonempty(t *testing.T) {
t.Fatalf("directory with 6-byte file and nested directory with 6-byte file has size: %d", size)
}
}
// Test migration of directory to a subdir underneath itself
func TestMoveToSubdir(t *testing.T) {
var outerDir, subDir string
var err error
if outerDir, err = ioutil.TempDir(os.TempDir(), "TestMoveToSubdir"); err != nil {
t.Fatalf("failed to create directory: %v", err)
}
if subDir, err = ioutil.TempDir(outerDir, "testSub"); err != nil {
t.Fatalf("failed to create subdirectory: %v", err)
}
// write 4 temp files in the outer dir to get moved
filesList := []string{"a", "b", "c", "d"}
for _, fName := range filesList {
if file, err := os.Create(filepath.Join(outerDir, fName)); err != nil {
t.Fatalf("couldn't create temp file %q: %v", fName, err)
} else {
file.WriteString(fName)
file.Close()
}
}
if err = MoveToSubdir(outerDir, filepath.Base(subDir)); err != nil {
t.Fatalf("Error during migration of content to subdirectory: %v", err)
}
// validate that the files were moved to the subdirectory
infos, err := ioutil.ReadDir(subDir)
if len(infos) != 4 {
t.Fatalf("Should be four files in the subdir after the migration: actual length: %d", len(infos))
}
var results []string
for _, info := range infos {
results = append(results, info.Name())
}
sort.Sort(sort.StringSlice(results))
if !reflect.DeepEqual(filesList, results) {
t.Fatalf("Results after migration do not equal list of files: expected: %v, got: %v", filesList, results)
}
}

View file

@ -5,7 +5,6 @@ package directory
import (
"os"
"path/filepath"
"strings"
"github.com/docker/docker/pkg/longpath"
)

View file

@ -68,7 +68,11 @@ func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) e
return err
}
defer body.Close()
return json.NewDecoder(body).Decode(&ret)
if err := json.NewDecoder(body).Decode(&ret); err != nil {
logrus.Errorf("%s: error reading plugin resp: %v", serviceMethod, err)
return err
}
return nil
}
// Stream calls the specified method with the specified arguments for the plugin and returns the response body
@ -86,7 +90,11 @@ func (c *Client) SendFile(serviceMethod string, data io.Reader, ret interface{})
if err != nil {
return err
}
return json.NewDecoder(body).Decode(&ret)
if err := json.NewDecoder(body).Decode(&ret); err != nil {
logrus.Errorf("%s: error reading plugin resp: %v", serviceMethod, err)
return err
}
return nil
}
func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) (io.ReadCloser, error) {

View file

@ -11,6 +11,7 @@ import (
"path/filepath"
"sync"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/volume"
)
@ -28,10 +29,10 @@ var ErrNotFound = errors.New("volume not found")
// New instantiates a new Root instance with the provided scope. Scope
// is the base path that the Root instance uses to store its
// volumes. The base path is created here if it does not exist.
func New(scope string) (*Root, error) {
func New(scope string, rootUID, rootGID int) (*Root, error) {
rootDirectory := filepath.Join(scope, volumesPathName)
if err := os.MkdirAll(rootDirectory, 0700); err != nil {
if err := idtools.MkdirAllAs(rootDirectory, 0700, rootUID, rootGID); err != nil {
return nil, err
}
@ -39,6 +40,8 @@ func New(scope string) (*Root, error) {
scope: scope,
path: rootDirectory,
volumes: make(map[string]*localVolume),
rootUID: rootUID,
rootGID: rootGID,
}
dirs, err := ioutil.ReadDir(rootDirectory)
@ -66,6 +69,8 @@ type Root struct {
scope string
path string
volumes map[string]*localVolume
rootUID int
rootGID int
}
// List lists all the volumes
@ -100,7 +105,7 @@ func (r *Root) Create(name string, _ map[string]string) (volume.Volume, error) {
}
path := r.DataPath(name)
if err := os.MkdirAll(path, 0755); err != nil {
if err := idtools.MkdirAllAs(path, 0755, r.rootUID, r.rootGID); err != nil {
if os.IsExist(err) {
return nil, fmt.Errorf("volume already exists under %s", filepath.Dir(path))
}

View file

@ -13,7 +13,7 @@ func TestRemove(t *testing.T) {
}
defer os.RemoveAll(rootDir)
r, err := New(rootDir)
r, err := New(rootDir, 0, 0)
if err != nil {
t.Fatal(err)
}
@ -55,7 +55,7 @@ func TestInitializeWithVolumes(t *testing.T) {
}
defer os.RemoveAll(rootDir)
r, err := New(rootDir)
r, err := New(rootDir, 0, 0)
if err != nil {
t.Fatal(err)
}
@ -65,7 +65,7 @@ func TestInitializeWithVolumes(t *testing.T) {
t.Fatal(err)
}
r, err = New(rootDir)
r, err = New(rootDir, 0, 0)
if err != nil {
t.Fatal(err)
}