Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

overlay: add detection for overlay support in a user namespace #845

Merged
merged 7 commits into from
Mar 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 93 additions & 50 deletions drivers/overlay/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ func hasVolatileOption(opts []string) bool {
return false
}

func getMountProgramFlagFile(path string) string {
return filepath.Join(path, ".has-mount-program")
}

func checkSupportVolatile(home, runhome string) (bool, error) {
feature := fmt.Sprintf("volatile")
volatileCacheResult, _, err := cachedFeatureCheck(runhome, feature)
Expand Down Expand Up @@ -172,6 +176,47 @@ func checkSupportVolatile(home, runhome string) (bool, error) {
return usingVolatile, nil
}

func checkAndRecordOverlaySupport(fsMagic graphdriver.FsMagic, home, runhome string) (bool, error) {
var supportsDType bool

if os.Geteuid() != 0 {
return false, nil
}

feature := "overlay"
overlayCacheResult, overlayCacheText, err := cachedFeatureCheck(runhome, feature)
if err == nil {
if overlayCacheResult {
logrus.Debugf("cached value indicated that overlay is supported")
} else {
logrus.Debugf("cached value indicated that overlay is not supported")
}
supportsDType = overlayCacheResult
if !supportsDType {
return false, errors.New(overlayCacheText)
}
} else {
supportsDType, err = supportsOverlay(home, fsMagic, 0, 0)
if err != nil {
os.Remove(filepath.Join(home, linkDir))
os.Remove(home)
patherr, ok := err.(*os.PathError)
if ok && patherr.Err == syscall.ENOSPC {
return false, err
}
err = errors.Wrap(err, "kernel does not support overlay fs")
if err2 := cachedFeatureRecord(runhome, feature, false, err.Error()); err2 != nil {
return false, errors.Wrapf(err2, "error recording overlay not being supported (%v)", err)
}
return false, err
}
if err = cachedFeatureRecord(runhome, feature, supportsDType, ""); err != nil {
return false, errors.Wrap(err, "error recording overlay support status")
}
}
return supportsDType, nil
}

// Init returns the a native diff driver for overlay filesystem.
// If overlay filesystem is not supported on the host, a wrapped graphdriver.ErrNotSupported is returned as error.
// If an overlay filesystem is not supported over an existing filesystem then a wrapped graphdriver.ErrIncompatibleFS is returned.
Expand All @@ -189,8 +234,13 @@ func Init(home string, options graphdriver.Options) (graphdriver.Driver, error)
backingFs = fsName
}

