Fuse Rust

  1. Electric Fuse Rust
  2. Rust Fuse Airfield
  3. Fuse Restaurant Naples

If the fuses are placed in wet or humid environments they can corrode at the metal connection points. Corroded connections can cause over heating and failure of the holder. A safeguard is to check fuses and corresponding holders regularly, and to clean the holder of any corrosion. The fuse holders on the bottom side of the fuse box are corroded/rusty on two fuse holders. I am not getting any power out of the two Fuse Holders near the E Brake, even with the key on. I checked the fuses, they are good. I can see rust on the fuse holders.

Relevancy: 1.8 nightly

We finished part 1 with a working FUSE filesystem representing an empty directory. Today we will continue the story and finally add some files to the mix.

Directory listing revisited

We need to add a minimal amount of JSON handling before actually presenting the data in our filesystem. The JsonFilesystem struct will hold a json::Object value (this is a typedef for a TreeMap) representing the root object in the input JSON.

We used the json! macro to embed JSON data directly in the source. The readdir method must be updated to add directory entries for each key in the JSON.

Let's look at the result:

Starting to look good! Our JSON keys are there, but what's with these question marks? Turns out FUSE calls another method called lookup() and since we haven't defined it yet, a default implementation will be used. But before we write our customized lookup, let's refactor the code a bit.

Both getattr and lookup create FileAttr values based on inode number or filename (JSON key in our example). Since we intend our filesystem to be read-only, let's create all necessary atributes upon initialization. This will simplify the other methods a lot.

We used time::now() to set current timestamps for creation/modification times instead of the epoch time (January 1, 1970). We also calculate the size of our 'files' and store that in the attribute struct.

With that in our toolkit, let's implement lookup().

This method is quite straightforward since we moved attribute-related logic to JsonFilesystem::new. If we run ls now:

No more question marks! The getattr method is even simpler now:

Reading files

Electric Fuse Rust

This is the final piece of the puzzle in our filesystem. Fortunately it is pretty simple after all we've done so far. We need to implement the read method.

Let's try reading a 'file' now:


