#!/bin/sh
set -e

# /bin/lsbilibop
# List BILIBOP tagged devices, display some udev properties about them,
# or trigger uevents to update their properties.

PATH="/sbin:/bin"
PROG="${0##*/}"
TAG_DIR="/run/udev/tags/BILIBOP"

info="false"
list="false"
trigger="false"
action=""

short_usage() {
	cat <<EOF
Usage:
  ${PROG} -h|-l
  ${PROG} [-a|-c|-i] [DEVICE [DEVICE [...]]]
EOF
}

usage() {
	cat <<EOF
${PROG}: list BILIBOP tagged devices, display or update some of their udev properties.

Usage:
  ${PROG} -h|-l
  ${PROG} [OPTION] [DEVICES]

Options:
  -a	Trigger uevents (action=add) for BILIBOP tagged devices.
  -c	Trigger uevents (action=change) for BILIBOP tagged devices.
  -h	Print this help on stdout and exit.
  -i    Display some udev properties of BILIBOP tagged devices.
  -l    Don't rely on BILIBOP tag to list bilibop devices.
EOF
}

bilibop_list() {
	. /lib/bilibop/common.sh
	get_udev_root
	BILIBOP_DISK="$(physical_hard_disk /)"
	for node in $(device_nodes); do
		if [ "$(physical_hard_disk ${udev_root}/${node})" = "${BILIBOP_DISK}" ]; then
			echo "${udev_root}/${node}"
		fi
	done
}

# Parse options. We use this order of priority -h > -l > -i > -a = -c:
# -h (help) overrides unconditionally all other options.
# -l (list) overrides unconditionally -i, -a and -c.
# -i (info) overrides unconditionally -a (add) and -c (change).
while getopts :achil opt; do
	case "${opt}" in
		h)
			usage
			exit 0
			;;
		a)
			trigger="true"
			action="add"
			;;
		c)
			trigger="true"
			action="change"
			;;
		i)
			info="true"
			;;
		l)
			list="true"
			;;
		'?')
			echo "${PROG}: unrecognized option '-${OPTARG}'." >&2
			short_usage >&2
			exit 1
			;;
	esac
done

if [ "${list}" = "true" ]; then
	bilibop_list
	exit $?
elif [ "${info}" = "true" ]; then
	trigger="false"
fi

shift $((OPTIND-1))
if [ "${1}" = "--" ]; then shift; fi

# At least, check if BILIBOP tagged devices exist, or exit with a useful
# message:
if [ ! -d "${TAG_DIR}" ]; then
	echo "${PROG}: ${TAG_DIR}: no such directory.\n" >&2
	if [ -f "/etc/udev/rules.d/66-bilibop.rules" ]; then
		cat >&2 <<EOF
You should modify /etc/udev/rules.d/66-bilibop.rules
or even remove it.
EOF
	else
		cat >&2 <<EOF
First, run /usr/share/bilibop/bilibop_rules_generator
and maybe modify /etc/udev/rules.d/66-bilibop.rules.
EOF
	fi
	cat >&2 <<EOF

	'udevadm info --query property --name <NODE>',
	'udevadm trigger --sysname-match <NODE>' and
	'udevadm test <DEVPATH>' can help you, if they
	are applied to the following device nodes:

EOF
	bilibop_list >&2
	echo >&2
	echo "But maybe your system is not running from a writable and removable media ?" >&2
	exit 8
fi

# Trigger uevents can only be done by root, but if it is tried by a
# unprivileged user, udevadm will silently do NOTHING without failure
# (i.e. with exit code = 0). So we place a poor checkpoint here.
if [ "${trigger}" = "true" ]; then
	if [ "${USER}" != "root" ]; then
		echo "${0##*/}: only root can use the '-a' or '-c' options." >&2
		exit 4
	fi
fi

# Get/set udev_root variable. It should be '/dev', but...
if [ -f /etc/udev/udev.conf ]; then
	. /etc/udev/udev.conf
fi
udev_root="${udev_root:-/dev}"

# Store device names given as arguments, if any:
for arg; do
	if [ -b "${arg}" ]; then
		arg="$(readlink -f ${arg})"
		DEVICE="${DEVICE:+${DEVICE} }${arg}"
	fi
done

# If ALL arguments are invalid, exit.
if [ -n "$*" -a -z "${DEVICE}" ]; then
	echo "${PROG}: bad argument(s) '$@'." >&2
	short_usage >&2
	exit 2
fi

# Trigger uevents for all BILIBOP tagged devices, and exit:
if [ -z "${DEVICE}" -a "${trigger}" = "true" ]; then
	udevadm trigger --tag-match="BILIBOP" --action="${action}"
	exit $?
fi

grep '[[:digit:]]' /proc/partitions |
while read major minor size node; do
	# Skip devices that are not tagged 'BILIBOP':
	[ ! -e "/run/udev/tags/BILIBOP/b${major}:${minor}" ] && continue

	# Filter by device names given as arguments:
	if [ -n "${DEVICE}" ]; then
		for device in ${DEVICE}; do
			[ "${device}" = "${udev_root}/${node}" ] && break
		done
		[ "${device}" = "${udev_root}/${node}" ] || continue
	fi

	if [ "${info}" = "true" ]; then
		echo "DEVNAME='${udev_root}/${node}'"
		udevadm info --query property --export --name ${node} |
		grep -E '^(BILIBOP_(DISK|(UNDERLYING_)?PARTITION)|DEVLINKS|UDISKS_(IGNORE|SYSTEM(_INTERNAL)?|(PRESENTATION_)?(ICON_)?NAME|PRESENTATION_HIDE|CAN_POWER_OFF)|ID_(FS_(UUID|LABEL|TYPE|USAGE)|DRIVE_DETACHABLE))='
		echo

	elif [ "${trigger}" = "true" ]; then
		udevadm trigger --sysname-match="${node}" --action="${action}"
	else
		echo "${udev_root}/${node}"
	fi
done

# vim: ts=4 sts=4 sw=4
