Skip to content

Commit

Permalink
overlay: warn if overlay backing fs doesn't support d_type
Browse files Browse the repository at this point in the history
Signed-off-by: Akihiro Suda <[email protected]>
  • Loading branch information
AkihiroSuda committed Nov 9, 2016
1 parent 64a42d6 commit 2e20e63
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 19 deletions.
31 changes: 23 additions & 8 deletions daemon/graphdriver/overlay/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)},
}
}

Expand Down
36 changes: 25 additions & 11 deletions daemon/graphdriver/overlay2/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)},
}
}

Expand Down
18 changes: 18 additions & 0 deletions daemon/graphdriver/overlayutils/overlayutils.go
Original file line number Diff line number Diff line change
@@ -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 changes: 89 additions & 0 deletions pkg/fsutils/fsutils_linux.go
Original file line number Diff line number Diff line change
@@ -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 changes: 91 additions & 0 deletions pkg/fsutils/fsutils_linux_test.go
Original file line number Diff line number Diff line change
@@ -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")
}

0 comments on commit 2e20e63

Please sign in to comment.