Skip to content

Commit a98877b

Browse files
dmcgowanruncom
authored andcommitted
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)
1 parent f3c5f4f commit a98877b

3 files changed

Lines changed: 255 additions & 0 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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"path"
1313
"strconv"
1414
"strings"
15+
"sync"
1516
"syscall"
1617

1718
"github.com/Sirupsen/logrus"
@@ -94,6 +95,9 @@ type Driver struct {
9495
var (
9596
backingFs = "<unknown>"
9697
projectQuotaSupported = false
98+
99+
useNaiveDiffLock sync.Once
100+
useNaiveDiffOnly bool
97101
)
98102

99103
func init() {
@@ -228,6 +232,16 @@ func supportsOverlay() error {
228232
return graphdriver.ErrNotSupported
229233
}
230234

235+
func useNaiveDiff(home string) bool {
236+
useNaiveDiffLock.Do(func() {
237+
if err := hasOpaqueCopyUpBug(home); err != nil {
238+
logrus.Warnf("Not using native diff for overlay2: %v", err)
239+
useNaiveDiffOnly = true
240+
}
241+
})
242+
return useNaiveDiffOnly
243+
}
244+
231245
func (d *Driver) String() string {
232246
return driverName
233247
}
@@ -237,6 +251,7 @@ func (d *Driver) String() string {
237251
func (d *Driver) Status() [][2]string {
238252
return [][2]string{
239253
{"Backing Filesystem", backingFs},
254+
{"Native Overlay Diff", strconv.FormatBool(!useNaiveDiff(d.home))},
240255
}
241256
}
242257

@@ -575,12 +590,19 @@ func (d *Driver) getDiffPath(id string) string {
575590
// and its parent and returns the size in bytes of the changes
576591
// relative to its base filesystem directory.
577592
func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
593+
if useNaiveDiff(d.home) {
594+
return d.naiveDiff.DiffSize(id, parent)
595+
}
578596
return directory.Size(d.getDiffPath(id))
579597
}
580598

581599
// Diff produces an archive of the changes between the specified
582600
// layer and its parent layer which may be "".
583601
func (d *Driver) Diff(id, parent string) (archive.Archive, error) {
602+
if useNaiveDiff(d.home) {
603+
return d.naiveDiff.Diff(id, parent)
604+
}
605+
584606
diffPath := d.getDiffPath(id)
585607
logrus.Debugf("Tar with options on %s", diffPath)
586608
return archive.TarWithOptions(diffPath, &archive.TarOptions{
@@ -594,6 +616,9 @@ func (d *Driver) Diff(id, parent string) (archive.Archive, error) {
594616
// Changes produces a list of changes between the specified layer
595617
// and its parent layer. If parent is "", then all changes will be ADD changes.
596618
func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
619+
if useNaiveDiff(d.home) {
620+
return d.naiveDiff.Changes(id, parent)
621+
}
597622
// Overlay doesn't have snapshots, so we need to get changes from all parent
598623
// layers.
599624
diffPath := d.getDiffPath(id)

integration-cli/docker_cli_build_test.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7311,3 +7311,154 @@ func (s *DockerSuite) TestBuildNetContainer(c *check.C) {
73117311
host, _ := dockerCmd(c, "run", "testbuildnetcontainer", "cat", "/otherhost")
73127312
c.Assert(strings.TrimSpace(host), check.Equals, "foobar")
73137313
}
7314+
7315+
// Test case for #24693
7316+
func (s *DockerSuite) TestBuildRunEmptyLineAfterEscape(c *check.C) {
7317+
name := "testbuildemptylineafterescape"
7318+
_, out, err := buildImageWithOut(name,
7319+
`
7320+
FROM busybox
7321+
RUN echo x \
7322+
7323+
RUN echo y
7324+
RUN echo z
7325+
# Comment requires the '#' to start from position 1
7326+
# RUN echo w
7327+
`, true)
7328+
c.Assert(err, checker.IsNil)
7329+
c.Assert(out, checker.Contains, "Step 1/4 : FROM busybox")
7330+
c.Assert(out, checker.Contains, "Step 2/4 : RUN echo x")
7331+
c.Assert(out, checker.Contains, "Step 3/4 : RUN echo y")
7332+
c.Assert(out, checker.Contains, "Step 4/4 : RUN echo z")
7333+
7334+
// With comment, see #24693
7335+
name = "testbuildcommentandemptylineafterescape"
7336+
_, out, err = buildImageWithOut(name,
7337+
`
7338+
FROM busybox
7339+
RUN echo grafana && \
7340+
echo raintank \
7341+
#echo env-load
7342+
RUN echo vegeta
7343+
`, true)
7344+
c.Assert(err, checker.IsNil)
7345+
c.Assert(out, checker.Contains, "Step 1/3 : FROM busybox")
7346+
c.Assert(out, checker.Contains, "Step 2/3 : RUN echo grafana && echo raintank")
7347+
c.Assert(out, checker.Contains, "Step 3/3 : RUN echo vegeta")
7348+
}
7349+
7350+
// Verifies if COPY file . when WORKDIR is set to a non-existing directory,
7351+
// the directory is created and the file is copied into the directory,
7352+
// as opposed to the file being copied as a file with the name of the
7353+
// directory. Fix for 27545 (found on Windows, but regression good for Linux too)
7354+
func (s *DockerSuite) TestBuildCopyFileDotWithWorkdir(c *check.C) {
7355+
name := "testbuildcopyfiledotwithworkdir"
7356+
7357+
ctx, err := fakeContext(`FROM busybox
7358+
WORKDIR /foo
7359+
COPY file .
7360+
RUN ["cat", "/foo/file"]
7361+
`,
7362+
map[string]string{})
7363+
if err != nil {
7364+
c.Fatal(err)
7365+
}
7366+
defer ctx.Close()
7367+
if err := ctx.Add("file", "content"); err != nil {
7368+
c.Fatal(err)
7369+
}
7370+
if _, err = buildImageFromContext(name, ctx, true); err != nil {
7371+
c.Fatal(err)
7372+
}
7373+
}
7374+
7375+
func (s *DockerSuite) TestBuildSquashParent(c *check.C) {
7376+
testRequires(c, ExperimentalDaemon)
7377+
dockerFile := `
7378+
FROM busybox
7379+
RUN echo hello > /hello
7380+
RUN echo world >> /hello
7381+
RUN echo hello > /remove_me
7382+
ENV HELLO world
7383+
RUN rm /remove_me
7384+
`
7385+
// build and get the ID that we can use later for history comparison
7386+
origID, err := buildImage("test", dockerFile, false)
7387+
c.Assert(err, checker.IsNil)
7388+
7389+
// build with squash
7390+
id, err := buildImage("test", dockerFile, true, "--squash")
7391+
c.Assert(err, checker.IsNil)
7392+
7393+
out, _ := dockerCmd(c, "run", "--rm", id, "/bin/sh", "-c", "cat /hello")
7394+
c.Assert(strings.TrimSpace(out), checker.Equals, "hello\nworld")
7395+
7396+
dockerCmd(c, "run", "--rm", id, "/bin/sh", "-c", "[ ! -f /remove_me ]")
7397+
dockerCmd(c, "run", "--rm", id, "/bin/sh", "-c", `[ "$(echo $HELLO)" == "world" ]`)
7398+
7399+
// make sure the ID produced is the ID of the tag we specified
7400+
inspectID, err := inspectImage("test", ".ID")
7401+
c.Assert(err, checker.IsNil)
7402+
c.Assert(inspectID, checker.Equals, id)
7403+
7404+
origHistory, _ := dockerCmd(c, "history", origID)
7405+
testHistory, _ := dockerCmd(c, "history", "test")
7406+
7407+
splitOrigHistory := strings.Split(strings.TrimSpace(origHistory), "\n")
7408+
splitTestHistory := strings.Split(strings.TrimSpace(testHistory), "\n")
7409+
c.Assert(len(splitTestHistory), checker.Equals, len(splitOrigHistory)+1)
7410+
7411+
out, err = inspectImage(id, "len .RootFS.Layers")
7412+
c.Assert(err, checker.IsNil)
7413+
c.Assert(strings.TrimSpace(out), checker.Equals, "3")
7414+
}
7415+
7416+
func (s *DockerSuite) TestBuildContChar(c *check.C) {
7417+
name := "testbuildcontchar"
7418+
7419+
_, out, err := buildImageWithOut(name,
7420+
`FROM busybox\`, true)
7421+
c.Assert(err, checker.IsNil)
7422+
c.Assert(out, checker.Contains, "Step 1/1 : FROM busybox")
7423+
7424+
_, out, err = buildImageWithOut(name,
7425+
`FROM busybox
7426+
RUN echo hi \`, true)
7427+
c.Assert(err, checker.IsNil)
7428+
c.Assert(out, checker.Contains, "Step 1/2 : FROM busybox")
7429+
c.Assert(out, checker.Contains, "Step 2/2 : RUN echo hi\n")
7430+
7431+
_, out, err = buildImageWithOut(name,
7432+
`FROM busybox
7433+
RUN echo hi \\`, true)
7434+
c.Assert(err, checker.IsNil)
7435+
c.Assert(out, checker.Contains, "Step 1/2 : FROM busybox")
7436+
c.Assert(out, checker.Contains, "Step 2/2 : RUN echo hi \\\n")
7437+
7438+
_, out, err = buildImageWithOut(name,
7439+
`FROM busybox
7440+
RUN echo hi \\\`, true)
7441+
c.Assert(err, checker.IsNil)
7442+
c.Assert(out, checker.Contains, "Step 1/2 : FROM busybox")
7443+
c.Assert(out, checker.Contains, "Step 2/2 : RUN echo hi \\\\\n")
7444+
}
7445+
7446+
// TestBuildOpaqueDirectory tests that a build succeeds which
7447+
// creates opaque directories.
7448+
// See https://github.com/docker/docker/issues/25244
7449+
func (s *DockerSuite) TestBuildOpaqueDirectory(c *check.C) {
7450+
testRequires(c, DaemonIsLinux)
7451+
7452+
dockerFile := `
7453+
FROM busybox
7454+
RUN mkdir /dir1 && touch /dir1/f1
7455+
RUN rm -rf /dir1 && mkdir /dir1 && touch /dir1/f2
7456+
RUN touch /dir1/f3
7457+
RUN [ -f /dir1/f2 ]
7458+
`
7459+
7460+
// Test that build succeeds, last command fails if opaque directory
7461+
// was not handled correctly
7462+
_, err := buildImage("testopaquedirectory", dockerFile, false)
7463+
c.Assert(err, checker.IsNil)
7464+
}

0 commit comments

Comments
 (0)