Run an shell script via a udev rule

Hi :slight_smile:
I’m quite new with OpenSUSE, but I’m used to another distro from the RPM family : Fedora.
I used it for several years, but now I’m working on a development project.

I am using OpenSUSE Leap 42.3 on my laptop, and am working on a program that will detect when a CD/DVD drive is inserted or removed.
For now what I achieved is to have a udev rule that launches a bash script, and this script logs output to a log file.
I can successfully read the entries in this log file, the disc insertion/removal is logged correctly.

/etc/udev/rules.d/autodvd.rules :

SUBSYSTEM=="block", ENV{ID_CDROM}=="?*", 
ENV{ID_PATH}=="pci-0000:00:1f.2-scsi-0:0:0:0", ACTION=="change", 
RUN+="/usr/local/bin/autodvd"

/usr/local/bin/autodvd :

#!/bin/bash
{
  echo "ENTER SCRIPT"
  if  "$ID_CDROM_MEDIA" = "1" ]; then
    echo "$(date)    DISC INSERTED"
  else
    echo "$(date)    DISC EJECTED"
  fi
} &>> "/var/log/autodvd.log" &

An example of the output I got in this log file :

ENTER SCRIPT
Fri Feb  9 23:40:44 CET 2018    DISC EJECTED
ENTER SCRIPT
Fri Feb  9 23:40:58 CET 2018    DISC INSERTED
ENTER SCRIPT
Fri Feb  9 23:41:28 CET 2018    DISC EJECTED
ENTER SCRIPT
Fri Feb  9 23:41:28 CET 2018    DISC EJECTED

What I want to do next is to launch a SH script when the disc is inserted/removed, so I tried to add a line like this one

sh /home/jean/some-script.sh

just after the “echo” command, either for the insert or ejected case, but I can’t get it to work.
For instance, if i used this shell script to launch an instance of firefox or leafpad, the application is launched if I run the script from terminal.
But nothing happens when I try to call the shell script from my “autodvd” script.

I’m quite new to bash/shell scripting, and completely new to udev rules, so I’m sure I’m missing something important. :
Could you please help me on this topic ?

Any help will be greatly appreciated :wink:

Hi,

First of all you can remove the sh in front of your script, if it is a bash script. The extension .sh is a personal choice but it is not needed as well.

It has been a while but I have done something like that in the past and i found my answer here.

https://unix.stackexchange.com/questions/177897/run-a-script-that-displays-an-x-window-from-an-udev-rule

It states the use of

su - username -c ..... 

So udev knows which user is running the script and where to display the output.

As an side note leap has bash4 and has an additional feature for the builtin printf that can display time/date.

An example of printing date and time.

printf '%(%Y-%m-%d %H:%M:%S)T %s' -1

the format is supported by **strftime.

**For your use case, you can probably use.

printf '%(%Y-%m-%d %H:%M:%S)T] %s ' -1 'Disc Inserted'

One advantage of that code is that it does not fork the date utility.

For instance, if i used this shell script to launch an instance of firefox or leafpad, the application is launched if I run the script from terminal.
But nothing happens when I try to call the shell script from my “autodvd” script.

It would be more useful if you showed us your actual /home/jean/some-script.sh script ie what is it you’re trying to do. If you’re really trying to launch a graphical program within a user’s X-session from outside that session (via udev for example), the display environment and xauthrority become important considerations

A recent thread on related topic
https://forums.opensuse.org/showthread.php/529053-unable-to-run-a-graphical-app-via-cron

Any program launched by udev rule will be killed after very short time. udev by design does not allow long-running processes to remain.

Yep, true. We need to know what the OP is trying to accomplish :wink:

I am using OpenSUSE Leap 42.3 on my laptop, and am working on a program that will detect when a CD/DVD drive is inserted or removed.

@taronyu: There is a nice approach described in this blog which uses a custom systemd user service which is started when a CDROM/DVD is present. This might be more appropriate for your need, (otherwise please provide more info about what you’re trying to accomplish).

I tested by creating a minimal service (~/.config/systemd/user/dvd.service) with

[Unit]
Description=Automatically start application when optical media inserted
After=dev-dvd.device
Requisite=dev-dvd.device

[Service]
Type=oneshot
ExecStart=/usr/bin/firefox
StandardOutput=journal

[Install]
WantedBy=dev-dvd.device

