瀏覽代碼

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

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
Akihiro Suda 8 年之前
父節點
當前提交
2e20e63da2

+ 23 - 8
daemon/graphdriver/overlay/overlay.go

@@ -10,11 +10,14 @@ import (
 	"os"
 	"os/exec"
 	"path"
+	"strconv"
 	"syscall"
 
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/daemon/graphdriver"
+	"github.com/docker/docker/daemon/graphdriver/overlayutils"
 	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/pkg/fsutils"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/opencontainers/runc/libcontainer/label"
@@ -89,10 +92,11 @@ func (d *naiveDiffDriverWithApply) ApplyDiff(id, parent string, diff io.Reader)
 
 // Driver contains information about the home directory and the list of active mounts that are created using this driver.
 type Driver struct {
-	home    string
-	uidMaps []idtools.IDMap
-	gidMaps []idtools.IDMap
-	ctr     *graphdriver.RefCounter
+	home          string
+	uidMaps       []idtools.IDMap
+	gidMaps       []idtools.IDMap
+	ctr           *graphdriver.RefCounter
+	supportsDType bool
 }
 
 func init() {
@@ -135,11 +139,21 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 		return nil, err
 	}
 
+	supportsDType, err := fsutils.SupportsDType(home)
+	if err != nil {
+		return nil, err
+	}
+	if !supportsDType {
+		// not a fatal error until v1.16 (#27443)
+		logrus.Warn(overlayutils.ErrDTypeNotSupported("overlay", backingFs))
+	}
+
 	d := &Driver{
-		home:    home,
-		uidMaps: uidMaps,
-		gidMaps: gidMaps,
-		ctr:     graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
+		home:          home,
+		uidMaps:       uidMaps,
+		gidMaps:       gidMaps,
+		ctr:           graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
+		supportsDType: supportsDType,
 	}
 
 	return NaiveDiffDriverWithApply(d, uidMaps, gidMaps), nil
@@ -175,6 +189,7 @@ func (d *Driver) String() string {
 func (d *Driver) Status() [][2]string {
 	return [][2]string{
 		{"Backing Filesystem", backingFs},
+		{"Supports d_type", strconv.FormatBool(d.supportsDType)},
 	}
 }
 

+ 25 - 11
daemon/graphdriver/overlay2/overlay.go

@@ -19,10 +19,12 @@ import (
 	"github.com/Sirupsen/logrus"
 
 	"github.com/docker/docker/daemon/graphdriver"
+	"github.com/docker/docker/daemon/graphdriver/overlayutils"
 	"github.com/docker/docker/daemon/graphdriver/quota"
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/chrootarchive"
 	"github.com/docker/docker/pkg/directory"
+	"github.com/docker/docker/pkg/fsutils"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/parsers"
@@ -87,13 +89,14 @@ type overlayOptions struct {
 
 // Driver contains information about the home directory and the list of active mounts that are created using this driver.
 type Driver struct {
-	home      string
-	uidMaps   []idtools.IDMap
-	gidMaps   []idtools.IDMap
-	ctr       *graphdriver.RefCounter
-	quotaCtl  *quota.Control
-	options   overlayOptions
-	naiveDiff graphdriver.DiffDriver
+	home          string
+	uidMaps       []idtools.IDMap
+	gidMaps       []idtools.IDMap
+	ctr           *graphdriver.RefCounter
+	quotaCtl      *quota.Control
+	options       overlayOptions
+	naiveDiff     graphdriver.DiffDriver
+	supportsDType bool
 }
 
 var (
@@ -158,11 +161,21 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 		return nil, err
 	}
 
+	supportsDType, err := fsutils.SupportsDType(home)
+	if err != nil {
+		return nil, err
+	}
+	if !supportsDType {
+		// not a fatal error until v1.16 (#27443)
+		logrus.Warn(overlayutils.ErrDTypeNotSupported("overlay2", backingFs))
+	}
+
 	d := &Driver{
-		home:    home,
-		uidMaps: uidMaps,
-		gidMaps: gidMaps,
-		ctr:     graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
+		home:          home,
+		uidMaps:       uidMaps,
+		gidMaps:       gidMaps,
+		ctr:           graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
+		supportsDType: supportsDType,
 	}
 
 	d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps)
@@ -231,6 +244,7 @@ func (d *Driver) String() string {
 func (d *Driver) Status() [][2]string {
 	return [][2]string{
 		{"Backing Filesystem", backingFs},
+		{"Supports d_type", strconv.FormatBool(d.supportsDType)},
 	}
 }
 

+ 18 - 0
daemon/graphdriver/overlayutils/overlayutils.go

@@ -0,0 +1,18 @@
+// +build linux
+
+package overlayutils
+
+import (
+	"errors"
+	"fmt"
+)
+
+// ErrDTypeNotSupported denotes that the backing filesystem doesn't support d_type.
+func ErrDTypeNotSupported(driver, backingFs string) error {
+	msg := fmt.Sprintf("%s: the backing %s filesystem is formatted without d_type support, which leads to incorrect behavior.", driver, backingFs)
+	if backingFs == "xfs" {
+		msg += " Reformat the filesystem with ftype=1 to enable d_type support."
+	}
+	msg += " Running without d_type support will no longer be supported in Docker 1.16."
+	return errors.New(msg)
+}

+ 89 - 0
pkg/fsutils/fsutils_linux.go

@@ -0,0 +1,89 @@
+// +build linux
+
+package fsutils
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"syscall"
+	"unsafe"
+)
+
+func locateDummyIfEmpty(path string) (string, error) {
+	children, err := ioutil.ReadDir(path)
+	if err != nil {
+		return "", err
+	}
+	if len(children) != 0 {
+		return "", nil
+	}
+	dummyFile, err := ioutil.TempFile(path, "fsutils-dummy")
+	if err != nil {
+		return "", err
+	}
+	name := dummyFile.Name()
+	if err = dummyFile.Close(); err != nil {
+		return name, err
+	}
+	return name, nil
+}
+
+// SupportsDType returns whether the filesystem mounted on path supports d_type
+func SupportsDType(path string) (bool, error) {
+	// locate dummy so that we have at least one dirent
+	dummy, err := locateDummyIfEmpty(path)
+	if err != nil {
+		return false, err
+	}
+	if dummy != "" {
+		defer os.Remove(dummy)
+	}
+
+	visited := 0
+	supportsDType := true
+	fn := func(ent *syscall.Dirent) bool {
+		visited++
+		if ent.Type == syscall.DT_UNKNOWN {
+			supportsDType = false
+			// stop iteration
+			return true
+		}
+		// continue iteration
+		return false
+	}
+	if err = iterateReadDir(path, fn); err != nil {
+		return false, err
+	}
+	if visited == 0 {
+		return false, fmt.Errorf("did not hit any dirent during iteration %s", path)
+	}
+	return supportsDType, nil
+}
+
+func iterateReadDir(path string, fn func(*syscall.Dirent) bool) error {
+	d, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	defer d.Close()
+	fd := int(d.Fd())
+	buf := make([]byte, 4096)
+	for {
+		nbytes, err := syscall.ReadDirent(fd, buf)
+		if err != nil {
+			return err
+		}
+		if nbytes == 0 {
+			break
+		}
+		for off := 0; off < nbytes; {
+			ent := (*syscall.Dirent)(unsafe.Pointer(&buf[off]))
+			if stop := fn(ent); stop {
+				return nil
+			}
+			off += int(ent.Reclen)
+		}
+	}
+	return nil
+}

+ 91 - 0
pkg/fsutils/fsutils_linux_test.go

@@ -0,0 +1,91 @@
+// +build linux
+
+package fsutils
+
+import (
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"syscall"
+	"testing"
+)
+
+func testSupportsDType(t *testing.T, expected bool, mkfsCommand string, mkfsArg ...string) {
+	// check whether mkfs is installed
+	if _, err := exec.LookPath(mkfsCommand); err != nil {
+		t.Skipf("%s not installed: %v", mkfsCommand, err)
+	}
+
+	// create a sparse image
+	imageSize := int64(32 * 1024 * 1024)
+	imageFile, err := ioutil.TempFile("", "fsutils-image")
+	if err != nil {
+		t.Fatal(err)
+	}
+	imageFileName := imageFile.Name()
+	defer os.Remove(imageFileName)
+	if _, err = imageFile.Seek(imageSize-1, 0); err != nil {
+		t.Fatal(err)
+	}
+	if _, err = imageFile.Write([]byte{0}); err != nil {
+		t.Fatal(err)
+	}
+	if err = imageFile.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	// create a mountpoint
+	mountpoint, err := ioutil.TempDir("", "fsutils-mountpoint")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(mountpoint)
+
+	// format the image
+	args := append(mkfsArg, imageFileName)
+	t.Logf("Executing `%s %v`", mkfsCommand, args)
+	out, err := exec.Command(mkfsCommand, args...).CombinedOutput()
+	if len(out) > 0 {
+		t.Log(string(out))
+	}
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// loopback-mount the image.
+	// for ease of setting up loopback device, we use os/exec rather than syscall.Mount
+	out, err = exec.Command("mount", "-o", "loop", imageFileName, mountpoint).CombinedOutput()
+	if len(out) > 0 {
+		t.Log(string(out))
+	}
+	if err != nil {
+		t.Skip("skipping the test because mount failed")
+	}
+	defer func() {
+		if err := syscall.Unmount(mountpoint, 0); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// check whether it supports d_type
+	result, err := SupportsDType(mountpoint)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Logf("Supports d_type: %v", result)
+	if result != expected {
+		t.Fatalf("expected %v, got %v", expected, result)
+	}
+}
+
+func TestSupportsDTypeWithFType0XFS(t *testing.T) {
+	testSupportsDType(t, false, "mkfs.xfs", "-m", "crc=0", "-n", "ftype=0")
+}
+
+func TestSupportsDTypeWithFType1XFS(t *testing.T) {
+	testSupportsDType(t, true, "mkfs.xfs", "-m", "crc=0", "-n", "ftype=1")
+}
+
+func TestSupportsDTypeWithExt4(t *testing.T) {
+	testSupportsDType(t, true, "mkfs.ext4")
+}