Browse code

Ensure opaque directory permissions respected

When converting an opaque directory always keep the original
directory tar entry to ensure directory is created with correct
permissions on restore.

Closes #27298

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)

Derek McGowan authored on 2016/10/25 09:03:56
Showing 3 changed files
... ...
@@ -241,7 +241,7 @@ func (compression *Compression) Extension() string {
241 241
 }
242 242
 
243 243
 type tarWhiteoutConverter interface {
244
-	ConvertWrite(*tar.Header, string, os.FileInfo) error
244
+	ConvertWrite(*tar.Header, string, os.FileInfo) (*tar.Header, error)
245 245
 	ConvertRead(*tar.Header, string) (bool, error)
246 246
 }
247 247
 
... ...
@@ -348,9 +348,25 @@ func (ta *tarAppender) addTarFile(path, name string) error {
348 348
 	}
349 349
 
350 350
 	if ta.WhiteoutConverter != nil {
351
-		if err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi); err != nil {
351
+		wo, err := ta.WhiteoutConverter.ConvertWrite(hdr, path, fi)
352
+		if err != nil {
352 353
 			return err
353 354
 		}
355
+
356
+		// If a new whiteout file exists, write original hdr, then
357
+		// replace hdr with wo to be written after. Whiteouts should
358
+		// always be written after the original. Note the original
359
+		// hdr may have been updated to be a whiteout with returning
360
+		// a whiteout header
361
+		if wo != nil {
362
+			if err := ta.TarWriter.WriteHeader(hdr); err != nil {
363
+				return err
364
+			}
365
+			if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
366
+				return fmt.Errorf("tar: cannot use whiteout for non-empty file")
367
+			}
368
+			hdr = wo
369
+		}
354 370
 	}
355 371
 
