7

I created a small sample-script which is reduced version of my real script and it looks basically as follows:

#!/bin/bash

echo "start" > /home/myName/log.txt

#get list with all attached devices for i in $(lsblk -lo name,fstype,hotplug,type|grep '1 part$'|tr -s ' ' ' '|sed 's/ 1 part$//'|grep ' ..*$'|tr ' ' '_') do

echo $i >> /home/myName/log.txt

done

If i call the script directly in the terminal, everything works correctly. So the return in the logfile is something like that.

start
sdc1_vfat

But as soon as the script is called from a udev rule, the loop does not work anymore. The output in the logfile is only

start

The loop does not work anymore.

The rule in the /dev/udev/ looks as follows

SUBSYSTEM=="usb", ACTION=="add", RUN+="/bin/bash /usr/bin/myScript.sh"

Anyone an idea what the cause could be?


Here some more information and as suggested a reworked version.based on your answers.

Here the udev rule:

# check usb sdb1 plugged unplugged
SUBSYSTEM=="block", ACTION=="add", RUN+="/bin/bash /usr/bin/usb_mount.sh"
SUBSYSTEM=="block", ACTION=="remove", RUN+="/bin/bash /usr/bin/usb_unmount.sh"

The mounting scriupt does now looks as follows:

#!/bin/bash

#general definitions MNT_PATH="/media/USB_DRIVE" DEV_ID="sdc1"

check connected device

udevadm info -q all "/dev/$DEV_ID" | tr '\n' ' ' | grep "/dev/$DEV_ID" | grep "ID_FS_VERSION=FAT32"

check return

if [ $? -eq 0 ] then

create folder and mount

mkdir -p $MNT_PATH mount -o rw,user,exec,umask=0000 "/dev/$DEV_ID" $MNT_PATH exit fi

If I check the mounting command, the exit code is 0 so it has been processed successfully. The folder is getting created correctly, but the drive is not mounted. If the script is called directly in the terminal, everything works as expected. But if it's called by the udev it is not mounted.

I still don't now the problem:

  • Is it a timing problem - even if the udevadm info -q all /dev/sdb* returns a connected device?
  • Is it a permission or path problem?

Any ideas how I can find the cause?


Additional Information

Just figured out, that in the syslog an exit code 127 is reported. So it seems to be a permission problem. But how can I ensure the udev script runs as root?

muru
  • 197,895
  • 55
  • 485
  • 740
hafisch
  • 135
  • Just a point: you wouldn't need the command substitution and the loop if you simply redirect the commands output to your log-file. lsblk -lo name,fstype,hotplug,type|grep '1 part$'|tr -s ' ' ' '|sed 's/ 1 part$//'|grep ' ..*$'|tr ' ' '_' >> /home/myName/log.txt replaces the whole loop. – mook765 Jul 06 '22 at 22:22
  • @mook765 Thanks, I'm aware of that. As mentioned, it is a reduced sample. In the real script, i use the variable i for further checks and finally for mounting the device. – hafisch Jul 07 '22 at 04:16
  • Use full path names to your executables. – vanadium Jul 07 '22 at 06:47
  • lsblk wont have the device until udev has finished doing its thing(including processing the rules file you created) ... i.e. your script runs but the output from lsblk is empty ... lsblk wont work, so find another way. – Raffa Jul 07 '22 at 07:28
  • Ok. Thanks for the answer. I expected, that the ACTION="add" for an usb-device means, that it is already available with lsblk. Is there anothger trigger which can be used in the udev rules which ensures, that it will be found with lsblk? What about the ACTION="bind"? – hafisch Jul 07 '22 at 07:35
  • 2
    Nope ... not that either ... The issue is, unless udev finishes its thing, no information is available to lsblk no matter what you do ... but it's available to udev itself ... so use udevadm info -q all /dev/sdb* instead – Raffa Jul 07 '22 at 08:04
  • No udev rule needed ... another approach for monitoring inotifywait -m --include "/dev/sdb1" -e create -e delete /dev/ | while read -r directory event partition; do echo "$directory$partition was $event"; done – Raffa Jul 08 '22 at 07:16
  • Nope :-) …. Exit code 127 is returned by /bin/bash when the given command is not found … Specify the full path to executables in your script to solve that … But, even then, mount will not work while your rule is holding udev from finishing … try another monitoring aproach. – Raffa Jul 08 '22 at 07:37
  • :-( I added the full path names of the commands. The exit code 127 disappears. But the device still does not get mounted. But how can it be, that the mounting command in the script returns the exit code 0 (success) and the device does not get mounted? In my point of view, it should throw an error blocked by another process, it should throw an error. Otherwise the diagnosis is quite impossible... – hafisch Jul 08 '22 at 09:16
  • If a udev rule does not work, what other possibilities do i have to mount specific devices to a defined folder after plugging them? A cyclic script? A service? Sounds odd. – hafisch Jul 08 '22 at 12:36
  • :-) Give me an hour or two until I get in-front of a computer and we'll make it happen together ... you did well by the way but udev rules are a bit weird when it comes to executing scripts that try to mount the device triggering the rule. – Raffa Jul 08 '22 at 12:58
  • I added an answer with a bash script that is portable(independent from the udev and the udisks utilities) … try it and if it works for you, then I suggest you change the title of this question to reflect “mounting a disk partition by name /dev/sdb1 when it becomes available on the system” and perhaps add a first paragraph explaining what you wand to achieve from your script i.e. make this post easier to find by people who tackle the same problem. – Raffa Jul 08 '22 at 15:49

