Skip to contentSkip to conversation

udev-cache related changes

PATCH In review Richard Tollerton 2 participants 15 messages Aug 22, 2014 – Aug 25, 2014

lore
Share & tools
lore
#0 lore raw

v2 cover udev-cache related changes

This is v2 of the changes submitted by Ben Shelton on August 4. I've reworked the patches quite a bit, larger changes include:

- All non-udev-cache-related patches have been omitted. I'll submit those separately.
- Use find to build tar filelist. This eliminates the need for GNU- and busybox-specific tar options.
- No new settings have been added to udev-cache.default in order to preserve compatibility with older overrides of udev-cache.default. Note, this means a lot of new settings are duplicated between initscripts.
- System config is now generated immediately before the tarball is generated, and is manipulated at the file level instead of the shell variable level.
- Internal/corporate commit message fields stripped.
- Spurious whitespace changes should all be fixed.

There is one requested fix I'm pushing back on: Copying both $CMP_FILE_LIST and /dev to tmpfs in a single operation to minimize race conditions. For a variety of reasons, I abandoned trying to make this work in a single command. Instead it's done in two: grabbing the system configuration (i.e reading /proc files and udev rules) is done first, then /dev is archived as an uncompressed tarball. Notably, using tar instead of cp means that I can use find -xdev and find -type to eliminate a lot of other complexity. I think this approach will wind up being faster than the straight-up copy approach, although I have not made any attempt to confirm that.

The following changes since commit 47d1fc9f5c38f3d092937c47bd4c2f45adaa7fe6:

qemu: fix Darwin cross-compilation (2014-08-18 20:43:24 +0100)

are available in the git repository at:

git://github.com/rtollert/openembedded-core dev/rtollert/udev-cache-7
https://github.com/rtollert/openembedded-core/tree/dev/rtollert/udev-cache-7

Richard Tollerton (12):
udev-cache: Update cache tarball atomically
udev-cache: Compress the cache
udev-cache: choose a more descriptive cache filename
busybox: enable tar -m
udev-cache: strip timestamps on extract
udev-cache: omit sockets and filesystems mounted under /dev
udev-cache: Don't ignore error messages from cache extract
udev-cache: parametrize sysconf file paths
udev-cache: get system config immediately before cache
udev-cache: invalidate on rules.d changes
udev-cache: Update cache asynchronously
udev-cache: refactor; improve verbosity and error handling

 meta/recipes-core/busybox/busybox/defconfig    |  2 +-
 meta/recipes-core/udev/udev/init               | 53 ++++++++++++--------------
 meta/recipes-core/udev/udev/udev-cache         | 42 ++++++++++++++++++--
 meta/recipes-core/udev/udev/udev-cache.default |  2 +-
 4 files changed, 64 insertions(+), 35 deletions(-)
Signature · 1 line
2.0.4
#1 lore raw

PATCH v2 1/12 udev-cache: Update cache tarball atomically

Overwriting the tarball in-place could cause a partial write, if the system stops at an inopportune time. This is mitigated by first writing to a temporary file, then moving that file on top of the final location.

---
 meta/recipes-core/udev/udev/udev-cache | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
meta/recipes-core/udev/udev/udev-cache +3 -1
index db5a513..c08cef2 100644
@@ -16,6 +16,7 @@ export TZ=/etc/localtime
 [ -d /sys/class ] || exit 1
 
 [ -f /etc/default/rcS ] && . /etc/default/rcS
+DEVCACHE_TMP="/dev/shm/udev-cache-tmp.tar"
 [ -f /etc/default/udev-cache ] && . /etc/default/udev-cache
 
 if [ "$ROOTFS_READ_ONLY" = "yes" ]; then
@@ -25,7 +26,8 @@ fi
 
 if [ "$DEVCACHE" != "" -a -e /dev/shm/udev.cache ]; then
 	echo "Populating dev cache"
-	(cd /; tar cf "$DEVCACHE" dev)
+	(cd /; tar cf "${DEVCACHE_TMP}" dev)
+	mv -f "${DEVCACHE_TMP}" "$DEVCACHE"
 	mv /dev/shm/udev.cache /etc/udev/cache.data
 fi
 
Signature · 1 line
2.0.4
#2 lore raw

PATCH v2 2/12 udev-cache: Compress the cache

In reply to Richard Tollerton

$DEVCACHE is observed to be 100k uncompressed; compressing it reduces its size to ~5k. But compress it outside of tar so that archival operation takes as little time as possible, to minimize the risk of devices being created/removed during execution.

---
 meta/recipes-core/udev/udev/udev-cache         | 3 ++-
 meta/recipes-core/udev/udev/udev-cache.default | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)
