From 8ce42baaef314a75bb6726891774393d540e9d06 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 24 Feb 2015 17:17:13 -0500 Subject: [PATCH] Make `docker cp` bind-mount volumes Allows `docker cp` to work seamlessly, and a lot more cleanly. Signed-off-by: Brian Goff --- daemon/container.go | 41 ++++++++++---------- daemon/volumes.go | 94 +++++++++++++++++++++++++++++++++------------ volumes/volume.go | 32 --------------- 3 files changed, 90 insertions(+), 77 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 228944d2d4..f144934c32 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -970,37 +970,35 @@ func (container *Container) GetSize() (int64, int64) { } func (container *Container) Copy(resource string) (io.ReadCloser, error) { + container.Lock() + defer container.Unlock() + var err error if err := container.Mount(); err != nil { return nil, err } + defer func() { + if err != nil { + container.Unmount() + } + }() + + if err = container.mountVolumes(); err != nil { + container.unmountVolumes() + return nil, err + } + defer func() { + if err != nil { + container.unmountVolumes() + } + }() basePath, err := container.getResourcePath(resource) if err != nil { - container.Unmount() return nil, err } - // Check if this is actually in a volume - for _, mnt := range container.VolumeMounts() { - if len(mnt.MountToPath) > 0 && strings.HasPrefix(resource, mnt.MountToPath[1:]) { - return mnt.Export(resource) - } - } - - // Check if this is a special one (resolv.conf, hostname, ..) - if resource == "etc/resolv.conf" { - basePath = container.ResolvConfPath - } - if resource == "etc/hostname" { - basePath = container.HostnamePath - } - if resource == "etc/hosts" { - basePath = container.HostsPath - } - stat, err := os.Stat(basePath) if err != nil { - container.Unmount() return nil, err } var filter []string @@ -1018,11 +1016,12 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { IncludeFiles: filter, }) if err != nil { - container.Unmount() return nil, err } + return ioutils.NewReadCloserWrapper(archive, func() error { err := archive.Close() + container.unmountVolumes() container.Unmount() return err }), diff --git a/daemon/volumes.go b/daemon/volumes.go index f40fdd3e49..7c6696d65f 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -2,7 +2,6 @@ package daemon import ( "fmt" - "io" "io/ioutil" "os" "path/filepath" @@ -12,6 +11,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/chrootarchive" + "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" "github.com/docker/docker/volumes" @@ -27,18 +27,6 @@ type Mount struct { isBind bool } -func (mnt *Mount) Export(resource string) (io.ReadCloser, error) { - var name string - if resource == mnt.MountToPath[1:] { - name = filepath.Base(resource) - } - path, err := filepath.Rel(mnt.MountToPath[1:], resource) - if err != nil { - return nil, err - } - return mnt.volume.Export(path, name) -} - func (container *Container) prepareVolumes() error { if container.Volumes == nil || len(container.Volumes) == 0 { container.Volumes = make(map[string]string) @@ -320,6 +308,20 @@ func validMountMode(mode string) bool { return validModes[mode] } +func (container *Container) specialMounts() []execdriver.Mount { + var mounts []execdriver.Mount + if container.ResolvConfPath != "" { + mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true}) + } + if container.HostnamePath != "" { + mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true}) + } + if container.HostsPath != "" { + mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true}) + } + return mounts +} + func (container *Container) setupMounts() error { mounts := []execdriver.Mount{} @@ -336,17 +338,7 @@ func (container *Container) setupMounts() error { }) } - if container.ResolvConfPath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true}) - } - - if container.HostnamePath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true}) - } - - if container.HostsPath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true}) - } + mounts = append(mounts, container.specialMounts()...) container.command.Mounts = mounts return nil @@ -401,3 +393,57 @@ func copyOwnership(source, destination string) error { return os.Chmod(destination, os.FileMode(stat.Mode())) } + +func (container *Container) mountVolumes() error { + for dest, source := range container.Volumes { + v := container.daemon.volumes.Get(source) + if v == nil { + return fmt.Errorf("could not find volume for %s:%s, impossible to mount", source, dest) + } + + destPath, err := container.getResourcePath(dest) + if err != nil { + return err + } + + if err := mount.Mount(source, destPath, "bind", "rbind,rw"); err != nil { + return fmt.Errorf("error while mounting volume %s: %v", source, err) + } + } + + for _, mnt := range container.specialMounts() { + destPath, err := container.getResourcePath(mnt.Destination) + if err != nil { + return err + } + if err := mount.Mount(mnt.Source, destPath, "bind", "bind,rw"); err != nil { + return fmt.Errorf("error while mounting volume %s: %v", mnt.Source, err) + } + } + return nil +} + +func (container *Container) unmountVolumes() { + for dest := range container.Volumes { + destPath, err := container.getResourcePath(dest) + if err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + continue + } + if err := mount.ForceUnmount(destPath); err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + continue + } + } + + for _, mnt := range container.specialMounts() { + destPath, err := container.getResourcePath(mnt.Destination) + if err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + continue + } + if err := mount.ForceUnmount(destPath); err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + } + } +} diff --git a/volumes/volume.go b/volumes/volume.go index c5191c48c5..0acc3068f3 100644 --- a/volumes/volume.go +++ b/volumes/volume.go @@ -2,14 +2,11 @@ package volumes import ( "encoding/json" - "io" "io/ioutil" "os" - "path" "path/filepath" "sync" - "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/symlink" ) @@ -24,35 +21,6 @@ type Volume struct { lock sync.Mutex } -func (v *Volume) Export(resource, name string) (io.ReadCloser, error) { - if v.IsBindMount && filepath.Base(resource) == name { - name = "" - } - - basePath, err := v.getResourcePath(resource) - if err != nil { - return nil, err - } - stat, err := os.Stat(basePath) - if err != nil { - return nil, err - } - var filter []string - if !stat.IsDir() { - d, f := path.Split(basePath) - basePath = d - filter = []string{f} - } else { - filter = []string{path.Base(basePath)} - basePath = path.Dir(basePath) - } - return archive.TarWithOptions(basePath, &archive.TarOptions{ - Compression: archive.Uncompressed, - Name: name, - IncludeFiles: filter, - }) -} - func (v *Volume) IsDir() (bool, error) { stat, err := os.Stat(v.Path) if err != nil {