Skip to content

Commit 28842d3

Browse files
committed
pkg/archive: Canonicalize stored paths
Currently pkg/archive stores nested windows files with backslashes (e.g. `dir\`, `dir\file.txt`) and this causes tar not being correctly extracted on Linux daemon. This change assures we canonicalize all paths to unix paths and add them to tar with that name independent of platform. Fixes the following test cases for Windows CI: - TestBuildAddFileWithWhitespace - TestBuildCopyFileWithWhitespace - TestBuildAddDirContentToRoot - TestBuildAddDirContentToExistingDir - TestBuildCopyDirContentToRoot - TestBuildCopyDirContentToExistDir - TestBuildDockerignore - TestBuildEnvUsage - TestBuildEnvUsage2 Signed-off-by: Ahmet Alp Balkan <[email protected]>
1 parent 2243e32 commit 28842d3

5 files changed

Lines changed: 132 additions & 3 deletions

File tree

pkg/archive/archive.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,21 @@ type tarAppender struct {
172172
SeenFiles map[uint64]string
173173
}
174174

175+
// canonicalTarName provides a platform-independent and consistent posix-style
176+
//path for files and directories to be archived regardless of the platform.
177+
func canonicalTarName(name string, isDir bool) (string, error) {
178+
name, err := canonicalTarNameForPath(name)
179+
if err != nil {
180+
return "", err
181+
}
182+
183+
// suffix with '/' for directories
184+
if isDir && !strings.HasSuffix(name, "/") {
185+
name += "/"
186+
}
187+
return name, nil
188+
}
189+
175190
func (ta *tarAppender) addTarFile(path, name string) error {
176191
fi, err := os.Lstat(path)
177192
if err != nil {
@@ -190,10 +205,10 @@ func (ta *tarAppender) addTarFile(path, name string) error {
190205
return err
191206
}
192207

193-
if fi.IsDir() && !strings.HasSuffix(name, "/") {
194-
name = name + "/"
208+
name, err = canonicalTarName(name, fi.IsDir())
209+
if err != nil {
210+
return fmt.Errorf("tar: cannot canonicalize path: %v", err)
195211
}
196-
197212
hdr.Name = name
198213

199214
nlink, inode, err := setHeaderForSpecialDevice(hdr, ta, name, fi.Sys())

pkg/archive/archive_unix.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import (
99
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
1010
)
1111

12+
// canonicalTarNameForPath returns platform-specific filepath
13+
// to canonical posix-style path for tar archival. p is relative
14+
// path.
15+
func canonicalTarNameForPath(p string) (string, error) {
16+
return p, nil // already unix-style
17+
}
18+
1219
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) {
1320
s, ok := stat.(*syscall.Stat_t)
1421

pkg/archive/archive_unix_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// +build !windows
2+
3+
package archive
4+
5+
import (
6+
"testing"
7+
)
8+
9+
func TestCanonicalTarNameForPath(t *testing.T) {
10+
cases := []struct{ in, expected string }{
11+
{"foo", "foo"},
12+
{"foo/bar", "foo/bar"},
13+
{"foo/dir/", "foo/dir/"},
14+
}
15+
for _, v := range cases {
16+
if out, err := canonicalTarNameForPath(v.in); err != nil {
17+
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
18+
} else if out != v.expected {
19+
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
20+
}
21+
}
22+
}
23+
24+
func TestCanonicalTarName(t *testing.T) {
25+
cases := []struct {
26+
in string
27+
isDir bool
28+
expected string
29+
}{
30+
{"foo", false, "foo"},
31+
{"foo", true, "foo/"},
32+
{"foo/bar", false, "foo/bar"},
33+
{"foo/bar", true, "foo/bar/"},
34+
}
35+
for _, v := range cases {
36+
if out, err := canonicalTarName(v.in, v.isDir); err != nil {
37+
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
38+
} else if out != v.expected {
39+
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
40+
}
41+
}
42+
}

pkg/archive/archive_windows.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,26 @@
33
package archive
44

55
import (
6+
"fmt"
7+
"strings"
8+
69
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
710
)
811

12+
// canonicalTarNameForPath returns platform-specific filepath
13+
// to canonical posix-style path for tar archival. p is relative
14+
// path.
15+
func canonicalTarNameForPath(p string) (string, error) {
16+
// windows: convert windows style relative path with backslashes
17+
// into forward slashes. since windows does not allow '/' or '\'
18+
// in file names, it is mostly safe to replace however we must
19+
// check just in case
20+
if strings.Contains(p, "/") {
21+
return "", fmt.Errorf("windows path contains forward slash: %s", p)
22+
}
23+
return strings.Replace(p, "\\", "/", -1), nil
24+
}
25+
926
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) {
1027
// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
1128
return
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// +build windows
2+
3+
package archive
4+
5+
import (
6+
"testing"
7+
)
8+
9+
func TestCanonicalTarNameForPath(t *testing.T) {
10+
cases := []struct {
11+
in, expected string
12+
shouldFail bool
13+
}{
14+
{"foo", "foo", false},
15+
{"foo/bar", "___", true}, // unix-styled windows path must fail
16+
{`foo\bar`, "foo/bar", false},
17+
{`foo\bar`, "foo/bar/", false},
18+
}
19+
for _, v := range cases {
20+
if out, err := canonicalTarNameForPath(v.in); err != nil && !v.shouldFail {
21+
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
22+
} else if v.shouldFail && err == nil {
23+
t.Fatalf("canonical path call should have pailed with error. in=%s out=%s", v.in, out)
24+
} else if !v.shouldFail && out != v.expected {
25+
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
26+
}
27+
}
28+
}
29+
30+
func TestCanonicalTarName(t *testing.T) {
31+
cases := []struct {
32+
in string
33+
isDir bool
34+
expected string
35+
}{
36+
{"foo", false, "foo"},
37+
{"foo", true, "foo/"},
38+
{`foo\bar`, false, "foo/bar"},
39+
{`foo\bar`, true, "foo/bar/"},
40+
}
41+
for _, v := range cases {
42+
if out, err := canonicalTarName(v.in, v.isDir); err != nil {
43+
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
44+
} else if out != v.expected {
45+
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)