Search code examples
bashdockerawkmount

How do I get USB device info from a script running within a Docker container in a bash script?


I made a mount.sh file inspired by balena-storage. It works when I login to the container via the balena.io dashboard where I'm deploying (could be the same elsewhere) and run the script manually. It hangs with unpopulated variables when the container runs the script when starting (a script that runs the script). I think it’s a permissions issue or script running script thing. I'm not sure how to proceed in reading USB device variables.

mount.sh:

# Automatically mount a USB drive by specified volume name.
# Note: make sure to have USB_VOLUME_NAME set in env vars.
# Thanks: https://github.com/balena-io-playground/balena-storage

echo "Checking for USB_VOLUME_NAME..."

echo "A"

if [[ -z $USB_VOLUME_NAME ]]; then
  echo "Make sure to set environment variable USB_VOLUME_NAME in order to find a connected USB drive by that label and connect to it. Exiting..." >> /usr/src/app/mount.log
  exit 1
fi

echo "B"

# Get device by label env var set in balena.io dashboard device env vars
USB_DEVICE=$(blkid -L $USB_VOLUME_NAME)
if [[ -z $USB_DEVICE ]]; then
  echo "Invalid USB_DEVICE name: $USB_DEVICE" >> /usr/src/app/mount.log
  exit 1
fi

echo $USB_DEVICE

echo "C"

# Get extra device info
ID_FS_TYPE=${ID_FS_TYPE:=$(/bin/udevadm info -n $USB_DEVICE | /usr/bin/awk -F "=" '/ID_FS_TYPE/{ print $2 }')}
ID_FS_UUID_ENC=${ID_FS_UUID_ENC:=$(/bin/udevadm info -n $USB_DEVICE | /usr/bin/awk -F "=" '/ID_FS_UUID_ENC/{ print $2 }')}
ID_FS_LABEL_ENC=${ID_FS_LABEL_ENC:=$(/bin/udevadm info -n $USB_DEVICE | /usr/bin/awk -F "=" '/ID_FS_LABEL_ENC/{ print $2 }')}

MOUNT_POINT=/mnt/$USB_VOLUME_NAME

echo $ID_FS_TYPE
echo $ID_FS_UUID_ENC
echo $ID_FS_LABEL_ENC
echo $MOUNT_POINT

echo "D"

# Bail if file system is not supported by the kernel
if ! /bin/grep -qw $ID_FS_TYPE /proc/filesystems; then
  echo "File system not supported: $ID_FS_TYPE" >> /usr/src/app/mount.log
  exit 1
fi

echo "E"

# Mount device
if /bin/findmnt -rno SOURCE,TARGET $USB_DEVICE >/dev/null; then
    echo "Device $USB_DEVICE is already mounted!" >> /usr/src/mount.log
else
    echo "Mounting - Source: $USB_DEVICE - Destination: $MOUNT_POINT" >> /usr/src/app/mount.log
    /bin/mkdir -p $MOUNT_POINT
    /bin/mount -t $ID_FS_TYPE -o rw $USB_DEVICE $MOUNT_POINT
fi

echo "F"

When the container runs the script, it gets stuck after "D", with ID_FS_TYPE, ID_FS_UUID_ENC and ID_FS_LABEL_ENC being empty (a good reason to hang).

output:

Checking for USB_VOLUME_NAME...
A
B
/dev/sda1
C



/mnt/MYDRIVE
D

My dockerfile.template:

FROM balenalib/%%BALENA_MACHINE_NAME%%-node

# Enable udev for detection of dynamically plugged devices
ENV UDEV=on
COPY udev/usb.rules /etc/udev/rules.d/usb.rules

# Install dependencies
RUN install_packages util-linux

WORKDIR /usr/src/app

# Move scripts used for mounting USB
COPY scripts scripts
RUN chmod +x scripts/*

# server.js will run when container starts up on the device
CMD ["/bin/bash", "/usr/src/app/scripts/start.sh"]

start.sh:

echo "Mounting USB drive..."
cd /usr/src/app/scripts
/bin/bash mount.sh

# It won't get this far while the script above hangs.
echo "Starting server..."
cd /usr/src/app
/usr/local/bin/yarn run serve

I can confirm that everything works when running from within the container manually:

cd /usr/src/app/scripts
/bin/bash mount.sh

Output:

Checking for USB_VOLUME_NAME...
A
B
/dev/sda1
C
vfat
BE23-31BA
MYDRIVE
/mnt/MYDRIVE
D
E
F
(and the drive mounted)

How would I resolve the empty variables?


Solution

  • Always quote every shell variable you use. (Unless you're absolutely sure of what you're doing, and what you expect to happen if the variable value is empty or includes spaces.)

    Without quoting, when you

    /bin/grep -qw $ID_FS_TYPE /proc/filesystems
    

    and $ID_FS_TYPE is empty, that word just gets omitted from the command line, so you get

    /bin/grep -qw /proc/filesystems
    

    which uses /proc/filesystems as a regexp, and tries to grep over its stdin; this leads to the apparent hang you see.

    If instead you quote it:

    /bin/grep -qw "$ID_FS_TYPE" /proc/filesystems
    

    it will get an empty string as the regexp parameter and a filename as the input parameter, which will trivially succeed (but not hang).

    For similar reasons, I'd expect you'd get a shell syntax error if $USB_VOLUME_NAME is unset, and the whole script will act oddly if that variable name has a space in it.