meta/recipes-core/udev/udev/udev-cache +2 -1
index c08cef2..ec07f50 100644
@@ -27,7 +27,8 @@ fi
 if [ "$DEVCACHE" != "" -a -e /dev/shm/udev.cache ]; then
 	echo "Populating dev cache"
 	(cd /; tar cf "${DEVCACHE_TMP}" dev)
-	mv -f "${DEVCACHE_TMP}" "$DEVCACHE"
+	gzip < "${DEVCACHE_TMP}" > "$DEVCACHE"
+	rm -f "${DEVCACHE_TMP}"
 	mv /dev/shm/udev.cache /etc/udev/cache.data
 fi
 
meta/recipes-core/udev/udev/udev-cache.default +1 -1
index 2093336..909ec87 100644
@@ -1,5 +1,5 @@
 # Default for /etc/init.d/udev
 
 # Comment this out to disable device cache
-DEVCACHE="/etc/dev.tar"
+DEVCACHE="/etc/dev.tar.gz"
 PROBE_PLATFORM_BUS="yes"
Signature · 1 line
2.0.4
#3 lore raw

PATCH v2 3/12 udev-cache: choose a more descriptive cache filename

In reply to Richard Tollerton

"/etc/dev.tar.gz" doesn't adequately imply that udev-cache maintains it. Instead, call it "/etc/udev-cache.tar.gz".

---
 meta/recipes-core/udev/udev/udev-cache.default | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
meta/recipes-core/udev/udev/udev-cache.default +1 -1
index 909ec87..a3b7326 100644
@@ -1,5 +1,5 @@
 # Default for /etc/init.d/udev
 
 # Comment this out to disable device cache
-DEVCACHE="/etc/dev.tar.gz"
+DEVCACHE="/etc/udev-cache.tar.gz"
 PROBE_PLATFORM_BUS="yes"
Signature · 1 line
2.0.4
#4 lore raw

PATCH v2 4/12 busybox: enable `tar -m`

In reply to Richard Tollerton

CONFIG_FEATURE_TAR_NOPRESERVE_TIME=y is needed for the commit "udev-cache: strip timestamps on extract". Enabling this flag increases the size of busybox by 0 bytes on x86.

---
 meta/recipes-core/busybox/busybox/defconfig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
meta/recipes-core/busybox/busybox/defconfig +1 -1
index 35f1026..8394067 100644
@@ -159,7 +159,7 @@ CONFIG_FEATURE_TAR_GNU_EXTENSIONS=y
 # CONFIG_FEATURE_TAR_LONG_OPTIONS is not set
 # CONFIG_FEATURE_TAR_TO_COMMAND is not set
 # CONFIG_FEATURE_TAR_UNAME_GNAME is not set
-# CONFIG_FEATURE_TAR_NOPRESERVE_TIME is not set
+CONFIG_FEATURE_TAR_NOPRESERVE_TIME=y
 # CONFIG_FEATURE_TAR_SELINUX is not set
 # CONFIG_UNCOMPRESS is not set
 # CONFIG_UNLZMA is not set
Signature · 1 line
2.0.4
#5 lore raw

PATCH v2 5/12 udev-cache: strip timestamps on extract

In reply to Richard Tollerton

Under normal udev operation, device nodes are obviously timestamped based on the system time at current boot. However, when using udev-cache, they are timestamped from a previous boot.

The existence of machines lacking RTCs makes this more than a cosmetic
issue: if the current time is set further on in the boot, so that the system time is still 1970 by the time the cache is extracted, tar will print a timestamp warning for every extracted file (potentially hundreds of them).

To fix, use -m on extract.

If using busybox tar, this commit requires
CONFIG_FEATURE_TAR_NOPRESERVE_TIME=y.

---
 meta/recipes-core/udev/udev/init | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
meta/recipes-core/udev/udev/init +1 -1
index f2c84d5..4a6df6e 100644
@@ -69,7 +69,7 @@ case "$1" in
 		    readfiles /etc/udev/cache.data
 		    OLDDATA="$READDATA"
 		    if [ "$OLDDATA" = "$NEWDATA" ]; then
-                            (cd /; tar xf $DEVCACHE > /dev/null 2>&1)
+                            (cd /; tar xmf $DEVCACHE > /dev/null 2>&1)
                             not_first_boot=1
                             [ "$VERBOSE" != "no" ] && echo "udev: using cache file $DEVCACHE"
                             [ -e /dev/shm/udev.cache ] && rm -f /dev/shm/udev.cache