I enabled (as user) with

systemctl --user enable firefox.service

*dev-cdrom.device and dev-cdrom.device both seem to work equally well. I only tested with a DVD ISO I had at hand, and firefox was just a means to test that the service file was started upon insertion.

I do not see how this service can possibly work. /dev/dvd existence does not depend on media presence. Can you show “systemctl --user status dev-dvd.device” before and after medium is inserted?

Hi and thanks to all of you for your answers ! :slight_smile:

So I can’t do anything that needs access to X, as it is executed in a text-only bash, and that might compute a while as it will be kicked…
This is interesting and obviously what I was doing does not work.

So yes, it seems that I need to explain a little more. :shame:

What I was trying to do in my SH script :
I was launching Firefox, asking it to open a specific local HTML page, to test if the whole process (udev rule -> bash script in /usr/local/bin/ -> .sh script in /home/jean/) was fine.
Something like this :

firefox "/home/jean/disc-inserted.html"

or

firefox "/home/jean/disc-ejected.html"

So, now I understand that firefox will not spawn as it needs an access to the graphical server.
And even if it achieved to launch, it would be kicked soon.
I’m quite new to all this system stuff but I will remember these two points !

I could easily replace this by a non-graphical, fast command that could allow me to ensure the whole process is working.
For instance, I might simply write “INSERTED” or “EJECTED” to another log file, from this .sh script… Doing this should be right, I think ?

But this is just for testing, what I need to do after will require to execute some commands and run a binary.
Time to explain this aspect of the project !

What I will need to do :
I want to be able to know when a disc in ejected and inserted in the drive. Then, if inserted, I have to run some commands that will mount the disc to a specific folder for later use, and finally run an application. This application will most probably be a custom program built from C++ and that uses sockets to inform another - and bigger - application that a disc has been inserted and mounted in a folder.
Why ?

I’m working with a friend on a personal project that aims to make a very little game machine from a Raspberry Pi 3.
We work on all the aspects of the project : installing and configuring the OS, configuring a develoment environment, development of a 2D game engine, and finally making some games.

I want to be able to detect disc insertion, auto-mount it in a folder, and then, once the bigger application has checked if it is a game disc, install/launch the game.

So, maybe my approach is bad ? The service one looks intersting, if I can get it working I will give it a try !
I’m open to any suggestion for this topic !

Thanks a lot :slight_smile:

A general FYI…
If you can describe what you want your script to do sufficiently well,
I’ve found that searching the gists at github usually turns up something useful.

TSU

It works because device units are reloaded by systemd whenever the corresponding device generates a ‘changed’ event. It has nothing to do with the presence of /dev/dvd directly - systemd abstracts these device nodes as .device units anyway.

From boot, and prior to media insertion…

~> systemctl --user status  dvd.service
● dvd.service - Automatically start application when optical media inserted
   Loaded: loaded (/home/dean/.config/systemd/user/dvd.service; enabled; vendor preset: enabled)
   Active: inactive (dead)

Feb 11 10:22:11 linux-54cw systemd[1776]: Stopped Automatically start application when optical media inserted.

Upon disk insertion…

