Browse code

Use naive diff for overlay2 when opaque copy up bug present

When running on a kernel which is not patched for the copy up bug
overlay2 will use the naive diff driver.

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

Derek McGowan authored on 2016/11/10 06:07:56
Showing 3 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,79 @@
0
+// +build linux
1
+
2
+package overlay2
3
+
4
+import (
5
+	"fmt"
6
+	"io/ioutil"
7
+	"os"
8
+	"path"
9
+	"path/filepath"
10
+	"syscall"
11
+
12
+	"github.com/Sirupsen/logrus"
13
+	"github.com/docker/docker/pkg/system"
14
+	"github.com/pkg/errors"
15
+)
16
+
17
+// hasOpaqueCopyUpBug checks whether the filesystem has a bug
18
+// which copies up the opaque flag when copying up an opaque
19
+// directory. When this bug exists naive diff should be used.
20
+func hasOpaqueCopyUpBug(d string) error {
21
+	td, err := ioutil.TempDir(d, "opaque-bug-check")
22
+	if err != nil {
23
+		return err
24
+	}
25
+	defer func() {
26
+		if err := os.RemoveAll(td); err != nil {
27
+			logrus.Warnf("Failed to remove check directory %v: %v", td, err)
28
+		}
29
+	}()
30
+
31
+	// Make directories l1/d, l2/d, l3, work, merged
32
+	if err := os.MkdirAll(filepath.Join(td, "l1", "d"), 0755); err != nil {
33
+		return err
34
+	}
35
+	if err := os.MkdirAll(filepath.Join(td, "l2", "d"), 0755); err != nil {
36
+		return err
37
+	}
38
+	if err := os.Mkdir(filepath.Join(td, "l3"), 0755); err != nil {
39
+		return err
40
+	}
41
+	if err := os.Mkdir(filepath.Join(td, "work"), 0755); err != nil {
42
+		return err
43
+	}
44
+	if err := os.Mkdir(filepath.Join(td, "merged"), 0755); err != nil {
45
+		return err
46
+	}
47
+
48
+	// Mark l2/d as opaque
49
+	if err := system.Lsetxattr(filepath.Join(td, "l2", "d"), "trusted.overlay.opaque", []byte("y"), 0); err != nil {
50
+		return errors.Wrap(err, "failed to set opaque flag on middle layer")
51
+	}
52
+
53
+	opts := fmt.Sprintf("lowerdir=%s:%s,upperdir=%s,workdir=%s", path.Join(td, "l2"), path.Join(td, "l1"), path.Join(td, "l3"), path.Join(td, "work"))
54
+	if err := syscall.Mount("overlay", filepath.Join(td, "merged"), "overlay", 0, opts); err != nil {
55
+		return errors.Wrap(err, "failed to mount overlay")
56
+	}
57
+	defer func() {
58
+		if err := syscall.Unmount(filepath.Join(td, "merged"), 0); err != nil {
59
+			logrus.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
60
+		}
61
+	}()
62
+
63
+	// Touch file in d to force copy up of opaque directory "d" from "l2" to "l3"
64
+	if err := ioutil.WriteFile(filepath.Join(td, "merged", "d", "f"), []byte{}, 0644); err != nil {
65
+		return errors.Wrap(err, "failed to write to merged directory")
66
+	}
67
+
68
+	// Check l3/d does not have opaque flag
69
+	xattrOpaque, err := system.Lgetxattr(filepath.Join(td, "l3", "d"), "trusted.overlay.opaque")
70
+	if err != nil {
71
+		return errors.Wrap(err, "failed to read opaque flag on upper layer")
72
+	}
73
+	if string(xattrOpaque) == "y" {
74
+		return errors.New("opaque flag erroneously copied up, consider update to kernel 4.8 or later to fix")
75
+	}
76
+
77
+	return nil
78
+}
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"path/filepath"
15 15
 	"strconv"
16 16
 	"strings"
17
+	"sync"
17 18
 	"syscall"
18 19
 
19 20
 	"github.com/Sirupsen/logrus"
