xref: /spdk/scripts/sync_dev_uevents.sh (revision aef00d4420639a8e1abff899f43eda21992dec42)
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