5

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 Answers 3

10

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"
…
2
  • 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, 2017 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. Dec 31, 2017 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)

1

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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .