14f1701f6SMichal Berger#!/usr/bin/env bash 2*eb53c232Spaul luse# SPDX-License-Identifier: BSD-3-Clause 3*eb53c232Spaul luse# Copyright (C) 2020 Intel Corporation 4*eb53c232Spaul luse# All rights reserved. 5*eb53c232Spaul luse# 64f1701f6SMichal Bergershopt -s extglob 74f1701f6SMichal Berger 84f1701f6SMichal Bergerexec {err}>&2 94f1701f6SMichal Berger 104f1701f6SMichal Bergerhelp() { 114f1701f6SMichal Berger cat <<- HELP 124f1701f6SMichal Berger ${0##*/}: subsystem dev [..devN] 134f1701f6SMichal Berger 144f1701f6SMichal Berger Env: 154f1701f6SMichal Berger UEVENT_TIMEOUT - how long to wait for sync - ${UEVENT_TIMEOUT:-10}s 164f1701f6SMichal Berger UEVENT_ACTION - uevent action to match on - ${UEVENT_ACTION:-add} 174f1701f6SMichal Berger DEVPATH_LOOKUP - check if given dev matches inside DEVPATH 184f1701f6SMichal Berger DEVPATH_SUBSYSTEM - subsystem given dev should match in DEVPATH 194f1701f6SMichal Berger HELP 204f1701f6SMichal Berger} 214f1701f6SMichal Berger 224f1701f6SMichal Bergerget_uevent_attr() ( 234f1701f6SMichal Berger source "$1" 244f1701f6SMichal Berger 254f1701f6SMichal Berger [[ -v $2 ]] && echo "${!2}" 264f1701f6SMichal Berger) 274f1701f6SMichal Berger 284f1701f6SMichal Bergerfilter_devs() { 294f1701f6SMichal Berger local dev p_dev 304f1701f6SMichal Berger local maj min type sub 314f1701f6SMichal Berger 324f1701f6SMichal Berger for dev in "${!devs[@]}"; do 334f1701f6SMichal Berger [[ -e /dev/${devs[dev]} ]] || continue 344f1701f6SMichal Berger [[ -c /dev/${devs[dev]} ]] && type=char 354f1701f6SMichal Berger [[ -b /dev/${devs[dev]} ]] && type=block 364f1701f6SMichal Berger maj=$((0x$(stat --printf="%t" "/dev/${devs[dev]}"))) 374f1701f6SMichal Berger min=$((0x$(stat --printf="%T" "/dev/${devs[dev]}"))) 384f1701f6SMichal Berger 394f1701f6SMichal Berger p_dev=/sys/dev/$type/$maj:$min 404f1701f6SMichal Berger if [[ -e $p_dev ]]; then 414f1701f6SMichal Berger printf '/dev/%s\n' "${devs[dev]}" 424f1701f6SMichal Berger 434f1701f6SMichal Berger type=$(get_uevent_attr "$p_dev/uevent" DEVTYPE) 444f1701f6SMichal Berger sub=$(readlink -f "$p_dev/subsystem") sub=${sub##*/} 454f1701f6SMichal Berger if [[ $sub != "${subsystem%%/*}" ]]; then 464f1701f6SMichal Berger printf ' wrong subsystem specified (%s != %s)\n' \ 474f1701f6SMichal Berger "${subsystem%%/*}" "$sub" 484f1701f6SMichal Berger fi >&2 494f1701f6SMichal Berger 504f1701f6SMichal Berger if [[ ${subsystem##*/} != "$subsystem" && -n $type ]]; then 514f1701f6SMichal Berger if [[ ${subsystem##*/} != "$type" ]]; then 524f1701f6SMichal Berger printf ' wrong devtype specified (%s != %s)\n' \ 534f1701f6SMichal Berger "${subsystem##*/}" "$type" 544f1701f6SMichal Berger fi 554f1701f6SMichal Berger fi >&2 564f1701f6SMichal Berger 574f1701f6SMichal Berger unset -v "devs[dev]" 584f1701f6SMichal Berger fi 594f1701f6SMichal Berger done 604f1701f6SMichal Berger} 614f1701f6SMichal Berger 624f1701f6SMichal Bergerlook_in_devpath() { 634f1701f6SMichal Berger local find=$1 644f1701f6SMichal Berger local path=$2 654f1701f6SMichal Berger local sub 664f1701f6SMichal Berger 674f1701f6SMichal Berger [[ -v DEVPATH_LOOKUP ]] || return 1 684f1701f6SMichal Berger 694f1701f6SMichal Berger if [[ -z $path ]]; then 704f1701f6SMichal Berger return 1 714f1701f6SMichal Berger fi 724f1701f6SMichal Berger 734f1701f6SMichal Berger if [[ -e $path/subsystem ]]; then 744f1701f6SMichal Berger sub=$(readlink -f "$path/subsystem") 754f1701f6SMichal Berger sub=${sub##*/} 764f1701f6SMichal Berger fi 774f1701f6SMichal Berger 784f1701f6SMichal Berger if [[ ${path##*/} == "$find" ]]; then 794f1701f6SMichal Berger if [[ -n $DEVPATH_SUBSYSTEM ]]; then 804f1701f6SMichal Berger [[ $DEVPATH_SUBSYSTEM == "$sub" ]] || return 1 814f1701f6SMichal Berger fi 824f1701f6SMichal Berger return 0 834f1701f6SMichal Berger fi 844f1701f6SMichal Berger look_in_devpath "$find" "${path%/*}" 854f1701f6SMichal Berger} 864f1701f6SMichal Berger 874f1701f6SMichal Bergerif (($# < 2)); then 884f1701f6SMichal Berger help 894f1701f6SMichal Berger exit 1 904f1701f6SMichal Bergerfi 914f1701f6SMichal Berger 924f1701f6SMichal Bergersubsystem=$1 devs=("${@:2}") 934f1701f6SMichal Bergertimeout=${UEVENT_TIMEOUT:-10} 944f1701f6SMichal Bergeraction=${UEVENT_ACTION:-add} 954f1701f6SMichal Berger 964f1701f6SMichal Bergerdevs=("${devs[@]#/dev/}") 974f1701f6SMichal Berger[[ $action == add ]] && filter_devs 984f1701f6SMichal Berger 994f1701f6SMichal Berger((${#devs[@]})) || exit 0 1004f1701f6SMichal Berger 1014f1701f6SMichal Bergerif [[ -S /run/udev/control ]]; then 1024f1701f6SMichal Berger # systemd-udevd realm 1034f1701f6SMichal Berger 1044f1701f6SMichal Berger # If devtmpfs is in place then all, e.g., block subsystem devices are going to 1054f1701f6SMichal Berger # be handled directly by the kernel. Otherwise, link to udev events in case we 1064f1701f6SMichal Berger # have some old udevd on board which is meant to mknod them instead. 1074f1701f6SMichal Berger if [[ $(< /proc/mounts) == *"/dev devtmpfs"* ]]; then 1084f1701f6SMichal Berger events+=(--kernel) 1094f1701f6SMichal Berger else 1104f1701f6SMichal Berger events+=(--udev) 1114f1701f6SMichal Berger fi 1124f1701f6SMichal Berger 1134f1701f6SMichal Berger if [[ $subsystem != all ]]; then 1144f1701f6SMichal Berger events+=("--subsystem-match=$subsystem") 1154f1701f6SMichal Berger fi 1164f1701f6SMichal Berger 1174f1701f6SMichal Berger # This trap targets a subshell which forks udevadm monitor. Since 1184f1701f6SMichal Berger # process substitution works in an async fashion, $$ won't wait 1194f1701f6SMichal Berger # for it, leaving it's child unattended after the main loop breaks 120d88f8ed4SMichal Berger # (udevadm won't exit on its own either). UPDATE: This is not true 121d88f8ed4SMichal Berger # anymore for Bash >= 5.2 where executing process replaces the 122d88f8ed4SMichal Berger # actual subshell so $! becomes the PID of the udevadm instance. 123d88f8ed4SMichal Berger # To accommodate that, attempt to signal $! directly in case pkill 124d88f8ed4SMichal Berger # fails. 125d88f8ed4SMichal Berger trap '[[ ! -e /proc/$!/status ]] || pkill -P $! || kill $!' EXIT 1264f1701f6SMichal Berger # Also, this will block while reading through a pipe with a timeout 1274f1701f6SMichal Berger # after not receiving any input. stdbuf is used since udevadm always 1284f1701f6SMichal Berger # line buffers the monitor output. 1294f1701f6SMichal Berger while ((${#devs[@]} > 0)) && IFS="=" read -t"$timeout" -r k v; do 1304f1701f6SMichal Berger if [[ $k == ACTION && $v == "$action" ]]; then 1314f1701f6SMichal Berger look_for_devname=1 1324f1701f6SMichal Berger continue 1334f1701f6SMichal Berger fi 1344f1701f6SMichal Berger if ((look_for_devname == 1)); then 1354f1701f6SMichal Berger for dev in "${!devs[@]}"; do 1364f1701f6SMichal Berger # Explicitly allow globbing of the rhs to allow more open matching. 1374f1701f6SMichal Berger # shellcheck disable=SC2053 1384f1701f6SMichal Berger if [[ ${v#/dev/} == ${devs[dev]} || ${v##*/} == ${devs[dev]##*/} ]] \ 1394f1701f6SMichal Berger || look_in_devpath "${devs[dev]}" "/sys/$v"; then 1404f1701f6SMichal Berger unset -v "devs[dev]" 1414f1701f6SMichal Berger look_for_devname=0 1424f1701f6SMichal Berger fi 1434f1701f6SMichal Berger done 1444f1701f6SMichal Berger fi 1454f1701f6SMichal Berger done < <(stdbuf --output=0 udevadm monitor --property "${events[@]}") 1464f1701f6SMichal Berger if ((${#devs[@]} > 0)); then 1474f1701f6SMichal Berger printf '* Events for some %s devices (%s) were not caught, they may be missing\n' \ 1484f1701f6SMichal Berger "$subsystem" "${devs[*]}" 1494f1701f6SMichal Berger fi >&"$err" 1504f1701f6SMichal Berger exit 0 1514f1701f6SMichal Bergerelif [[ -e /sys/kernel/uevent_helper ]]; then 1524f1701f6SMichal Berger # Check if someones uses mdev to serialize uevents. If yes, simply check 1534f1701f6SMichal Berger # if they are in sync, no need to lookup specific devices in this case. 1544f1701f6SMichal Berger # If not, fall through to plain sleep. 1554f1701f6SMichal Berger # To quote some wisdom from gentoo: 1564f1701f6SMichal Berger # "Even the craziest scenario deserves a fair chance". 1574f1701f6SMichal Berger 1584f1701f6SMichal Berger helper=$(< /sys/kernel/uevent_helper) 1594f1701f6SMichal Berger if [[ ${helper##*/} == mdev && -e /dev/mdev.seq ]]; then 1604f1701f6SMichal Berger # mdev keeps count of the seqnums on its own on each execution 1614f1701f6SMichal Berger # and saves the count under /dev/mdev.seq. This is then set to 1624f1701f6SMichal Berger # + 1 after the uevents finally settled. 163238251a1SMichal Berger while ((timeout-- && $(< /sys/kernel/uevent_seqnum) + 1 != $(< /dev/mdev.seq))); do 1644f1701f6SMichal Berger sleep 1s 1654f1701f6SMichal Berger done 1664f1701f6SMichal Berger if ((timeout < 0)); then 1674f1701f6SMichal Berger printf '* Events not synced in time, %s devices (%s) may be missing\n' \ 1684f1701f6SMichal Berger "$subsystem" "${devs[*]}" 1694f1701f6SMichal Berger fi 1704f1701f6SMichal Berger exit 0 1714f1701f6SMichal Berger fi >&"$err" 1724f1701f6SMichal Bergerfi 2> /dev/null 1734f1701f6SMichal Berger 1744f1701f6SMichal Berger# Fallback, sleep and hope for the best 1754f1701f6SMichal Bergersleep "${timeout}s" 176