'Why does Systemd_Wants not pass a parameter to a service file from a udev "Remove" rule?

I am trying to use udev rules and systemd to mount and unmount a USB key.

I am basing my solution below on an example from this blog where ENV{SYSTEMD_WANTS} is used in an "Add" udev rule. I could not get ENV{SYSTEMD_WANTS} to work for a "Remove" rule so I ended up using the example in this blog entry which runs a systemd service file from a udev RUN key.

Although this solution works the udev man page says: "Running an event process for a long period of time may block all further events for this or a dependent device."

These are the rules I use...

The Add Rule:

ACTION=="add", SUBSYSTEM=="block", ENV{ID_TYPE}="disk", \ 
ENV{DEVTYPE}=="partition", ENV{ENV_MOUNT_USB}="MOUNT", \ 
PROGRAM="/usr/bin/systemd-escape -p [email protected] \ 
$env{DEVNAME}", ENV{SYSTEMD_WANTS}+="%c"

The Remove Rule:

ACTION=="remove", SUBSYSTEM=="block", ENV{ID_TYPE}="disk", \ 
ENV{DEVTYPE}=="partition", RUN+="/bin/systemctl start usb-umount@%k.service"

These are the systemd service files that the udev rules start. They simply execute bash scripts to mount and un-mount the USB key.

[email protected]

[Unit]
Description=Mount USB Drive on %i
BindTo=%i.device
After=%i.device

[Service]
Type=oneshot
TimeoutStartSec=300
ExecStart=/usr/local/sbin/usb-mount.sh /%I

[email protected]

[Unit]
Description=UnMount USB Drive on %i

[Service]
Type=oneshot
TimeoutStartSec=300
ExecStart=/usr/local/sbin/usb-umount.sh /%I

My Problem
But if I use ENV{SYSTEM_WANTS} in the "remove" rule the device block (e.g: /dev/sdb1) of the detaching USB key does NOT get passed to the service file. Actually I don't think the service file runs at all.

My Question
Can my remove rule be changed to work with ENV{SYSTEM_WANTS} and NOT RUN+={..} such that the /dev/sdb1 is passed to the service file and on to a script that can do the un-mount?

Cheers,

Flex



Solution 1:[1]

After much investigation I have an answer to my question above.

Explanation of Problem

The key points are in the Systemd device man page:

• (In a udev rule) "Systemd_wants="... "Adds dependencies of type Wants= from the device unit (e.g: a USB Key) to the specified units (i.e: a systemd service unit)."

• “...systemd will only act on Wants= dependencies when a device first becomes active.”

I tested by inserting and removing a USB key. From testing this udev rule does not work:

ACTION=="remove", SUBSYSTEM=="block", ENV{ID_TYPE}="disk", \
ENV{DEVTYPE}=="partition", ENV{SYSTEMD_WANTS}+="usb-umount@%k.service"

I debugged like this:

sudo udevadm control --log-priority=debug
journalctl -f

When I removed the already inserted USB key I didn't see any mention of the usb-mount service in the journalctl debug output. So that service file doesn't even start which makes sense because of what the man page states (see above).

In contrast when I change the rule to this:

ACTION=="remove", SUBSYSTEM=="block", ENV{ID_TYPE}="disk", \
ENV{DEVTYPE}=="partition", RUN+="/bin/systemctl start usb-umount@%k.service"

Now in the journalctl output I see:

sdb1: Starting '/bin/systemctl start [email protected]'

There is a good discussion about why Systemd_Wants will not work in a udev remove rule in this Reddit thread.

To quote from that Reddit Thread:

“SYSTEMD_WANTS and SYSTEMD_USER_WANTS do not make sense with ACTION=="remove". It may look like these variables make Udev ask systemd to do something, but the relationship is actually the other way around: the variable is retrieved from Udev by systemd when the device unit is being added to the systemd manager. The variable is not retrieved at any other time.”

This also explains why Systemd_Wants does not make the usb-mount service run from a udev "remove" rule.

A New solution

That Reddit Thread also shows a roundabout way to use systemd_wants with a udev “remove” action:

Now to use udev to respond to a USB key being plugged and unplugged from my machine I need just ONE udev rule, inspired by the Reddit post and this blog:

The udev rule
• /etc/udev/rules.d/99-local.rules

ACTION=="add", SUBSYSTEM=="block", ENV{ID_TYPE}="disk", \
ENV{DEVTYPE}=="partition", ENV{ENV_MOUNT_USB}="USB-MOUNTED", \
PROGRAM="/usr/bin/systemd-escape -p [email protected] \ 
$env{DEVNAME}", ENV{SYSTEMD_WANTS}+="%c"

The Service file
• /etc/systemd/system/[email protected]

[Unit]
Description=Test-Mount USB Drive on %i
BindsTo=%i.device
After=%i.device

[Service]
Type=oneshot
RemainAfterExit=true
TimeoutStartSec=300
ExecStart=/usr/local/sbin/test-mount.sh /%I
ExecStop=/usr/local/sbin/test-umount.sh /%I

The scripts I used...
• /usr/local/sbin/test-mount.sh

#!/bin/bash

#
# This script runs when a USB device is plugged in
#

DEVBASE=$1
DEVICE="${DEVBASE}"

# Create a Timestamp, e.g: 06.05.2022-20.11.49
current_time=$(date "+%d.%m.%Y-%H.%M.%S")

# Query the udev database which is available while the USB key is plugged in.
# However these udev properties are unavailable once the USB key is plugged out.
eval $(udevadm info --query=env --export $DEVICE)

# Create a temporary file
touch /tmp/test-mount.txt

# Add content to the file
echo ${current_time} > "/tmp/test-mount.txt"
echo ${DEVICE} >> "/tmp/test-mount.txt"
echo $ID_FS_LABEL  >> "/tmp/test-mount.txt"
echo $ENV_MOUNT_USB  >> "/tmp/test-mount.txt"

• /usr/local/sbin/test-umount.sh

#!/bin/bash

#
# This script runs when a USB device is plugged out
#

DEVBASE=$1
DEVICE="${DEVBASE}"

# Create a Timestamp, e.g: 06.05.2022-20.11.49
current_time=$(date "+%d.%m.%Y-%H.%M.%S")

# Query the udev database which is available while the USB key is plugged in.
# However these udev properties are unavailable once the USB key is plugged out.
eval $(udevadm info --query=env --export $DEVICE)

# Create a temporary file
touch /tmp/test-umount.txt

# Add content to the file
echo ${current_time} > "/tmp/test-umount.txt"
echo ${DEVICE} >> "/tmp/test-umount.txt"
echo $ID_FS_LABEL  >> "/tmp/test-umount.txt"
echo $ENV_MOUNT_USB  >> "/tmp/test-umount.txt"
/usr/bin/printenv > /tmp/udev-env.txt

The commands I used to Test and Debug
• Insert USB and Remove USB key:

ls -l /tmp

Debug like this...
• This is really the only way you should bother testing a udev rule!
• Run the commands then insert and remove the USB key.

sudo udevadm control --log-priority=debug
journalctl -f

And also this...

udevadm monitor --subsystem-match=block --property --udev
udevadm monitor --property --udev

Test like this...
• This tests is not as useful as the above, as it only simulates what the udev rule would do!
• E.g: For /dev/sdb1 you test the udev rule like this:

# sudo udevadm test --action=add /sys/class/block/sdb1

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 FlexMcMurphy