2

I am working on creating a udev ruleset that writes to a log file each time a certain USB drive is inserted. My ruleset, stored in /etc/udev/rules.d /99-log-USB-drive.rules, currently contains the following:

# Skip if not the expected USB drive
ENV{ID_FS_UUID}!="SOMEUUID", GOTO="end"

# Try different ways of interacting with the file system
ACTION=="add", RUN+="/usr/bin/touch /home/myusername/udevtest.txt"
ACTION=="add", RUN+="/bin/chmod 664 /home/myusername/udevtest.txt"
ACTION=="add", RUN+="/bin/echo 1 | /usr/bin/tee /home/myusername/udevtest.txt"
ACTION=="add", RUN+="/bin/echo 2 >> /home/myusername/udevtest.txt"

# Exit
LABEL="end"

Both the touch and the chmod commands work as expected but when I try writing to the file I get nothing. Enabling debuging for udev using

udevadm control --log-priority=debug

renders the following output into /var/log/syslog:

Dec  3 18:00:49 Hostname systemd-udevd[9629]: starting '/bin/echo 1 | /usr/bin/tee /home/myusername/udevtest.txt'
Dec  3 18:00:49 Hostname systemd-udevd[9612]: '/bin/echo 1 | /usr/bin/tee /home/myusername/udevtest.txt'(out) '1 | /usr/bin/tee /home/myusername/udevtest.txt'
Dec  3 18:00:49 Hostname systemd-udevd[9612]: Process '/bin/echo 1 | /usr/bin/tee /home/myusername/udevtest.txt' succeeded.
Dec  3 18:00:49 Hostname systemd-udevd[9630]: starting '/bin/echo 2 >> /home/myusername/udevtest.txt'
Dec  3 18:00:49 Hostname systemd-udevd[9612]: '/bin/echo 2 >> /home/myusername/udevtest.txt'(out) '2 >> /home/myusername/udevtest.txt'
Dec  3 18:00:49 Hostname systemd-udevd[9612]: Process '/bin/echo 2 >> /home/myusername/udevtest.txt' succeeded.

upon insertion of the USB drive. So, the commands succeed but the output is not written to the file for some reason that I fail to see. This question indicates that writing to files should work.

Addendum

One of the requirements for this project is that I need to be able to write asterisks (*) to the file. Using the info from the excellent answer by @Kamil Maciorowski I can write to the file but I am unable to prevent the shell from expanding the asterisk.

ACTION=="add", RUN+="/bin/sh -c 'echo * >> /home/myusername/udevtest.txt'"

writes a listing of the contents of the root folder into the file upon insertion of the USB drive.

ACTION=="add", RUN+="/bin/sh -c 'echo "*" >> /home/myusername/udevtest.txt'"

writes nothing to the file and renders the following output into /var/log/syslog:

Jan  1 12:26:45 Hostname systemd-udevd[12359]: starting '/bin/sh -c 'echo '
Jan  1 12:26:45 Hostname systemd-udevd[12346]: '/bin/sh -c 'echo '(out) ''
Jan  1 12:26:45 Hostname systemd-udevd[12346]: Process '/bin/sh -c 'echo ' succeeded.

while

ACTION=="add", RUN+="/bin/sh -c 'echo '*' >> /home/myusername/udevtest.txt'"

also writes nothing to the file and renders the following output into /var/log/syslog:

Jan  1 12:30:48 Hostname systemd-udevd[12477]: starting '/bin/sh -c 'echo '*' >> /home/myusername/udevtest.txt''
Jan  1 12:30:48 Hostname systemd-udevd[12464]: '/bin/sh -c 'echo '*' >> /home/myusername/udevtest.txt''(out) ''
Jan  1 12:30:48 Hostname systemd-udevd[12464]: Process '/bin/sh -c 'echo '*' >> /home/myusername/udevtest.txt'' succeeded.
3

Operators like |, >> etc. mean something while inside a shell, but when sole /bin/echo 1 | … is run, there is no shell and | is just another argument to echo itself.

To use these operators you need to start a shell that will parse them. This should work:

…
ACTION=="add", RUN+="/bin/sh -c 'echo 1 | /usr/bin/tee /home/myusername/udevtest.txt'"
ACTION=="add", RUN+="/bin/sh -c 'echo 2 >> /home/myusername/udevtest.txt'"
…

Here I chose echo (shell builtin) over /bin/echo.

Alternatively you can gather few commands (or all of them) in a single shell script (with a proper shebang) and run just the script from the ruleset. It would be like:

#!/bin/sh

logfile="/home/myusername/udevtest.txt"

# /usr/bin should be in the defalut $PATH,
# so you probably don't need full paths to executables here

touch "$logfile"
chmod 664 "$logfile"
echo 1 | tee "$logfile"
echo 2 >> "$logfile"

Don't forget to make the script executable. Then in your ruleset:

…
ACTION=="add", RUN+="/path/to/the/script"
…
|improve this answer|||||
  • This works well. Is there a way to echo a literal * into the file /home/myusername/udevtest.txt? As far as I understand it there is no way of quoting the string passed to echo in this case. Is this correct? I would prefer not putting anything into a script. – fuumind Dec 31 '17 at 14:27
  • @fuumind I'm not sure I understand the issue. General sh rules apply in the script. Single quoting prevents shell globbing, so this should echo a literal asterisk: echo '*'. Redirect it to a file as you wish. – Kamil Maciorowski Dec 31 '17 at 16:57
1

When udevd start to run, rootfs still be mounted with readonly options. So you can't change anything to a file with udev RUN key.

Here are the boot processes. init -> mount kernel virtual file system (etc. /run/) -> udev running -> mount all (/etc/fstab)

|improve this answer|||||
0

Answering the addendum of my own question: Escaping the asterisk works.

ACTION=="add", RUN+="/bin/sh -c 'echo \* >> /home/myusername/udevtest.txt'"

writes an asterisk to the file.

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.