Signature · 1 line
2.0.4
#6 lore raw

PATCH v2 6/12 udev-cache: omit sockets and filesystems mounted under /dev

In reply to Richard Tollerton

Archiving sockets causes tar to report an error and return a nonzero exit status. Archiving a mounted filesystem is harmless, but may greatly bloat the size of the cache tarball, and wastes time on boot.

To fix these issues, use find to only include the files we want, which are the file types that udev will create (block/char devices and symlinks) that are on the same filesystem as /dev.

While we're at it, remove a subshell by archiving /dev as an absolute path. However, tar will complain about stripping the leading slash on stderr. To inhibit this, cut out the leading slash.

An alternative solution is to use tar --exclude, but that is modestly more brittle, since we'd need to explicitly list every socket and filesystem to exclude. Note that tar --one-file-system is GNU-specific, and tar implementations generally have nothing equivalent to find -type.

If using busybox find, this change requires CONFIG_FEATURE_FIND_TYPE=y and CONFIG_FEATURE_FIND_XDEV=y. If using busybox tar, this change requires CONFIG_FEATURE_TAR_FROM=y.

---
 meta/recipes-core/udev/udev/udev-cache | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
meta/recipes-core/udev/udev/udev-cache +2 -1
index ec07f50..aaf1ddd 100644
@@ -26,7 +26,8 @@ fi
 
 if [ "$DEVCACHE" != "" -a -e /dev/shm/udev.cache ]; then
 	echo "Populating dev cache"
-	(cd /; tar cf "${DEVCACHE_TMP}" dev)
+	find /dev -xdev \( -type b -o -type c -o -type l \) | cut -c 2- \
+		| xargs tar cf "${DEVCACHE_TMP}" -T-
 	gzip < "${DEVCACHE_TMP}" > "$DEVCACHE"
 	rm -f "${DEVCACHE_TMP}"
 	mv /dev/shm/udev.cache /etc/udev/cache.data
Signature · 1 line
2.0.4
#7 lore raw

PATCH v2 7/12 udev-cache: Don't ignore error messages from cache extract

In reply to Richard Tollerton

Previous changes should obviate all known spurious errors coming out of tar. Since real extraction failures can and will occur, stop redirecting stdout/stderr to /dev/null.

Take this opportunity to also remove an unnecessary subshell.

---
 meta/recipes-core/udev/udev/init | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
meta/recipes-core/udev/udev/init +1 -1
index 4a6df6e..1d073c2 100644
@@ -69,7 +69,7 @@ case "$1" in
 		    readfiles /etc/udev/cache.data
 		    OLDDATA="$READDATA"
 		    if [ "$OLDDATA" = "$NEWDATA" ]; then
-                            (cd /; tar xmf $DEVCACHE > /dev/null 2>&1)
+                            tar xmf $DEVCACHE -C / -m
                             not_first_boot=1
                             [ "$VERBOSE" != "no" ] && echo "udev: using cache file $DEVCACHE"
                             [ -e /dev/shm/udev.cache ] && rm -f /dev/shm/udev.cache
Signature · 1 line
2.0.4
#8 lore raw

PATCH v2 8/12 udev-cache: parametrize sysconf file paths

In reply to Richard Tollerton

The udev-cache facility uses files that represent system states, to ensure that the cache tarball is valid to apply. These paths were hardcoded in several places; collect them into SYSCONF_CACHED and SYSCONF_TMP.

---
 meta/recipes-core/udev/udev/init       | 10 ++++++----
 meta/recipes-core/udev/udev/udev-cache |  6 ++++--
 2 files changed, 10 insertions(+), 6 deletions(-)
meta/recipes-core/udev/udev/init +6 -4
index 1d073c2..7ffa4a6 100644
@@ -14,6 +14,8 @@ export TZ=/etc/localtime
 [ -d /sys/class ] || exit 1
 [ -r /proc/mounts ] || exit 1
 [ -x @UDEVD@ ] || exit 1
+SYSCONF_CACHED="/etc/udev/cache.data"
+SYSCONF_TMP="/dev/shm/udev.cache"
 [ -f /etc/default/udev-cache ] && . /etc/default/udev-cache
 [ -f /etc/udev/udev.conf ] && . /etc/udev/udev.conf
 [ -f /etc/default/rcS ] && . /etc/default/rcS
@@ -66,13 +68,13 @@ case "$1" in
             if [ -e $DEVCACHE ]; then
 		    readfiles $CMP_FILE_LIST
 		    NEWDATA="$READDATA"
