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