... ...
@@ -102,6 +103,9 @@ type Driver struct {
102 102
 var (
103 103
 	backingFs             = "<unknown>"
104 104
 	projectQuotaSupported = false
105
+
106
+	useNaiveDiffLock sync.Once
107
+	useNaiveDiffOnly bool
105 108
 )
106 109
 
107 110
 func init() {
... ...
@@ -235,6 +239,16 @@ func supportsOverlay() error {
235 235
 	return graphdriver.ErrNotSupported
236 236
 }
237 237
 
238
+func useNaiveDiff(home string) bool {
239
+	useNaiveDiffLock.Do(func() {
240
+		if err := hasOpaqueCopyUpBug(home); err != nil {
241
+			logrus.Warnf("Not using native diff for overlay2: %v", err)
242
+			useNaiveDiffOnly = true
243
+		}
244
+	})
245
+	return useNaiveDiffOnly
246
+}
247
+
238 248
 func (d *Driver) String() string {
239 249
 	return driverName
240 250
 }
... ...
@@ -245,6 +259,7 @@ func (d *Driver) Status() [][2]string {
245 245
 	return [][2]string{
246 246
 		{"Backing Filesystem", backingFs},
247 247
 		{"Supports d_type", strconv.FormatBool(d.supportsDType)},
248
+		{"Native Overlay Diff", strconv.FormatBool(!useNaiveDiff(d.home))},
248 249
 	}
249 250
 }
250 251
 
... ...
@@ -606,7 +621,7 @@ func (d *Driver) getDiffPath(id string) string {
606 606
 // and its parent and returns the size in bytes of the changes
607 607
 // relative to its base filesystem directory.
608 608
 func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
609
-	if !d.isParent(id, parent) {
609
+	if useNaiveDiff(d.home) || !d.isParent(id, parent) {
610 610
 		return d.naiveDiff.DiffSize(id, parent)
611 611
 	}
612 612
 	return directory.Size(d.getDiffPath(id))
... ...
@@ -615,7 +630,7 @@ func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
615 615
 // Diff produces an archive of the changes between the specified
616 616
 // layer and its parent layer which may be "".
617 617
 func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
618
-	if !d.isParent(id, parent) {
618
+	if useNaiveDiff(d.home) || !d.isParent(id, parent) {
619 619
 		return d.naiveDiff.Diff(id, parent)
620 620
 	}
621 621
 
... ...
@@ -632,7 +647,7 @@ func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
632 632
 // Changes produces a list of changes between the specified layer
633 633
 // and its parent layer. If parent is "", then all changes will be ADD changes.
634 634
 func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
635
-	if !d.isParent(id, parent) {
635
+	if useNaiveDiff(d.home) || !d.isParent(id, parent) {
636 636
 		return d.naiveDiff.Changes(id, parent)
637 637
 	}
638 638
 	// Overlay doesn't have snapshots, so we need to get changes from all parent
... ...
@@ -7267,3 +7267,23 @@ func (s *DockerSuite) TestBuildContChar(c *check.C) {
7267 7267
 	c.Assert(out, checker.Contains, "Step 1/2 : FROM busybox")
7268 7268
 	c.Assert(out, checker.Contains, "Step 2/2 : RUN echo hi \\\\\n")
7269 7269
 }
7270
+
7271
+// TestBuildOpaqueDirectory tests that a build succeeds which
7272
+// creates opaque directories.
7273
+// See https://github.com/docker/docker/issues/25244
7274
+func (s *DockerSuite) TestBuildOpaqueDirectory(c *check.C) {
7275
+	testRequires(c, DaemonIsLinux)
7276
+
7277
+	dockerFile := `
7278
+		FROM busybox
7279
+		RUN mkdir /dir1 && touch /dir1/f1
7280
+		RUN rm -rf /dir1 && mkdir /dir1 && touch /dir1/f2
7281
+		RUN touch /dir1/f3
7282
+		RUN [ -f /dir1/f2 ]
7283
+		`
7284
+
7285
+	// Test that build succeeds, last command fails if opaque directory
7286
+	// was not handled correctly
7287
+	_, err := buildImage("testopaquedirectory", dockerFile, false)
7288
+	c.Assert(err, checker.IsNil)
7289
+}