# 99-inotify-tools.test -- inotify-tools 3.22.6.0 (meta-oe, recipes-support). # # inotifywait blocks until a watched path sees an event; inotifywatch counts # events over an interval. Both sit directly on the kernel inotify interface -- # inotify_init1/inotify_add_watch/inotify_rm_watch (generic syscalls 26/27/28) # plus a read(2) of struct inotify_event from the returned fd. No part of that # path enters the host runtime, so this suite is really a test of the wasm32 # kernel's fsnotify plumbing, with the two tools as the instrument. # # Calibrated against a NATIVE x86_64 build of the SAME upstream commit # (c8bdbc0a2ed8), which scores all-green -- so a red here is a target-only # divergence rather than a miscalibrated assertion. . "$BST_LIB" require inotifywait inotify-tools/present W=${TMPDIR:-/tmp}/bst-ino.$$ D=$W/d mkdir -p "$D" 2>/dev/null || { bst_notok inotify-tools/workdir "cannot mkdir $D"; exit 0; } # --- helpers --------------------------------------------------------------- # _slurp : read into $_SL using only shell builtins. # # Every exec on this target stages a multi-MB wasm binary into kernel memory # before it runs, so a process costs ~200-400ms -- two orders more than on the # native host this suite is calibrated against. A `$(cat f)` or a `grep` per poll # iteration is therefore not free here: the first version of this file spent its # entire wall-clock budget on them and was cut off before its last cases ran. The # read loop below is a builtin, so it costs nothing. _slurp() { _SL='' [ -f "$1" ] || return 0 while IFS= read -r _l || [ -n "$_l" ]; do _SL="$_SL$_l " done < "$1" } # has : ok iff contains the literal . has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "want:'$2' got:'$(_bst_1line "$3")'" ;; esac } # hasnt : ok iff does NOT contain . hasnt() { case "$3" in *"$2"*) bst_notok "$1" "unwanted:'$2' in:'$(_bst_1line "$3")'" ;; *) bst_ok "$1" ;; esac } # iw : arrange for to fire shortly, run # inotifywait in the FOREGROUND, and collect its exit status (IW_RC), stdout # (IW_OUT) and stderr (IW_ERR). # # Ordering matters: a watch is armed only once inotifywait has called # inotify_add_watch(2), so a trigger that fires before that is never seen and the # case then fails on a timeout that says nothing about inotify. Hence the delay -- # the trigger fires from a detached subshell while inotifywait is already blocked # in read(2) on the inotify fd. # # inotifywait itself is deliberately NOT backgrounded, and nothing here calls # `wait`. The obvious shape -- `{ inotifywait ...; echo $? >rc; } & p=$!; wait $p` # -- hangs on this image: it was measured stalling past 390s, while the identical # command run in the FOREGROUND returned in under a second every time, for a # directory and a file target alike. That is a job-control property of the # platform, not of inotify-tools, so the suite stays clear of it instead of # reporting it as this package's failure. _iwn=0 iw() { _trig=$1; shift _iwn=$((_iwn + 1)) _e=$W/e.$_iwn ( sleep 2; eval "$_trig" ) >/dev/null 2>&1 & IW_OUT=$(inotifywait "$@" 2>"$_e") IW_RC=$? _slurp "$_e"; IW_ERR=$_SL } # --- self-description ------------------------------------------------------ # --help exits 1 upstream (usage is an error exit), so capture the text and assert # on it -- check_out would fail the case on the exit status alone and say nothing # about whether the tool is the one we think it is. _h=$(inotifywait --help 2>&1) has inotify-tools/help "Wait for a particular event" "$_h" has inotify-tools/version "inotifywait 3.22.6.0" "$_h" if command -v inotifywatch >/dev/null 2>&1; then bst_ok inotify-tools/watch-present else bst_notok inotify-tools/watch-present "inotifywatch absent while inotifywait is installed" fi # --- kernel interface ------------------------------------------------------ # inotify_user_setup() registers max_user_watches, so reading a number back is a # direct probe of the subsystem rather than an inference from a tool failure. # # Assert on the READ, not on `test -r`: access(2) answers from the inode alone and # has been seen to succeed on this image for a file whose read then fails, so a # `test -r` case flaps between green and red across runs and its verdict means # nothing either way. _mw=$(cat /proc/sys/fs/inotify/max_user_watches 2>&1) case "$_mw" in ''|*[!0-9]*) bst_notok inotify-tools/procfs-watches "want a number, got: $(_bst_1line "$_mw")" ;; *) bst_ok inotify-tools/procfs-watches ;; esac # --- is inotify reachable at all? ------------------------------------------ # One cheap probe, read by iw() to decide whether the readiness handshake is worth # waiting on. This is a timing decision only -- no case is skipped or softened by # it, and the probe's own answer is asserted below so the reason for a whole-suite # red is stated once, in one place, rather than inferred from 20 empty outputs. inotifywait -t 1 "$D" >/dev/null 2>"$W/probe" _slurp "$W/probe" case "$_SL" in *"Couldn't initialize inotify"*) INOTIFY_OK=0 ;; *) INOTIFY_OK=1 ;; esac check_eq inotify-tools/init 1 "$INOTIFY_OK" # --- core event delivery, tmpfs ------------------------------------------- # One background run feeds several assertions: the exit status (0 = "an event you # asked for arrived"), the event name, and the filename field. iw "touch '$D/created'" -e create --format '%e %f' -t 15 "$D" check_eq inotify-tools/create-rc 0 "$IW_RC" has inotify-tools/create "CREATE" "$IW_OUT" has inotify-tools/create-name "created" "$IW_OUT" has inotify-tools/established "Watches established." "$IW_ERR" printf 'seed\n' > "$D/f" iw "printf 'more\n' >> '$D/f'" -e modify --format '%e' -t 15 "$D/f" check_eq inotify-tools/modify-rc 0 "$IW_RC" has inotify-tools/modify "MODIFY" "$IW_OUT" iw "chmod 600 '$D/f'" -e attrib --format '%e' -t 15 "$D/f" has inotify-tools/attrib "ATTRIB" "$IW_OUT" touch "$D/gone" iw "rm -f '$D/gone'" -e delete --format '%e %f' -t 15 "$D" has inotify-tools/delete "DELETE" "$IW_OUT" has inotify-tools/delete-name "gone" "$IW_OUT" iw "printf 'z\n' > '$D/cw'" -e close_write --format '%e %f' -t 15 "$D" has inotify-tools/close-write "CLOSE_WRITE" "$IW_OUT" # rename delivers a pair on the same watch: MOVED_FROM for the old name and # MOVED_TO for the new, correlated by cookie. -m keeps listening so both land in # one run instead of two. touch "$D/mv1" iw "mv '$D/mv1' '$D/mv2'" -m -e moved_from -e moved_to --format '%e %f' -t 6 "$D" has inotify-tools/moved-from "MOVED_FROM mv1" "$IW_OUT" has inotify-tools/moved-to "MOVED_TO mv2" "$IW_OUT" # --- CLI surface ----------------------------------------------------------- # -e filters: a create must not be reported by a modify-only watch. The run is # expected to time out (rc 2), which is the assertion. touch "$D/filt" iw "touch '$D/filt-new'" -e modify --format '%e %f' -t 5 "$D" check_eq inotify-tools/event-filter-rc 2 "$IW_RC" hasnt inotify-tools/event-filter "CREATE" "$IW_OUT" # --csv emits %w,%e,%f. %w is the watched path as inotifywait stores it, which for # a directory keeps its trailing slash -- so anchor on the separator-delimited # tail, not on "$D," which would be a slash off. iw "touch '$D/csv1'" -e create --csv -t 15 "$D" has inotify-tools/csv "$D/,CREATE,csv1" "$IW_OUT" has inotify-tools/csv-comma ",CREATE,csv1" "$IW_OUT" mkdir -p "$D/sub" iw "touch '$D/sub/deep'" -r -e create --format '%w%f' -t 15 "$D" has inotify-tools/recursive "sub/deep" "$IW_OUT" # --exclude drops the first event by pattern, so the run reports the second. iw "touch '$D/skipme'; touch '$D/keepme'" -e create --exclude 'skipme' --format '%f' -t 15 "$D" has inotify-tools/exclude "keepme" "$IW_OUT" hasnt inotify-tools/exclude-drop "skipme" "$IW_OUT" iw "touch '$D/m1'; touch '$D/m2'" -m -e create --format '%f' -t 6 "$D" has inotify-tools/monitor-first "m1" "$IW_OUT" has inotify-tools/monitor-second "m2" "$IW_OUT" # Documented exit codes: 2 when --timeout expires with no event, 1 on error. inotifywait -e create -t 2 "$D/quiet-nonexistent" >/dev/null 2>&1 check_eq inotify-tools/enoent-rc 1 "$?" mkdir -p "$D/quiet" inotifywait -e create -t 2 "$D/quiet" >/dev/null 2>&1 check_eq inotify-tools/timeout-rc 2 "$?" # --- inotifywatch ---------------------------------------------------------- # Collects for -t seconds, then prints a per-path table of event counts. ( sleep 1; touch "$D/iw1"; printf 'a' >> "$D/iw1"; ) & _out=$(inotifywatch -t 4 "$D" 2>&1) has inotify-tools/watch-table "total" "$_out" has inotify-tools/watch-path "$D" "$_out" # --- the rootfs, not tmpfs ------------------------------------------------- # Everything above runs on tmpfs. The rootfs is a different backend entirely # (virtio-fs/FUSE), and fsnotify events are generated by the VFS layer above it, # so a guest-side write should still be seen. Assert it rather than assume it -- # a tool that only works on tmpfs would be a materially weaker port. RD="" for _base in /root "$HOME" /home; do [ -n "$_base" ] || continue if mkdir -p "$_base/.bst-ino.$$" 2>/dev/null; then RD="$_base/.bst-ino.$$"; break; fi done if [ -n "$RD" ]; then iw "touch '$RD/vfs'" -e create --format '%e %f' -t 15 "$RD" check_eq inotify-tools/rootfs-rc 0 "$IW_RC" has inotify-tools/rootfs "CREATE" "$IW_OUT" has inotify-tools/rootfs-name "vfs" "$IW_OUT" rm -rf "$RD" else bst_skip inotify-tools/rootfs "no writable non-tmpfs directory found" fi rm -rf "$W"