356 372
 	if err := ta.TarWriter.WriteHeader(hdr); err != nil {
... ...
@@ -19,7 +19,7 @@ func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
19 19
 
20 20
 type overlayWhiteoutConverter struct{}
21 21
 
22
-func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) error {
22
+func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, err error) {
23 23
 	// convert whiteouts to AUFS format
24 24
 	if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 {
25 25
 		// we just rename the file and make it normal
... ...
@@ -34,12 +34,16 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
34 34
 		// convert opaque dirs to AUFS format by writing an empty file with the prefix
35 35
 		opaque, err := system.Lgetxattr(path, "trusted.overlay.opaque")
36 36
 		if err != nil {
37
-			return err
37
+			return nil, err
38 38
 		}
39 39
 		if len(opaque) == 1 && opaque[0] == 'y' {
40
+			if hdr.Xattrs != nil {
41
+				delete(hdr.Xattrs, "trusted.overlay.opaque")
42
+			}
43
+
40 44
 			// create a header for the whiteout file
41 45
 			// it should inherit some properties from the parent, but be a regular file
42
-			*hdr = tar.Header{
46
+			wo = &tar.Header{
43 47
 				Typeflag:   tar.TypeReg,
44 48
 				Mode:       hdr.Mode & int64(os.ModePerm),
45 49
 				Name:       filepath.Join(hdr.Name, WhiteoutOpaqueDir),
... ...
@@ -54,7 +58,7 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
54 54
 		}
55 55
 	}
56 56
 
57
-	return nil
57
+	return
58 58
 }
59 59
 
60 60
 func (overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) {
61 61
new file mode 100644
... ...
@@ -0,0 +1,187 @@
0
+package archive
1
+
2
+import (
3
+	"io/ioutil"
4
+	"os"
5
+	"path/filepath"
6
+	"syscall"
7
+	"testing"
8
+
9
+	"github.com/docker/docker/pkg/system"
10
+)
11
+
12
+// setupOverlayTestDir creates files in a directory with overlay whiteouts
13
+// Tree layout
14
+// .
15
+// ├── d1     # opaque, 0700
16
+// │   └── f1 # empty file, 0600
17
+// ├── d2     # opaque, 0750
18
+// │   └── f1 # empty file, 0660
19
+// └── d3     # 0700
20
+//     └── f1 # whiteout, 0644
21
+func setupOverlayTestDir(t *testing.T, src string) {
22
+	// Create opaque directory containing single file and permission 0700
23
+	if err := os.Mkdir(filepath.Join(src, "d1"), 0700); err != nil {
24
+		t.Fatal(err)
25
+	}
26
+
27
+	if err := system.Lsetxattr(filepath.Join(src, "d1"), "trusted.overlay.opaque", []byte("y"), 0); err != nil {
28
+		t.Fatal(err)
29
+	}
30
+
31
+	if err := ioutil.WriteFile(filepath.Join(src, "d1", "f1"), []byte{}, 0600); err != nil {
32
+		t.Fatal(err)
33
+	}
34
+
35
+	// Create another opaque directory containing single file but with permission 0750
36
+	if err := os.Mkdir(filepath.Join(src, "d2"), 0750); err != nil {
37
+		t.Fatal(err)
38
+	}
39
+
40
+	if err := system.Lsetxattr(filepath.Join(src, "d2"), "trusted.overlay.opaque", []byte("y"), 0); err != nil {
41
+		t.Fatal(err)
42
+	}
43
+
44
+	if err := ioutil.WriteFile(filepath.Join(src, "d2", "f1"), []byte{}, 0660); err != nil {
45
+		t.Fatal(err)
46
+	}
47
+
48
+	// Create regular directory with deleted file
49
+	if err := os.Mkdir(filepath.Join(src, "d3"), 0700); err != nil {
50
+		t.Fatal(err)
51
+	}
52
+
53
+	if err := system.Mknod(filepath.Join(src, "d3", "f1"), syscall.S_IFCHR, 0); err != nil {
54
+		t.Fatal(err)
55
+	}
56
+}
57
+
58
+func checkOpaqueness(t *testing.T, path string, opaque string) {
59
+	xattrOpaque, err := system.Lgetxattr(path, "trusted.overlay.opaque")
60
+	if err != nil {
61
+		t.Fatal(err)
62
+	}
63
+	if string(xattrOpaque) != opaque {
64
+		t.Fatalf("Unexpected opaque value: %q, expected %q", string(xattrOpaque), opaque)
65
+	}
66
+
67
+}
68
+
69
+func checkOverlayWhiteout(t *testing.T, path string) {
70
+	stat, err := os.Stat(path)
71
+	if err != nil {
72
+		t.Fatal(err)
73
+	}
74
+	statT, ok := stat.Sys().(*syscall.Stat_t)
75
+	if !ok {
76
+		t.Fatalf("Unexpected type: %t, expected *syscall.Stat_t", stat.Sys())
77
+	}
78
+	if statT.Rdev != 0 {
79
+		t.Fatalf("Non-zero device number for whiteout")
80
+	}
81
+}
82
+
83
+func checkFileMode(t *testing.T, path string, perm os.FileMode) {
84
+	stat, err := os.Stat(path)
85
+	if err != nil {
86
+		t.Fatal(err)
87
+	}
88
+	if stat.Mode() != perm {
89
+		t.Fatalf("Unexpected file mode for %s: %o, expected %o", path, stat.Mode(), perm)
90
+	}
91
+}
92
+
93
+func TestOverlayTarUntar(t *testing.T) {
94
+	oldmask, err := system.Umask(0)
95
+	if err != nil {
96
+		t.Fatal(err)
97
+	}
98
+	defer system.Umask(oldmask)
99
+
100
+	src, err := ioutil.TempDir("", "docker-test-overlay-tar-src")
101
+	if err != nil {
102
+		t.Fatal(err)
103
+	}
104
+	defer os.RemoveAll(src)
105
+
106
+	setupOverlayTestDir(t, src)
107
+
108
+	dst, err := ioutil.TempDir("", "docker-test-overlay-tar-dst")
109
+	if err != nil {
110
+		t.Fatal(err)
111
+	}
112
+	defer os.RemoveAll(dst)
113
+
114
+	options := &TarOptions{
115
+		Compression:    Uncompressed,
116
+		WhiteoutFormat: OverlayWhiteoutFormat,
117
+	}
118
+	archive, err := TarWithOptions(src, options)
119
+	if err != nil {
120
+		t.Fatal(err)
121
+	}
122
+	defer archive.Close()
123
+
124
+	if err := Untar(archive, dst, options); err != nil {
125
+		t.Fatal(err)
126
+	}
127
+
128
+	checkFileMode(t, filepath.Join(dst, "d1"), 0700|os.ModeDir)
129
+	checkFileMode(t, filepath.Join(dst, "d2"), 0750|os.ModeDir)
130
+	checkFileMode(t, filepath.Join(dst, "d3"), 0700|os.ModeDir)
131
+	checkFileMode(t, filepath.Join(dst, "d1", "f1"), 0600)
132
+	checkFileMode(t, filepath.Join(dst, "d2", "f1"), 0660)
133
+	checkFileMode(t, filepath.Join(dst, "d3", "f1"), os.ModeCharDevice|os.ModeDevice)
134
+
135
+	checkOpaqueness(t, filepath.Join(dst, "d1"), "y")
136
+	checkOpaqueness(t, filepath.Join(dst, "d2"), "y")
137
+	checkOpaqueness(t, filepath.Join(dst, "d3"), "")
138
+	checkOverlayWhiteout(t, filepath.Join(dst, "d3", "f1"))
139
+}
140
+
141
+func TestOverlayTarAUFSUntar(t *testing.T) {
142
+	oldmask, err := system.Umask(0)
143
+	if err != nil {
144
+		t.Fatal(err)
145
+	}
146
+	defer system.Umask(oldmask)
147
+
148
+	src, err := ioutil.TempDir("", "docker-test-overlay-tar-src")
149
+	if err != nil {
150
+		t.Fatal(err)
151
+	}
152
+	defer os.RemoveAll(src)
153
+
154
+	setupOverlayTestDir(t, src)
155
+
156
+	dst, err := ioutil.TempDir("", "docker-test-overlay-tar-dst")
157
+	if err != nil {
158
+		t.Fatal(err)
159
+	}
160
+	defer os.RemoveAll(dst)
161
+
162
+	archive, err := TarWithOptions(src, &TarOptions{
163
+		Compression:    Uncompressed,
164
+		WhiteoutFormat: OverlayWhiteoutFormat,
165
+	})
166
+	if err != nil {
167
+		t.Fatal(err)
168
+	}
169
+	defer archive.Close()
170
+
171
+	if err := Untar(archive, dst, &TarOptions{
172
+		Compression:    Uncompressed,
173
+		WhiteoutFormat: AUFSWhiteoutFormat,
174
+	}); err != nil {
175
+		t.Fatal(err)
176
+	}
177
+
178
+	checkFileMode(t, filepath.Join(dst, "d1"), 0700|os.ModeDir)
179
+	checkFileMode(t, filepath.Join(dst, "d1", WhiteoutOpaqueDir), 0700)
180
+	checkFileMode(t, filepath.Join(dst, "d2"), 0750|os.ModeDir)
181
+	checkFileMode(t, filepath.Join(dst, "d2", WhiteoutOpaqueDir), 0750)
182
+	checkFileMode(t, filepath.Join(dst, "d3"), 0700|os.ModeDir)
183
+	checkFileMode(t, filepath.Join(dst, "d1", "f1"), 0600)
184
+	checkFileMode(t, filepath.Join(dst, "d2", "f1"), 0660)
185
+	checkFileMode(t, filepath.Join(dst, "d3", WhiteoutPrefix+"f1"), 0600)
186
+}