Browse code

Make `docker cp` bind-mount volumes

Allows `docker cp` to work seamlessly, and a lot more cleanly.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2015/02/25 07:17:13
Showing 3 changed files
... ...
@@ -970,37 +970,35 @@ func (container *Container) GetSize() (int64, int64) {
970 970
 }
971 971
 
972 972
 func (container *Container) Copy(resource string) (io.ReadCloser, error) {
973
+	container.Lock()
974
+	defer container.Unlock()
975
+	var err error
973 976
 	if err := container.Mount(); err != nil {
974 977
 		return nil, err
975 978
 	}
979
+	defer func() {
980
+		if err != nil {
981
+			container.Unmount()
982
+		}
983
+	}()
976 984
 
977
-	basePath, err := container.getResourcePath(resource)
978
-	if err != nil {
979
-		container.Unmount()
985
+	if err = container.mountVolumes(); err != nil {
986
+		container.unmountVolumes()
980 987
 		return nil, err
981 988
 	}
982
-
983
-	// Check if this is actually in a volume
984
-	for _, mnt := range container.VolumeMounts() {
985
-		if len(mnt.MountToPath) > 0 && strings.HasPrefix(resource, mnt.MountToPath[1:]) {
986
-			return mnt.Export(resource)
989
+	defer func() {
990
+		if err != nil {
991
+			container.unmountVolumes()
987 992
 		}
988
-	}
993
+	}()
989 994
 
990
-	// Check if this is a special one (resolv.conf, hostname, ..)
991
-	if resource == "etc/resolv.conf" {
992
-		basePath = container.ResolvConfPath
993
-	}
994
-	if resource == "etc/hostname" {
995
-		basePath = container.HostnamePath
996
-	}
997
-	if resource == "etc/hosts" {
998
-		basePath = container.HostsPath
995
+	basePath, err := container.getResourcePath(resource)
996
+	if err != nil {
997
+		return nil, err
999 998
 	}
1000 999
 
1001 1000
 	stat, err := os.Stat(basePath)
1002 1001
 	if err != nil {
1003
-		container.Unmount()
1004 1002
 		return nil, err
1005 1003
 	}
1006 1004
 	var filter []string
... ...
@@ -1018,11 +1016,12 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
1018 1018
 		IncludeFiles: filter,
1019 1019
 	})
1020 1020
 	if err != nil {
1021
-		container.Unmount()
1022 1021
 		return nil, err
1023 1022
 	}
1023
+
1024 1024
 	return ioutils.NewReadCloserWrapper(archive, func() error {
1025 1025
 			err := archive.Close()
1026
+			container.unmountVolumes()
1026 1027
 			container.Unmount()
1027 1028
 			return err
1028 1029
 		}),
... ...
@@ -2,7 +2,6 @@ package daemon
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"io"
6 5
 	"io/ioutil"
7 6
 	"os"
8 7
 	"path/filepath"
... ...
@@ -12,6 +11,7 @@ import (
12 12
 	"github.com/Sirupsen/logrus"
13 13
 	"github.com/docker/docker/daemon/execdriver"
14 14
 	"github.com/docker/docker/pkg/chrootarchive"
15
+	"github.com/docker/docker/pkg/mount"
15 16
 	"github.com/docker/docker/pkg/symlink"
16 17
 	"github.com/docker/docker/pkg/system"
17 18
 	"github.com/docker/docker/volumes"
... ...
@@ -27,18 +27,6 @@ type Mount struct {
27 27
 	isBind      bool
28 28
 }
29 29
 
30
-func (mnt *Mount) Export(resource string) (io.ReadCloser, error) {
31
-	var name string
32
-	if resource == mnt.MountToPath[1:] {
33
-		name = filepath.Base(resource)
34
-	}
35
-	path, err := filepath.Rel(mnt.MountToPath[1:], resource)
36
-	if err != nil {
37
-		return nil, err
38
-	}
39
-	return mnt.volume.Export(path, name)
40
-}
41
-
42 30
 func (container *Container) prepareVolumes() error {
43 31
 	if container.Volumes == nil || len(container.Volumes) == 0 {
44 32
 		container.Volumes = make(map[string]string)
... ...
@@ -320,6 +308,20 @@ func validMountMode(mode string) bool {
320 320
 	return validModes[mode]
321 321
 }
322 322
 
323
+func (container *Container) specialMounts() []execdriver.Mount {
324
+	var mounts []execdriver.Mount
325
+	if container.ResolvConfPath != "" {
326
+		mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true})
327
+	}
328
+	if container.HostnamePath != "" {
329
+		mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true})
330
+	}
331
+	if container.HostsPath != "" {
332
+		mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true})
333
+	}
334
+	return mounts
335
+}
336
+
323 337
 func (container *Container) setupMounts() error {
324 338
 	mounts := []execdriver.Mount{}
325 339
 
... ...
@@ -336,17 +338,7 @@ func (container *Container) setupMounts() error {
336 336
 		})
