This setup is specifically for Ubuntu 20.04 running in console mode, and it is meant to solve two things at once:
- mounting an NTFS-formatted USB drive
- making it work without a desktop environment
If you are using Ubuntu Desktop, there are much easier ways to handle removable storage. This approach is mainly useful in a stripped-down system, especially in cases where the OS was built manually.
Before touching userspace configuration, make sure the kernel already includes the required features. In the kernel configuration, both FUSE and NTFS support need to be built in:
File Systems —> FUSEFile Systems —> DOS/FAT/EXFAT/NT Filesystems —> NTFS file system support + NTFS write support + NTFS Read-Write file system support
Then install the packages needed in userspace:
apt-get install -y fuse
apt-get install -y ntfs-3g
The next step is to adjust systemd-udevd so mounting from udev-triggered actions can work correctly.
vi /etc/systemd/system/systemd-udevd.service
Add the following:
#保留默认配置 .include /usr/lib/systemd/system/systemd-udevd.service [Service] #以用户态身份Mount PrivateMounts=no
This is only being done here because it is easier to maintain than editing the original file directly. Changing /usr/lib/systemd/system/systemd-udevd.service itself would also work.
The only line that really matters is the last one:
PrivateMounts=no
A common gotcha here is version differences. On older Ubuntu releases, this was reportedly written as MountFlags=shared. That does not apply here and is one of the easy places to go wrong on Ubuntu 20.04.
After that, create a udev rule for block devices:
vi /etc/udev/rules.d/99-usb-mount.rules
The filename matters in two ways:
- it should end with
.rules - using a
99-prefix makes it run late, which is usually safer
Put these two rules into the file:
KERNEL=="sd? [1-9] ", SUBSYSTEM=="block", ACTION=="add", RUN+="/bin/bash /etc/udev/rules.d/usb-disk-monitor.sh %k" KERNEL=="sd? [1-9] ", SUBSYSTEM=="block", ACTION=="remove", RUN+="/bin/bash /etc/udev/rules.d/usb-disk-monitor.sh %k"
The idea is straightforward: when a device like sdX1 is added or removed, udev runs usb-disk-monitor.sh from the same directory. The %k variable is the kernel device name generated by udev, such as sda1 or sdb1, and the script needs it as an argument.
All paths are written as absolute paths on purpose, to avoid surprises in the udev environment.
Now create the script itself:
vi /etc/udev/rules.d/usb-disk-monitor.sh
The script below is placed under /etc/udev/rules.d/. It may be possible to store it elsewhere, but this layout is what was actually used.
#!/bin/bash DEV_NAME=$1 #输出到串口 FLOG=/dev/ttyPS0 echo "" >> $FLOG echo $(date) >> $FLOG MNT_PATH=/run/media/ if [ ! -d "$MNT_PATH" ] ; then mkdir -p "$MNT_PATH" fi DEST_NAME=$MNT_PATH$DEV_NAME if [ "$ACTION" = "add" ] ; then /bin/mkdir -p $DEST_NAME &>> $FLOG /usr/bin/systemd-mount --no-block --collect $DEVNAME $DEST_NAME &>> $FLOG elif [ "$ACTION" = remove ] ; then /usr/bin/systemd-mount --umount $DEST_NAME &>> $FLOG /bin/rmdir $DEST_NAME &>> $FLOG fi
There are two more pitfalls in this script.
The second one is the shell test syntax. Under Ubuntu, the condition here must use:
=
not:
==
Using the wrong operator in this context will break the logic.
The third issue is the mount operation itself. If you detect the filesystem type through something like ID_FS_TYPE, then mounting a FAT32 USB drive with mount -t vfat works fine. But NTFS is different. If you try to mount it with mount -t ntfs-3g, it may look successful at first, yet accessing the mounted drive later results in:
Transport endpoint not connected
This happens because FUSE filesystems should not be mounted directly from udev rules. The relevant behavior is documented by udev: if a FUSE mount is started that way, the mount process may be killed a few seconds later. That is why systemd-mount is used instead of mount.
The mount point in this setup is:
/run/media/
If you want a different location, change the MNT_PATH variable in the script.
One more practical detail: if the script does not redirect its output anywhere, you will not see any logs. In this example, logging is sent to /dev/ttyPS0, because serial port 0 on the target board is mapped there. If your hardware is different, adjust FLOG accordingly.
Once everything is in place, reboot the system and test the behavior by plugging in an NTFS USB drive.
In theory, restarting the udev service might be enough to make the changes take effect. In practice, that is strongly discouraged here; a full reboot is the safer option.