]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
testsuite: portable make_data_file helper; drop hard /dev/urandom dependency
authorAndrew Tridgell <tridge60@gmail.com>
Wed, 20 May 2026 21:28:03 +0000 (07:28 +1000)
committerAndrew Tridgell <andrew@tridgell.net>
Wed, 20 May 2026 21:40:30 +0000 (07:40 +1000)
symlink-dirlink-basis.test and chdir-symlink-race.test both
require a multi-kilobyte non-trivial-content source file for the
rsync delta algorithm to exercise.  Both used dd / head against
/dev/urandom directly, which fails on platforms that don't ship
/dev/urandom (e.g. HPE NonStop).  The dd error gets swallowed by
'2>/dev/null' and the test then fails with a misleading 'failed
to create test file' that hides the real cause.

Add make_data_file <path> <size> to testsuite/rsync.fns.  Prefers
/dev/urandom when readable (kernel-provided randomness, fast),
falling back to a deterministic awk LCG seeded from PID and a
POSIX cksum of the destination path.  Output is constrained to
printable ASCII (33..126) so the helper survives two awk-portability
quirks:

  - printf '%c', 0 terminates the string in some awks, emitting
    fewer than sz bytes;
  - gawk in UTF-8 locales encodes printf '%c', N for N > 127 as
    a 2-byte UTF-8 sequence, emitting more than sz bytes.

The tests don't need 8-bit binary entropy -- they just need
non-trivial bytes for rsync's block-matching algorithm.

Update both call sites to use the helper.  Linux/FreeBSD/macOS
still take the /dev/urandom fast path; NonStop and any other
platform missing the device get the awk fallback transparently.
Both paths verified locally with the symlink-dirlink-basis test.

testsuite/chdir-symlink-race.test
testsuite/rsync.fns
testsuite/symlink-dirlink-basis.test

index f5d4cb3f33382b1efd7c46afd2be3bc6c8595cba..c464101f233d9ca4124ff0aa46803cb8ef9b8186 100755 (executable)
@@ -66,8 +66,10 @@ ln -s "$outside" "$mod/subdir"
 # different content, mode 0666 (the perms the attacker tries to push).
 SIZE=$(stat -c %s "$outside/target.txt" 2>/dev/null \
        || stat -f %z "$outside/target.txt")
-head -c "$SIZE" /dev/urandom > "$src/target.txt"
-head -c "$SIZE" /dev/urandom > "$src/subdir/target.txt"
+make_data_file "$src/target.txt" "$SIZE" \
+    || test_fail "failed to create source file"
+make_data_file "$src/subdir/target.txt" "$SIZE" \
+    || test_fail "failed to create source file"
 chmod 0666 "$src/target.txt" "$src/subdir/target.txt"
 
 cat > "$conf" <<EOF
index 4caec58b54640464887d25008e28a8c05e79d19f..c7077fee328261a3884ebc5205272f472353fed0 100644 (file)
@@ -235,6 +235,50 @@ makepath() {
 }
 
 
+###########################
+# Create a file at $1 of $2 bytes containing non-trivial content
+# suitable for rsync's delta algorithm to chew on.  Prefers
+# /dev/urandom for speed and entropy, falling back to a
+# deterministic awk pseudo-random generator on platforms that
+# lack /dev/urandom (e.g. HPE NonStop).  The tests using this
+# helper don't need cryptographic randomness -- they only need
+# bytes that compress and delta-match like normal file content.
+
+make_data_file() {
+    if [ $# -ne 2 ]; then
+       echo "usage: make_data_file PATH SIZE" >&2
+       return 2
+    fi
+    if [ -r /dev/urandom ] && \
+       dd if=/dev/urandom of="$1" bs="$2" count=1 2>/dev/null && \
+       [ -s "$1" ]; then
+       return 0
+    fi
+    # Fallback: a 32-bit linear congruential generator with BSD/glibc
+    # parameters.  Seeded from PID and a POSIX cksum of the destination
+    # path so successive calls with different paths produce distinct
+    # content.  Output is constrained to the printable-ASCII range
+    # (33..126, i.e. '!' through '~') for two portability reasons:
+    #   - awk implementations vary on whether printf "%c", 0 emits a
+    #     NUL byte or terminates the string;
+    #   - gawk in UTF-8 locales encodes printf "%c", N for N > 127
+    #     as a 2-byte UTF-8 sequence, which would make the output
+    #     larger than the requested sz.
+    # The tests using this helper don't need 8-bit binary data, only
+    # non-trivial content for the rsync delta algorithm.
+    _path_seed=$(printf '%s' "$1" | cksum 2>/dev/null | awk '{print $1}')
+    awk -v sz="$2" -v seed_a="$$" -v seed_b="${_path_seed:-0}" 'BEGIN {
+       s = (seed_a + seed_b) % 2147483648
+       if (s < 0) s = -s
+       for (i = 0; i < sz; i++) {
+           s = (s * 1103515245 + 12345) % 2147483648
+           b = (int(s / 65536) % 94) + 33    # 33..126
+           printf "%c", b
+       }
+    }' > "$1"
+}
+
+
 ###########################
 # Run a test (in '$1') then compare directories $2 and $3 to see if
 # there are any difference.  If there are, explain them.
index a14eb5cf595859690ff8f619f97fc5b4e37172b5..88c55d2ba916994eb91d614b0468ec72abb6027d 100755 (executable)
@@ -46,8 +46,10 @@ export RSYNC_RSH
 
 # Helper: create a large file suitable for delta transfers.
 # ~32KB is large enough for rsync's block matching to find matches.
+# make_data_file lives in rsync.fns and falls back to an awk PRNG
+# on platforms without /dev/urandom (e.g. HPE NonStop).
 make_testfile() {
-    dd if=/dev/urandom of="$1" bs=1024 count=32 2>/dev/null \
+    make_data_file "$1" 32768 \
        || test_fail "failed to create test file $1"
 }