Demonstrating mounting a FUSE filesystem using github.com/containerd/containerd/mount
instead of fusermount
.
$ make
2019/06/18 15:45:29 Mounting hellofs on "./mnt"
$ cd ./mnt
$ ls -lah
total 0
-rw-r--r-- 0 root root 5 Dec 31 1969 hello
$ cat hello
hello
Ctrl-C
out of make
will trigger the unmount. If you were using ./mnt
when unmounting, you may get this message:
^C2019/06/18 15:47:31 Unmounting "./mnt"
panic: /bin/fusermount: failed to unmount /home/edgarl/go/src/github.com/hinshun/hellofs/mnt: Device or resource busy
(code exit status 1)
// ...
$ ls
ls: cannot access 'mnt': Transport endpoint is not connected
mnt vendor go.mod go.sum hellofs LICENSE main.go Makefile README.md
You can cleanup the mount by ensuring no processes are still using ./mnt
and then run make umount
:
// ...
import (
// ...
cmount "github.com/containerd/containerd/mount"
)
// ...
// Create a FUSE FS on the specified mount point. The returned
// mount point is always absolute.
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
user, err := user.Current()
if err != nil {
return 0, err
}
f, err := os.OpenFile("/dev/fuse", os.O_RDWR, 0666)
if err != nil {
return 0, err
}
fd = int(f.Fd())
m := cmount.Mount{
Type: fmt.Sprintf("fuse.%s", opts.Name),
Source: opts.FsName,
Options: []string{
"nosuid",
"nodev",
fmt.Sprintf("fd=%d", fd),
fmt.Sprintf("rootmode=%#o", syscall.S_IFDIR),
fmt.Sprintf("user_id=%s", user.Uid),
fmt.Sprintf("group_id=%s", user.Gid),
},
}
if opts.AllowOther {
m.Options = append(m.Options, "allow_other")
}
m.Options = append(m.Options, opts.Options...)
err = m.Mount(mountPoint)
if err != nil {
return 0, err
}
close(ready)
return fd, err
}
FUSE is an userspace filesystem framework, it consists of a kernel module fuse.ko
that has a FUSE VFS interfaced by a device /dev/fuse
on your system. Many FUSE implementations rely on libfuse
(a C library to interact with /dev/fuse
and its protocol), and fusermount
(a binary owned by root
but executable by users with a suid
bit set, so that users can use the mount FUSE filesystems without being root). Libraries like github.com/hanwen/go-fuse and github.com/bazil/fuse use fusermount
to mount but not libfuse
.
fusermount
is a binary commonly used to mount FUSE filesystems. FUSE implementers typically call fusermount
as a subprocess, and provides a binary like sshfs
that users invoke to mount the filesystem instead of calling fusermount
, mount(8)
or mount(2)
directly. The subprocess needs to have an environment variable _FUSE_COMMFD
set. The FUSE implementation needs to create a file descriptor, set _FUSE_COMMFD=<fd>
, which fusermount
will use to pass back the control fd open on /dev/fuse
.
mount(8)
is a binary that mounts a filesystem. Internally it uses mount(2)
the syscall in order to actually get the kernel to mount. mount(8)
registers filesystems using executables with the name /sbin/mount.*
. These executables need to fulfill a specific interface, that mount(8)
will invoke on the /sbin/mount.*
binary. You can add a /sbin/mount.<your-fuse-mount-wrapper>
that should daemonize a process running your FUSE server. However, mount(2)
will not know about these /sbin/mount.*
binaries, this is just an implementation detail of mount(8)
. There is a /sbin/mount.fuse
that one of these mount wrappers that will execute fusermount
under the hood.
mount(2)
is the mount syscall. It only knows about filesystems registered in the kernel (in kernel filesystems, including fuse
, register themselves via register_filesystem), which are visible via cat /proc/filesystems
. However, there seems to be undocumented behaviour in that if you call mount(2)
with a type is prefixed like fuse.<subtype>
, it will actually mount via FUSE. The subtype
doesn't seem to actually matter other than being the type of the mount when you run mount -l
.
The mount(2)
signature looks like this:
#include <sys/mount.h>
int mount(const char *source, const char *target,
const char *filesystemtype, unsigned long mountflags,
const void *data);
For filesystemtype
of fuse.*
type:
source
is unused, you can use it to supply the FUSE name likesshfs
.target
is the mountpointfilesystemtype
looks likefuse.*
(i.e.fuse.sshfs
).mountflags
are flags you can find onmount(2)
's manpage. If mountflags is empty, then it creates a mount. You can supplymountflags
to runmount(2)
on existing mounts to change properties like making it readonly, change propagation types, etc. In some of those cases,source
,filesystemtype
orfilesystemtype
anddata
are ignored.data
is an optional buffer to provide filesystem specific options.
For fuse.*
filesystem types, the options for data
are delimited by comma, and must have these 4(?) fields:
fd
is the file descriptor you get when you open/dev/fuse
withO_RDWR
, this is the control FD where FUSE protocol messages are sent between the FUSE VFS in the kernel, and your FUSE server in userspace.rootmode
is file mode of your mountpoint bitwise AND withS_IFMT
, its a bitmask to show only bits of the file mode that says if its a regular, directory, device file, etc. I believe it expects it to beS_IFDIR
, which is the bit that represents it being a directory.user_id
is the uid of the user mounting.group_id
is the gid of the user mounting.
So the FUSE workflow is:
- Open
/dev/fuse
withO_RDWR
to producecontrol FD
. mount(2)
withsource=<fuse-name>
,target=<mountpoint>
,filesystemtype=fuse.<fuse-name>
,mountflags=0
,data=fd=<control FD>,rootmode=<S_IFDIR in octal str>,user_id=<uid>,group_id=<gid>
.- FUSE server reads initialization request from
control FD
and responds with some data about its implementation. - FUSE starts listening on
control FD
. - I/O performed on file in
mountpoint
is handled by FUSE VFS in kernel, which sends a request to FUSE server viacontrol FD
. - FUSE server responds via
control FD
, to complete the I/O.
- http://man7.org/linux/man-pages/man8/mount.fuse.8.html
- https://stackoverflow.com/questions/1554178/how-to-register-fuse-filesystem-type-with-mount8-and-fstab
- https://stackoverflow.com/questions/6469557/mounting-fuse-gives-invalid-argument-error-in-python
- https://unix.stackexchange.com/questions/118090/mounting-mmcblk0p1-failed-with-invalid-argument
- https://engineering.facile.it/blog/eng/write-filesystem-fuse/
- https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/mounting.md#mount8-and-fstab-compatibility
- https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/tools/mount_gcsfuse/main.go