-		    readfiles /etc/udev/cache.data
+		    readfiles "$SYSCONF_CACHED"
 		    OLDDATA="$READDATA"
 		    if [ "$OLDDATA" = "$NEWDATA" ]; then
                             tar xmf $DEVCACHE -C / -m
                             not_first_boot=1
                             [ "$VERBOSE" != "no" ] && echo "udev: using cache file $DEVCACHE"
-                            [ -e /dev/shm/udev.cache ] && rm -f /dev/shm/udev.cache
+                            [ -e $SYSCONF_TMP ] && rm -f "$SYSCONF_TMP"
                     else
 			    # Output detailed reason why the cached /dev is not used
 			    if [ "$VERBOSE" != "no" ]; then
@@ -81,14 +83,14 @@ case "$1" in
 				    echo "udev: olddata: $OLDDATA"
 				    echo "udev: newdata: $NEWDATA"
 			    fi
-			    echo "$NEWDATA" > /dev/shm/udev.cache
+			    echo "$NEWDATA" > "$SYSCONF_TMP"
                     fi
 	    else
 		    if [ "$ROOTFS_READ_ONLY" != "yes" ]; then
 			    # If rootfs is not read-only, it's possible that a new udev cache would be generated;
 			    # otherwise, we do not bother to read files.
 			    readfiles $CMP_FILE_LIST
-			    echo "$READDATA" > /dev/shm/udev.cache
+			    echo "$READDATA" > "$SYSCONF_TMP"
 		    fi
             fi
     fi
meta/recipes-core/udev/udev/udev-cache +4 -2
index aaf1ddd..497d257 100644
@@ -17,6 +17,8 @@ export TZ=/etc/localtime
 
 [ -f /etc/default/rcS ] && . /etc/default/rcS
 DEVCACHE_TMP="/dev/shm/udev-cache-tmp.tar"
+SYSCONF_CACHED="/etc/udev/cache.data"
+SYSCONF_TMP="/dev/shm/udev.cache"
 [ -f /etc/default/udev-cache ] && . /etc/default/udev-cache
 
 if [ "$ROOTFS_READ_ONLY" = "yes" ]; then
@@ -24,13 +26,13 @@ if [ "$ROOTFS_READ_ONLY" = "yes" ]; then
     exit 0
 fi
 
-if [ "$DEVCACHE" != "" -a -e /dev/shm/udev.cache ]; then
+if [ "$DEVCACHE" != "" -a -e "$SYSCONF_TMP" ]; then
 	echo "Populating dev cache"
 	find /dev -xdev \( -type b -o -type c -o -type l \) | cut -c 2- \
 		| xargs tar cf "${DEVCACHE_TMP}" -T-
 	gzip < "${DEVCACHE_TMP}" > "$DEVCACHE"
 	rm -f "${DEVCACHE_TMP}"
-	mv /dev/shm/udev.cache /etc/udev/cache.data
+	mv "$SYSCONF_TMP" "$SYSCONF_CACHED"
 fi
 
 exit 0
Signature · 1 line
2.0.4
#9 lore raw

PATCH v2 9/12 udev-cache: get system config immediately before cache

In reply to Richard Tollerton

The system device configuration -- /proc/devices, /proc/cmdline, etc. -- and the contents of /dev must be stored as simultaneously as possible. Otherwise, hotplug events could be processed between the time we get the system config and the time we archive /dev, and the udev cache may be incorrect.

To solve this, update the cached system configuration inside the udev-cache initscript, not the udev initscript. Also, run it before archiving /dev, because it should execute nearly instantaneously.

We still need to compute the system configuration inside the udev initscript, to detect if it changed, so refactor it into a new function sysconf_cmd(). This also allows administrators to modify it for machine-specific requirements.

Evaluating the system configuration requires a shell function readfiles() defined in the udev initscript. The only salient differences between readfiles() and just using cat directly are a) readfiles() skips files without error if they don't exist, which is necessary because /proc/atags is in CMP_FILE_LIST but does not exist on all targets; and b) input lines are concatenated into an output shell variable with newlines stripped. (a) can be eliminated by not including /proc/atags in CMP_FILE_LIST if it doesn't exist. (b) is O(n^2) anyway, and makes $NEWDATA, $OLDDATA etc. very hard to read, and we don't need to strip newlines anyway. So make sysconf_cmd() use cat and remove readfiles().

Instead of storing system configuration in shell variables ($NEWDATA, $OLDDATA), store them directly in files: redirect to $SYSCONF_TMP instead of storing in $NEWDATA; read from $SYSCONF_CACHED directly instead of reading it into $OLDDATA. This turns comparing $NEWDATA vs. $OLDDATA into a call to cmp. However, busybox lacks cmp -q, so instead, redirect cmp's output to /dev/null.

