Skip to content

Commit 64b43ed

Browse files
committed
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 <[email protected]> (github: dmcgowan)
1 parent 9e2d4f9 commit 64b43ed

3 files changed

Lines changed: 117 additions & 3 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// +build linux
2+
3+
package overlay2
4+
5+
import (
6+
"fmt"
7+
"io/ioutil"
8+
"os"
9+
"path"
10+
"path/filepath"
11+
"syscall"
12+
13+
"github.com/Sirupsen/logrus"
14+
"github.com/docker/docker/pkg/system"
15+
"github.com/pkg/errors"
16+
)
17+
18+
// hasOpaqueCopyUpBug checks whether the filesystem has a bug
19+
// which copies up the opaque flag when copying up an opaque
20+
// directory. When this bug exists naive diff should be used.
21+
func hasOpaqueCopyUpBug(d string) error {
22+
td, err := ioutil.TempDir(d, "opaque-bug-check")
23+
if err != nil {
24+
return err
25+
}
26+
defer func() {
27+
if err := os.RemoveAll(td); err != nil {
28+
logrus.Warnf("Failed to remove check directory %v: %v", td, err)
29+
}
30+
}()
31+
32+
// Make directories l1/d, l2/d, l3, work, merged
33+
if err := os.MkdirAll(filepath.Join(td, "l1", "d"), 0755); err != nil {
34+
return err
35+
}
36+
if err := os.MkdirAll(filepath.Join(td, "l2", "d"), 0755); err != nil {
37+
return err
38+
}
39+
if err := os.Mkdir(filepath.Join(td, "l3"), 0755); err != nil {
40+
return err
41+
}
42+
if err := os.Mkdir(filepath.Join(td, "work"), 0755); err != nil {
43+
return err
44+
}
45+
if err := os.Mkdir(filepath.Join(td, "merged"), 0755); err != nil {
46+
return err
47+
}
48+
49+
// Mark l2/d as opaque
50+
if err := system.Lsetxattr(filepath.Join(td, "l2", "d"), "trusted.overlay.opaque", []byte("y"), 0); err != nil {
51+
return errors.Wrap(err, "failed to set opaque flag on middle layer")
52+
}
53+
54+
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"))
55+
if err := syscall.Mount("overlay", filepath.Join(td, "merged"), "overlay", 0, opts); err != nil {
56+
return errors.Wrap(err, "failed to mount overlay")
57+
}
58+
defer func() {
59+
if err := syscall.Unmount(filepath.Join(td, "merged"), 0); err != nil {
60+
logrus.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
61+
}
62+
}()
63+
64+
// Touch file in d to force copy up of opaque directory "d" from "l2" to "l3"
65+
if err := ioutil.WriteFile(filepath.Join(td, "merged", "d", "f"), []byte{}, 0644); err != nil {
66+
return errors.Wrap(err, "failed to write to merged directory")
67+
}
68+
69+
// Check l3/d does not have opaque flag
70+
xattrOpaque, err := system.Lgetxattr(filepath.Join(td, "l3", "d"), "trusted.overlay.opaque")
71+
if err != nil {
72+
return errors.Wrap(err, "failed to read opaque flag on upper layer")
73+
}
74+
if string(xattrOpaque) == "y" {
75+
return errors.New("opaque flag erroneously copied up, consider update to kernel 4.8 or later to fix")
76+
}
77+
78+
return nil
79+
}

daemon/graphdriver/overlay2/overlay.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"path/filepath"
1515
"strconv"
1616
"strings"
17+
"sync"
1718
"syscall"
1819

1920
"github.com/Sirupsen/logrus"
@@ -102,6 +103,9 @@ type Driver struct {
102103
var (
103104
backingFs = "<unknown>"
104105
projectQuotaSupported = false
106+
107+
useNaiveDiffLock sync.Once
108+
useNaiveDiffOnly bool
105109
)
106110

107111
func init() {
@@ -235,6 +239,16 @@ func supportsOverlay() error {
235239
return graphdriver.ErrNotSupported
236240
}
237241

242+
func useNaiveDiff(home string) bool {
243+
useNaiveDiffLock.Do(func() {
244+
if err := hasOpaqueCopyUpBug(home); err != nil {
245+
logrus.Warnf("Not using native diff for overlay2: %v", err)
246+
useNaiveDiffOnly = true
247+
}
248+
})
249+
return useNaiveDiffOnly
250+
}
251+
238252
func (d *Driver) String() string {
239253
return driverName
240254
}
@@ -245,6 +259,7 @@ func (d *Driver) Status() [][2]string {
245259
return [][2]string{
246260
{"Backing Filesystem", backingFs},
247261
{"Supports d_type", strconv.FormatBool(d.supportsDType)},
262+
{"Native Overlay Diff", strconv.FormatBool(!useNaiveDiff(d.home))},
248263
}
249264
}
250265

@@ -606,7 +621,7 @@ func (d *Driver) getDiffPath(id string) string {
606621
// and its parent and returns the size in bytes of the changes
607622
// relative to its base filesystem directory.
608623
func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
609-
if !d.isParent(id, parent) {
624+
if useNaiveDiff(d.home) || !d.isParent(id, parent) {
610625
return d.naiveDiff.DiffSize(id, parent)
611626
}
612627
return directory.Size(d.getDiffPath(id))
@@ -615,7 +630,7 @@ func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
615630
// Diff produces an archive of the changes between the specified
616631
// layer and its parent layer which may be "".
617632
func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
618-
if !d.isParent(id, parent) {
633+
if useNaiveDiff(d.home) || !d.isParent(id, parent) {
619634
return d.naiveDiff.Diff(id, parent)
620635
}
621636

@@ -632,7 +647,7 @@ func (d *Driver) Diff(id, parent string) (io.ReadCloser, error) {
632647
// Changes produces a list of changes between the specified layer
633648
// and its parent layer. If parent is "", then all changes will be ADD changes.
634649
func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
635-
if !d.isParent(id, parent) {
650+
if useNaiveDiff(d.home) || !d.isParent(id, parent) {
636651
return d.naiveDiff.Changes(id, parent)
637652
}
638653
// Overlay doesn't have snapshots, so we need to get changes from all parent

integration-cli/docker_cli_build_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7267,3 +7267,23 @@ func (s *DockerSuite) TestBuildContChar(c *check.C) {
72677267
c.Assert(out, checker.Contains, "Step 1/2 : FROM busybox")
72687268
c.Assert(out, checker.Contains, "Step 2/2 : RUN echo hi \\\\\n")
72697269
}
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+
}

0 commit comments

Comments
 (0)