337 337
 	}
338 338
 
339
-	if container.ResolvConfPath != "" {
340
-		mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true})
341
-	}
342
-
343
-	if container.HostnamePath != "" {
344
-		mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true})
345
-	}
346
-
347
-	if container.HostsPath != "" {
348
-		mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true})
349
-	}
339
+	mounts = append(mounts, container.specialMounts()...)
350 340
 
351 341
 	container.command.Mounts = mounts
352 342
 	return nil
... ...
@@ -401,3 +393,57 @@ func copyOwnership(source, destination string) error {
401 401
 
402 402
 	return os.Chmod(destination, os.FileMode(stat.Mode()))
403 403
 }
404
+
405
+func (container *Container) mountVolumes() error {
406
+	for dest, source := range container.Volumes {
407
+		v := container.daemon.volumes.Get(source)
408
+		if v == nil {
409
+			return fmt.Errorf("could not find volume for %s:%s, impossible to mount", source, dest)
410
+		}
411
+
412
+		destPath, err := container.getResourcePath(dest)
413
+		if err != nil {
414
+			return err
415
+		}
416
+
417
+		if err := mount.Mount(source, destPath, "bind", "rbind,rw"); err != nil {
418
+			return fmt.Errorf("error while mounting volume %s: %v", source, err)
419
+		}
420
+	}
421
+
422
+	for _, mnt := range container.specialMounts() {
423
+		destPath, err := container.getResourcePath(mnt.Destination)
424
+		if err != nil {
425
+			return err
426
+		}
427
+		if err := mount.Mount(mnt.Source, destPath, "bind", "bind,rw"); err != nil {
428
+			return fmt.Errorf("error while mounting volume %s: %v", mnt.Source, err)
429
+		}
430
+	}
431
+	return nil
432
+}
433
+
434
+func (container *Container) unmountVolumes() {
435
+	for dest := range container.Volumes {
436
+		destPath, err := container.getResourcePath(dest)
437
+		if err != nil {
438
+			logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
439
+			continue
440
+		}
441
+		if err := mount.ForceUnmount(destPath); err != nil {
442
+			logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
443
+			continue
444
+		}
445
+	}
446
+
447
+	for _, mnt := range container.specialMounts() {
448
+		destPath, err := container.getResourcePath(mnt.Destination)
449
+		if err != nil {
450
+			logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
451
+			continue
452
+		}
453
+		if err := mount.ForceUnmount(destPath); err != nil {
454
+			logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
455
+		}
456
+	}
457
+}
... ...
@@ -2,14 +2,11 @@ package volumes
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"io"
6 5
 	"io/ioutil"
7 6
 	"os"
8
-	"path"
9 7
 	"path/filepath"
10 8
 	"sync"
11 9
 
12
-	"github.com/docker/docker/pkg/archive"
13 10
 	"github.com/docker/docker/pkg/symlink"
14 11
 )
15 12
 
... ...
@@ -24,35 +21,6 @@ type Volume struct {
24 24
 	lock        sync.Mutex
25 25
 }
26 26
 
27
-func (v *Volume) Export(resource, name string) (io.ReadCloser, error) {
28
-	if v.IsBindMount && filepath.Base(resource) == name {
29
-		name = ""
30
-	}
31
-
32
-	basePath, err := v.getResourcePath(resource)
33
-	if err != nil {
34
-		return nil, err
35
-	}
36
-	stat, err := os.Stat(basePath)
37
-	if err != nil {
38
-		return nil, err
39
-	}
40
-	var filter []string
41
-	if !stat.IsDir() {
42
-		d, f := path.Split(basePath)
43
-		basePath = d
44
-		filter = []string{f}
45
-	} else {
46
-		filter = []string{path.Base(basePath)}
47
-		basePath = path.Dir(basePath)
48
-	}
49
-	return archive.TarWithOptions(basePath, &archive.TarOptions{
50
-		Compression:  archive.Uncompressed,
51
-		Name:         name,
52
-		IncludeFiles: filter,
53
-	})
54
-}
55
-
56 27
 func (v *Volume) IsDir() (bool, error) {
57 28
 	stat, err := os.Stat(v.Path)
58 29
 	if err != nil {