$OLDDATA and $NEWDATA go away. This obviates the most of error message printed when the udev cache can't be used, but it probably wasn't of much help, because $OLDDATA and $NEWDATA are so large that they would have scrolled off the screen anyway. Beyond that, this error is so important that it should probably be printed regardless of the setting of VERBOSE. So leave the "udev: cache not used" log message and remove everything else.

Because the udev-cache initscript evaluates the system configuration itself, it makes no sense to signal it to run by storing the system configuration in a file. Instead, signal udev-cache execution by touching a file, $DEVCACHE_REGEN.

---
 meta/recipes-core/udev/udev/init       | 44 +++++++++++++---------------------
 meta/recipes-core/udev/udev/udev-cache | 17 +++++++++++--
 2 files changed, 31 insertions(+), 30 deletions(-)
meta/recipes-core/udev/udev/init +16 -28
index 7ffa4a6..10cbf56 100644
@@ -16,21 +16,20 @@ export TZ=/etc/localtime
 [ -x @UDEVD@ ] || exit 1
 SYSCONF_CACHED="/etc/udev/cache.data"
 SYSCONF_TMP="/dev/shm/udev.cache"
+DEVCACHE_REGEN="/dev/shm/udev-regen" # create to request cache regen
+
+# List of files whose contents will be included in cached system state.
+CMP_FILE_LIST="/proc/version /proc/cmdline /proc/devices"
+[ -f /proc/atags ] && CMP_FILE_LIST="$CMP_FILE_LIST /proc/atags"
+
+# Command to compute system configuration.
+sysconf_cmd () {
+	cat -- $CMP_FILE_LIST
+}
 [ -f /etc/default/udev-cache ] && . /etc/default/udev-cache
 [ -f /etc/udev/udev.conf ] && . /etc/udev/udev.conf
 [ -f /etc/default/rcS ] && . /etc/default/rcS
 
