udev is the dynamic device manager for Linux, smartly manages different hardware devices, and it directly interacts with the Linux kernel, mode about udev here.
What if you want to run a specific linux commands when a specific hardware added to the system ? Let say run a backup script when a pendrive is connected.
Background: I faced a strange problem on a laptop running Debian, udev rules failed to switch an USB 4G dongle to modem mode from CD-ROM mode.
So I decided to run a shell script whenever the 4G dongle plugged in, rather than switching it manually.
This method could be implemented almost everywhere, but I'm using the example above to make the tutorial simple.
Contents
1. Prepare the shell script
This step depends upon your requirements, write the script to do whatever you want, and save it somewhere, I'm storing it under the /usr/local/bin
folder.
My script to switch the 4G dongle,
#!/bin/sh # Switch 4G dongle usb_modeswitch -Q -v 1c9e -p f000 -V 1c9e -P 9605 \ -M "55534243123456788000000080000606f50402527000000000000000000000"
Now make it executable with chmod +x
sudo chmod +x /usr/local/bin/switch_modem
2. Create a systemd service
As directly running the script from a custom udev rule didn't worked, I had to create a new systemd service to run the script through it, when the device is plugged in, a USB modem in this case.
User created systemd service files are usually stored under /etc/systemd/system/
, I'm naming it switch_modem.service
sudo nano /etc/systemd/system/switch_modem.service
The systemd service file,
[Unit] Description=4G modem switcher [Service] Type=oneshot RemainAfterExit=no ExecStart=/usr/local/bin/switch_modem [Install] WantedBy=multi-user.target
This wiki article is very helpful to understand systemd and how to create systemd services.
3. Edit or create custom udev rules
You could directly edit a desired udev rule or create a new rule under /etc/udev/rules.d/ folder, I'm creating a new rule
sudo nano /etc/udev/rules.d/40-switch_modem.rules
My custom rule looks like bellow
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="1c9e", ATTR{idProduct}=="f000", RUN+="switch_modem.service"
Now save and exit and reload newly created rules.
sudo udevadm control --reload-rules
You may need to restart the udev service too, use
sudo service udev restart
Or use this if the service command is not available
sudo systemctl restart systemd-udevd.service
In my case, now the 4G modem is switching automatically, and working fine, more on how to write udev rules.
Conclusion
This tutorial is not limited to just switch a unsupported 4G dongle, could be repurposed for anything you want to run from an udev event, i.e. when a new hardware added to the system.
I hope you will find this tutorial helpful and it's simple enough to understand. If you have any suggestion or question, please leave comments, I'll be happy to hear from you 🙂 .
Helios-8 says
Udev rules should properly start services with TAG+="systemd", ENV{SYSTEMD_WANTS}="switch_modem.service" instead of RUN+=
Arnab Satapathi says
Wow, thanks for pointing out.
And perhaps RUN+= is for old sysvinit, what do you think ?
felixyadomi says
But how to access udev env variables in a systemd service files without RUN+= ?
Raza says
I found that I could pass one long argument with spaces and then use systemd's environment variable space-splitting feature to separate the arguments.
I made a service with filename argtest@.service (note the trailing 'at sign' which is required when a service takes arguments).
[Unit]
Description=Test passing multiple arguments
[Service]
Environment="SCRIPT_ARGS=%I"
ExecStart=/tmp/test.py $SCRIPT_ARGS
I run this with sudo systemctl start argtest@"arg1 arg2 arg3".service and it passes arg1, arg2 and arg3 as separate command-line arguments to test.py.
Tom says
Found this after lots of searching. Works well with OpenSUSE 42.2
Arnab Satapathi says
Thanks !
Emmanuel says
This is indeed what I need.