From 47e087d8eb4567989db066fa1da7eb5ad88554d2 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Thu, 21 May 2026 07:28:03 +1000 Subject: [PATCH] testsuite: portable make_data_file helper; drop hard /dev/urandom dependency 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 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 | 6 ++-- testsuite/rsync.fns | 44 ++++++++++++++++++++++++++++ testsuite/symlink-dirlink-basis.test | 4 ++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/testsuite/chdir-symlink-race.test b/testsuite/chdir-symlink-race.test index f5d4cb3f..c464101f 100755 --- a/testsuite/chdir-symlink-race.test +++ b/testsuite/chdir-symlink-race.test @@ -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" <&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. diff --git a/testsuite/symlink-dirlink-basis.test b/testsuite/symlink-dirlink-basis.test index a14eb5cf..88c55d2b 100755 --- a/testsuite/symlink-dirlink-basis.test +++ b/testsuite/symlink-dirlink-basis.test @@ -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" }