-readfiles () {
-   READDATA=""
-   for filename in $@; do
-	   if [ -r $filename ]; then
-		   while read line; do
-			   READDATA="$READDATA$line"
-		   done < $filename
-	   fi
-   done
-}
-
 kill_udevd () {
     pid=`pidof -x udevd`
     [ -n "$pid" ] && kill $pid
@@ -62,35 +61,24 @@ case "$1" in
     mkdir -p /var/volatile/tmp
 
     # Cache handling.
-    # A list of files which are used as a criteria to judge whether the udev cache could be reused.
-    CMP_FILE_LIST="/proc/version /proc/cmdline /proc/devices /proc/atags"
     if [ "$DEVCACHE" != "" ]; then
             if [ -e $DEVCACHE ]; then
-		    readfiles $CMP_FILE_LIST
-		    NEWDATA="$READDATA"
-		    readfiles "$SYSCONF_CACHED"
-		    OLDDATA="$READDATA"
-		    if [ "$OLDDATA" = "$NEWDATA" ]; then
+		    sysconf_cmd > $SYSCONF_TMP
+		    if cmp $SYSCONF_CACHED $SYSCONF_TMP >/dev/null; then
                             tar xmf $DEVCACHE -C / -m
                             not_first_boot=1
                             [ "$VERBOSE" != "no" ] && echo "udev: using cache file $DEVCACHE"
-                            [ -e $SYSCONF_TMP ] && rm -f "$SYSCONF_TMP"
+                            [ -e "$DEVCACHE_REGEN" ] && rm -f "$DEVCACHE_REGEN"
                     else
 			    # Output detailed reason why the cached /dev is not used
-			    if [ "$VERBOSE" != "no" ]; then
-				    echo "udev: udev cache not used"
-				    echo "udev: we use $CMP_FILE_LIST as criteria to judge whether the cache /dev could be resued"
-				    echo "udev: olddata: $OLDDATA"
-				    echo "udev: newdata: $NEWDATA"
-			    fi
-			    echo "$NEWDATA" > "$SYSCONF_TMP"
+			    echo "udev: cache not used"
+			    touch "$DEVCACHE_REGEN"
                     fi
 	    else
 		    if [ "$ROOTFS_READ_ONLY" != "yes" ]; then
 			    # If rootfs is not read-only, it's possible that a new udev cache would be generated;
 			    # otherwise, we do not bother to read files.
-			    readfiles $CMP_FILE_LIST
-			    echo "$READDATA" > "$SYSCONF_TMP"
+			    touch "$DEVCACHE_REGEN"
 		    fi
             fi
     fi
meta/recipes-core/udev/udev/udev-cache +15 -2
index 497d257..5a1bc78 100644
@@ -19,6 +19,16 @@ export TZ=/etc/localtime
 DEVCACHE_TMP="/dev/shm/udev-cache-tmp.tar"
 SYSCONF_CACHED="/etc/udev/cache.data"
 SYSCONF_TMP="/dev/shm/udev.cache"
+DEVCACHE_REGEN="/dev/shm/udev-regen" # create to request cache regen
+
+# List of files whose contents will be included in cached system state.
+CMP_FILE_LIST="/proc/version /proc/cmdline /proc/devices"
+[ -f /proc/atags ] && CMP_FILE_LIST="$CMP_FILE_LIST /proc/atags"
+
+# Command to compute system configuration.
+sysconf_cmd () {
+	cat -- $CMP_FILE_LIST
+}
 [ -f /etc/default/udev-cache ] && . /etc/default/udev-cache
 
 if [ "$ROOTFS_READ_ONLY" = "yes" ]; then
@@ -26,13 +36,16 @@ if [ "$ROOTFS_READ_ONLY" = "yes" ]; then
     exit 0
 fi
 
-if [ "$DEVCACHE" != "" -a -e "$SYSCONF_TMP" ]; then
+if [ "$DEVCACHE" != "" -a -e "$DEVCACHE_REGEN" ]; then
 	echo "Populating dev cache"
+	# Run sysconf_cmd before `tar`, not after, so that as little time as
+	# possible elapses between creating $SYSCONF_CACHED and $DEVCACHE.
+	sysconf_cmd > "$SYSCONF_CACHED"
 	find /dev -xdev \( -type b -o -type c -o -type l \) | cut -c 2- \
 		| xargs tar cf "${DEVCACHE_TMP}" -T-
 	gzip < "${DEVCACHE_TMP}" > "$DEVCACHE"
 	rm -f "${DEVCACHE_TMP}"
-	mv "$SYSCONF_TMP" "$SYSCONF_CACHED"
+	rm -f "$DEVCACHE_REGEN"
 fi
 
 exit 0
Signature · 1 line
2.0.4
#10 lore raw

Re: [PATCH v2 09/12] udev-cache: get system config immediately before cache

Quoted 15 lines · Richard Tollerton

The system device configuration -- /proc/devices, /proc/cmdline, etc. -- and the contents of /dev must be stored as simultaneously as possible. Otherwise, hotplug events could be processed between the time we get the system config and the time we archive /dev, and the udev cache may be incorrect.

To solve this, update the cached system configuration inside the udev-cache initscript, not the udev initscript. Also, run it before archiving /dev, because it should execute nearly instantaneously.

We still need to compute the system configuration inside the udev initscript, to detect if it changed, so refactor it into a new function sysconf_cmd(). This also allows administrators to modify it for machine-specific requirements.

Quoted 11 lines

Evaluating the system configuration requires a shell function readfiles() defined in the udev initscript. The only salient differences between readfiles() and just using cat directly are a) readfiles() skips files without error if they don't exist, which is necessary because /proc/atags is in CMP_FILE_LIST but does not exist on all targets; and b) input lines are concatenated into an output shell variable with newlines stripped. (a) can be eliminated by not including /proc/atags in CMP_FILE_LIST if it doesn't exist. (b) is O(n^2) anyway, and makes $NEWDATA, $OLDDATA etc. very hard to read, and we don't need to strip newlines anyway. So make sysconf_cmd() use cat and remove readfiles().

The reason this was done was actually c) avoid having to execute cat as a new process.

Quoted 6 lines

Instead of storing system configuration in shell variables ($NEWDATA, $OLDDATA), store them directly in files: redirect to $SYSCONF_TMP instead of storing in $NEWDATA; read from $SYSCONF_CACHED directly instead of reading it into $OLDDATA. This turns comparing $NEWDATA vs. $OLDDATA into a call to cmp. However, busybox lacks cmp -q, so instead, redirect cmp's output to /dev/null.

The above reason c) is also why we don't use cmp. It turned out to be faster to read into a variable than fork/exec cmp.

Cheers,

Richard

#11 lore raw

Re: [PATCH v2 09/12] udev-cache: get system config immediately before cache

Quoted 2 lines · Richard Purdie

The above reason c) is also why we don't use cmp. It turned out to be faster to read into a variable than fork/exec cmp.

You appear to be wrong: cat+cmp is 27-41% faster than readfiles. In fact, testing cat alone (without cmp) shows that cat to tmpfs file is 2x-3x as fast as readfiles.

These figures reproduce on both a Xilinx Zynq Cortex-A9 and my Xeon dev machine; the former running busybox sh, the latter running bash.