dean@linux-54cw:~> systemctl --user status  dvd.service
● dvd.service - Automatically start application when optical media inserted
   Loaded: loaded (/home/dean/.config/systemd/user/dvd.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since Sun 2018-02-11 10:27:46 NZDT; 12s ago
  Process: 2512 ExecStart=/usr/bin/firefox (code=exited, status=0/SUCCESS)
 Main PID: 2512 (code=exited, status=0/SUCCESS)

Feb 11 10:27:46 linux-54cw systemd[1776]: Starting Automatically start application when optical media inserted...
Feb 11 10:27:46 linux-54cw systemd[1776]: Started Automatically start application when optical media inserted.
dean@linux-54cw:~> 

If I remove media and insert another, I get another event generated, and the service file is triggered again.

A custom service can be triggered from a udev rule using the ACTION==“change” match and the following: TAG+=“systemd”, ENV{SYSTEMD_WANTS}==“foo.service”. For example, a custom service autodvd.service triggered by this rule…

SUBSYSTEM=="block", ENV{ID_CDROM}=="?*", ACTION=="change", TAG+="systemd", ENV{SYSTEMD_WANTS}="autodvd.service"

This question and answer discusses a similar example, and how pertinent environment variables can be got from udevinfo within the script for evaluation purposes…
https://superuser.com/questions/924683/passing-udev-environment-variables-to-systemd-service-execution

The service one looks intersting, if I can get it working I will give it a try !
I’m open to any suggestion for this topic !

Thanks a lot :slight_smile:

This was just an example of how a simple systemd user service might be used. If you need root processes invoked in your project, stay with normal (root owned) systemd services.

No, you misunderstand how it works,

It has nothing to do with the presence of /dev/dvd directly - systemd abstracts these device nodes as .device units anyway.

Yes - and it misuses “device is present” for “media is present”.

~> systemctl --user status  dvd.service

I did not ask you status of dvd.service - I asked you status of dev-dvd.device. Anyway, systemd indeed ships udev rule that pretends device does not exist if media is not present.

The main thing is that it works as mentioned in the blog…and as you could have tested for yourself. :wink:

Before media…

dean@linux-kgxs:~> systemctl --user status dev-dvd.device
● dev-dvd.device
   Loaded: loaded
   Active: inactive (dead)

After media inserted…

dean@linux-kgxs:~> systemctl --user status dev-dvd.device
● dev-dvd.device - VBOX_CD-ROM GParted-live
   Follow: unit currently follows state of sys-devices-pci0000:00-0000:00:01.1-ata2-host1-target1:0:0-1:0:0:0-block-sr0.device
   Loaded: loaded
   Active: active (plugged) since Sun 2018-02-11 19:23:49 NZDT; 2s ago
   Device: /sys/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/block/sr0

No, I do not have CD/DVD/BD drive to test it.

dean@linux-kgxs:~> systemctl --user status dev-dvd.device
● dev-dvd.device
   Loaded: loaded
   Active: inactive (dead)

So device does not exist … and yes, a lot of systemd issues today originate in attitude “the main thing is that it works”.

It can equally be demonstrated in a VM guest environment.

So device does not exist … and yes, a lot of systemd issues today originate in attitude “the main thing is that it works”.

Perhaps, but I have no wish (or time) to debate/discuss that further here. You can always file a bug report if you think the behaviour is wrong.

Hi :slight_smile:
Thanks again to everyone for the help.

With the suggestions and link provided by deano_ferrari, I finally get it to work in accordance with my goals !
So, about the whole process :

  1. The udev rule is used to start a systemd service once the disc has been inserted.
  2. The systemd service, once started by the udev rule, starts a custom binary I built from a very simple C++ source I wrote.
  3. The binary opens a log file for text operations in append mode, writes the date & time, followed by a “Disc inserted !” message.

This binary will soon be replaced by another one that uses UDP networking over sockets to communicate with another application.

My /etc/udev/rules.d/autodvd.rules file :

SUBSYSTEM=="block", ENV{ID_CDROM}=="?*", ACTION=="change", TAG+="systemd", ENV{SYSTEMD_WANTS}="drive-change.service"

My /etc/systemd/system/drive-change.service file :

[Unit]
Description=Automatically start application when optical media inserted
After=dev-dvd.device
Requisite=dev-dvd.device

[Service]
Type=oneshot
ExecStart=/home/jean/Documents/Developpement/CPP/autodvd-log.bin
StandardOutput=journal

[Install]
WantedBy=dev-dvd.device

And this is what the log file, /home/jean/autodvd.log, looks like :

 2018-02-12.00:48:13 ]    Disc inserted !
 2018-02-12.00:48:32 ]    Disc inserted !
 2018-02-12.00:54:40 ]    Disc inserted !
 2018-02-12.00:57:57 ]    Disc inserted !
 2018-02-12.00:59:11 ]    Disc inserted !
 2018-02-12.01:11:40 ]    Disc inserted !

This whole process now seems clear for me. I think this is good way of handling this problem, without the biggest cons of my first approach, for instance the risk of being killed before doing all of the tasks I have to do.

Now, I’m looking for the proper way to run a shell command before running my custom binary.
This shell command will be mount and will mount the drive to a specific folder (for later use in our future “games browser” application).

I’m obviously going to do some research again, and try different ways to achieve this, but if you have any suggestion for this your help will be welcome ! :wink:

Good to read of your progress with this. You actually don’t need the udev rule, as the service will be triggered by tha activation of ‘WantedBy=dev-dvd.device’ alone. The udev rule essentially does the same.

