Userspace file systems on Mac
I recently had the need for sshfs on Mac, so I did the usual:
$ brew install sshfs
To my surprise it was no longer available:
$ brew install sshfs
sshfs: Linux is required for this software.
libfuse: Linux is required for this software.
Error: sshfs: Unsatisfied requirements failed this build.
The reason for its disappearance became obvious when I started digging:
- The Mac version of sshfs uses MacFuse, which is no longer open source and therefore not compatible with brew
- MacFuse is, among other things, a kernel extension. Apple is in the process of deprecating kernel extensions.
Learning that, I put on my spelunking gear and went headfirst down the rabbit hole looking for a solution.
Userspace file systems without kernel extensions
The current version of MacOS (Sonoma) does contain a remote file system API, known as file providers, but this is more designed for cloud storage synchronization (such as DropBox and iCloud) than to be a Virtual file system (VFS). While it can enumerate a directory structure without downloading everything, it’s quite limited as a VFS, and does not support things such as partially reading a file (head, tail, streaming) or loading/creating directory structures on demand.
All hope isn’t lost though, there are ways to do it. One way, a less
than ideal solution, is to piggy back off of existing file systems. Just
by tab completing on mount_
we’re given a few ideas:
$ mount_
mount_9p mount_apfs mount_devfs mount_ftp mount_msdos mount_tmpfs
mount_webdav mount_acfs mount_cd9660 mount_exfat mount_hfs mount_nfs
mount_udf mount_afp mount_cddafs mount_fdesc mount_lifs mount_smbfs
mount_virtiofs
Out of these there are a few extra interesting ones: 9p
,
virtiofs
, ftp
, smbfs
,
nfs
, and webdav
. What these have in common is
that they are file system abstractions used to expose file systems over
a network, or to share a file system with a virtual machine. So by
creating a bridge from, say sftp, to one of the aforementioned mountable
file system abstractions we can implement a file system without touching
kernel extensions.
NFS + SSH
While digging I came across an interesting project known as nfsserve. It, as its name implies, implements a NFS server, but instead of directly mounting a network file system it exposes a file system trait which you implement as you see fit.
Using an example from the above project, and combining it with a sftp implementation in Rust, I was able to create an experimental, but working sshfs implementation over the course of a weekend. And since I live in 🌨️ Sunny Bergen, Norway, I found some time to improve its performance:
Currently it’s a read-only file system, and should be considered experimental. Adding write support shouldn’t be too difficult, I might do so if I find the time. As it’s built on the NFSv3 protocol features such as file locking are not supported. Credit really belongs to both nfsserve and russh though, with them a Rust novice like myself was able to quickly put together something that actually works:
There are obviously downsides to implementing file systems like this. There are feature limitations, it adds overhead, and in the case of file systems such as NFS or FTP it also exposes the remote file system via a port to other users on the same machine (it’s bound to 127.0.0.1).
fuse-t
It’s worth mentioning that there does exist a a proprietary, closed-source, userspace file system implementation for MacOS, known as fuse-t. I haven’t tried it myself, but I believe it employs the same trick as nfsserve, but uses nfsv4/samba and provides a proper fuse implementation, allowing existing fuse based file systems to work on MacOS.
FSKit - Coming soon!
Before investing a lot of effort in implementing something similar to the above, it might be worth waiting a bit. The next version of MacOS, Sequoia, will include a new API known as FSKit, which looks like it will offer a proper VFS API. Currently, the FSKit API is sadly nearly undocumented, but I did find two examples, Apples’s msdosfs and a much simpler unofficial FSKit sample.