See below for test script.

---

#!/bin/busybox sh

files="/proc/version /proc/cmdline /proc/devices"
runs=100

readfiles () {
READDATA=""
for filename in $@; do
if [ -r $filename ]; then
while read line; do
READDATA="$READDATA$line"
done < $filename
fi
done
}

dotest_cat_inner () {
	cat $@
}
dotest_cat () {
	cat $@ > /tmp/a.txt
	cat $@ > /tmp/b.txt
	cmp /tmp/a.txt /tmp/b.txt
}

dotest_readfiles_inner () {
readfiles $@
echo "$READDATA"
}

dotest_readfiles () {
	readfiles $@
	NEWDATA="$READDATA"
	readfiles $@
	OLDDATA="$OLDDATA"
	[ "$OLDDATA" = "$NEWDATA" ]
}
timetest () {
	cmd=$1
	i=$2
	shift 2
	while [ "$i" -gt 0 ]; do
		$cmd $@ >/dev/null
		i=$(( i-1 ))
	done
}

case $1 in
cat) timetest dotest_cat $runs $files ;;
readfiles) timetest dotest_readfiles $runs $files ;; inner_cat) timetest dotest_cat_inner $runs $files ;; inner_readfiles) timetest dotest_readfiles_inner $runs $files ;; *)
printf 'cat:\n'
time $0 cat

printf '\nreadfiles:\n'
time $0 readfiles

printf '\ncat_inner:\n'
time $0 inner_cat

printf '\nreadfiles_inner:\n'
time $0 inner_readfiles
;;
esac

Quoted 4 lines

Cheers,

Richard

Signature · 1 line
Richard Tollerton <[email protected]>
#12 lore raw

PATCH v2 10/12 udev-cache: invalidate on rules.d changes

In reply to Richard Tollerton

Presently, the cache is not regenerated if udev rules are modified, which may cause the cache to preserve an old configuration. To fix, include the size, mtime, and filename of all udev rules in the system configuration.

This change requires stat. If busybox supplies stat, CONFIG_FEATURE_STAT_FORMAT must be enabled.

---
 meta/recipes-core/udev/udev/init       | 5 +++++
 meta/recipes-core/udev/udev/udev-cache | 5 +++++
 2 files changed, 10 insertions(+)
meta/recipes-core/udev/udev/init +5 -0
index 10cbf56..c44dc2d 100644
@@ -22,9 +22,14 @@ DEVCACHE_REGEN="/dev/shm/udev-regen" # create to request cache regen
 CMP_FILE_LIST="/proc/version /proc/cmdline /proc/devices"
 [ -f /proc/atags ] && CMP_FILE_LIST="$CMP_FILE_LIST /proc/atags"
 
+# List of files whose metadata (size/mtime/name) will be included in cached
+# system state.
+META_FILE_LIST="lib/udev/rules.d/* etc/udev/rules.d/*"
+
 # Command to compute system configuration.
 sysconf_cmd () {
 	cat -- $CMP_FILE_LIST
+	stat -L -c '%s %Y %n' -- $META_FILE_LIST | awk -F/ '{print $1 " " $NF;}'
 }
 [ -f /etc/default/udev-cache ] && . /etc/default/udev-cache
 [ -f /etc/udev/udev.conf ] && . /etc/udev/udev.conf
meta/recipes-core/udev/udev/udev-cache +5 -0
index 5a1bc78..c499676 100644
@@ -25,9 +25,14 @@ DEVCACHE_REGEN="/dev/shm/udev-regen" # create to request cache regen
 CMP_FILE_LIST="/proc/version /proc/cmdline /proc/devices"
 [ -f /proc/atags ] && CMP_FILE_LIST="$CMP_FILE_LIST /proc/atags"
 
+# List of files whose metadata (size/mtime/name) will be included in cached
+# system state.
+META_FILE_LIST="lib/udev/rules.d/* etc/udev/rules.d/*"
+
 # Command to compute system configuration.
 sysconf_cmd () {
 	cat -- $CMP_FILE_LIST
+	stat -L -c '%s %Y %n' -- $META_FILE_LIST | awk -F/ '{print $1 " " $NF;}'
 }
 [ -f /etc/default/udev-cache ] && . /etc/default/udev-cache
 
Signature · 1 line
2.0.4
#13 lore raw

PATCH v2 11/12 udev-cache: Update cache asynchronously

In reply to Richard Tollerton

Don't hold up the boot while the cache is being updated.

---
 meta/recipes-core/udev/udev/udev-cache | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)
