浏览代码

Fix one-character directory issue in the volume option (#20122).

The issue comes from the implementation of volumeSplitN() where a
driver letter (`[a-zA-Z]:`) was assumed to follow either `:`, `/`,
or `\\`.

In Windows driver letter appears in two situations:
a. `^[a-zA-Z]:` (A colon followed  by `^[a-zA-Z]:` is OK as colon is
the separator in volume option)
b. A string in the format like `\\?\C:\Windows\...` (UNC).
Therefore, a driver letter can only follow either a `:` or `\\`

This PR removes the condition of `/` before the driver letter so
that options like `-v /tmp/q:/foo` could be handled correctly. A
couple of tests has also been added.

This PR fixes #20122.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
Yong Tang 9 年之前
父节点
当前提交
800a7d513d
共有 3 个文件被更改,包括 20 次插入3 次删除
  1. 9 0
      integration-cli/docker_cli_run_test.go
  2. 8 3
      runconfig/opts/parse.go
  3. 3 0
      runconfig/opts/parse_test.go

+ 9 - 0
integration-cli/docker_cli_run_test.go

@@ -4243,3 +4243,12 @@ func (s *DockerSuite) TestRunAttachFailedNoLeak(c *check.C) {
 	// NGoroutines is not updated right away, so we need to wait before failing
 	// NGoroutines is not updated right away, so we need to wait before failing
 	c.Assert(waitForGoroutines(nroutines), checker.IsNil)
 	c.Assert(waitForGoroutines(nroutines), checker.IsNil)
 }
 }
+
+// Test for one character directory name case (#20122)
+func (s *DockerSuite) TestRunVolumeWithOneCharacter(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+
+	out, _ := dockerCmd(c, "run", "-v", "/tmp/q:/foo", "busybox", "sh", "-c", "find /foo")
+	fmt.Printf("OUTPUT: %+v", out)
+	c.Assert(strings.TrimSpace(out), checker.Equals, "/foo")
+}

+ 8 - 3
runconfig/opts/parse.go

@@ -695,8 +695,12 @@ func validatePath(val string, validator func(string) bool) (string, error) {
 }
 }
 
 
 // volumeSplitN splits raw into a maximum of n parts, separated by a separator colon.
 // volumeSplitN splits raw into a maximum of n parts, separated by a separator colon.
-// A separator colon is the last `:` character in the regex `[/:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
-// This allows to correctly split strings such as `C:\foo:D:\:rw`.
+// A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
+// In Windows driver letter appears in two situations:
+// a. `^[a-zA-Z]:` (A colon followed  by `^[a-zA-Z]:` is OK as colon is the separator in volume option)
+// b. A string in the format like `\\?\C:\Windows\...` (UNC).
+// Therefore, a driver letter can only follow either a `:` or `\\`
+// This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`.
 func volumeSplitN(raw string, n int) []string {
 func volumeSplitN(raw string, n int) []string {
 	var array []string
 	var array []string
 	if len(raw) == 0 || raw[0] == ':' {
 	if len(raw) == 0 || raw[0] == ':' {
@@ -721,7 +725,8 @@ func volumeSplitN(raw string, n int) []string {
 		if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
 		if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
 			if right > 1 {
 			if right > 1 {
 				beforePotentialDriveLetter := raw[right-2]
 				beforePotentialDriveLetter := raw[right-2]
-				if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '/' && beforePotentialDriveLetter != '\\' {
+				// Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`)
+				if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' {
 					// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
 					// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
 					array = append(array, raw[left:right])
 					array = append(array, raw[left:right])
 					left = right + 1
 					left = right + 1

+ 3 - 0
runconfig/opts/parse_test.go

@@ -801,6 +801,9 @@ func TestVolumeSplitN(t *testing.T) {
 		{`..\`, -1, []string{`..\`}},
 		{`..\`, -1, []string{`..\`}},
 		{`c:\:..\`, -1, []string{`c:\`, `..\`}},
 		{`c:\:..\`, -1, []string{`c:\`, `..\`}},
 		{`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
 		{`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
+
+		// Cover directories with one-character name
+		{`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}},
 	} {
 	} {
 		res := volumeSplitN(x.input, x.n)
 		res := volumeSplitN(x.input, x.n)
 		if len(res) < len(x.expected) {
 		if len(res) < len(x.expected) {