1 Answers1

4

Bash behavior is the same … It’s just lsblk returns an empty string … The reason is that udev doesn’t make your disk available under /dev until it finishes processing all the rules triggered by plugging in the same disk.

So, your approach to monitoring and mounting that disk partition is simply not going to work.

Alternatively, you can achieve your goal in one of two ways below.

Monitor for a certain partition name on any disk

This is a rather more portable(independent from udisks and udev rules) bash script that when run will monitor for a certain USB(or otherwise) disk partition that you specify(like e.g. /dev/sdb1 assuming /dev/sdb is always free and reserved for first USB disk inserted), mount it to the mount-point that you specify when the USB disk is plugged and un-mount it when the USB disk is unplugged with all events logged to a log file that you specify ... Read the comments in the script for help.

The script needs to be run with administrator permissions ... so either:

  • Run it with sudo /bin/bash scriptfile.
  • Add a cron-job(it only needs to be run once after boot process completes) for it to root's crontab with sudo crontab -e.
  • Add a systemd service(and enable the service) to run it.
#!/bin/bash

Set the name of the partition to be monitored.

mpartition="/dev/sdb1"

Set the full path to the mount-point.

mount_point="/home/user/USB_DRIVE"

Set the full path to the logfile(will be created if it doesn't exist)

log_file="/home/user/usb_mount.log"

Start "inotifywait"(need be installed first with "sudo apt install inotify-tools") to monitor the partition

inotifywait -q -m --include "$mpartition" -e create -e delete /dev/ |

while read -r directory event partition; do if [ "$event" == "CREATE" ]; then note=$(/usr/bin/mount -v -o rw "$directory$partition" "$mount_point" 2>&1) # If needed, add extra options after -o like "-o rw,umask=000" to allow all write and read status="$?" if [ "$status" -eq 0 ]; then echo "$(date): Successful mount with exit code $status [$note]" >> "$log_file" else echo "$(date): Failed mount with exit code $status [$note]" >> "$log_file" fi elif [ "$event" == "DELETE" ]; then note=$(/usr/bin/umount -v "$mount_point" 2>&1) status="$?" if [ "$status" -eq 0 ]; then echo "$(date): Successful un-mount with exit code $status [$note]" >> "$log_file" else echo "$(date): Failed un-mount with exit code $status [$note]" >> "$log_file" fi fi done

Monitor for a certain partition on a certain disk

There are many ways to identify a certain partition on a certain disk ... The one most suitable is to connect the USB disk you want to use then look under /dev/disk/ to find other ways disks are identified on the system ... do ls /dev/disk/ and you will get:

$ ls /dev/disk/
by-id  by-label  by-partlabel  by-partuuid  by-path  by-uuid

Each one of the above is a directory containing symbolic links to your disks and the name says it all and all there is left is for you to chose how to identify your disk ... a rather more reliable method would be by-id as this is supposed to be a unique fixed string containing the manufacture's name and serial number for the disk device itself and will be followed by the partition number for the partitions on the disk ... a useful thing is that USB disks are also prefixed with usb- ... so an ls /dev/disk/by-id/ would yield something like this:

usb-SanDisk_Cruzer_Blade_4C530200811130110350-0:0
usb-SanDisk_Cruzer_Blade_4C530200811130110350-0:0-part1

Once you identify that ID for your device, you can simply check if it's connected or not with something like:

$ [ "$(readlink -e /dev/disk/by-id/usb-SanDisk_Cruzer_Blade_4C530200811130110350-0:0)" ] && echo "connected"
connected

and know its name under /dev with readlink like so:

$ readlink -f /dev/disk/by-id/usb-SanDisk_Cruzer_Blade_4C530200811130110350-0:0
/dev/sdb  

So, the script becomes like this:

#!/bin/bash

Set the ID of the partition to be monitored.

mpartition="usb-SanDisk_Cruzer_Blade_4C530200811130110350-0:0-part1"

Set the full path to the mount-point.

mount_point="/home/user/USB_DRIVE"

Set the full path to the logfile(will be created if it doesn't exist)

log_file="/home/user/usb_mount.log"

Start "inotifywait"(need be installed first with "sudo apt install inotify-tools") to monitor the partition

inotifywait -q -m --include "$mpartition" -e create -e delete /dev/disk/by-id/ |

while read -r directory event partition; do if [ "$event" == "CREATE" ]; then device=$(/usr/bin/readlink -f /dev/disk/by-id/"$mpartition") note=$(/usr/bin/mount -v -o rw "$device" "$mount_point" 2>&1) # If needed, add extra options after -o like "-o rw,umask=000" to allow all write and read status="$?" if [ "$status" -eq 0 ]; then echo "$(date): Successful mount with exit code $status [$note]" >> "$log_file" else echo "$(date): Failed mount with exit code $status [$note]" >> "$log_file" fi elif [ "$event" == "DELETE" ]; then note=$(/usr/bin/umount -v "$mount_point" 2>&1) status="$?" if [ "$status" -eq 0 ]; then echo "$(date): Successful un-mount with exit code $status [$note]" >> "$log_file" else echo "$(date): Failed un-mount with exit code $status [$note]" >> "$log_file" fi fi done

Raffa
  • 32,237
  • You are amazing. Thanks a lot. I really appreciate it. I'll test it soon. – hafisch Jul 08 '22 at 15:45
  • Hi @Raffa, Thanks a lot for your amazing support. I'm deeply impressed of the support I get here. The script works perfectly! But solving it with a cyclical script and not with udev leads to my last question. Whats the difference regarding performance between a cyclical script with a while loop (maybe with a 1 second sleep) a cron-job repeated each second an a system service? What are the advantages of the 3 approaches? What would you suggest? – hafisch Jul 08 '22 at 18:32
  • @hafisch Crnjobs minimum interval is 1 minute … the script above uses inotifywait that intern initiates the while loop not a sleep call … udev can’t mount a device by a script run from a rule that is triggered by the same device … inotifywait, a systemd service or a cronjob are almost identical in performance and system load … while true; do sleep 1; done loops might strain the system … a root’s crontab line with@reboot sleep 1m && /bin/bash /full/path/to/scriptfile is more than enough for the above script. – Raffa Jul 08 '22 at 18:57
  • @hafisch udisks/udev is supposed to simplify mounting disks(mind that udev is also involved in setting up other devices and not only disks) on the user level for every day usual usage performance aside … your requirement however doesn’t fall into that category … so sticking with udisks and udev has no added advantage to you … besides different *nix OSes should respond identical to this script but not to other methods … hence “portable” – Raffa Jul 08 '22 at 19:10