]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
old_versions: commit static binaries of old rsync releases
authorAndrew Tridgell <andrew@tridgell.net>
Sun, 31 May 2026 11:01:09 +0000 (21:01 +1000)
committerAndrew Tridgell <andrew@tridgell.net>
Mon, 1 Jun 2026 09:21:35 +0000 (19:21 +1000)
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) <noreply@anthropic.com>
12 files changed:
.gitattributes
old_versions/README.md [new file with mode: 0644]
old_versions/build_static.sh [new file with mode: 0755]
old_versions/rsync_2.6.0 [new file with mode: 0755]
old_versions/rsync_3.0.0 [new file with mode: 0755]
old_versions/rsync_3.1.0 [new file with mode: 0755]
old_versions/rsync_3.1.3 [new file with mode: 0755]
old_versions/rsync_3.2.0 [new file with mode: 0755]
old_versions/rsync_3.2.7 [new file with mode: 0755]
old_versions/rsync_3.3.0 [new file with mode: 0755]
old_versions/rsync_3.4.0 [new file with mode: 0755]
old_versions/rsync_3.4.1 [new file with mode: 0755]

index dd440b78c8f0f2af22fff94cf4b1c31f36a3d2cd..17c3856c44c9229c4f838d206e1d6a8a8f710dd9 100644 (file)
@@ -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 (file)
index 0000000..a5ce3a5
--- /dev/null
@@ -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_<version>`:
+
+| 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_<version>` 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 (executable)
index 0000000..7cabcc0
--- /dev/null
@@ -0,0 +1,128 @@
+#!/bin/bash
+# Build a static rsync binary from a historical git tag, for cross-version
+# behaviour testing. Produces ./rsync_<version> in this directory.
+#
+# Usage:   ./build_static.sh <version> [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 <version> [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 (executable)
index 0000000..face7a0
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 (executable)
index 0000000..c844826
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 (executable)
index 0000000..ebb8760
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 (executable)
index 0000000..3cd1ac4
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 (executable)
index 0000000..a28c405
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 (executable)
index 0000000..7ca1682
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 (executable)
index 0000000..1169447
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 (executable)
index 0000000..7432dbd
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 (executable)
index 0000000..4c66add
Binary files /dev/null and b/old_versions/rsync_3.4.1 differ