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) <noreply@anthropic.com>
This commit is contained in:
Andrew TridgellandClaude Opus 4.8 committed 2026-06-01 19:21:35 +10:00
1 parent ad14569561
commit e8d10dc2ad
12 files changed
+224

No files matched your search

+9
View 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
+87
View File
@@ -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.
+128
View File
@@ -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"
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.