As usual, the code for yesterday and today is in the 24daysofrust repository on GitHub. Of course this is by no means a complete JSON-as-a-filesystem implementation. Here are some ideas for future expansion:

  • nesting - if a key contains a JSON object, why not represent it as a subdirectory
  • creating files, writing to them, deleting etc. - a fully writeable filesystem
  • getting rid of linear search in read (that's evidently not very efficient)

– Contributed by Alan Somers

During 2019 the FreeBSD Foundation sponsored me to rewrite FreeBSD’s FUSE driver. That project is now complete.

What is FUSE?

FUSE (Filesystem in USErspace) is a driver and a protocol for allowing userspace processes to implement a file system, which the kernel presents to other processes just like any other. Running in userspace makes file systems considerably easier to develop and debug than kernelspace file systems. That’s why file systems like HFS use FUSE. Userspace programs can also access libraries and utilities that aren’t normally available in kernelspace, enabling virtual file systems like sshfs (which mounts a remote server’s files over an SSH connection) and encfs (an encrypted file system that uses an ordinary file system as its backing store). Finally, FUSE provides license hygiene for kernel modules. For example, a GPLv3 kernel module cannot coexist alongside either a CDDL or a GPLv2 module; the result would likely not be redistributable in compliance with all licenses. Running a GPLv3 file system in userspace via FUSE neatly sidesteps this issue.

FUSE was originally written for Linux in 2005, first appearing in kernel version 2.6.14. It proved so useful, however, that ports soon began to appear. Today, OSX, OpenBSD, Illumos, Minix, and of course FreeBSD also support FUSE. NetBSD does not use the FUSE protocol, but it still supports many FUSE file systems via a userspace compatibility layer. And a FUSE driver for DragonflyBSD is under development.

FreeBSD’s FUSE port

FreeBSD’s FUSE driver began life as a GSoC project in 2005 by Csaba Henk, but wasn’t integrated into the base system. A further GSoC project in 2011 by Ilya Putsikau finished the port, and Attilio Rao merged it soon after. However, the 2011 version was still a few years behind the then-current protocol, and had some unresolved bugs. In the subsequent 8 years many of those bugs went unaddressed, and there was little maintenance or new features until now. Thanks to Foundation sponsorship I was able to rectify this situation. I’ve largely rewritten the driver, updated the kernel version, fixed dozens of bugs, and added new features and performance enhancements.

Fuse Rust

Using FUSE file systems

Using FUSE file systems is fantastically easy; that’s the whole point of FUSE. Once mounted, they can be accessed just like a normal file system. The mount command varies between different file systems. For example, to mount an ext2 file system:

sudo pkg install -y fusefs-lkl e2fsprogs
truncate -s 1g /tmp/ext2.img
mkfs.ext2 /tmp/ext2.img
mkdir /tmp/mnt
lklfuse -o type=ext2 /tmp/ext2.img /tmp/mnt

Notice the lackof `sudo` in the second command. That’s intentional[1]. FUSE daemons can run unprivileged. When they do, the mountpoint is only accessible by the user running the daemon. That’s to prevent the daemon’s user from spying on the I/O of other users. In fact, many FUSE daemons eschew any explicit permission checks in this mode, allowing the mounting user to do virtually whatever he wants with the file system, like this:

install -m 755 -o root -g wheel -d /tmp/mnt/bin
install -m 755 -o root -g wheel /bin/sh /tmp/mnt/bin/sh

The unprivileged user can create files owned by root! At first glance, that looks like a whopping security hole. But it’s actually ok, since other users can’t access that file system at all[2]. All that’s really happening is that the user is changing /tmp/ext2.img, which he owns. This feature is very cool. For example, you can use it to create a complete bootable image, such as for an embedded system.

Of course that’s only one use case. More traditional mounts are possible. For example, if a certain file system weren’t available in the running kernel, you could mount a FUSE implementation as root with the `allow_other` and `default_permissions` options. That way it would be available to all users, and would function just like any other filesystem:

umount /tmp/mnt
sudo lklfuse -o type=ext2,allow_other,default_permissions /tmp/ext2.img /tmp/mnt

Developing FUSE file systems

Compared to an in-kernel file system, developing file systems for FUSE is much easier. And not only is it easier to write a FUSE file system, it’s also very easy to write it portably. Most FUSE file systems need few to no changes in order to run on several operating systems.

Rust Fuse Airfield

One of the benefits of programming in userland is the program is not limited to C. Indeed, FUSE bindings are available for Perl, Python, Rust, Javascript, Java, Ruby, Nim, C#, Go, and probably others too.

Starting a FUSE daemon is slightly complicated: the daemon first opens `/dev/fuse`, then calls `nmount` with that file descriptor as one of the arguments. Then it begins to read FUSE requests from that same file descriptor and write the responses back. However, developers rarely need to worry about those details, because libfuse takes care of it. Instead, whether writing in C or another language, the developer generally just has to define callbacks for each supported FUSE operation. Then the library takes care of all the plumbing. For example, in Python the crucial code for a “Hello World” example is just 37 lines[3]:

class HelloFS(Fuse):

def getattr(self, path):
st = MyStat()
if path '/':
st.st_mode = stat.S_IFDIR 0o755
st.st_nlink = 2
elif path hello_path:
st.st_mode = stat.S_IFREG 0o444
st.st_nlink = 1
st.st_size = len(hello_str)
return -errno.ENOENT
return st

def readdir(self, path, offset):
for r in '.', '..', hello_path[1:]:
yield fuse.Direntry(r)

def open(self, path, flags):
if path != hello_path:
return -errno.ENOENT
accmode = os.O_RDONLY os.O_WRONLY os.O_RDWR
if (flags & accmode) != os.O_RDONLY:
return -errno.EACCES

def read(self, path, size, offset):
if path != hello_path:
return -errno.ENOENT
slen = len(hello_str)
if offset < slen:
if offset + size > slen:
size = slen - offset
buf = hello_str[offset:offset+size]
buf = b'
return buf

The security model of FUSE may come as a surprise: by default the daemon is responsible for authorizing all operations. That does place an extra burden on the FUSE file system developer. However, the usual behavior can be achieved by always using the `default_permissions` mount option. The upside is that the FUSE file system can use exotic authorization strategies, like bespoke ACL formats. This feature is also very useful for networked file systems that do authorization on the server, rather than on the clients.

New Features

FreeBSD 13’s new fusefs(5) driver adds several new features, all of which should be immediately useable by existing FUSE file systems. Here are the most interesting:

* Kernel-side permissions checks (`-o default_permissions`) is now fully implemented.

* `mknod(2)`, `pipe(2)`, and `socket(2)` are now supported, so it’s possible to create any type of file on a fusefs file system.

* Server-side support for `fcntl(2)` advisory locks has been added. Previously `fcntl` locks were always implemented in-kernel, but that was insufficient for network file systems that do distributed locking.

* When mounted with `-o intr`, and if the server supports it, fusefs mounts are now fully interruptible. That means that a signal can interrupt an operation that’s blocked waiting for a response from the server. It’s similar to NFS’s mount option of the same name.

* A fusefs mountpoint can now be exported over NFS.

* The kernel will now cache file names and attributes, if the server allows it.
The server can also asynchronously evict part of the kernel’s cache. The kernel can also cache reads and writes, if permitted by the server. Finally, it will read ahead when a process appears to be reading sequentially. These features will all improve performance with no application or configuration changes required.

[1]: To enable this behavior, set the vfs.usermount sysctl to 1.

[2]: An astute and paranoid reader might ask whether the mounting user can set create a SUID file and elevate his privileges that way. Rest assured; he can’t. Unprivileged mounts automatically get `nosuid` set.

Fuse Restaurant Naples

[3]: For the full example, see https://github.com/libfuse/python-fuse/blob/master/example/hello.py