Browse code

overlay: warn if overlay backing fs doesn't support d_type

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>

Akihiro Suda authored on 2016/10/17 13:30:16
Showing 5 changed files
... ...
@@ -10,11 +10,14 @@ import (
10 10
 	"os"
11 11
 	"os/exec"
12 12
 	"path"
13
+	"strconv"
13 14
 	"syscall"
14 15
 
15 16
 	"github.com/Sirupsen/logrus"
16 17
 	"github.com/docker/docker/daemon/graphdriver"
18
+	"github.com/docker/docker/daemon/graphdriver/overlayutils"
17 19
 	"github.com/docker/docker/pkg/archive"
20
+	"github.com/docker/docker/pkg/fsutils"
18 21
 	"github.com/docker/docker/pkg/idtools"
19 22
 	"github.com/docker/docker/pkg/mount"
20 23
 	"github.com/opencontainers/runc/libcontainer/label"
... ...
@@ -89,10 +92,11 @@ func (d *naiveDiffDriverWithApply) ApplyDiff(id, parent string, diff io.Reader)
89 89
 
90 90
 // Driver contains information about the home directory and the list of active mounts that are created using this driver.
91 91
 type Driver struct {
92
-	home    string
93
-	uidMaps []idtools.IDMap
94
-	gidMaps []idtools.IDMap
95
-	ctr     *graphdriver.RefCounter
92
+	home          string
93
+	uidMaps       []idtools.IDMap
94
+	gidMaps       []idtools.IDMap
95
+	ctr           *graphdriver.RefCounter
96
+	supportsDType bool
96 97
 }
97 98
 
98 99
 func init() {
... ...
@@ -135,11 +139,21 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
135 135
 		return nil, err
136 136
 	}
137 137
 
138
+	supportsDType, err := fsutils.SupportsDType(home)
139
+	if err != nil {
140
+		return nil, err
141
+	}
142
+	if !supportsDType {
143
+		// not a fatal error until v1.16 (#27443)
144
+		logrus.Warn(overlayutils.ErrDTypeNotSupported("overlay", backingFs))
145
+	}
146
+
138 147
 	d := &Driver{
139
-		home:    home,
140
-		uidMaps: uidMaps,
141
-		gidMaps: gidMaps,
142
-		ctr:     graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
148
+		home:          home,
149
+		uidMaps:       uidMaps,
150
+		gidMaps:       gidMaps,
151
+		ctr:           graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
152
+		supportsDType: supportsDType,
143 153
 	}
144 154
 
145 155
 	return NaiveDiffDriverWithApply(d, uidMaps, gidMaps), nil
... ...
@@ -175,6 +189,7 @@ func (d *Driver) String() string {
175 175
 func (d *Driver) Status() [][2]string {
176 176
 	return [][2]string{
177 177
 		{"Backing Filesystem", backingFs},
178
+		{"Supports d_type", strconv.FormatBool(d.supportsDType)},
178 179
 	}
179 180
 }
180 181
 
... ...
@@ -19,10 +19,12 @@ import (
19 19
 	"github.com/Sirupsen/logrus"
20 20
 
21 21
 	"github.com/docker/docker/daemon/graphdriver"
22
+	"github.com/docker/docker/daemon/graphdriver/overlayutils"
22 23
 	"github.com/docker/docker/daemon/graphdriver/quota"
23 24
 	"github.com/docker/docker/pkg/archive"
24 25
 	"github.com/docker/docker/pkg/chrootarchive"
25 26
 	"github.com/docker/docker/pkg/directory"
27
+	"github.com/docker/docker/pkg/fsutils"
26 28
 	"github.com/docker/docker/pkg/idtools"
27 29
 	"github.com/docker/docker/pkg/mount"
28 30
 	"github.com/docker/docker/pkg/parsers"
... ...
@@ -87,13 +89,14 @@ type overlayOptions struct {
87 87
 
88 88
 // Driver contains information about the home directory and the list of active mounts that are created using this driver.
89 89
 type Driver struct {
90
-	home      string
91
-	uidMaps   []idtools.IDMap
92
-	gidMaps   []idtools.IDMap
93
-	ctr       *graphdriver.RefCounter
94
-	quotaCtl  *quota.Control
95
-	options   overlayOptions
96
-	naiveDiff graphdriver.DiffDriver
90
+	home          string
91
+	uidMaps       []idtools.IDMap
92
+	gidMaps       []idtools.IDMap
93
+	ctr           *graphdriver.RefCounter
94
+	quotaCtl      *quota.Control
95
+	options       overlayOptions
96
+	naiveDiff     graphdriver.DiffDriver
97
+	supportsDType bool
97 98
 }
98 99
 
99 100
 var (
... ...
@@ -158,11 +161,21 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
158 158
 		return nil, err
159 159
 	}
160 160
 
161
+	supportsDType, err := fsutils.SupportsDType(home)
162
+	if err != nil {
163
+		return nil, err
164
+	}
165
+	if !supportsDType {
166
+		// not a fatal error until v1.16 (#27443)
167
+		logrus.Warn(overlayutils.ErrDTypeNotSupported("overlay2", backingFs))
168
+	}
169
+
161 170
 	d := &Driver{
162
-		home:    home,
163
-		uidMaps: uidMaps,
164
-		gidMaps: gidMaps,
165
-		ctr:     graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
171
+		home:          home,
172
+		uidMaps:       uidMaps,
173
+		gidMaps:       gidMaps,
174
+		ctr:           graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
175
+		supportsDType: supportsDType,
166 176
 	}
167 177
 
168 178
 	d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps)
... ...
@@ -231,6 +244,7 @@ func (d *Driver) String() string {
231 231
 func (d *Driver) Status() [][2]string {
232 232
 	return [][2]string{
233 233
 		{"Backing Filesystem", backingFs},
234
+		{"Supports d_type", strconv.FormatBool(d.supportsDType)},
234 235
 	}
235 236
 }
236 237
 
237 238
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+// +build linux
1
+
2
+package overlayutils
3
+
4
+import (
5
+	"errors"
6
+	"fmt"
7
+)
8
+
9
+// ErrDTypeNotSupported denotes that the backing filesystem doesn't support d_type.
10
+func ErrDTypeNotSupported(driver, backingFs string) error {
11
+	msg := fmt.Sprintf("%s: the backing %s filesystem is formatted without d_type support, which leads to incorrect behavior.", driver, backingFs)
12
+	if backingFs == "xfs" {
13
+		msg += " Reformat the filesystem with ftype=1 to enable d_type support."
14
+	}
15
+	msg += " Running without d_type support will no longer be supported in Docker 1.16."
16
+	return errors.New(msg)
17
+}
0 18
new file mode 100644
... ...
@@ -0,0 +1,89 @@
0
+// +build linux
1
+
2
+package fsutils
3
+
4
+import (
5
+	"fmt"
6
+	"io/ioutil"
7
+	"os"
8
+	"syscall"
9
+	"unsafe"
10
+)
11
+
12
+func locateDummyIfEmpty(path string) (string, error) {
13
+	children, err := ioutil.ReadDir(path)
14
+	if err != nil {
15
+		return "", err
16
+	}
17
+	if len(children) != 0 {
18
+		return "", nil
19
+	}
20
+	dummyFile, err := ioutil.TempFile(path, "fsutils-dummy")
21
+	if err != nil {
22
+		return "", err
23
+	}
24
+	name := dummyFile.Name()
25
+	if err = dummyFile.Close(); err != nil {
26
+		return name, err
27
+	}
28
+	return name, nil
29
+}
30
+
31
+// SupportsDType returns whether the filesystem mounted on path supports d_type
32
+func SupportsDType(path string) (bool, error) {
33
+	// locate dummy so that we have at least one dirent
34
+	dummy, err := locateDummyIfEmpty(path)
35
+	if err != nil {
36
+		return false, err
37
+	}
38
+	if dummy != "" {
39
+		defer os.Remove(dummy)
40
+	}
41
+
42
+	visited := 0
43
+	supportsDType := true
44
+	fn := func(ent *syscall.Dirent) bool {
45
+		visited++
46
+		if ent.Type == syscall.DT_UNKNOWN {
47
+			supportsDType = false
48
+			// stop iteration
49
+			return true
50
+		}
51
+		// continue iteration
52
+		return false
53
+	}
54
+	if err = iterateReadDir(path, fn); err != nil {
55
+		return false, err
56
+	}
57
+	if visited == 0 {
58
+		return false, fmt.Errorf("did not hit any dirent during iteration %s", path)
59
+	}
60
+	return supportsDType, nil
61
+}
62
+
63
+func iterateReadDir(path string, fn func(*syscall.Dirent) bool) error {
64
+	d, err := os.Open(path)
65
+	if err != nil {
66
+		return err
67
+	}
68
+	defer d.Close()
69
+	fd := int(d.Fd())
70
+	buf := make([]byte, 4096)
71
+	for {
72
+		nbytes, err := syscall.ReadDirent(fd, buf)
73
+		if err != nil {
74
+			return err
75
+		}
76
+		if nbytes == 0 {
77
+			break
78
+		}
79
+		for off := 0; off < nbytes; {
80
+			ent := (*syscall.Dirent)(unsafe.Pointer(&buf[off]))
81
+			if stop := fn(ent); stop {
82
+				return nil
83
+			}
84
+			off += int(ent.Reclen)
85
+		}
86
+	}
87
+	return nil
88
+}
0 89
new file mode 100644
... ...
@@ -0,0 +1,91 @@
0
+// +build linux
1
+
2
+package fsutils
3
+
4
+import (
5
+	"io/ioutil"
6
+	"os"
7
+	"os/exec"
8
+	"syscall"
9
+	"testing"
10
+)
11
+
12
+func testSupportsDType(t *testing.T, expected bool, mkfsCommand string, mkfsArg ...string) {
13
+	// check whether mkfs is installed
14
+	if _, err := exec.LookPath(mkfsCommand); err != nil {
15
+		t.Skipf("%s not installed: %v", mkfsCommand, err)
16
+	}
17
+
18
+	// create a sparse image
19
+	imageSize := int64(32 * 1024 * 1024)
20
+	imageFile, err := ioutil.TempFile("", "fsutils-image")
21
+	if err != nil {
22
+		t.Fatal(err)
23
+	}
24
+	imageFileName := imageFile.Name()
25
+	defer os.Remove(imageFileName)
26
+	if _, err = imageFile.Seek(imageSize-1, 0); err != nil {
27
+		t.Fatal(err)
28
+	}
29
+	if _, err = imageFile.Write([]byte{0}); err != nil {
30
+		t.Fatal(err)
31
+	}
32
+	if err = imageFile.Close(); err != nil {
33
+		t.Fatal(err)
34
+	}
35
+
36
+	// create a mountpoint
37
+	mountpoint, err := ioutil.TempDir("", "fsutils-mountpoint")
38
+	if err != nil {
39
+		t.Fatal(err)
40
+	}
41
+	defer os.RemoveAll(mountpoint)
42
+
43
+	// format the image
44
+	args := append(mkfsArg, imageFileName)
45
+	t.Logf("Executing `%s %v`", mkfsCommand, args)
46
+	out, err := exec.Command(mkfsCommand, args...).CombinedOutput()
47
+	if len(out) > 0 {
48
+		t.Log(string(out))
49
+	}
50
+	if err != nil {
51
+		t.Fatal(err)
52
+	}
53
+
54
+	// loopback-mount the image.
55
+	// for ease of setting up loopback device, we use os/exec rather than syscall.Mount
56
+	out, err = exec.Command("mount", "-o", "loop", imageFileName, mountpoint).CombinedOutput()
57
+	if len(out) > 0 {
58
+		t.Log(string(out))
59
+	}
60
+	if err != nil {
61
+		t.Skip("skipping the test because mount failed")
62
+	}
63
+	defer func() {
64
+		if err := syscall.Unmount(mountpoint, 0); err != nil {
65
+			t.Fatal(err)
66
+		}
67
+	}()
68
+
69
+	// check whether it supports d_type
70
+	result, err := SupportsDType(mountpoint)
71
+	if err != nil {
72
+		t.Fatal(err)
73
+	}
74
+	t.Logf("Supports d_type: %v", result)
75
+	if result != expected {
76
+		t.Fatalf("expected %v, got %v", expected, result)
77
+	}
78
+}
79
+
80
+func TestSupportsDTypeWithFType0XFS(t *testing.T) {
81
+	testSupportsDType(t, false, "mkfs.xfs", "-m", "crc=0", "-n", "ftype=0")
82
+}
83
+
84
+func TestSupportsDTypeWithFType1XFS(t *testing.T) {
85
+	testSupportsDType(t, true, "mkfs.xfs", "-m", "crc=0", "-n", "ftype=1")
86
+}
87
+
88
+func TestSupportsDTypeWithExt4(t *testing.T) {
89
+	testSupportsDType(t, true, "mkfs.ext4")
90
+}