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 <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
Akihiro Suda 2019-10-20 01:32:16 +09:00
parent 537afb1498
commit e739314ed4
2 changed files with 100 additions and 2 deletions

View File

@ -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
}

View File

@ -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)
}
}