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