I have a systemd based system which contains one System V style init.d script with LSB headers. The init.d script must only be started after all filesystems in fstab are mounted. How can I specify this type of dependency in the LSB headers?
Systemd vs init.d Specify systemd dependencies in LSB headers
systemd
Related Solutions
Seriously, a systemd unit file is trivial to write for a service like this...or for most services.
This ought to get you about 95% of the way there. Put this in, for example, /etc/systemd/system/solr.service
[Unit]
Description=Apache Solr
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=simple
EnvironmentFile=/etc/courtlistener
WorkingDirectory=/usr/local/solr/example
ExecStart=/usr/bin/java -jar -server -Xmx${CL_SOLR_XMX} start.jar -DINSTALL_ROOT=${INSTALL_ROOT}
Restart=on-failure
LimitNOFILE=10000
[Install]
WantedBy=multi-user.target
Note the stuff that isn't here, like the log file and such; systemd will automatically capture and log the service output under the service's name.
After several false starts I figured this out. The key is to add a systemd unit service between udev and a mounting script.
(For the record, I was not able to get this working using udisks2 (via something like udisksctl mount -b /dev/sdb1
) called either directly from a udev rule or from a systemd unit file. There seems to be a race condition and the device node isn't quite ready, resulting in Error looking up object for device /dev/sdb1
. Unfortunate, since udisks2 could take care of all the mount point messyness...)
The heavy lifting is done by a shell script, which takes care of creating and removing mount points, and mounting and unmounting the drives.
/usr/local/bin/usb-mount.sh
#!/bin/bash
# This script is called from our systemd unit file to mount or unmount
# a USB drive.
usage()
{
echo "Usage: $0 {add|remove} device_name (e.g. sdb1)"
exit 1
}
if [[ $# -ne 2 ]]; then
usage
fi
ACTION=$1
DEVBASE=$2
DEVICE="/dev/${DEVBASE}"
# See if this drive is already mounted, and if so where
MOUNT_POINT=$(/bin/mount | /bin/grep ${DEVICE} | /usr/bin/awk '{ print $3 }')
do_mount()
{
if [[ -n ${MOUNT_POINT} ]]; then
echo "Warning: ${DEVICE} is already mounted at ${MOUNT_POINT}"
exit 1
fi
# Get info for this drive: $ID_FS_LABEL, $ID_FS_UUID, and $ID_FS_TYPE
eval $(/sbin/blkid -o udev ${DEVICE})
# Figure out a mount point to use
LABEL=${ID_FS_LABEL}
if [[ -z "${LABEL}" ]]; then
LABEL=${DEVBASE}
elif /bin/grep -q " /media/${LABEL} " /etc/mtab; then
# Already in use, make a unique one
LABEL+="-${DEVBASE}"
fi
MOUNT_POINT="/media/${LABEL}"
echo "Mount point: ${MOUNT_POINT}"
/bin/mkdir -p ${MOUNT_POINT}
# Global mount options
OPTS="rw,relatime"
# File system type specific mount options
if [[ ${ID_FS_TYPE} == "vfat" ]]; then
OPTS+=",users,gid=100,umask=000,shortname=mixed,utf8=1,flush"
fi
if ! /bin/mount -o ${OPTS} ${DEVICE} ${MOUNT_POINT}; then
echo "Error mounting ${DEVICE} (status = $?)"
/bin/rmdir ${MOUNT_POINT}
exit 1
fi
echo "**** Mounted ${DEVICE} at ${MOUNT_POINT} ****"
}
do_unmount()
{
if [[ -z ${MOUNT_POINT} ]]; then
echo "Warning: ${DEVICE} is not mounted"
else
/bin/umount -l ${DEVICE}
echo "**** Unmounted ${DEVICE}"
fi
# Delete all empty dirs in /media that aren't being used as mount
# points. This is kind of overkill, but if the drive was unmounted
# prior to removal we no longer know its mount point, and we don't
# want to leave it orphaned...
for f in /media/* ; do
if [[ -n $(/usr/bin/find "$f" -maxdepth 0 -type d -empty) ]]; then
if ! /bin/grep -q " $f " /etc/mtab; then
echo "**** Removing mount point $f"
/bin/rmdir "$f"
fi
fi
done
}
case "${ACTION}" in
add)
do_mount
;;
remove)
do_unmount
;;
*)
usage
;;
esac
The script, in turn, is called by a systemd unit file. We use the "@" filename syntax so we can pass the device name as an argument.
/etc/systemd/system/usb-mount@.service
[Unit]
Description=Mount USB Drive on %i
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/local/bin/usb-mount.sh add %i
ExecStop=/usr/local/bin/usb-mount.sh remove %i
Finally, some udev rules start and stop the systemd unit service on hotplug/unplug:
/etc/udev/rules.d/99-local.rules
KERNEL=="sd[a-z][0-9]", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/bin/systemctl start usb-mount@%k.service"
KERNEL=="sd[a-z][0-9]", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/bin/systemctl stop usb-mount@%k.service"
This seems to do the trick! A couple of useful commands for debugging stuff like this:
udevadm control -l debug
turns on verbose logging to/var/log/syslog
so you can see what's happening.udevadm control --reload-rules
after you modify files in the rules.d dir (may not be necessary, but can't hurt...).systemctl daemon-reload
after you modify systemd unit files.
Best Answer
When you say you check for a file system mount, it's clear you'll run it if found and mounted, but what's not clear is that if it's not found, what do you want it to do?
I ask because one possible answer is yes, in LSB, it is the run level that determines this. In Linux/Unix boot up, file systems are available at run level 1. So, set in your LSB header "Default-Start: 2 3 4 5". Then, put the filesystem mount entry in /etc/fstab and optionally set it to 'bootwait' to hang your system and prevent transitioning to run level two until it mounts, no matter how long it takes. This is actually how some systems are configured when the (remote) file system is super critical.
Otherwise, the answer is no, you cannot check the existence of a mounted partition ONLY within an LSB header entry itself. And, since you allow this particular system to boot without this particular file system, the file system is obviously not 'important' enough to hang the system awaiting mount availability.
A consideration is if you want it to not run at all when the file system is not mounted because you're trying to satisfy a "Required-Start:" dependency in another init script? Hopefully not as you can see you are sliding down a very slippery slope of init script dependencies.
Hopefully, you want it to not run because if it did, it messes stuff up like filling the root file system (as opposed to being an init dependency)? Then, you can let it run, but just code the init script to check and exit gracefully, as appropriate. The logic to check a file system mount and, if not found, exit, is probably one line of code. It can be inserted just after of the LSB header.