From: Andrew Tridgell Date: Sun, 31 May 2026 11:01:09 +0000 (+1000) Subject: old_versions: commit static binaries of old rsync releases X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=e8d10dc2adc62ace14c07dd394daa9ca7fdcae47;p=thirdparty%2Frsync.git old_versions: commit static binaries of old rsync releases Nine statically-linked, stripped binaries for the version-mixing test suite (and ad-hoc cross-version behaviour checks): every x.y.0 release from 2.6.0 (2004, protocol 27) through 3.4.0, plus the 3.1.3/3.2.7/3.4.1 point releases. 2.6.0 is the practical floor; older tags need more porting to build on a current toolchain. build_static.sh rebuilds any release from its git tag, applying the minimal patches needed to compile old sources on a modern toolchain: K&R lseek64 redecl, gettimeofday, -std=gnu11, --disable-openssl, and _FORTIFY_SOURCE disabled (modern FORTIFY=3 turns latent benign over-reads in old rsync into aborts when it runs as a server). Pre-3.0 trees ship configure.in, so it regenerates configure (autoheader/autoconf) after neutralizing the dead AC_LIBOBJ replacement fallbacks, generates proto.h, and stubs the dropped vendored lib/addrinfo.h -- all guarded to no-op on newer versions. .gitattributes marks the binaries binary (so the text=auto rule can't corrupt them) and export-ignore (kept out of the release tarball). Co-Authored-By: Claude Opus 4.8 (1M context) --- diff --git a/.gitattributes b/.gitattributes index dd440b78..17c3856c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,3 +6,12 @@ # packaging/release.py step_7_tarball does not bloat with HTML the # tarball doesn't need. /rsync-web/ export-ignore + +# old_versions/ holds static binaries of historical rsync releases, used by the +# version-mixing test suite (.github/workflows/ubuntu-version-mix.yml) to run +# the current code against a real old peer over the daemon / remote-shell. +# Mark the binaries as binary so the `text=auto eol=lf` rule above can't try to +# normalise line endings and corrupt them; export-ignore keeps them out of the +# release source tarball. +/old_versions/rsync_* binary +/old_versions/rsync_* export-ignore diff --git a/old_versions/README.md b/old_versions/README.md new file mode 100644 index 00000000..a5ce3a5b --- /dev/null +++ b/old_versions/README.md @@ -0,0 +1,87 @@ +# Old rsync version archive + +Static rsync binaries built from historical release tags. Two uses: + +1. **Cross-version behaviour checks** — confirming whether a behaviour a user + reported on an old release is version-specific or option-driven. +2. **The version-mixing test suite** — `runtests.py --rsync-bin2=...` runs the + current code against one of these as the daemon / remote-shell peer; CI + (`.github/workflows/ubuntu-version-mix.yml`) does this for every binary + here against the per-version manifests in `testsuite/expect/`. + +Binaries are **statically linked** so they run regardless of the host's +shared libraries, and named `rsync_`: + +| Binary | Version | Protocol | Notes | +|----------------|---------|----------|-----------------------------------------| +| `rsync_2.6.0` | 2.6.0 | 27 | 2004; needs autoconf regen (see below) | +| `rsync_3.0.0` | 3.0.0 | 30 | 2008 | +| `rsync_3.1.0` | 3.1.0 | 31 | 2013 | +| `rsync_3.1.3` | 3.1.3 | 31 | Ubuntu 18.04 / Debian buster era (2018) | +| `rsync_3.2.0` | 3.2.0 | 31 | 2020 (zstd/lz4/xxhash negotiation added)| +| `rsync_3.2.7` | 3.2.7 | 31 | 2022 | +| `rsync_3.3.0` | 3.3.0 | 31 | 2024 | +| `rsync_3.4.0` | 3.4.0 | 32 | 2025 | +| `rsync_3.4.1` | 3.4.1 | 32 | 2025 | + +These are every `x.y.0` release from 2.6.0 (2004) onward plus a few point +releases. 2.6.0 is the practical floor: older tags need progressively more +porting to build on a current toolchain. + +All built `--disable-openssl` and with `_FORTIFY_SOURCE` disabled (see below); +xxhash/zstd/lz4 are compiled in where the version supports them. + +## Adding a version + +```bash +./build_static.sh 3.2.7 # uses git tag v3.2.7 +./build_static.sh 3.0.9 v3.0.9 # explicit tag if naming differs +``` + +The script checks out the tag into a throwaway `git worktree`, applies the +minimal patches needed to compile old sources on a modern toolchain, links +statically, verifies the result is static and reports the requested version, +then installs `rsync_` here and removes the worktree. + +Override the source repo with `RSYNC_REPO=/path/to/rsync ./build_static.sh ...` +(defaults to `../rsync.4`). + +## Why the patches? + +Modern GCC (>= 14, C23 default) and glibc reject things old rsync relied on. +`build_static.sh` handles these, each guarded so it's a no-op when not needed: + +1. **K&R `lseek64()` redeclaration** in `syscall.c` clashes with glibc's real + prototype — removed. +2. **`gettimeofday()`** — glibc only has the 2-arg form; configure misdetects + the 1-arg form, so `HAVE_GETTIMEOFDAY_TZ` is forced on in `config.h`. +3. **C23 `()` == `(void)`** breaks K&R prototypes called with arguments + (`qsort` comparator, `pool->bomb`, etc.) — built with `-std=gnu11`. +4. Assorted modern `-Werror` promotions (incompatible pointer types, implicit + declarations) downgraded to warnings; bundled zlib/popt used to keep the + static link self-contained. + +5. **OpenSSL (3.2+)** is disabled with `--disable-openssl`: linking + `libcrypto.a` statically drags in jitterentropy (`jent_*`) and zlib's + `uncompress` (OpenSSL's COMP module), which don't resolve here. OpenSSL only + provided optional MD4/MD5, which rsync implements natively, so checksum + behaviour is unaffected. + +6. **`_FORTIFY_SOURCE` disabled** (`-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0`): + modern Ubuntu defaults it to `=3`, whose stricter object-size checks turn + latent (historically benign) over-reads in OLD rsync into hard + `*** buffer overflow detected ***` aborts when the binary runs as a + server/daemon — which made e.g. 3.1.3 and 3.2.7 unusable as peers. Disabling + it makes the archival binaries behave as the released versions did. + +7. **Pre-3.0 tags (e.g. 2.6.0)** ship `configure.in`, not a generated + `configure`. The script runs `autoheader`/`autoconf` to generate it, after + neutralizing the `AC_CHECK_FUNCS(fn,,AC_LIBOBJ(lib/...))` fallbacks for + `inet_ntop`/`inet_pton`/`getaddrinfo`/`getnameinfo` — modern autoconf emits + broken shell for those never-taken branches (the funcs exist in glibc). It + also generates `proto.h` (no make rule in that era) and stubs the vendored + `lib/addrinfo.h` the tag dropped (modern glibc supplies `struct addrinfo`). + All guarded so they no-op on 3.x. + +Newer versions may need fewer or different tweaks; if a build fails, the +script prints the first compiler errors from its log. diff --git a/old_versions/build_static.sh b/old_versions/build_static.sh new file mode 100755 index 00000000..7cabcc0c --- /dev/null +++ b/old_versions/build_static.sh @@ -0,0 +1,128 @@ +#!/bin/bash +# Build a static rsync binary from a historical git tag, for cross-version +# behaviour testing. Produces ./rsync_ in this directory. +# +# Usage: ./build_static.sh [git-tag] +# Example: ./build_static.sh 3.1.3 # uses tag v3.1.3 +# ./build_static.sh 3.2.7 v3.2.7 +# +# Old rsync releases don't compile cleanly on a modern toolchain (GCC >= 14 +# defaults to C23, where an empty () prototype means (void); glibc dropped the +# 1-arg gettimeofday; lseek64 K&R redeclarations clash). This script applies +# the minimal, best-effort workarounds and links statically so the result is +# self-contained and reproducible regardless of the host's shared libraries. +# +# Each workaround is guarded so it's a no-op on versions that don't need it. +set -euo pipefail + +VERSION="${1:?usage: build_static.sh [git-tag]}" +TAG="${2:-v$VERSION}" + +ARCHIVE_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO="${RSYNC_REPO:-/home/tridge/project/rsync/rsync.4}" # any rsync worktree +WORKTREE="$(mktemp -d /tmp/rsync-build-XXXXXX)" +OUT="$ARCHIVE_DIR/rsync_$VERSION" + +# C standard restores K&R () semantics; permissive flags downgrade the pile of +# modern -Werror promotions (incompatible pointers, implicit decls) to warnings. +# _FORTIFY_SOURCE is forced OFF: modern Ubuntu defaults it to =3, whose stricter +# object-size checks turn latent (historically benign) over-reads in OLD rsync +# into hard "*** buffer overflow detected ***" aborts when the binary acts as a +# server/daemon. Disabling it makes these archival binaries behave the way the +# released versions did, which is the whole point of the archive. +CFLAGS_OLD="-I. -I./zlib -O2 -g -std=gnu11 -fcommon -DHAVE_CONFIG_H -Wno-error \ +-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ +-Wno-incompatible-pointer-types -Wno-implicit-function-declaration -Wno-int-conversion" + +cleanup() { + cd "$REPO" + git worktree remove --force "$WORKTREE" 2>/dev/null || true + git worktree prune 2>/dev/null || true +} +trap cleanup EXIT + +echo ">>> checking out $TAG into $WORKTREE" +# prefer an exact tag to avoid ambiguity with similarly-named branches +REF="$TAG" +if git -C "$REPO" rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then + REF="refs/tags/$TAG" +fi +git -C "$REPO" worktree add --detach "$WORKTREE" "$REF" +cd "$WORKTREE" + +# --- workaround 1: K&R lseek64 redeclaration clashes with glibc's prototype --- +if grep -q 'off64_t lseek64();' syscall.c 2>/dev/null; then + echo ">>> patching syscall.c lseek64 redeclaration" + perl -0pi -e 's/#ifdef HAVE_LSEEK64\n#if !SIZEOF_OFF64_T\n\tOFF_T lseek64\(\);\n#else\n\toff64_t lseek64\(\);\n#endif\n\treturn lseek64/#ifdef HAVE_LSEEK64\n\treturn lseek64/' syscall.c +fi + +# --- workaround 0: pre-3.0 tags ship configure.in, not a generated configure. +# Generate it. Modern autoconf emits broken shell for their +# AC_CHECK_FUNCS(fn,,AC_LIBOBJ(lib/...)) fallbacks -- but those branches are +# dead on a modern host (glibc has inet_ntop/inet_pton/getaddrinfo/getnameinfo), +# so neutralize the AC_LIBOBJ replacements before regenerating. +OLD_TREE=0 +if [ ! -f ./configure ] && { [ -f configure.in ] || [ -f configure.ac ]; }; then + OLD_TREE=1 + acsrc=configure.ac; [ -f configure.in ] && acsrc=configure.in + echo ">>> generating configure for an old tag (autoheader/autoconf)" + sed -i 's#AC_LIBOBJ(lib/[a-zA-Z_]*)#:#g' "$acsrc" + autoheader 2>/dev/null || true + autoconf 2>/dev/null || { echo "autoconf failed"; exit 1; } +fi + +CONF_ARGS=(--disable-md2man --with-included-zlib=yes --with-included-popt=yes) +# OpenSSL (3.2+) only adds optional MD4/MD5 that rsync already implements, but +# linking libcrypto.a statically drags in jitterentropy + zlib's uncompress, +# which aren't resolvable here. Drop it when the flag exists. +if ./configure --help 2>/dev/null | grep -q -- '--disable-openssl'; then + echo ">>> disabling openssl for self-contained static link" + CONF_ARGS+=(--disable-openssl) +fi + +echo ">>> configure (bundled zlib + popt, static-friendly)" +./configure "${CONF_ARGS[@]}" \ + >"$WORKTREE/conf.log" 2>&1 || { tail -20 "$WORKTREE/conf.log"; exit 1; } + +# --- workaround 2: modern glibc only has the 2-arg gettimeofday --------------- +if grep -q '/\* #undef HAVE_GETTIMEOFDAY_TZ \*/' config.h; then + echo ">>> forcing HAVE_GETTIMEOFDAY_TZ (configure misdetects it)" + sed -i 's|/\* #undef HAVE_GETTIMEOFDAY_TZ \*/|#define HAVE_GETTIMEOFDAY_TZ 1|' config.h +fi + +# --- workaround 4 (old trees only): generate proto.h if the tree has no make +# rule for it, and stub a vendored lib/addrinfo.h that the git tag dropped +# (modern glibc supplies struct addrinfo / sockaddr_storage, so empty is right). +if [ "$OLD_TREE" = 1 ]; then + if [ ! -f proto.h ] && [ -f mkproto.awk ]; then + echo ">>> generating proto.h" + cat ./*.c ./lib/compat.c 2>/dev/null | awk -f ./mkproto.awk > proto.h + fi + if grep -q 'include "lib/addrinfo.h"' rsync.h 2>/dev/null && [ ! -f lib/addrinfo.h ]; then + echo ">>> stubbing lib/addrinfo.h" + echo '/* emptied: modern glibc provides struct addrinfo */' > lib/addrinfo.h + fi +fi + +echo ">>> building (static)" +make -j"$(nproc)" CFLAGS="$CFLAGS_OLD" LDFLAGS="-static" \ + >"$WORKTREE/make.log" 2>&1 || { grep -E 'error:|\*\*\*' "$WORKTREE/make.log" | head; exit 1; } + +# verify it's actually static before we keep it +if ldd ./rsync 2>&1 | grep -qv 'not a dynamic executable'; then + echo "ERROR: binary is not statically linked:" >&2 + ldd ./rsync >&2 + exit 1 +fi + +GOT="$(./rsync --version | head -1 | awk '{print $3}')" +if [ "$GOT" != "$VERSION" ]; then + echo "ERROR: built version '$GOT' != requested '$VERSION'" >&2 + exit 1 +fi + +cp ./rsync "$OUT" +strip "$OUT" +echo ">>> installed $OUT" +"$OUT" --version | head -1 +file "$OUT" diff --git a/old_versions/rsync_2.6.0 b/old_versions/rsync_2.6.0 new file mode 100755 index 00000000..face7a02 Binary files /dev/null and b/old_versions/rsync_2.6.0 differ diff --git a/old_versions/rsync_3.0.0 b/old_versions/rsync_3.0.0 new file mode 100755 index 00000000..c844826c Binary files /dev/null and b/old_versions/rsync_3.0.0 differ diff --git a/old_versions/rsync_3.1.0 b/old_versions/rsync_3.1.0 new file mode 100755 index 00000000..ebb87607 Binary files /dev/null and b/old_versions/rsync_3.1.0 differ diff --git a/old_versions/rsync_3.1.3 b/old_versions/rsync_3.1.3 new file mode 100755 index 00000000..3cd1ac4a Binary files /dev/null and b/old_versions/rsync_3.1.3 differ diff --git a/old_versions/rsync_3.2.0 b/old_versions/rsync_3.2.0 new file mode 100755 index 00000000..a28c4058 Binary files /dev/null and b/old_versions/rsync_3.2.0 differ diff --git a/old_versions/rsync_3.2.7 b/old_versions/rsync_3.2.7 new file mode 100755 index 00000000..7ca16820 Binary files /dev/null and b/old_versions/rsync_3.2.7 differ diff --git a/old_versions/rsync_3.3.0 b/old_versions/rsync_3.3.0 new file mode 100755 index 00000000..1169447b Binary files /dev/null and b/old_versions/rsync_3.3.0 differ diff --git a/old_versions/rsync_3.4.0 b/old_versions/rsync_3.4.0 new file mode 100755 index 00000000..7432dbd4 Binary files /dev/null and b/old_versions/rsync_3.4.0 differ diff --git a/old_versions/rsync_3.4.1 b/old_versions/rsync_3.4.1 new file mode 100755 index 00000000..4c66addd Binary files /dev/null and b/old_versions/rsync_3.4.1 differ