Update (May 3rd, 2021): I have updated this post with a newer installation script that patches the latest version of the driver available on the mailing list (v26) to work with the 5.4 kernel on Raspberry Pi.
We all know how Raspberry Pi is such a great little device. Numerous use cases have been found for it, including being used as a file server. Personally, the one I have at home, besides doing some other tasks, also takes care of exposing a USB SSD using Samba on the network, where my Windows PC regularly saves backups using File History.
One of the problems for such a setup is what file system to choose for the external hard drive where you store the backups. Since I use Windows on some computers, it’s been proven on a number occasions that it is very convenient that, in case of an emergency, one can simply plug the drive in a Windows machine and explore its contents freely using File Explorer. For this to work, the drive’s file system has to be supported in Windows in one form or another.
I have used a number of strategies in the past:
Format the drive as ext4, which offers the best performance in Linux, since it is natively supported, and then use some third party driver in Windows, like Ext2Fsd; the problem with this solution is that you have to rely on some third party component that is not part of Windows, sometimes it may be closed source, and it is prone to being left behind and becoming outdated (that’s what happened largely with Ext2Fsd, but it seems someone has picked up development again).
Format the drive as ext4 and use Windows Subsystem for Linux. Specifically, with WSL2 and some recent Windows 10 builds, Microsoft allows mounting physical disks directly in the Linux subsystem, as explained here; the problem is, it is not available in the latest stable Windows 10 build (20h2, 19042), plus, you still rely on WSL2, so another abstraction etc - kind of hard to use in an emergency.
Format the drive as FAT32 - not a solution because I have files larger than 4GB.
Format the drive as exFAT. The problem with this is that exFAT suffers from fragmentation problems, plus not the best support on power losses etc. It really is more of a file system designed for thumb drives (i.e. removable media), not disks running 24/7. There is a FUSE driver implementation that ships with the latest Raspbian, plus newer Linux kernels have a built-in module.
Format the drive as NTFS; this time, we need a solution on Linux. The kernel still ships with a very outdated NTFS driver that allows browsing drives only read only. Fortunately, most distros include the ntfs-3g package, a FUSE driver which is pretty good.
In the end, I settled with the last solution, as a good balance between convenience and performance. On Raspberry Pi, when mounting a drive using ntfs-3g, make sure to specify the option big_writes in order to have acceptable performance, like so:
# mount -o big_writes /dev/sdb1 /mnt
The problem with ntfs-3g is that it is kind of slow. Even with big_writes, it simply cannot saturate the Gigabit link between the Pi and the computer. That would mean around 125 MB/s. That’s the speed I get when transferring over Samba to the SSD I boot the Pi from which is ext4 formatted. With ntfs-3g, it fluctuates between 55 MB/s and the best I could get is around 90 MB/s. So quite a drop in performance. The CPU is almost maxed out - a lot of context switches happen, and it all boils down to, probably, the fact that we are talking about a user space implementation, which simply has these kind of limitations.
Now, 80% performance in the best scenarios is not that bad, but, I don’t know, it is 2021, not even saturating a gigabit connection is pretty lame. I was about to format the external drive as exFAT and do some tests, then decided to give up on that and go with good old trusted ext4, when I accidentally learned about a new driver that Paragon Software is contributing (these guys do all kinds of file systems utilities for Windows/macOS/Linux) to the kernel. And it is not only talks, they have actually submitted code for review, the most recent patch a few days ago. Read here, for example.
Now, that’s what I call news! That’s awesome. I naturally wanted to use that. Knowing that it will eventually be upstreamed and included with the kernel definitely makes it a very good candidate. Sure, it is going to take a while before it officially reaches the Pi, but it is nevertheless promising.
Since I don’t want to mess with any rpi-update and upgrade the kernel of my working setup, I decided to try it on the current kernel and patch my way if the situation demands it. So, let’s compile and install that on the Raspberry Pi.
The first step when you want to do such things is to look on the Arch Wiki or in the Arch User Repository for any relevant packages regarding this. Arch is a very good distro, very well documented, it is my go to choice when I have the opportunity. On Raspberry Pi, of course, I run Raspbian since Arch is not really an option. It does not matter that Raspbian is Debian while Arch is Arch - they use a very friendly packaging system and we can get a lot of useful info from there and come up with a script that will run just fine on the Pi.
A good candidate for our search is ntfs3-dkms from AUR. Looking at the PKGBUILD, I can see it downloads the files directly off the mailing list in the form of patches which apply one after the other to generate the files for the driver. What’s great about this package is that it comes with a proper dkms.conf file, so it is really easy to integrate this with DKMS, which will make sure the driver gets rebuilt when the kernel is updated etc. Also, another advantage is that is gets properly installed in the system, so it can be loaded automatically on demand, without you having to do any insmod ntfs3.ko beforehand.
So, I came up with an installation script that largely mimics the PKGBUILD of the Arch package. The single issue I have found is that the driver does not compile as is on Raspberry Pi. The kernel on my Pi is 5.4.72-v7l+ (uname -r). This kernel does not have support for the readahead operation in file systems. This has been introduced around version 5.8 as far as I can tell. Fortunately, this is not that big of a thing - it is just an improvement that the kernel offers that drivers can take advantage of. This new driver takes advantage of that, but since our current kernel does not support that, we can take out that support (which equates to deleting a few lines from a certain source file) and then it compiles and works just fine.
Update (May 3rd, 2021): I have updated the script to work with the latest v26 of the driver, the very latest submitted for review. For this to work with kernel 5.4, a few more patches are needed, namely:
1) readahead support in struct address_space_operation (this was already a problem with v17 for which I made a patch)
2) BIO_MAX_VECS is called BIO_MAX_PAGES in kernel 5.4
3) callback prototypes in struct inode_operations do not take a *struct user_namespace** parameter as first argument in 5.4; this is due to a very recent change in the kernel (it made it into 5.12), where it was required to add this information to these callbacks; more details here
For (1), we already have a patch from the previous version. For (2), it is easy to replace BIO_MAX_VECS with BIO_MAX_PAGE. For (3), my approach is to patch out all functions in the driver that take a struct user_namespace** and remove that. For the functions where such a pointer is still required (like *posix_acl_from_xattr), the fallback variable to provide as an argument is init_user_ns (the driver already does this in a few places - that’s the “initial user namespace” which makes everything behave like in older kernels, so to say).
Note that (3) is required even on the newer 5.11 kernel in more recent Raspbian releases, as the struct inode_operations was changed only in 5.12.
I have included the relevant patch in the installation script (make sure to run it as root, so with sudo):
The script above downloads the files in the appropiate directory, and then uses the DKMS to build and install the module. Also, it should execute very fast, at most 1 minute with a decent Internet connection, this including the actual compilation which does not take long at all.
Beware that I add a line to the module that marks it as part of the kernel tree, so that when the module is loaded, the kernel does not get tainted. I do this because a tainted kernel loses some debugging functionality. While it will be in tree at some point, it is not at the moment. If you experience problems with your kernel, consider it tainted and do not submit reports with this module loaded. Instead, make sure the module does not get loaded, reboot the system, catch the problem and submit a report with an untainted kernel. You can verify the condition of your kernel by issuing:
~ cat /proc/sys/kernel/tainted
Decode the value as explained here. If you don’t want this behavior, take out this line from the script above: echo 'MODULE_INFO(intree, "Y");' >> super.c.
In the end, what you have to do, in order to mount an NTFS partition using this driver, is to explicitly specify the type when issuing the mount command, like so:
# mount -t ntfs3 /dev/sdb1 /mnt
If everything went ok, the drive should be mounted (check in lsblk and findmnt). Should any error occur, start by first taking a look in dmesg.
Based on my use case (sharing via Samba), plus the documentation of this driver (available at /usr/src/ntfs3-17.0.0/ntfs3.rst) and of the commercial variant (available here; the commercial variant is different from this driver but has some similarities), I mount my drives using this command line that I deemed optimal for my situation:
# mount -t ntfs3 -o umask=000,fmask=000,dmask=000,discard,force,prealloc,no_acs_rules,acl /dev/sdb1 /mnt
To avoid having to specify the type, the comments on the AUR package suggest using an udev rule. I personally haven’t used that, as I mount my disks at boot using /etc/fstab anyway, but it definitely should work and is worth a try. They also offer suggestions for enabling easy mounting in desktop environments (I use Raspbian Lite, so headless).
Now, do some testing. For me, I easily saturate the link (around 115MB/s), which is awesome. On par with ext4, at least for the kind of speeds a gigabit connection supports, which is what I was looking for. Indeed, a proper solution. And this without any overclock on the Pi, which should not be required (my Pi4 is passively cooled, so I try not to push it excessively - I value quietness more).
I also provide an uninstall script, in case you want to completely get rid of this from your system (again, run as root and unmount all the disks using the driver so it can be unloaded from the kernel):
That’s it, hope it serves you well. Hopefully, this will be integrated as soon as possible in the kernel and eventually reach the Pi as well, as this is pretty big. Finally a proper file system that works best with both Windows and Linux and with no headaches. I mean, I don’t want to be the one to suggest it, but I am sure someone will soon boot Linux off NTFS. Maybe have a single drive with both Windows and Linux, with home and My Documents mapped to the same folder and boot in either OS using two different entries in the EFI boot menu. Interesting times ahead. This module definitely is an incredible contribution, I can’t wait for it to ship - probably with 5.12, if not 5.11.
P.S. You can use /etc/fstab to mount the filesystem instead of running the mount command every time, especially if you want to have the drive mounted automatically at boot. If you do not know what to write there, which I don’t know as well, again, Arch has a nifty utility to help us, courtesy of their excellent installation guide: genfstab - this generates an fstab file from the current mounts in the system. To install it on the Pi, do:
# apt install arch-install-scripts
Indeed, there is a Debian package for that. Then, mount the partition using ntfs3 as shown above, after which generate an fstab:
# genfstab -U /
This will write the fstab at stdout. Just copy the line containing your partition that is mounted using the ntfs3 driver and put it into your real fstab. It’s that easy.
P.P.S. If you simply want to build the module manually in some folder where you get the files, execute this:
~ KVERSION=$(uname -r) CONFIG_NTFS3_FS=m CONFIG_NTFS3_LZX_XPRESS=y CONFIG_NTFS3_FS_POSIX_ACL=y make KDIR=/lib/modules/$(uname -r)/build
To load the module in the kernel, use insmod in the same folder:
# insmod ntfs3.ko
To remove it from the kernel, use:
# rmmod ntfs3
P.P.P.S. Of course, the script is universal, it works on any distro and you can simply delete the part that patches out the readahead support if your kernel is 5.8+.