Browse code

Windows: docker cp consistent paths

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2016/04/27 11:26:12
Showing 6 changed files
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"path/filepath"
6 6
 
7 7
 	"github.com/docker/docker/pkg/archive"
8
+	"github.com/docker/docker/pkg/system"
8 9
 	"github.com/docker/engine-api/types"
9 10
 )
10 11
 
... ...
@@ -13,6 +14,12 @@ import (
13 13
 // the absolute path to the resource relative to the container's rootfs, and
14 14
 // an error if the path points to outside the container's rootfs.
15 15
 func (container *Container) ResolvePath(path string) (resolvedPath, absPath string, err error) {
16
+	// Check if a drive letter supplied, it must be the system drive. No-op except on Windows
17
+	path, err = system.CheckSystemDriveAndRemoveDriveLetter(path)
18
+	if err != nil {
19
+		return "", "", err
20
+	}
21
+
16 22
 	// Consider the given path as an absolute path in the container.
17 23
 	absPath = archive.PreserveTrailingDotOrSeparator(filepath.Join(string(filepath.Separator), path), path)
18 24
 
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"github.com/docker/docker/pkg/chrootarchive"
14 14
 	"github.com/docker/docker/pkg/idtools"
15 15
 	"github.com/docker/docker/pkg/ioutils"
16
+	"github.com/docker/docker/pkg/system"
16 17
 	"github.com/docker/engine-api/types"
17 18
 )
18 19
 
... ...
@@ -188,6 +189,12 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path
188 188
 		return err
189 189
 	}
190 190
 
191
+	// Check if a drive letter supplied, it must be the system drive. No-op except on Windows
192
+	path, err = system.CheckSystemDriveAndRemoveDriveLetter(path)
193
+	if err != nil {
194
+		return err
195
+	}
196
+
191 197
 	// The destination path needs to be resolved to a host path, with all
192 198
 	// symbolic links followed in the scope of the container's rootfs. Note
193 199
 	// that we do not use `container.ResolvePath(path)` here because we need
... ...
@@ -3,9 +3,10 @@
3 3
 package daemon
4 4
 
5 5
 import (
6
-	"github.com/docker/docker/container"
7 6
 	"os"
8 7
 	"path/filepath"
8
+
9
+	"github.com/docker/docker/container"
9 10
 )
10 11
 
11 12
 // checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
... ...
@@ -6,3 +6,9 @@ package system
6 6
 // executables. Each directory is separated from the next by a colon
7 7
 // ':' character .
8 8
 const DefaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
9
+
10
+// CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter,
11
+// is the system drive. This is a no-op on Linux.
12
+func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) {
13
+	return path, nil
14
+}
... ...
@@ -2,6 +2,36 @@
2 2
 
3 3
 package system
4 4
 
5
+import (
6
+	"fmt"
7
+	"path/filepath"
8
+	"strings"
9
+)
10
+
5 11
 // DefaultPathEnv is deliberately empty on Windows as the default path will be set by
6 12
 // the container. Docker has no context of what the default path should be.
7 13
 const DefaultPathEnv = ""
14
+
15
+// CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path.
16
+// This is used, for example, when validating a user provided path in docker cp.
17
+// If a drive letter is supplied, it must be the system drive. The drive letter
18
+// is always removed. Also, it translates it to OS semantics (IOW / to \). We
19
+// need the path in this syntax so that it can ultimately be contatenated with
20
+// a Windows long-path which doesn't support drive-letters. Examples:
21
+// C:			--> Fail
22
+// C:\			--> \
23
+// a			--> a
24
+// /a			--> \a
25
+// d:\			--> Fail
26
+func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) {
27
+	if len(path) == 2 && string(path[1]) == ":" {
28
+		return "", fmt.Errorf("No relative path specified in %q", path)
29
+	}
30
+	if !filepath.IsAbs(path) || len(path) < 2 {
31
+		return filepath.FromSlash(path), nil
32
+	}
33
+	if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") {
34
+		return "", fmt.Errorf("The specified path is not on the system drive (C:)")
35
+	}
36
+	return filepath.FromSlash(path[2:]), nil
37
+}
8 38
new file mode 100644
... ...
@@ -0,0 +1,78 @@
0
+// +build windows
1
+
2
+package system
3
+
4
+import "testing"
5
+
6
+// TestCheckSystemDriveAndRemoveDriveLetter tests CheckSystemDriveAndRemoveDriveLetter
7
+func TestCheckSystemDriveAndRemoveDriveLetter(t *testing.T) {
8
+	// Fails if not C drive.
9
+	path, err := CheckSystemDriveAndRemoveDriveLetter(`d:\`)
10
+	if err == nil || (err != nil && err.Error() != "The specified path is not on the system drive (C:)") {
11
+		t.Fatalf("Expected error for d:")
12
+	}
13
+
14
+	// Single character is unchanged
15
+	if path, err = CheckSystemDriveAndRemoveDriveLetter("z"); err != nil {
16
+		t.Fatalf("Single character should pass")
17
+	}
18
+	if path != "z" {
19
+		t.Fatalf("Single character should be unchanged")
20
+	}
21
+
22
+	// Two characters without colon is unchanged
23
+	if path, err = CheckSystemDriveAndRemoveDriveLetter("AB"); err != nil {
24
+		t.Fatalf("2 characters without colon should pass")
25
+	}
26
+	if path != "AB" {
27
+		t.Fatalf("2 characters without colon should be unchanged")
28
+	}
29
+
30
+	// Abs path without drive letter
31
+	if path, err = CheckSystemDriveAndRemoveDriveLetter(`\l`); err != nil {
32
+		t.Fatalf("abs path no drive letter should pass")
33
+	}
34
+	if path != `\l` {
35
+		t.Fatalf("abs path without drive letter should be unchanged")
36
+	}
37
+
38
+	// Abs path without drive letter, linux style
39
+	if path, err = CheckSystemDriveAndRemoveDriveLetter(`/l`); err != nil {
40
+		t.Fatalf("abs path no drive letter linux style should pass")
41
+	}
42
+	if path != `\l` {
43
+		t.Fatalf("abs path without drive letter linux failed %s", path)
44
+	}
45
+
46
+	// Drive-colon should be stripped
47
+	if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:\`); err != nil {
48
+		t.Fatalf("An absolute path should pass")
49
+	}
50
+	if path != `\` {
51
+		t.Fatalf(`An absolute path should have been shortened to \ %s`, path)
52
+	}
53
+
54
+	// Verify with a linux-style path
55
+	if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:/`); err != nil {
56
+		t.Fatalf("An absolute path should pass")
57
+	}
58
+	if path != `\` {
59
+		t.Fatalf(`A linux style absolute path should have been shortened to \ %s`, path)
60
+	}
61
+
62
+	// Failure on c:
63
+	if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:`); err == nil {
64
+		t.Fatalf("c: should fail")
65
+	}
66
+	if err.Error() != `No relative path specified in "c:"` {
67
+		t.Fatalf(path, err)
68
+	}
69
+
70
+	// Failure on d:
71
+	if path, err = CheckSystemDriveAndRemoveDriveLetter(`d:`); err == nil {
72
+		t.Fatalf("c: should fail")
73
+	}
74
+	if err.Error() != `No relative path specified in "d:"` {
75
+		t.Fatalf(path, err)
76
+	}
77
+}