How can I create a userspace filesystem with FUSE without using libfuse?
Asked Answered
G

1

17

I've found that the FUSE userspace library and kernel interface has been ported, since its inception on Linux, to many other systems, and presents a relatively stable API with a supposedly small surface area. If I wanted to author a filesystem in userspace, and I were not on Plan 9 or Hurd, I would think that FUSE is my best choice.

However, I am not going to use libfuse. This is partially because of pragmatism; using C is hard in my language of choice (Monte). It's also because I am totally uninterested in writing C support code, and libfuse's recommended usage is incompatible with Monte philosophy. This shouldn't be a problem, since C is not magical and /dev/fuse can be opened with standard system calls.

Going to look for documentation, however, I've found none. There is no documentation that I can find for the /dev/fuse ABI/API, and no stories of others taking this same non-C-bound route. Frustrating.

Does any kind of documentation exist on how to interact in a language-agnostic way with /dev/fuse and the FUSE subsystem of the kernel? If so, could you point me to it? Thanks!

Update: There exists go-fuse, which is in Go, a slightly more readable language than C. However, it does not contain any ABI/API documentation either.

Update: I notice that people have voted to close this. Don't worry, there is no need for that. I have satisfied myself that the documentation that I desire does not yet exist. I will write the documentation myself, publish it, and then link to it in an accepted answer. Hopefully the next person to search for this documentation will not be disappointed.

Geniegenii answered 24/7, 2015 at 2:33 Comment(6)
I know C; I've been around for a while (openhub.net/accounts/MostAwesomeDude). Monte has a philosophy of safety which means that most extant C code is not usable. I'm happy to expound on this in a blog post, if there's demand. More importantly, C is not privileged in its mechanics. I can make syscalls from any competent platform, and strace shows me that libfuse makes syscalls. I want to avoid having to reverse-engineer an open-source library.Geniegenii
You don't need to reverse engineer anything since you can simply read the source code and the documentationTamworth
The documentation you've linked to does not cover the ABI of /dev/fuse, nor the magic numbers which are sprinkled throughout the source code. (It does not even cover the main source files of libfuse, only an example usage!) I understand that the source code is available; I read it for about 2-3h before asking this SO question. I'm not claiming that the lack of documentation is insurmountable; I'm claiming that I would prefer to not waste time reverse-engineering what might already be documented somewhere.Geniegenii
I don't get how a standard system call should be implemented without using C - at some level. In fact monte is written in python where python is written in C. You are indeed using C.Tamworth
To answer your query, a Linux syscall is made by putting things in registers and making a software interrupt; the details are at e.g. en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux . As to Monte, I'm using Typhon, written in RPython, which is turned into a native executable. If I'm going to tie myself to a C FFI, it won't be for something like libfuse. As mentioned in my edit, I'm going to just document this thing myself and link it in an accepted answer once I'm done.Geniegenii
It looks like one of your stated objectives is portability. Libfuse is Linux-specific. If you reimplement libfuse in some other language, you reimplement a Linux library. Libfuse ports to other OSes are API-compatible with libfuse but use different kernel interfaces. It looks like Go-fuse works in Linux and Mac OS X/Darwin, but not in other OSes.Retroversion
G
19

(I'm not accepting this until it's complete. In the interim, edits are welcome!)

The basic outline of a FUSE session:

  • open() is called on /dev/fuse. I'll call the resulting FD the control FD.
  • mount() is called with the target mount point, filesystem type "fuse" for normal mode or "fuseblk" for block-device mode, and options including "fd=X" where X is the control FD.
  • FUSE-specific structs are transferred on the control FD repeatedly. The general pattern of communication follows a request-response pattern, where the program read()s filesystem commands from the control FD and then write()s responses back.
  • umount() is called with the target mount point.
  • close() is called on the control FD.

With that all said, there's a handful of complications that one should be aware of. First, mount() is almost always a privileged syscall, so you'll have to be root to mount a FUSE filesystem. However, as one may have noticed, FUSE programs can generally be started as non-root! How?

There's a helper, /bin/fusermount, installed setuid. Usage is totally undocumented, but that's what I'm here for. Instead of open()ing /dev/fuse yourself, run fusermount as a subprocess, passing the target mount point as an argument, any extra mount options you like with -o, and (crucially) with the environment variable _FUSE_COMMFD exported and set to the ASCII string of an open FD, which I'll call the comm FD. You must create the comm FD yourself using e.g. pipe(). fusermount will call open() and mount() for you, and share the control FD back to you along the comm FD, using the sendmsg() trick for sharing FDs. Use recvmsg() to read it back.

Editorial: I really don't understand why this is structured to be so difficult. FDs are inherited by subprocesses; it would have been so much easier to open() the control FD in the top process and pass it down into fusermount. True, there's some confused deputy dangers, but fusermount is already installed and setuid and dangerous.

Anyway! fusermount will crudely daemonize and take care of calling umount() and close() to clean up once your main process exits.

Things not yet covered:

  • How is non-blocking access to FUSE handled? Can the control FD just be kicked into non-blocking mode? Does it actually not block, or does it behave like an ordinary file and secretly block on access?
  • The struct layouts. These can be more or less rediscovered from the C or Go source, but that's no excuse. I'll document them more seriously when I've worked up sufficient masochism.
Geniegenii answered 30/7, 2015 at 3:10 Comment(3)
I think you already found the answer. You might be interested in fusermount code. read.pudn.com/downloads96/sourcecode/unix_linux/392124/…Morbihan
@Geniegenii so ... did ever get the answer to the remaining questions and did you actually use this approach?Lantz
I realized that FUSE was not really what I wanted. There's a difference between "in userspace" and "unprivileged", and I wanted the latter. I haven't done any research on this since, sorry.Geniegenii

© 2022 - 2024 — McMap. All rights reserved.