1#!/usr/bin/env bash 2shopt -s extglob 3 4exec {err}>&2 5 6help() { 7 cat <<- HELP 8 ${0##*/}: subsystem dev [..devN] 9 10 Env: 11 UEVENT_TIMEOUT - how long to wait for sync - ${UEVENT_TIMEOUT:-10}s 12 UEVENT_ACTION - uevent action to match on - ${UEVENT_ACTION:-add} 13 DEVPATH_LOOKUP - check if given dev matches inside DEVPATH 14 DEVPATH_SUBSYSTEM - subsystem given dev should match in DEVPATH 15 HELP 16} 17 18get_uevent_attr() ( 19 source "$1" 20 21 [[ -v $2 ]] && echo "${!2}" 22) 23 24filter_devs() { 25 local dev p_dev 26 local maj min type sub 27 28 for dev in "${!devs[@]}"; do 29 [[ -e /dev/${devs[dev]} ]] || continue 30 [[ -c /dev/${devs[dev]} ]] && type=char 31 [[ -b /dev/${devs[dev]} ]] && type=block 32 maj=$((0x$(stat --printf="%t" "/dev/${devs[dev]}"))) 33 min=$((0x$(stat --printf="%T" "/dev/${devs[dev]}"))) 34 35 p_dev=/sys/dev/$type/$maj:$min 36 if [[ -e $p_dev ]]; then 37 printf '/dev/%s\n' "${devs[dev]}" 38 39 type=$(get_uevent_attr "$p_dev/uevent" DEVTYPE) 40 sub=$(readlink -f "$p_dev/subsystem") sub=${sub##*/} 41 if [[ $sub != "${subsystem%%/*}" ]]; then 42 printf ' wrong subsystem specified (%s != %s)\n' \ 43 "${subsystem%%/*}" "$sub" 44 fi >&2 45 46 if [[ ${subsystem##*/} != "$subsystem" && -n $type ]]; then 47 if [[ ${subsystem##*/} != "$type" ]]; then 48 printf ' wrong devtype specified (%s != %s)\n' \ 49 "${subsystem##*/}" "$type" 50 fi 51 fi >&2 52 53 unset -v "devs[dev]" 54 fi 55 done 56} 57 58look_in_devpath() { 59 local find=$1 60 local path=$2 61 local sub 62 63 [[ -v DEVPATH_LOOKUP ]] || return 1 64 65 if [[ -z $path ]]; then 66 return 1 67 fi 68 69 if [[ -e $path/subsystem ]]; then 70 sub=$(readlink -f "$path/subsystem") 71 sub=${sub##*/} 72 fi 73 74 if [[ ${path##*/} == "$find" ]]; then 75 if [[ -n $DEVPATH_SUBSYSTEM ]]; then 76 [[ $DEVPATH_SUBSYSTEM == "$sub" ]] || return 1 77 fi 78 return 0 79 fi 80 look_in_devpath "$find" "${path%/*}" 81} 82 83if (($# < 2)); then 84 help 85 exit 1 86fi 87 88subsystem=$1 devs=("${@:2}") 89timeout=${UEVENT_TIMEOUT:-10} 90action=${UEVENT_ACTION:-add} 91 92devs=("${devs[@]#/dev/}") 93[[ $action == add ]] && filter_devs 94 95((${#devs[@]})) || exit 0 96 97if [[ -S /run/udev/control ]]; then 98 # systemd-udevd realm 99 100 # If devtmpfs is in place then all, e.g., block subsystem devices are going to 101 # be handled directly by the kernel. Otherwise, link to udev events in case we 102 # have some old udevd on board which is meant to mknod them instead. 103 if [[ $(< /proc/mounts) == *"/dev devtmpfs"* ]]; then 104 events+=(--kernel) 105 else 106 events+=(--udev) 107 fi 108 109 if [[ $subsystem != all ]]; then 110 events+=("--subsystem-match=$subsystem") 111 fi 112 113 # This trap targets a subshell which forks udevadm monitor. Since 114 # process substitution works in an async fashion, $$ won't wait 115 # for it, leaving it's child unattended after the main loop breaks 116 # (udevadm won't exit on its own either). UPDATE: This is not true 117 # anymore for Bash >= 5.2 where executing process replaces the 118 # actual subshell so $! becomes the PID of the udevadm instance. 119 # To accommodate that, attempt to signal $! directly in case pkill 120 # fails. 121 trap '[[ ! -e /proc/$!/status ]] || pkill -P $! || kill $!' EXIT 122 # Also, this will block while reading through a pipe with a timeout 123 # after not receiving any input. stdbuf is used since udevadm always 124 # line buffers the monitor output. 125 while ((${#devs[@]} > 0)) && IFS="=" read -t"$timeout" -r k v; do 126 if [[ $k == ACTION && $v == "$action" ]]; then 127 look_for_devname=1 128 continue 129 fi 130 if ((look_for_devname == 1)); then 131 for dev in "${!devs[@]}"; do 132 # Explicitly allow globbing of the rhs to allow more open matching. 133 # shellcheck disable=SC2053 134 if [[ ${v#/dev/} == ${devs[dev]} || ${v##*/} == ${devs[dev]##*/} ]] \ 135 || look_in_devpath "${devs[dev]}" "/sys/$v"; then 136 unset -v "devs[dev]" 137 look_for_devname=0 138 fi 139 done 140 fi 141 done < <(stdbuf --output=0 udevadm monitor --property "${events[@]}") 142 if ((${#devs[@]} > 0)); then 143 printf '* Events for some %s devices (%s) were not caught, they may be missing\n' \ 144 "$subsystem" "${devs[*]}" 145 fi >&"$err" 146 exit 0 147elif [[ -e /sys/kernel/uevent_helper ]]; then 148 # Check if someones uses mdev to serialize uevents. If yes, simply check 149 # if they are in sync, no need to lookup specific devices in this case. 150 # If not, fall through to plain sleep. 151 # To quote some wisdom from gentoo: 152 # "Even the craziest scenario deserves a fair chance". 153 154 helper=$(< /sys/kernel/uevent_helper) 155 if [[ ${helper##*/} == mdev && -e /dev/mdev.seq ]]; then 156 # mdev keeps count of the seqnums on its own on each execution 157 # and saves the count under /dev/mdev.seq. This is then set to 158 # + 1 after the uevents finally settled. 159 while ((timeout-- && $(< /sys/kernel/uevent_seqnum) + 1 != $(< /dev/mdev.seq))); do 160 sleep 1s 161 done 162 if ((timeout < 0)); then 163 printf '* Events not synced in time, %s devices (%s) may be missing\n' \ 164 "$subsystem" "${devs[*]}" 165 fi 166 exit 0 167 fi >&"$err" 168fi 2> /dev/null 169 170# Fallback, sleep and hope for the best 171sleep "${timeout}s" 172