meta/recipes-core/udev/udev/udev-cache +11 -8
index c499676..baeb125 100644
@@ -43,14 +43,17 @@ fi
 
 if [ "$DEVCACHE" != "" -a -e "$DEVCACHE_REGEN" ]; then
 	echo "Populating dev cache"
-	# Run sysconf_cmd before `tar`, not after, so that as little time as
-	# possible elapses between creating $SYSCONF_CACHED and $DEVCACHE.
-	sysconf_cmd > "$SYSCONF_CACHED"
-	find /dev -xdev \( -type b -o -type c -o -type l \) | cut -c 2- \
-		| xargs tar cf "${DEVCACHE_TMP}" -T-
-	gzip < "${DEVCACHE_TMP}" > "$DEVCACHE"
-	rm -f "${DEVCACHE_TMP}"
-	rm -f "$DEVCACHE_REGEN"
+	(
+		# Run sysconf_cmd before `tar`, not after, so that as little
+		# time as possible elapses between creating $SYSCONF_CACHED and
+		# $DEVCACHE.
+		sysconf_cmd > "$SYSCONF_CACHED"
+		find /dev -xdev \( -type b -o -type c -o -type l \) | cut -c 2- \
+			| xargs tar cf "${DEVCACHE_TMP}" -T-
+		gzip < "${DEVCACHE_TMP}" > "$DEVCACHE"
+		rm -f "${DEVCACHE_TMP}"
+		rm -f "$DEVCACHE_REGEN"
+	) &
 fi
 
 exit 0
Signature · 1 line
2.0.4
#14 lore raw

PATCH v2 12/12 udev-cache: refactor; improve verbosity and error handling

In reply to Richard Tollerton

Most of /etc/init.d/udev-cache is in a conditional block which can be replaced by a [ ... ] || exit 0 to reduce nesting. This also provides an opportunity to improve handling of VERBOSE a bit.

Previously, errors encountered during find, tar, etc. were ignored, which could mask some problems, or cause invalid cache files to get created. To solve: abort the update process (and report an error) if any command in the process fails; and write the system configuration to $SYSCONF_TMP first -- only update $SYSCONF_CACHED when we are certain that the cache tarball was successfully created.

---
 meta/recipes-core/udev/udev/udev-cache | 33 ++++++++++++++++++++-------------
 1 file changed, 20 insertions(+), 13 deletions(-)
meta/recipes-core/udev/udev/udev-cache +20 -13
index baeb125..dc97198 100644
@@ -41,19 +41,26 @@ if [ "$ROOTFS_READ_ONLY" = "yes" ]; then
     exit 0
 fi
 
-if [ "$DEVCACHE" != "" -a -e "$DEVCACHE_REGEN" ]; then
-	echo "Populating dev cache"
-	(
-		# Run sysconf_cmd before `tar`, not after, so that as little
-		# time as possible elapses between creating $SYSCONF_CACHED and
-		# $DEVCACHE.
-		sysconf_cmd > "$SYSCONF_CACHED"
-		find /dev -xdev \( -type b -o -type c -o -type l \) | cut -c 2- \
-			| xargs tar cf "${DEVCACHE_TMP}" -T-
-		gzip < "${DEVCACHE_TMP}" > "$DEVCACHE"
-		rm -f "${DEVCACHE_TMP}"
-		rm -f "$DEVCACHE_REGEN"
-	) &
+[ "$DEVCACHE" != "" ] || exit 0
+[ "${VERBOSE}" == "no" ] || echo -n "udev-cache: checking for ${DEVCACHE_REGEN}... "
+if ! [ -e "$DEVCACHE_REGEN" ]; then
+	[ "${VERBOSE}" == "no" ] || echo "not found."
+	exit 0
 fi
+[ "${VERBOSE}" == "no" ] || echo "found; building cache."
+
+(
+	# Run sysconf_cmd before `tar`, not after, so that as little time as
+	# possible elapses between creating $SYSCONF_CACHED and $DEVCACHE.
+	if ! { sysconf_cmd > "$SYSCONF_TMP" \
+		&& find /dev -xdev \( -type b -o -type c -o -type l \) | cut -c 2- \
+		| xargs tar cf "${DEVCACHE_TMP}" -T- \
+		&& gzip < "${DEVCACHE_TMP}" > "$DEVCACHE" \
+		&& mv "$SYSCONF_TMP" "$SYSCONF_CACHED" \
+		&& rm -f "$DEVCACHE_REGEN" "${DEVCACHE_TMP}"; }
+	then
+		echo "udev-cache: update failed!"
+	fi
+) &
 
 exit 0
Signature · 1 line
2.0.4