// check if they are running over btrfs, aufs, zfs, overlay, or ecryptfs
if opts.mountProgram == "" {
if opts.mountProgram != "" {
f, err := os.Create(getMountProgramFlagFile(home))
if err == nil {
f.Close()
}
} else {
// check if they are running over btrfs, aufs, zfs, overlay, or ecryptfs
if opts.forceMask != nil {
return nil, errors.New("'force_mask' is supported only with 'mount_program'")
}
Expand Down Expand Up @@ -221,39 +271,11 @@ func Init(home string, options graphdriver.Options) (graphdriver.Driver, error)
supportsDType = true
supportsVolatile = true
} else {
feature := "overlay"
overlayCacheResult, overlayCacheText, err := cachedFeatureCheck(runhome, feature)
if err == nil {
if overlayCacheResult {
logrus.Debugf("cached value indicated that overlay is supported")
} else {
logrus.Debugf("cached value indicated that overlay is not supported")
}
supportsDType = overlayCacheResult
if !supportsDType {
return nil, errors.New(overlayCacheText)
}
} else {
supportsDType, err = supportsOverlay(home, fsMagic, rootUID, rootGID)
if err != nil {
os.Remove(filepath.Join(home, linkDir))
os.Remove(home)
patherr, ok := err.(*os.PathError)
if ok && patherr.Err == syscall.ENOSPC {
return nil, err
}
err = errors.Wrap(err, "kernel does not support overlay fs")
if err2 := cachedFeatureRecord(runhome, feature, false, err.Error()); err2 != nil {
return nil, errors.Wrapf(err2, "error recording overlay not being supported (%v)", err)
}
return nil, err
}
if err = cachedFeatureRecord(runhome, feature, supportsDType, ""); err != nil {
return nil, errors.Wrap(err, "error recording overlay support status")
}
supportsDType, err = checkAndRecordOverlaySupport(fsMagic, home, runhome)
if err != nil {
return nil, err
}

feature = fmt.Sprintf("metacopy(%s)", opts.mountOptions)
feature := fmt.Sprintf("metacopy(%s)", opts.mountOptions)
metacopyCacheResult, _, err := cachedFeatureCheck(runhome, feature)
if err == nil {
if metacopyCacheResult {
Expand Down Expand Up @@ -439,11 +461,44 @@ func cachedFeatureRecord(runhome, feature string, supported bool, text string) (
return err
}

func SupportsNativeOverlay(graphroot, rundir string) (bool, error) {
if os.Geteuid() != 0 || graphroot == "" || rundir == "" {
return false, nil
}

home := filepath.Join(graphroot, "overlay")
runhome := filepath.Join(rundir, "overlay")

if _, err := os.Stat(getMountProgramFlagFile(home)); err == nil {
logrus.Debugf("overlay storage already configured with a mount-program")
return false, nil
}

for _, dir := range []string{home, runhome} {
if _, err := os.Stat(dir); err != nil {
_ = idtools.MkdirAllAs(dir, 0700, 0, 0)
}
}

fsMagic, err := graphdriver.GetFSMagic(home)
if err != nil {
return false, err
}

supportsDType, _ := checkAndRecordOverlaySupport(fsMagic, home, runhome)
return supportsDType, nil
}

func supportsOverlay(home string, homeMagic graphdriver.FsMagic, rootUID, rootGID int) (supportsDType bool, err error) {
// We can try to modprobe overlay first

exec.Command("modprobe", "overlay").Run()

logLevel := logrus.ErrorLevel
if unshare.IsRootless() {
logLevel = logrus.DebugLevel
}

layerDir, err := ioutil.TempDir(home, "compat")
if err != nil {
patherr, ok := err.(*os.PathError)
Expand Down Expand Up @@ -483,7 +538,7 @@ func supportsOverlay(home string, homeMagic graphdriver.FsMagic, rootUID, rootGI
_ = idtools.MkdirAs(workDir, 0700, rootUID, rootGID)
flags := fmt.Sprintf("lowerdir=%s:%s,upperdir=%s,workdir=%s", lower1Dir, lower2Dir, upperDir, workDir)
if len(flags) < unix.Getpagesize() {
err := mountFrom(filepath.Dir(home), "overlay", mergedDir, "overlay", 0, flags)
err := unix.Mount("overlay", mergedDir, "overlay", 0, flags)
if err == nil {
logrus.Debugf("overlay test mount with multiple lowers succeeded")
return supportsDType, nil
Expand All @@ -492,18 +547,18 @@ func supportsOverlay(home string, homeMagic graphdriver.FsMagic, rootUID, rootGI
}
flags = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower1Dir, upperDir, workDir)
if len(flags) < unix.Getpagesize() {
err := mountFrom(filepath.Dir(home), "overlay", mergedDir, "overlay", 0, flags)
err := unix.Mount("overlay", mergedDir, "overlay", 0, flags)
if err == nil {
logrus.Errorf("overlay test mount with multiple lowers failed, but succeeded with a single lower")
return supportsDType, errors.Wrap(graphdriver.ErrNotSupported, "kernel too old to provide multiple lowers feature for overlay")
}
logrus.Debugf("overlay test mount with a single lower failed %v", err)
}
logrus.Errorf("'overlay' is not supported over %s at %q", backingFs, home)
logrus.StandardLogger().Logf(logLevel, "'overlay' is not supported over %s at %q", backingFs, home)
return supportsDType, errors.Wrapf(graphdriver.ErrIncompatibleFS, "'overlay' is not supported over %s at %q", backingFs, home)
}

logrus.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
logrus.StandardLogger().Logf(logLevel, "'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
return supportsDType, errors.Wrap(graphdriver.ErrNotSupported, "'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
}

Expand Down Expand Up @@ -575,18 +630,6 @@ func (d *Driver) Metadata(id string) (map[string]string, error) {
return metadata, nil
}

// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
// For Overlay, it attempts to check the XFS quota for size, and falls back to
// finding the size of the "diff" directory.
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
usage := &directory.DiskUsage{}
if d.quotaCtl != nil {
err := d.quotaCtl.GetDiskUsage(d.dir(id), usage)
return usage, err
}
return directory.Usage(path.Join(d.dir(id), "diff"))
}

// Cleanup any state created by overlay which should be cleaned when daemon
// is being shutdown. For now, we just have to unmount the bind mounted
// we had created.
Expand Down
21 changes: 21 additions & 0 deletions drivers/overlay/overlay_cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// +build linux,cgo

package overlay

import (
"path"

"github.com/containers/storage/pkg/directory"
)

// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
// For Overlay, it attempts to check the XFS quota for size, and falls back to
// finding the size of the "diff" directory.
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
usage := &directory.DiskUsage{}
if d.quotaCtl != nil {
err := d.quotaCtl.GetDiskUsage(d.dir(id), usage)
return usage, err
}
return directory.Usage(path.Join(d.dir(id), "diff"))
}
16 changes: 16 additions & 0 deletions drivers/overlay/overlay_nocgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// +build linux,!cgo

package overlay

import (
"path"

"github.com/containers/storage/pkg/directory"
)

// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
// For Overlay, it attempts to check the XFS quota for size, and falls back to
// finding the size of the "diff" directory.
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
return directory.Usage(path.Join(d.dir(id), "diff"))
}
4 changes: 4 additions & 0 deletions drivers/overlay/overlay_unsupported.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// +build !linux

package overlay

func SupportsNativeOverlay(graphroot, rundir string) (bool, error) {
return false, nil
}
20 changes: 16 additions & 4 deletions types/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

"github.com/BurntSushi/toml"
"github.com/containers/storage/drivers/overlay"
cfg "github.com/containers/storage/pkg/config"
"github.com/containers/storage/pkg/idtools"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -163,6 +164,7 @@ func isRootlessDriver(driver string) bool {
// getRootlessStorageOpts returns the storage opts for containers running as non root
func getRootlessStorageOpts(rootlessUID int, systemOpts StoreOptions) (StoreOptions, error) {
var opts StoreOptions
const overlayDriver = "overlay"

dataDir, rootlessRuntime, err := getRootlessDirInfo(rootlessUID)
if err != nil {
Expand All @@ -181,10 +183,20 @@ func getRootlessStorageOpts(rootlessUID int, systemOpts StoreOptions) (StoreOpti
if driver := os.Getenv("STORAGE_DRIVER"); driver != "" {
opts.GraphDriverName = driver
}
if opts.GraphDriverName == "" || opts.GraphDriverName == "overlay" {
if path, err := exec.LookPath("fuse-overlayfs"); err == nil {
opts.GraphDriverName = "overlay"
opts.GraphDriverOptions = []string{fmt.Sprintf("overlay.mount_program=%s", path)}
if opts.GraphDriverName == "" || opts.GraphDriverName == overlayDriver {
supported, err := overlay.SupportsNativeOverlay(opts.GraphRoot, rootlessRuntime)
if err != nil {
return opts, err
}
if supported {
opts.GraphDriverName = overlayDriver
} else {
if path, err := exec.LookPath("fuse-overlayfs"); err == nil {
opts.GraphDriverName = overlayDriver
opts.GraphDriverOptions = []string{fmt.Sprintf("overlay.mount_program=%s", path)}
}
}
if opts.GraphDriverName == overlayDriver {
for _, o := range systemOpts.GraphDriverOptions {
if strings.Contains(o, "ignore_chown_errors") {
opts.GraphDriverOptions = append(opts.GraphDriverOptions, o)
Expand Down