From e739314ed421ecf10d52a47dcdf27859022fa124 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Sun, 20 Oct 2019 01:32:16 +0900 Subject: [PATCH] mount: support FUSE helper When m.Type starts with either `fuse.` or `fuse3`, the mount helper binary `mount.fuse` or `mount.fuse3` is executed. Signed-off-by: Akihiro Suda --- mount/mount_linux.go | 57 +++++++++++++++++++++++++++++++++++++-- mount/mount_linux_test.go | 45 +++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/mount/mount_linux.go b/mount/mount_linux.go index 6bbc50bbf..a7edd4552 100644 --- a/mount/mount_linux.go +++ b/mount/mount_linux.go @@ -19,6 +19,7 @@ package mount import ( "fmt" "os" + "os/exec" "path" "strings" "time" @@ -28,14 +29,27 @@ import ( "golang.org/x/sys/unix" ) -var pagesize = 4096 +var ( + pagesize = 4096 + allowedHelperBinaries = []string{"mount.fuse", "mount.fuse3"} +) func init() { pagesize = os.Getpagesize() } -// Mount to the provided target path +// Mount to the provided target path. +// +// If m.Type starts with "fuse." or "fuse3.", "mount.fuse" or "mount.fuse3" +// helper binary is called. func (m *Mount) Mount(target string) error { + for _, helperBinary := range allowedHelperBinaries { + // helperBinary = "mount.fuse", typePrefix = "fuse." + typePrefix := strings.TrimPrefix(helperBinary, "mount.") + "." + if strings.HasPrefix(m.Type, typePrefix) { + return m.mountWithHelper(helperBinary, typePrefix, target) + } + } var ( chdir string options = m.Options @@ -92,7 +106,28 @@ func Unmount(target string, flags int) error { return nil } +func isFUSE(dir string) (bool, error) { + // fuseSuperMagic is defined in statfs(2) + const fuseSuperMagic = 0x65735546 + var st unix.Statfs_t + if err := unix.Statfs(dir, &st); err != nil { + return false, err + } + return st.Type == fuseSuperMagic, nil +} + func unmount(target string, flags int) error { + // For FUSE mounts, attempting to execute fusermount helper binary is preferred + // https://github.com/containerd/containerd/pull/3765#discussion_r342083514 + if ok, err := isFUSE(target); err == nil && ok { + for _, helperBinary := range []string{"fusermount3", "fusermount"} { + cmd := exec.Command(helperBinary, "-u", target) + if err := cmd.Run(); err == nil { + return nil + } + // ignore error and try unix.Unmount + } + } for i := 0; i < 50; i++ { if err := unix.Unmount(target, flags); err != nil { switch err { @@ -317,3 +352,21 @@ func mountAt(chdir string, source, target, fstype string, flags uintptr, data st } return errors.Wrap(sys.FMountat(f.Fd(), source, target, fstype, flags, data), "failed to mountat") } + +func (m *Mount) mountWithHelper(helperBinary, typePrefix, target string) error { + // helperBinary: "mount.fuse3" + // target: "/foo/merged" + // m.Type: "fuse3.fuse-overlayfs" + // command: "mount.fuse3 overlay /foo/merged -o lowerdir=/foo/lower2:/foo/lower1,upperdir=/foo/upper,workdir=/foo/work -t fuse-overlayfs" + args := []string{m.Source, target} + for _, o := range m.Options { + args = append(args, "-o", o) + } + args = append(args, "-t", strings.TrimPrefix(m.Type, typePrefix)) + cmd := exec.Command(helperBinary, args...) + out, err := cmd.CombinedOutput() + if err != nil { + return errors.Wrapf(err, "mount helper [%s %v] failed: %q", helperBinary, args, string(out)) + } + return nil +} diff --git a/mount/mount_linux_test.go b/mount/mount_linux_test.go index c3e6a018d..131294d8e 100644 --- a/mount/mount_linux_test.go +++ b/mount/mount_linux_test.go @@ -19,8 +19,15 @@ package mount import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" "reflect" "testing" + + "github.com/containerd/continuity/testutil" ) func TestLongestCommonPrefix(t *testing.T) { @@ -92,3 +99,41 @@ func TestCompactLowerdirOption(t *testing.T) { } } } + +func TestFUSEHelper(t *testing.T) { + testutil.RequiresRoot(t) + const fuseoverlayfsBinary = "fuse-overlayfs" + _, err := exec.LookPath(fuseoverlayfsBinary) + if err != nil { + t.Skip("fuse-overlayfs not installed") + } + td, err := ioutil.TempDir("", "fuse") + if err != nil { + t.Fatal(err) + } + defer func() { + if err := os.RemoveAll(td); err != nil { + t.Fatal(err) + } + }() + + for _, dir := range []string{"lower1", "lower2", "upper", "work", "merged"} { + if err := os.Mkdir(filepath.Join(td, dir), 0755); err != nil { + t.Fatal(err) + } + } + + opts := fmt.Sprintf("lowerdir=%s:%s,upperdir=%s,workdir=%s", filepath.Join(td, "lower2"), filepath.Join(td, "lower1"), filepath.Join(td, "upper"), filepath.Join(td, "work")) + m := Mount{ + Type: "fuse3." + fuseoverlayfsBinary, + Source: "overlay", + Options: []string{opts}, + } + dest := filepath.Join(td, "merged") + if err := m.Mount(dest); err != nil { + t.Fatal(err) + } + if err := UnmountAll(dest, 0); err != nil { + t.Fatal(err) + } +}