Browse code

pkg/archive: adjust chmod bits on windows

This change modifies the chmod bits of build context archives built on
windows to preserve the execute bit and remove the r/w bits from
grp/others.

Also adjusted integ-cli tests to verify permissions based on the platform
the tests are running.

Fixes #11047.

Signed-off-by: Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>

Ahmet Alp Balkan authored on 2015/03/04 11:40:16
Showing 8 changed files
... ...
@@ -690,15 +690,15 @@ func TestBuildSixtySteps(t *testing.T) {
690 690
 func TestBuildAddSingleFileToRoot(t *testing.T) {
691 691
 	name := "testaddimg"
692 692
 	defer deleteImages(name)
693
-	ctx, err := fakeContext(`FROM busybox
693
+	ctx, err := fakeContext(fmt.Sprintf(`FROM busybox
694 694
 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
695 695
 RUN echo 'dockerio:x:1001:' >> /etc/group
696 696
 RUN touch /exists
697 697
 RUN chown dockerio.dockerio /exists
698 698
 ADD test_file /
699 699
 RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ]
700
-RUN [ $(ls -l /test_file | awk '{print $1}') = '-rw-r--r--' ]
701
-RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`,
700
+RUN [ $(ls -l /test_file | awk '{print $1}') = '%s' ]
701
+RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod),
702 702
 		map[string]string{
703 703
 			"test_file": "test1",
704 704
 		})
... ...
@@ -1263,7 +1263,7 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`,
1263 1263
 func TestBuildAddWholeDirToRoot(t *testing.T) {
1264 1264
 	name := "testaddwholedirtoroot"
1265 1265
 	defer deleteImages(name)
1266
-	ctx, err := fakeContext(`FROM busybox
1266
+	ctx, err := fakeContext(fmt.Sprintf(`FROM busybox
1267 1267
 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
1268 1268
 RUN echo 'dockerio:x:1001:' >> /etc/group
1269 1269
 RUN touch /exists
... ...
@@ -1272,8 +1272,8 @@ ADD test_dir /test_dir
1272 1272
 RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ]
1273 1273
 RUN [ $(ls -l / | grep test_dir | awk '{print $1}') = 'drwxr-xr-x' ]
1274 1274
 RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ]
1275
-RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '-rw-r--r--' ]
1276
-RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`,
1275
+RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '%s' ]
1276
+RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod),
1277 1277
 		map[string]string{
1278 1278
 			"test_dir/test_file": "test1",
1279 1279
 		})
... ...
@@ -1336,15 +1336,15 @@ RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ]`,
1336 1336
 func TestBuildCopySingleFileToRoot(t *testing.T) {
1337 1337
 	name := "testcopysinglefiletoroot"
1338 1338
 	defer deleteImages(name)
1339
-	ctx, err := fakeContext(`FROM busybox
1339
+	ctx, err := fakeContext(fmt.Sprintf(`FROM busybox
1340 1340
 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
1341 1341
 RUN echo 'dockerio:x:1001:' >> /etc/group
1342 1342
 RUN touch /exists
1343 1343
 RUN chown dockerio.dockerio /exists
1344 1344
 COPY test_file /
1345 1345
 RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ]
1346
-RUN [ $(ls -l /test_file | awk '{print $1}') = '-rw-r--r--' ]
1347
-RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`,
1346
+RUN [ $(ls -l /test_file | awk '{print $1}') = '%s' ]
1347
+RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod),
1348 1348
 		map[string]string{
1349 1349
 			"test_file": "test1",
1350 1350
 		})
... ...
@@ -1496,7 +1496,7 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`,
1496 1496
 func TestBuildCopyWholeDirToRoot(t *testing.T) {
1497 1497
 	name := "testcopywholedirtoroot"
1498 1498
 	defer deleteImages(name)
1499
-	ctx, err := fakeContext(`FROM busybox
1499
+	ctx, err := fakeContext(fmt.Sprintf(`FROM busybox
1500 1500
 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
1501 1501
 RUN echo 'dockerio:x:1001:' >> /etc/group
1502 1502
 RUN touch /exists
... ...
@@ -1505,8 +1505,8 @@ COPY test_dir /test_dir
1505 1505
 RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ]
1506 1506
 RUN [ $(ls -l / | grep test_dir | awk '{print $1}') = 'drwxr-xr-x' ]
1507 1507
 RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ]
1508
-RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '-rw-r--r--' ]
1509
-RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`,
1508
+RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '%s' ]
1509
+RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expectedFileChmod),
1510 1510
 		map[string]string{
1511 1511
 			"test_dir/test_file": "test1",
1512 1512
 		})
... ...
@@ -5,4 +5,6 @@ package main
5 5
 const (
6 6
 	// identifies if test suite is running on a unix platform
7 7
 	isUnixCli = true
8
+
9
+	expectedFileChmod = "-rw-r--r--"
8 10
 )
... ...
@@ -5,4 +5,7 @@ package main
5 5
 const (
6 6
 	// identifies if test suite is running on a unix platform
7 7
 	isUnixCli = false
8
+
9
+	// this is the expected file permission set on windows: gh#11047
10
+	expectedFileChmod = "-rwx------"
8 11
 )
... ...
@@ -204,6 +204,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
204 204
 	if err != nil {
205 205
 		return err
206 206
 	}
207
+	hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
207 208
 
208 209
 	name, err = canonicalTarName(name, fi.IsDir())
209 210
 	if err != nil {
... ...
@@ -696,6 +697,8 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
696 696
 			return err
697 697
 		}
698 698
 		hdr.Name = filepath.Base(dst)
699
+		hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
700
+
699 701
 		tw := tar.NewWriter(w)
700 702
 		defer tw.Close()
701 703
 		if err := tw.WriteHeader(hdr); err != nil {
... ...
@@ -4,6 +4,7 @@ package archive
4 4
 
5 5
 import (
6 6
 	"errors"
7
+	"os"
7 8
 	"syscall"
8 9
 
9 10
 	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
... ...
@@ -16,6 +17,13 @@ func CanonicalTarNameForPath(p string) (string, error) {
16 16
 	return p, nil // already unix-style
17 17
 }
18 18
 
19
+// chmodTarEntry is used to adjust the file permissions used in tar header based
20
+// on the platform the archival is done.
21
+
22
+func chmodTarEntry(perm os.FileMode) os.FileMode {
23
+	return perm // noop for unix as golang APIs provide perm bits correctly
24
+}
25
+
19 26
 func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) {
20 27
 	s, ok := stat.(*syscall.Stat_t)
21 28
 
... ...
@@ -3,6 +3,7 @@
3 3
 package archive
4 4
 
5 5
 import (
6
+	"os"
6 7
 	"testing"
7 8
 )
8 9
 
... ...
@@ -40,3 +41,20 @@ func TestCanonicalTarName(t *testing.T) {
40 40
 		}
41 41
 	}
42 42
 }
43
+
44
+func TestChmodTarEntry(t *testing.T) {
45
+	cases := []struct {
46
+		in, expected os.FileMode
47
+	}{
48
+		{0000, 0000},
49
+		{0777, 0777},
50
+		{0644, 0644},
51
+		{0755, 0755},
52
+		{0444, 0444},
53
+	}
54
+	for _, v := range cases {
55
+		if out := chmodTarEntry(v.in); out != v.expected {
56
+			t.Fatalf("wrong chmod. expected:%v got:%v", v.expected, out)
57
+		}
58
+	}
59
+}
... ...
@@ -4,6 +4,7 @@ package archive
4 4
 
5 5
 import (
6 6
 	"fmt"
7
+	"os"
7 8
 	"strings"
8 9
 
9 10
 	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
... ...
@@ -23,6 +24,17 @@ func CanonicalTarNameForPath(p string) (string, error) {
23 23
 	return strings.Replace(p, "\\", "/", -1), nil
24 24
 }
25 25
 
26
+// chmodTarEntry is used to adjust the file permissions used in tar header based
27
+// on the platform the archival is done.
28
+func chmodTarEntry(perm os.FileMode) os.FileMode {
29
+	// Clear r/w on grp/others: no precise equivalen of group/others on NTFS.
30
+	perm &= 0711
31
+	// Add the x bit: make everything +x from windows
32
+	perm |= 0100
33
+
34
+	return perm
35
+}
36
+
26 37
 func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) {
27 38
 	// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
28 39
 	return
... ...
@@ -3,6 +3,7 @@
3 3
 package archive
4 4
 
5 5
 import (
6
+	"os"
6 7
 	"testing"
7 8
 )
8 9
 
... ...
@@ -46,3 +47,20 @@ func TestCanonicalTarName(t *testing.T) {
46 46
 		}
47 47
 	}
48 48
 }
49
+
50
+func TestChmodTarEntry(t *testing.T) {
51
+	cases := []struct {
52
+		in, expected os.FileMode
53
+	}{
54
+		{0000, 0100},
55
+		{0777, 0711},
56
+		{0644, 0700},
57
+		{0755, 0711},
58
+		{0444, 0500},
59
+	}
60
+	for _, v := range cases {
61
+		if out := chmodTarEntry(v.in); out != v.expected {
62
+			t.Fatalf("wrong chmod. expected:%v got:%v", v.expected, out)
63
+		}
64
+	}
65
+}