Off Topic a bit:

I think it would have been “cooler” if they would have called it, instead, udevil
:stuck_out_tongue:

LOL! …

Hi :slight_smile:

Seems I still need some help from the community to achieve this custom autorun feature !
Here is what I achieved so far :
[ul]
[li]I built a custom TCP server binary that is started on XFCE startup that listens for requests [/li][li]The udev rule detects a change on the optical disc drive [/li][li]The systemd service is triggered from udev and runs a shell script that runs several commands :[/li][LIST]
[li]create each directory that would not already exist in the path /run/media/jean/Autoplay [/li][li]mount the disc into this /run/media/jean/Autoplay directory [/li][li]run a custom TCP client binary that sends a request to the server [/li][/ul]

[li]Then, the server runs a shell script that calls /lib64/ld-linux-x86-64.so.2 to run the binary /run/media/jean/Autoplay/autoplay-game.bin [/li][/LIST]

So, once I logged in and the XFCE session is ready, I can put my test disc in the drive and, after a few seconds (well, maybe more than few, it’s an optical drive after all), the game is started. Basically I mean that a window opens, and it’s a SFML sample that runs in it.
I’m pretty satisfied with this pipeline, but I still have one problem.

The fact is that this works every time, except the first time I put the disc in the drive.
Once logged in XFCE, inserting the disc does nothing. Ejecting and inserting it again, then, works fine and the whole process is ran. Every time I try it, it’s okay. If I reboot, then the very first attempt does nothing again.
So, I think I have a problem with the udev rule, somewhat not triggering the very first time a change is detected on the optical drive.

Once I have logged in XFCE after a fresh start or reboot, I can confirm my server is already listening, running

netstat -lt

and seeing the entry

tcp   0   0   *:15892   *:*   LISTEN

for my binary. I tried to run my tests by manually running my server from a XFCE terminal instead of asking XFCE to run it on startup, with no difference.
If i run

systemctl status drive-change.service

, I get :

drive-change.service - AUTODVD Service
Loaded: loaded (/etc/systemd/system/drive-change.service; disabled; vendor preset: disabled)
Active: inactive (dead)

Then, inserting the disc in the drive, waiting for it to stop spinning, I can tell that the game did not run, neither the disc was mounter or the directory created. And, in fact, the service itself was not triggered. If i run

systemctl status drive-change.service

again, I get the same exact output.

Ejecting, and inserting it again, then does a difference. The directory now exists, the disc is mounted (done by my script, as I configured XFCE to not mount the drive on insertion), and the game is started.
Doing yet another call to

systemctl status drive-change.service

gives this output :

drive-change.service - AUTODVD Service
Loaded: loaded (/etc/systemd/system/drive-change.service; disabled; vendor preset: disabled)
Active: inactive (dead) since mer. 2018-02-14 19:41:00 CET; 1min 23s ago
Process: 3083 ExecStart=/home/jean/Documents/Developpement/Shell/start-autodvd-cli.sh (code=exited, status=0/SUCCESS)
Main PID: 3083 (code=exited, status=0/SUCCESS)

févr. 14 19:41:00 cerbere.suse systemd[1]: Starting AUTODVD Service...
févr. 14 19:41:00 cerbere.suse start-autodvd-cli.sh[3083]: mount: /dev/sr0 is write-protected, mounting read-only
févr. 14 19:41:00 cerbere.suse start-autodvd-cli.sh[3083]: Server sent back : Acknowledge
févr. 14 19:41:00 cerbere.suse systemd[1]: Started AUTODVD Service.

So, I think this is related to the udev rule, but I can’t figure out why it would not trigger my service the very first time a disc is inserted after the system startup. :
Any ides on this issue ?

Here is my udev rule,

/etc/udev/rules.d/autodvd.rules

:

SUBSYSTEM=="block", ENV{ID_CDROM}=="?*", ACTION=="change", TAG+="systemd", ENV{SYSTEMD_WANTS}="drive-change.service"

And here is the service,

/etc/systemd/system/drive-change.service

, which has been modified a little :

[Unit]
Description=AUTODVD Service

[Service]
Type=oneshot
ExecStart=/home/jean/Documents/Developpement/Shell/start-autodvd-cli.sh
StandardOutput=journal

[Install]
WantedBy=dev-dvd.device

Any help will be much appreciated ! :shame:
Thanks by advance