Compare commits

..

1 Commits

Author SHA1 Message Date
Andrew Tridgell
f7ebb57c5a update maintainer address
use rsync.project@gmail.com
2025-01-15 08:20:12 +11:00
100 changed files with 1945 additions and 6179 deletions

View File

@@ -1,77 +0,0 @@
name: Test rsync on AlmaLinux 8
# Older-LTS coverage on the Fedora/RHEL family to help with backporting
# security fixes. AlmaLinux 8 is the RHEL 8 rebuild and is the oldest
# active LTS in this family (RHEL 8 full support runs to 2029).
# GitHub Actions has no native runner for this family, so the job runs
# inside an almalinux:8 container hosted on ubuntu-latest.
on:
push:
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/almalinux-8-build.yml'
pull_request:
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/almalinux-8-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: ubuntu-latest
container:
image: almalinux:8
name: Test rsync on AlmaLinux 8
steps:
- name: install git
# actions/checkout needs git in the container before the checkout step.
run: dnf -y install git
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
# PowerTools is needed for libzstd-devel etc; xxhash and lz4 dev
# headers live in EPEL on RHEL 8. The default python3 on RHEL 8
# is 3.6, which is too old for runtests.py (uses capture_output=
# / text= introduced in 3.7), so install python39 and point
# /usr/bin/python3 at it.
run: |
dnf -y install epel-release
dnf config-manager --set-enabled powertools
dnf -y install gcc gcc-c++ make autoconf automake m4 \
python39 python39-pip diffutils \
openssl openssl-devel \
attr libattr-devel acl libacl-devel \
zstd libzstd-devel \
lz4 lz4-devel \
xxhash xxhash-devel
alternatives --set python3 /usr/bin/python3.9
pip3 install commonmark
- name: configure
run: ./configure --with-rrsync
- name: make
run: make
- name: info
run: ./rsync --version
- name: check
# In the container we already run as root, so no sudo. The
# crtimes-not-supported skip matches the other Linux jobs.
run: RSYNC_EXPECT_SKIPPED=crtimes make check
- name: ssl file list
run: ./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: almalinux-8-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -2,12 +2,12 @@ name: Test rsync on Cygwin
on:
push:
branches: [ master, '*-stable' ]
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/cygwin-build.yml'
pull_request:
branches: [ master, '*-stable' ]
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/cygwin-build.yml'
@@ -39,7 +39,7 @@ jobs:
- name: info
run: bash -c '/usr/local/bin/rsync --version'
- name: check
run: bash -c 'RSYNC_EXPECT_SKIPPED=acls-default,acls,bare-do-open-symlink-race,chdir-symlink-race,chmod-symlink-race,chown,daemon-chroot-acl,devices,dir-sgid,open-noatime,protected-regular,sender-flist-symlink-leak,simd-checksum,symlink-dirlink-basis make check'
run: bash -c 'RSYNC_EXPECT_SKIPPED=acls-default,acls,chown,devices,dir-sgid,protected-regular make check'
- name: ssl file list
run: bash -c 'PATH="/usr/local/bin:$PATH" rsync-ssl --no-motd download.samba.org::rsyncftp/ || true'
- name: save artifact

View File

@@ -2,12 +2,12 @@ name: Test rsync on FreeBSD
on:
push:
branches: [ master, '*-stable' ]
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/freebsd-build.yml'
pull_request:
branches: [ master, '*-stable' ]
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/freebsd-build.yml'
@@ -34,7 +34,6 @@ jobs:
./configure --with-rrsync -disable-zstd --disable-md2man --disable-xxhash --disable-lz4
make
./rsync --version
make check
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4

View File

@@ -2,12 +2,12 @@ name: Test rsync on macOS
on:
push:
branches: [ master, '*-stable' ]
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/macos-build.yml'
pull_request:
branches: [ master, '*-stable' ]
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/macos-build.yml'
@@ -25,15 +25,10 @@ jobs:
- name: prep
run: |
brew install automake openssl xxhash zstd lz4
pip3 install --user --break-system-packages commonmark
echo "$(brew --prefix)/bin" >>$GITHUB_PATH
sudo pip3 install commonmark
echo "/usr/local/bin" >>$GITHUB_PATH
- name: configure
run: |
BREW_PREFIX=$(brew --prefix)
OPENSSL_PREFIX=$(brew --prefix openssl)
CPPFLAGS="-I${BREW_PREFIX}/include -I${OPENSSL_PREFIX}/include" \
LDFLAGS="-L${BREW_PREFIX}/lib -L${OPENSSL_PREFIX}/lib" \
./configure --with-rrsync
run: CPPFLAGS=-I/usr/local/opt/openssl/include/ LDFLAGS=-L/usr/local/opt/openssl/lib/ ./configure --with-rrsync
- name: make
run: make
- name: install
@@ -41,11 +36,11 @@ jobs:
- name: info
run: rsync --version
- name: check
run: sudo RSYNC_EXPECT_SKIPPED=acls-default,chmod-temp-dir,chown-fake,daemon-chroot-acl,devices-fake,dir-sgid,open-noatime,protected-regular,simd-checksum,xattrs-hlink,xattrs make check
run: sudo RSYNC_EXPECT_SKIPPED=acls-default,chmod-temp-dir,chown-fake,devices-fake,dir-sgid,protected-regular,xattrs-hlink,xattrs make check
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: macos-bin
path: |

View File

@@ -1,51 +0,0 @@
name: Test rsync on NetBSD
on:
push:
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/netbsd-build.yml'
pull_request:
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/netbsd-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: ubuntu-latest
name: Test rsync on NetBSD
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Test in NetBSD VM
id: test
uses: vmactions/netbsd-vm@v1
with:
usesh: true
prepare: |
PATH=/usr/sbin:$PATH pkg_add autoconf automake python312
ln -sf /usr/pkg/bin/python3.12 /usr/pkg/bin/python3
run: |
uname -a
./configure --with-rrsync --disable-zstd --disable-md2man --disable-xxhash --disable-lz4
make
./rsync --version
make check
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: netbsd-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -1,52 +0,0 @@
name: Test rsync on OpenBSD
on:
push:
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/openbsd-build.yml'
pull_request:
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/openbsd-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: ubuntu-latest
name: Test rsync on OpenBSD
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Test in OpenBSD VM
id: test
uses: vmactions/openbsd-vm@v1
with:
usesh: true
prepare: |
pkg_add -I bash autoconf%2.71 automake%1.16
run: |
uname -a
export AUTOCONF_VERSION=2.71
export AUTOMAKE_VERSION=1.16
./configure --with-rrsync --disable-zstd --disable-md2man --disable-xxhash --disable-lz4
make
./rsync --version
make check
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: openbsd-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -2,12 +2,12 @@ name: Test rsync on Solaris
on:
push:
branches: [ master, '*-stable' ]
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/solaris-build.yml'
pull_request:
branches: [ master, '*-stable' ]
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/solaris-build.yml'
@@ -34,7 +34,6 @@ jobs:
./configure --with-rrsync -disable-zstd --disable-md2man --disable-xxhash --disable-lz4
make
./rsync --version
make check
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4

View File

@@ -1,76 +0,0 @@
name: Stable testsuite
# Regression coverage for the 3.4.x stable branch. The stable branch keeps the
# old shell test suite, so the modern Python suite is maintained separately on
# the v34-stable-testsuite branch. This job builds rsync from this branch and
# runs that suite against the freshly-built binary (the same "testsuite from one
# branch, code from another" split fleettest uses). Helper programs and
# config.h come from this branch's build (tooldir); the test scripts come from
# the stable-testsuite checkout (--srcdir).
on:
push:
branches: [ v3.4, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/stable-testsuite.yml'
pull_request:
branches: [ v3.4, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/stable-testsuite.yml'
workflow_dispatch:
schedule:
- cron: '23 6 * * *'
jobs:
stable-testsuite:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, ubuntu-22.04 ]
name: Stable testsuite on ${{ matrix.os }}
steps:
- name: checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: checkout stable testsuite
uses: actions/checkout@v4
with:
repository: RsyncProject/rsync
ref: v34-stable-testsuite
path: stable-testsuite
fetch-depth: 1
- name: prep
run: |
sudo apt-get update
sudo apt-get install -y gcc g++ gawk autoconf automake \
acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev \
python3-cmarkgfm openssl
echo "/usr/local/bin" >>$GITHUB_PATH
- name: configure
run: ./configure --with-rrsync
- name: make check-progs
run: make check-progs
- name: info
run: ./rsync --version
# Pipe transport (the secure stdio default). The TCP-only daemon tests
# (daemon-access-ip, proxy-response-line-too-long) skip here and are run in
# the --use-tcp pass below; crtimes/daemon-chroot-acl/recv-discard-nullderef
# skip on the runner's filesystem / under root.
- name: run stable testsuite (pipe)
run: |
sudo RSYNC_EXPECT_SKIPPED=crtimes,daemon-access-ip,daemon-chroot-acl,proxy-response-line-too-long,recv-discard-nullderef \
./stable-testsuite/runtests.py \
--srcdir="$GITHUB_WORKSPACE/stable-testsuite" \
--rsync-bin="$GITHUB_WORKSPACE/rsync" \
-j16
# TCP transport over loopback, exercising the daemon paths the pipe run skips.
- name: run stable testsuite (tcp)
run: |
sudo ./stable-testsuite/runtests.py \
--srcdir="$GITHUB_WORKSPACE/stable-testsuite" \
--rsync-bin="$GITHUB_WORKSPACE/rsync" \
--use-tcp -j8

View File

@@ -1,60 +0,0 @@
name: Test rsync on Ubuntu 22.04
# Older-LTS coverage to help with backporting security fixes. ubuntu-22.04
# is currently the oldest GitHub Actions runner image (20.04 was retired
# in April 2025).
on:
push:
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-22.04-build.yml'
pull_request:
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-22.04-build.yml'
schedule:
- cron: '42 8 * * *'
jobs:
test:
runs-on: ubuntu-22.04
name: Test rsync on Ubuntu 22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
sudo apt-get install acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev python3-cmarkgfm openssl
echo "/usr/local/bin" >>$GITHUB_PATH
- name: configure
run: ./configure --with-rrsync
- name: make
run: make
- name: install
run: sudo make install
- name: info
run: rsync --version
- name: check
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check
- name: check30
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check30
- name: check29
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check29
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
name: ubuntu-22.04-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync

View File

@@ -2,12 +2,12 @@ name: Test rsync on Ubuntu
on:
push:
branches: [ master, '*-stable' ]
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-build.yml'
pull_request:
branches: [ master, '*-stable' ]
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-build.yml'
@@ -16,7 +16,7 @@ on:
jobs:
test:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
name: Test rsync on Ubuntu
steps:
- uses: actions/checkout@v4

1
.gitignore vendored
View File

@@ -58,4 +58,3 @@ aclocal.m4
/auto-build-save
.deps
/*.exe
*.dSYM/

View File

@@ -44,26 +44,25 @@ LIBOBJ=lib/wildmatch.o lib/compat.o lib/snprintf.o lib/mdfour.o lib/md5.o \
zlib_OBJS=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o
OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \
util1.o util2.o main.o checksum.o match.o syscall.o android.o log.o backup.o delete.o
util1.o util2.o main.o checksum.o match.o syscall.o log.o backup.o delete.o
OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o hashtable.o \
usage.o fileio.o batch.o clientname.o chmod.o acls.o xattrs.o
OBJS3=progress.o pipe.o @MD5_ASM@ @ROLL_SIMD@ @ROLL_ASM@
DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o
popt_OBJS= popt/popt.o popt/poptconfig.o \
popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
popt/popthelp.o popt/poptparse.o popt/poptint.o
OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@
TLS_OBJ = tls.o syscall.o android.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
TLS_OBJ = tls.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
# Programs we must have to run the test cases
CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
testrun$(EXEEXT) trimslash$(EXEEXT) t_unsafe$(EXEEXT) t_chmod_secure$(EXEEXT) \
t_secure_relpath$(EXEEXT) wildtest$(EXEEXT) simdtest$(EXEEXT)
testrun$(EXEEXT) trimslash$(EXEEXT) t_unsafe$(EXEEXT) wildtest$(EXEEXT)
CHECK_SYMLINKS = testsuite/chown-fake.test testsuite/devices-fake.test testsuite/xattrs-hlink.test
# Objects for CHECK_PROGS to clean
CHECK_OBJS=tls.o testrun.o getgroups.o getfsdev.o t_stub.o t_unsafe.o t_chmod_secure.o t_secure_relpath.o trimslash.o wildtest.o
CHECK_OBJS=tls.o testrun.o getgroups.o getfsdev.o t_stub.o t_unsafe.o trimslash.o wildtest.o
# note that the -I. is needed to handle config.h when using VPATH
.c.o:
@@ -83,19 +82,12 @@ install: all
$(INSTALLCMD) -m 755 $(srcdir)/rsync-ssl $(DESTDIR)$(bindir)
-$(MKDIR_P) $(DESTDIR)$(mandir)/man1
-$(MKDIR_P) $(DESTDIR)$(mandir)/man5
for fn in rsync.1 rsync-ssl.1; do \
if test -f $$fn; then $(INSTALLMAN) -m 644 $$fn $(DESTDIR)$(mandir)/man1; \
elif test -f $(srcdir)/$$fn; then $(INSTALLMAN) -m 644 $(srcdir)/$$fn $(DESTDIR)$(mandir)/man1; fi; \
done
for fn in rsyncd.conf.5; do \
if test -f $$fn; then $(INSTALLMAN) -m 644 $$fn $(DESTDIR)$(mandir)/man5; \
elif test -f $(srcdir)/$$fn; then $(INSTALLMAN) -m 644 $(srcdir)/$$fn $(DESTDIR)$(mandir)/man5; fi; \
done
if test -f rsync.1; then $(INSTALLMAN) -m 644 rsync.1 $(DESTDIR)$(mandir)/man1; fi
if test -f rsync-ssl.1; then $(INSTALLMAN) -m 644 rsync-ssl.1 $(DESTDIR)$(mandir)/man1; fi
if test -f rsyncd.conf.5; then $(INSTALLMAN) -m 644 rsyncd.conf.5 $(DESTDIR)$(mandir)/man5; fi
if test "$(with_rrsync)" = yes; then \
$(INSTALLCMD) -m 755 rrsync $(DESTDIR)$(bindir); \
fn=rrsync.1; \
if test -f $$fn; then $(INSTALLMAN) -m 644 $$fn $(DESTDIR)$(mandir)/man1; \
elif test -f $(srcdir)/$$fn; then $(INSTALLMAN) -m 644 $(srcdir)/$$fn $(DESTDIR)$(mandir)/man1; fi; \
if test -f rrsync.1; then $(INSTALLMAN) -m 644 rrsync.1 $(DESTDIR)$(mandir)/man1; fi; \
fi
install-ssl-daemon: stunnel-rsyncd.conf
@@ -178,22 +170,14 @@ getgroups$(EXEEXT): getgroups.o
getfsdev$(EXEEXT): getfsdev.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
TRIMSLASH_OBJ = trimslash.o syscall.o android.o util2.o t_stub.o lib/compat.o lib/snprintf.o
TRIMSLASH_OBJ = trimslash.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o
trimslash$(EXEEXT): $(TRIMSLASH_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS)
T_UNSAFE_OBJ = t_unsafe.o syscall.o android.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o
T_UNSAFE_OBJ = t_unsafe.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o
t_unsafe$(EXEEXT): $(T_UNSAFE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_UNSAFE_OBJ) $(LIBS)
T_CHMOD_SECURE_OBJ = t_chmod_secure.o syscall.o android.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
t_chmod_secure$(EXEEXT): $(T_CHMOD_SECURE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_CHMOD_SECURE_OBJ) $(LIBS)
T_SECURE_RELPATH_OBJ = t_secure_relpath.o syscall.o android.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
t_secure_relpath$(EXEEXT): $(T_SECURE_RELPATH_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_SECURE_RELPATH_OBJ) $(LIBS)
.PHONY: conf
conf: configure.sh config.h.in
@@ -326,36 +310,22 @@ test: check
# catch Bash-isms earlier even if we're running on GNU. Of course, we
# might lose in the future where POSIX diverges from old sh.
# Build the test-helper programs (CHECK_PROGS) without running the suite, so
# an external harness (e.g. fleettest.py) can invoke runtests.py with its own
# options.
.PHONY: check-progs
check-progs: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
.PHONY: check
check: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
$(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT)
rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh
.PHONY: check29
check29: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
$(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT) --protocol=29
rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh --protocol=29
.PHONY: check30
check30: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
$(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT) --protocol=30
rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh --protocol=30
wildtest.o: wildtest.c t_stub.o lib/wildmatch.c rsync.h config.h
wildtest$(EXEEXT): wildtest.o lib/compat.o lib/snprintf.o @BUILD_POPT@
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ wildtest.o lib/compat.o lib/snprintf.o @BUILD_POPT@ $(LIBS)
simdtest$(EXEEXT): simd-checksum-x86_64.cpp $(HEADERS)
@if test x"@ROLL_SIMD@" != x; then \
$(CXX) -I. $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) -DTEST_SIMD_CHECKSUM1 \
-o $@ $(srcdir)/simd-checksum-x86_64.cpp @ROLL_ASM@ $(LIBS); \
else \
touch $@; \
fi
testsuite/chown-fake.test:
ln -s chown.test $(srcdir)/testsuite/chown-fake.test
@@ -371,7 +341,7 @@ testsuite/xattrs-hlink.test:
.PHONY: installcheck
installcheck: $(CHECK_PROGS) $(CHECK_SYMLINKS)
$(srcdir)/runtests.py --rsync-bin="$(bindir)/rsync$(EXEEXT)" --srcdir="$(srcdir)" --tooldir=`pwd`
POSIXLY_CORRECT=1 TOOLDIR=`pwd` rsync_bin="$(bindir)/rsync$(EXEEXT)" srcdir="$(srcdir)" $(srcdir)/runtests.sh
# TODO: Add 'dist' target; need to know which files will be included

451
NEWS.md
View File

@@ -1,447 +1,3 @@
# NEWS for rsync 3.4.4 (8 Jun 2026)
## Changes in this version:
This is a conservative point release that backports regression fixes
on top of 3.4.3. No new features are included.
### BUG FIXES:
- Honour a relative alt-basis directory (e.g. `--link-dest=../sibling`,
`--copy-dest`, `--compare-dest`) on a daemon receiver running with
`use chroot = no`. Such a path is re-anchored at the module root but
was then rejected by the receiver's secure open; it now works where
kernel-enforced confinement is available. See the PORTABILITY note
below for the platform limitation. Fixes #915.
- sender: open a module-root-absolute path for a `path = /` module so a
daemon serving the filesystem root can satisfy absolute request
paths again. Fixes #897.
- flist: accept the missing-args mode-0 entry in recv_file_entry.
Fixes #910.
- receiver: fix a false "failed verification -- update discarded" when
resuming a delta transfer with an absolute `--partial-dir`.
- receiver: fix a NULL dereference on the delta discard path.
- generator: cap the block s2length at the negotiated checksum length.
- main: fix `--mkpath` with `--dry-run` for a file-to-file copy.
Fixes #880.
- daemon: un-backslash escaped option args. Fixes #829.
- token: drain the matched-block insert deflate. Fixes #951.
- Fix the "update skips a file of a different type" case and the
daemon upload delete stats.
- alloc: revert "zero all new memory from allocations". Fixes #959.
- Always clear the stat buffer and validate nanoseconds before use.
### PORTABILITY / BUILD:
- The relative alt-basis fix for daemon receivers (#915) relies on
kernel "stay below dirfd" path resolution -- `openat2(RESOLVE_BENEATH)`
on Linux 5.6+, or `openat()` with `O_RESOLVE_BENEATH` on FreeBSD 13+
and macOS 15+. On platforms that lack it (Solaris, OpenBSD, NetBSD,
Cygwin and older Linux) `secure_relative_open()` deliberately rejects
any path with a `..` component, so relative alt-basis directories
remain unavailable there -- function traded for safety, matching the
trade-off already documented for the #715 fix. Absolute alt-basis
paths are unaffected on every platform.
- openat2 is now autodetected at configure time (HAVE_OPENAT2): the
`openat2(RESOLVE_BENEATH)` resolver is compiled in only when both
`<linux/openat2.h>` and the `SYS_openat2` syscall number are present,
fixing the build on older kernels/headers. Fixes #924, #905, #900,
#904.
- Fall back to do_mknod() when mknodat() / mkfifoat() are unavailable.
Fixes #896.
- Install generated manpages correctly in an out-of-tree build.
### DEVELOPER RELATED:
- Added a CI workflow that builds this stable branch and runs the
`v34-stable-testsuite` regression suite against the built binary,
giving regression coverage without importing the full master test
suite into the stable branch.
- Added a check-progs target for fleettest and extended the build
workflows to run on `*-stable` release branches.
### CREDITS:
Thanks to everyone who helped with this release:
- Code contributions from Zen Dodd (steadytao), Mike-Goutokuji,
pterror, and Stiliyan Tonev (Bark).
- Zen Dodd (steadytao) also reviewed the 3.4.4 backport set (PR #980).
- Bug reports from @mmayer (#924), @fda77 (#905), @darkshram (#900),
@ketas (#904), @pkzc (#880), @brabalan (#951), @elcamlost (#829),
@debohman (#896), @guilherme-puida (#959), @fufu65 (#915),
@JetAppsClark (#928), @moonlitbugs (#897), @mgkeeley (#910), and
@sylvain-ilm (#724, #725).
# NEWS for rsync 3.4.3 (20 May 2026)
## Changes in this version:
### SECURITY FIXES:
Six CVEs are fixed in this release. All six are assigned by
VulnCheck as CNA. Affected versions are 3.4.2 and earlier in every
case. Three of the six (CVE-2026-29518, CVE-2026-43617,
CVE-2026-43619) require non-default daemon configuration to reach:
the first and third need `use chroot = no` for a module, the second
needs `daemon chroot = ...` set in rsyncd.conf. Two (CVE-2026-43618,
CVE-2026-43620) are reachable from a normal pull or a normal
authenticated daemon connection. The sixth (CVE-2026-45232) is
reachable only when `RSYNC_PROXY` is set and the proxy (or a MITM)
returns a pathological response. Many thanks to the external
researchers who reported these issues.
- CVE-2026-29518 (CVSS v4.0 7.3, HIGH): TOCTOU symlink race condition
allowing local privilege escalation in daemon mode without chroot.
An rsync daemon configured with "use chroot = no" was exposed to a
time-of-check / time-of-use race on parent path components: a local
attacker with write access to a module could replace a parent
directory component with a symlink between the receiver's check and
its open(), redirecting reads (basis-file disclosure) and writes
(file overwrite) outside the module. Default "use chroot = yes" is
not exposed. `secure_relative_open()` (added in 3.4.0 for
CVE-2024-12086) was previously unused in the daemon-no-chroot
case; the fix enables it there and reroutes the sender's
read-path opens through it. Reported by Nullx3D (Batuhan Sancak),
Damien Neil and Michael Stapelberg.
- CVE-2026-43617 (CVSS v3.1 4.8, MEDIUM): Hostname/ACL bypass on an
rsync daemon configured with `daemon chroot = /X` in rsyncd.conf
when the chroot tree lacks DNS resolution support. The
reverse-DNS lookup of the connecting client was performed *after*
the daemon chroot had been entered; if /X did not contain the
libc resolver fixtures (`/etc/resolv.conf`, `/etc/nsswitch.conf`,
`/etc/hosts`, NSS service modules) the lookup failed and the
connecting hostname was set to "UNKNOWN", causing hostname-based
deny rules to silently fail open. IP-based ACLs are unaffected.
The per-module `use chroot` setting is unrelated to this issue.
The fix performs the lookup before entering the daemon chroot.
Reported by MegaManSec.
- CVE-2026-43618 (CVSS v3.1 8.1, HIGH): Integer overflow in the
compressed-token decoder enabling remote memory disclosure to an
authenticated daemon peer. The receiver accumulated a 32-bit
signed counter without overflow checking; a malicious sender could
trigger an overflow that, with careful manipulation, leaked process
memory contents to the attacker -- environment variables,
passwords, heap and library pointers -- significantly weakening
ASLR. The fix bounds the counter and adds wire-input validation in
several adjacent places (defence-in-depth). Workaround for older
releases: `refuse options = compress` in rsyncd.conf. Reported by
Omar Elsayed.
- CVE-2026-43619 (CVSS v3.1 6.3, MEDIUM): Symlink races on path-based
system calls in "use chroot = no" daemon mode (generalisation of
CVE-2026-29518). Earlier fixes for symlink races on the receiver's
open() call missed the same race class on every other path-based
system call: chmod, lchown, utimes, rename, unlink, mkdir, symlink,
mknod, link, rmdir and lstat. The fix routes each affected
path-based syscall through a parent dirfd opened under
RESOLVE_BENEATH-equivalent kernel-enforced confinement (openat2 on
Linux 5.6+, O_RESOLVE_BENEATH on FreeBSD 13+ and macOS 15+,
per-component O_NOFOLLOW walk elsewhere). Default "use chroot =
yes" is not exposed. Reported by Andrew Tridgell as a follow-on
audit of CVE-2026-29518.
- CVE-2026-43620 (CVSS v3.1 6.5, MEDIUM): Out-of-bounds read in the
receiver's recv_files() enabling remote denial-of-service of any
client pulling from a malicious server (incomplete fix of commit
797e17f). The earlier parent_ndx<0 guard added to send_files() was
not applied to the visually-identical block in recv_files(). A
malicious rsync server can drive any connecting client into a
deterministic SIGSEGV by setting CF_INC_RECURSE in the
compatibility flags and sending a crafted file list and transfer
record. inc_recurse is the protocol-30+ default, so no special
options are required on the victim. Workaround for older
releases: `--no-inc-recursive` on the client. Reported by Pratham
Gupta.
- CVE-2026-45232 (CVSS v3.1 3.1, LOW): Off-by-one out-of-bounds stack
write in the rsync client's HTTP CONNECT proxy handler
(`establish_proxy_connection()` in `socket.c`). After issuing the
CONNECT request, rsync read the proxy's first response line one
byte at a time into a 1024-byte stack buffer with the bound
`cp < &buffer[sizeof buffer - 1]`. If the proxy (or a MITM in
front of it) returned 1023+ bytes on that first line without a
newline terminator, `cp` exited the loop pointing at a buffer slot
the loop never wrote, leaving `*cp` holding stale stack data from
the earlier `snprintf()` of the outgoing CONNECT request. The
post-loop logic then wrote a single `\0` one byte past the end of
the buffer on the stack. Reach is client-side only, and only when
`RSYNC_PROXY` is set so rsync tunnels an `rsync://` connection
through an HTTP CONNECT proxy. The written byte is always `\0`
and the offset is fixed by the buffer size, not attacker-chosen,
so this is not an arbitrary-write primitive: practical impact is
corruption of one adjacent stack byte and possible later
misbehaviour or crash. The fix detects the "buffer filled without
finding `\n`" case explicitly by position and refuses the response
with "proxy response line too long". Reported by Aisle Research
via Michal Ruprich (rsync-3.4.1-2.el10 QE).
In addition to the six CVE fixes, this release adds defence-in-depth
hardening on several adjacent paths: bounded wire-supplied counts and
lengths in flist/io/acls/xattrs, a guard against length underflow in
cumulative `snprintf()` callers, a parent block-index bounds check on
the receiver, a NULL check in `read_delay_line()`, a lower ceiling on
`MAX_WIRE_DEL_STAT` to avoid signed-int overflow in the
`read_del_stats()` accumulator, rejection of hyphen-prefixed
remote-shell hostnames (defence-in-depth against argv-injection in
tooling that forwards untrusted input into the hostspec position;
reported by Aisle Research via Michal Ruprich), and a NULL-check on
`localtime_r()` in `timestring()` to keep a malicious server from
crashing the client by advertising a file with an out-of-range
modtime.
### BUG FIXES:
- Fixed a regression introduced by the 3.4.0 secure_relative_open()
CVE fix where legitimate directory symlinks on the receiver side
(e.g. when using `-K` / `--copy-dirlinks`) caused "failed
verification -- update discarded" errors on delta transfers. The
old code rejected every symlink in the path with a per-component
`O_NOFOLLOW` walk; the receiver now uses kernel-enforced "stay
below dirfd" path resolution where available. Fixes #715.
### PORTABILITY / BUILD:
- secure_relative_open() now uses `openat2(RESOLVE_BENEATH |
RESOLVE_NO_MAGICLINKS)` on Linux 5.6+, and `openat()` with
`O_RESOLVE_BENEATH` on FreeBSD 13+ and macOS 15+ (Sequoia) /
iOS 18+. The kernel rejects ".." escapes, absolute symlinks, and
symlinks whose target lies outside the starting directory, while
still following symlinks that resolve within it -- the same
trade-off that fixes the issue #715 regression without weakening
the original CVE protection. Other platforms (Solaris, OpenBSD,
NetBSD, Cygwin) retain the previous per-component `O_NOFOLLOW`
walk; on those platforms the issue #715 regression remains
visible.
- testsuite/xattrs: ignore `SUNWattr_*` in the Solaris `xls`
helper.
### DEVELOPER RELATED:
- Added testsuite/symlink-dirlink-basis.test (taken from PR #864
by Samuel Henrique) covering the issue #715 regression and
several edge cases (`--backup`, `--inplace`, `--partial-dir`
with protocol < 29, top-level files). The test skips on
platforms without a RESOLVE_BENEATH equivalent.
- Added regression tests for the new security fixes:
`chmod-symlink-race.test`, `chdir-symlink-race.test`,
`bare-do-open-symlink-race.test`, `alt-dest-symlink-race.test`,
`copy-dest-source-symlink.test`, `sender-flist-symlink-leak.test`,
`secure-relpath-validation.test`, `daemon-chroot-acl.test` and
`daemon-refuse-compress.test`. The symlink-race tests skip on
Cygwin, Solaris, OpenBSD and NetBSD (no RESOLVE_BENEATH
equivalent on those platforms).
- runtests.py now errors early with a clear message when any of
the test helper programs (`tls`, `trimslash`, `t_unsafe`,
`t_chmod_secure`, `t_secure_relpath`, `wildtest`, `getgroups`,
`getfsdev`) are missing, instead of letting many tests fail with
confusing "not found" errors.
- Added OpenBSD and NetBSD CI jobs that run `make check` on those
platforms.
- Added Ubuntu 22.04 and AlmaLinux 8 CI workflows so future
backports to the two mainstream LTS families build and test on
the same CI surface as trunk.
- testsuite/protected-regular.test now runs unprivileged via
`unshare` with user-namespace UID mapping, falling back to skip
if `unshare`/`uidmap` is not available; previously it required
real root.
- Added `symlink-dirlink-basis` to the Cygwin CI's expected-skipped
list.
- Removed the old release system (replaced by the new release
script in 3.4.2).
------------------------------------------------------------------------------
# NEWS for rsync 3.4.2 (28 Apr 2026)
## Changes in this version:
### SECURITY RELATED:
Several security-relevant defects were reported and fixed since 3.4.1.
None were assigned a CVE — rsync's fork-per-connection design scopes
the impact of each of these to the attacker's own connection, which is
equivalent to the client closing the socket itself — but they are
fixed here as a matter of hygiene and to reduce the chances of a
future exploitable combination. Many thanks to the external
researchers who reported these issues.
- Fixed a signed integer overflow in the PROXY protocol v2 header
parser: a negative `len` field could bypass the size check and cause
a stack buffer overflow in `read_buf()`. Reported by John Walker of
ZeroPath.
- Fixed an invalid access to the files array. Reported by Calum
Hutton of Rapid7.
- Reject negative token values in the compressed-stream token
decoder; a negative value could cause callers to misinterpret a
missing data pointer as literal data. Reported by Will Sergeant.
- Fixed the element count passed to the xattr `qsort()` (see
https://www.openwall.com/lists/oss-security/2026/04/16/2).
- Fixed a buffer underflow in `clean_fname()`, and added a regression
test.
- Fixed an uninitialized `mul_one` in the AVX2 get_checksum1 path
(undefined behaviour), and added a SIMD-checksum self-test that
cross-checks SSE2, SSSE3 and AVX2 against the C reference on both
aligned and unaligned buffers.
- Fixed an uninitialized `buf1` on the first call to
`get_checksum2()` in the MD4 path (fixes #673).
- Zero all new memory from internal allocations: `my_alloc()` now uses
`calloc`, and `expand_item_list()` zeros the expanded portion after
`realloc`. This gives more predictable behaviour if stale or
uninitialised memory is ever accidentally read.
### BUG FIXES:
- Call `tzset()` before chroot so that log timestamps continue to
reflect the configured local timezone after the daemon chroots
(glibc needs `/etc/localtime`, which is unreachable post-chroot).
- Use the correct time when writing to the log file.
- Do not clear `DISPLAY` unconditionally.
- Fixed a Y2038 bug in `syscall.c` by replacing the `Int32x32To64`
macro (which truncates its arguments to 32 bits) with a plain
64-bit multiplication.
- Fixed ACL ID mapping for non-root users (closes #618).
- Fixed handling of objects with many xattrs on FreeBSD.
- Fixed `--open-noatime` not taking effect when opening regular
files: `O_NOATIME` is now also passed to `do_open_nofollow()`, which
has been used for regular files since the CVE fix "fixed symlink
race condition in sender".
- Ignore "directory has vanished" errors.
- Fixed the removal of multiple leading slashes.
- Added the missing `--dirs` long option.
- Fixed a segfault if `poptGetContext()` returns NULL (e.g. under
OOM) by not passing NULL to `poptReadDefaultConfig()`. Reported by
Ronnie Sahlberg; found with `malloc-fail-tester`.
- Fixed a build error on ia64 NonStop (which treats missing
prototypes as an error, not a warning).
- Fixed a flaky hardlinks test (fixes #735).
### ENHANCEMENTS:
- Added multi-threaded `zstd` compression, gated by a new
`--compress-threads=N` option, with validation and man-page
coverage.
- Documented the `temp dir` parameter in the rsyncd.conf man page
(fixes #820).
- Improved rendering of interior dashes in long-option names in
`md-convert` (perhaps fixes #686).
### PORTABILITY / BUILD:
- Fixed glibc 2.43 const-preserving overloads of `strtok()`,
`strchr()` etc. by declaring the affected locals with the right
constness. Contributed by Holger Hoffstätte.
- Converted the bundled zlib 1.2.8 from K&R-style function
definitions to ANSI prototypes, so it builds with clang 16+.
- Avoid using `bool` as an identifier; it is a keyword in C23.
- `configure.ac`: check for xattr functions in libc first and only
fall back to `-lattr`, avoiding spurious overlinking when `-lattr`
happens to be installed. Contributed by Eli Schwartz.
- Made the build reproducible by honouring `SOURCE_DATE_EPOCH` for
the manpage date.
- Removed obsolete `popt/findme.c` and `popt/findme.h` that upstream
popt 1.14 folded into `popt.c` (fixes #710). Contributed by Alan
Coopersmith.
### INTERNAL:
- Made many module-global variables `const` so they can live in
`.rodata` and enable additional compiler optimization.
### DEVELOPER RELATED:
- Replaced `runtests.sh` with `runtests.py`, a Python test runner
that supports `--valgrind` (with per-process log files so valgrind
output no longer interferes with output comparisons) and
`-j/--parallel` execution for roughly a 7× speed-up on typical
hardware.
- Added a SIMD checksum self-test and a `clean-fname-underflow`
regression test.
- Various CI fixes for macOS and Cygwin (including adding
`simd-checksum` to the expected-skipped lists on platforms without
SIMD), and tests now run on `ubuntu-latest`.
- removed support for the unmaintained rsync-patches archive
------------------------------------------------------------------------------
# NEWS for rsync 3.4.1 (16 Jan 2025)
Release 3.4.1 is a fix for regressions introduced in 3.4.0
## Changes in this version:
### BUG FIXES:
- fixed handling of -H flag with conflict in internal flag values
- fixed a user after free in logging of failed rename
- fixed build on systems without openat()
- removed dependency on alloca() in bundled popt
### DEVELOPER RELATED:
- fix to permissions handling in the developer release script
------------------------------------------------------------------------------
# NEWS for rsync 3.4.0 (15 Jan 2025)
Release 3.4.0 is a security release that fixes a number of important vulnerabilities.
@@ -496,7 +52,6 @@ to develop and test fixes.
- added FreeBSD and Solaris CI builds
------------------------------------------------------------------------------
# NEWS for rsync 3.3.0 (6 Apr 2024)
## Changes in this version:
@@ -5261,11 +4816,7 @@ to develop and test fixes.
| RELEASE DATE | VER. | DATE OF COMMIT\* | PROTOCOL |
|--------------|--------|------------------|-------------|
| 08 Jun 2026 | 3.4.4 | | 32 |
| 20 May 2026 | 3.4.3 | | 32 |
| 28 Apr 2026 | 3.4.2 | | 32 |
| 16 Jan 2025 | 3.4.1 | | 32 |
| 15 Jan 2025 | 3.4.0 | 15 Jan 2025 | 32 |
| 15 Jan 2025 | 3.4.0 | | 32 |
| 06 Apr 2024 | 3.3.0 | | 31 |
| 20 Oct 2022 | 3.2.7 | | 31 |
| 09 Sep 2022 | 3.2.6 | | 31 |

View File

@@ -99,7 +99,7 @@ static void make_mask(char *mask, int plen, int addrlen)
return;
}
static int match_address(const char *addr, char *tok)
static int match_address(const char *addr, const char *tok)
{
char *p;
struct addrinfo hints, *resa, *rest;

4
acls.c
View File

@@ -697,7 +697,7 @@ static uint32 recv_acl_access(int f, uchar *name_follows_ptr)
static uchar recv_ida_entries(int f, ida_entries *ent)
{
uchar computed_mask_bits = 0;
int i, count = read_varint_bounded(f, 0, MAX_WIRE_ACL_COUNT, "ACL count");
int i, count = read_varint(f);
ent->idas = count ? new_array(id_access, count) : NULL;
ent->count = count;
@@ -713,7 +713,7 @@ static uchar recv_ida_entries(int f, ida_entries *ent)
else
id = recv_group_name(f, id, NULL);
} else if (access & NAME_IS_USER) {
if (inc_recurse && !numeric_ids)
if (inc_recurse && am_root && !numeric_ids)
id = match_uid(id);
} else {
if (inc_recurse && (!am_root || !numeric_ids))

View File

@@ -1,82 +0,0 @@
/*
* Android-specific helpers.
*
* openat2() usability probe
* -------------------------
* openat2(2) is invoked directly via syscall() because the C library lacked a
* wrapper for it for years. Under a seccomp filter that uses
* SECCOMP_RET_TRAP -- as the Android application sandbox does -- a disallowed
* syscall raises SIGSYS and *kills the process* rather than failing with
* ENOSYS, so inspecting errno after the call is too late. We therefore probe
* openat2() once, behind a temporary SIGSYS handler, so a trapped syscall is
* caught and secure_relative_open_linux() can fall back to the portable
* per-component O_NOFOLLOW resolver instead of the whole process dying.
*
* This is only needed on Android, so the probe body is compiled only there.
* __ANDROID__ is defined by Bionic's headers and reflects the *target*, not
* the build host: it is set both for NDK cross-compiles (from a Linux/macOS
* host) and for native Termux builds, and is unset on every other platform.
* That makes it a reliable compile-time switch for cross builds -- there is
* nothing to detect in configure. Everywhere else openat2() is never
* seccomp-trapped to SIGSYS (a missing syscall simply returns ENOSYS), so
* openat2_usable() collapses to a constant 1 with no run-time cost.
*/
#include "rsync.h"
#if defined(__ANDROID__) && defined(HAVE_OPENAT2)
#include <setjmp.h>
#include <sys/syscall.h>
#include <linux/openat2.h>
static sigjmp_buf openat2_probe_env;
static void openat2_probe_handler(int signo)
{
(void)signo;
siglongjmp(openat2_probe_env, 1);
}
#endif
int openat2_usable(void)
{
#if defined(__ANDROID__) && defined(HAVE_OPENAT2)
static int cached = -1;
struct sigaction sa, old_sa;
if (cached >= 0)
return cached;
memset(&sa, 0, sizeof sa);
sa.sa_handler = openat2_probe_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGSYS, &sa, &old_sa) != 0)
return cached = 0;
if (sigsetjmp(openat2_probe_env, 1) != 0) {
/* SIGSYS delivered: openat2 is blocked by a seccomp filter. */
cached = 0;
} else {
struct open_how how;
int fd;
memset(&how, 0, sizeof how);
how.flags = O_RDONLY | O_DIRECTORY;
how.resolve = RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS;
fd = syscall(SYS_openat2, AT_FDCWD, ".", &how, sizeof how);
if (fd >= 0)
close(fd);
/* Usable only if the probe actually succeeded. Any failure --
* ENOSYS (kernel < 5.6), a seccomp SECCOMP_RET_ERRNO denial
* (EPERM/EACCES), or EINVAL (RESOLVE_BENEATH unsupported) --
* means we must fall back to the portable O_NOFOLLOW walk. */
cached = fd >= 0;
}
sigaction(SIGSYS, &old_sa, NULL);
return cached;
#else
return 1;
#endif
}

View File

@@ -39,7 +39,7 @@ static int validate_backup_dir(void)
{
STRUCT_STAT st;
if (do_lstat_at(backup_dir_buf, &st) < 0) {
if (do_lstat(backup_dir_buf, &st) < 0) {
if (errno == ENOENT)
return 0;
rsyserr(FERROR, errno, "backup lstat %s failed", backup_dir_buf);
@@ -98,7 +98,7 @@ static BOOL copy_valid_path(const char *fname)
for ( ; b; name = b + 1, b = strchr(name, '/')) {
*b = '\0';
while (do_mkdir_at(backup_dir_buf, ACCESSPERMS) < 0) {
while (do_mkdir(backup_dir_buf, ACCESSPERMS) < 0) {
if (errno == EEXIST) {
val = validate_backup_dir();
if (val > 0)
@@ -197,7 +197,7 @@ static inline int link_or_rename(const char *from, const char *to,
if (IS_SPECIAL(stp->st_mode) || IS_DEVICE(stp->st_mode))
return 0; /* Use copy code. */
#endif
if (do_link_at(from, to) == 0) {
if (do_link(from, to) == 0) {
if (DEBUG_GTE(BACKUP, 1))
rprintf(FINFO, "make_backup: HLINK %s successful.\n", from);
return 2;
@@ -207,7 +207,7 @@ static inline int link_or_rename(const char *from, const char *to,
return 0;
}
#endif
if (do_rename_at(from, to) == 0) {
if (do_rename(from, to) == 0) {
if (stp->st_nlink > 1 && !S_ISDIR(stp->st_mode)) {
/* If someone has hard-linked the file into the backup
* dir, rename() might return success but do nothing! */
@@ -246,7 +246,7 @@ int make_backup(const char *fname, BOOL prefer_rename)
goto success;
if (errno == EEXIST || errno == EISDIR) {
STRUCT_STAT bakst;
if (do_lstat_at(buf, &bakst) == 0) {
if (do_lstat(buf, &bakst) == 0) {
int flags = get_del_for_flag(bakst.st_mode) | DEL_FOR_BACKUP | DEL_RECURSE;
if (delete_item(buf, bakst.st_mode, flags) != 0)
return 0;
@@ -277,7 +277,7 @@ int make_backup(const char *fname, BOOL prefer_rename)
/* Check to see if this is a device file, or link */
if ((am_root && preserve_devices && IS_DEVICE(file->mode))
|| (preserve_specials && IS_SPECIAL(file->mode))) {
if (do_mknod_at(buf, file->mode, sx.st.st_rdev) < 0)
if (do_mknod(buf, file->mode, sx.st.st_rdev) < 0)
rsyserr(FERROR, errno, "mknod %s failed", full_fname(buf));
else if (DEBUG_GTE(BACKUP, 1))
rprintf(FINFO, "make_backup: DEVICE %s successful.\n", fname);
@@ -294,7 +294,7 @@ int make_backup(const char *fname, BOOL prefer_rename)
}
ret = 2;
} else {
if (do_symlink_at(sl, buf) < 0)
if (do_symlink(sl, buf) < 0)
rsyserr(FERROR, errno, "link %s -> \"%s\"", full_fname(buf), sl);
else if (DEBUG_GTE(BACKUP, 1))
rprintf(FINFO, "make_backup: SYMLINK %s successful.\n", fname);

View File

@@ -75,7 +75,7 @@ static int *flag_ptr[] = {
NULL
};
static const char *const flag_name[] = {
static char *flag_name[] = {
"--recurse (-r)",
"--owner (-o)",
"--group (-g)",

View File

@@ -176,7 +176,7 @@ void parse_checksum_choice(int final_call)
if (valid_checksums.negotiated_nni)
xfer_sum_nni = file_sum_nni = valid_checksums.negotiated_nni;
else {
const char *cp = checksum_choice ? strchr(checksum_choice, ',') : NULL;
char *cp = checksum_choice ? strchr(checksum_choice, ',') : NULL;
if (cp) {
xfer_sum_nni = parse_csum_name(checksum_choice, cp - checksum_choice);
file_sum_nni = parse_csum_name(cp+1, -1);
@@ -366,8 +366,9 @@ void get_checksum2(char *buf, int32 len, char *sum)
mdfour_begin(&m);
if (len > len1 || !buf1) {
free(buf1);
if (len > len1) {
if (buf1)
free(buf1);
buf1 = new_array(char, len+4);
len1 = len;
}

View File

@@ -198,7 +198,7 @@ NORETURN void _exit_cleanup(int code, const char *file, int line)
switch_step++;
if (cleanup_fname)
do_unlink_at(cleanup_fname);
do_unlink(cleanup_fname);
if (exit_code)
kill_all(SIGUSR1);
if (cleanup_pid && cleanup_pid == getpid()) {

View File

@@ -167,7 +167,7 @@ int read_proxy_protocol_header(int fd)
char sig[PROXY_V2_SIG_SIZE];
char ver_cmd;
char fam;
unsigned char len[2];
char len[2];
union {
struct {
char src_addr[4];

View File

@@ -30,7 +30,6 @@ extern int list_only;
extern int am_sender;
extern int am_server;
extern int am_daemon;
extern int am_chrooted;
extern int am_root;
extern int msgs2stderr;
extern int rsync_port;
@@ -39,7 +38,6 @@ extern int ignore_errors;
extern int preserve_xattrs;
extern int kluge_around_eof;
extern int munge_symlinks;
extern int use_secure_symlinks;
extern int open_noatime;
extern int sanitize_paths;
extern int numeric_ids;
@@ -978,14 +976,11 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
}
if (use_chroot) {
/* Cache timezone data before chroot makes /etc/localtime inaccessible */
tzset();
if (chroot(module_chdir)) {
rsyserr(FLOG, errno, "chroot(\"%s\") failed", module_chdir);
io_printf(f_out, "@ERROR: chroot failed\n");
return -1;
}
am_chrooted = 1;
module_chdir = module_dir;
}
@@ -1008,15 +1003,6 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
}
}
/* Enable secure symlink handling for any non-chrooted daemon module.
* This prevents TOCTOU race attacks where an attacker could switch a
* directory to a symlink between path validation and file open.
* Match the gate used by the do_*_at() wrappers in syscall.c
* (am_daemon && !am_chrooted) -- the protection has nothing to do
* with symlink munging, so a module configured with
* "munge symlinks = false" must still get the secure-open path. */
use_secure_symlinks = am_daemon && !am_chrooted;
if (gid_list.count) {
gid_t *gid_array = gid_list.items;
if (setgid(gid_array[0])) {
@@ -1070,7 +1056,7 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
io_printf(f_out, "@RSYNCD: OK\n");
read_args(f_in, name, line, sizeof line, rl_nulls, 1, &argv, &argc, &request);
read_args(f_in, name, line, sizeof line, rl_nulls, &argv, &argc, &request);
orig_argv = argv;
save_munge_symlinks = munge_symlinks;
@@ -1080,7 +1066,7 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
if (protect_args && ret) {
orig_early_argv = orig_argv;
protect_args = 2;
read_args(f_in, name, line, sizeof line, 1, 0, &argv, &argc, &request);
read_args(f_in, name, line, sizeof line, 1, &argv, &argc, &request);
orig_argv = argv;
ret = parse_arguments(&argc, (const char ***) &argv);
} else
@@ -1312,49 +1298,13 @@ int start_daemon(int f_in, int f_out)
if (lp_proxy_protocol() && !read_proxy_protocol_header(f_in))
return -1;
/* Do reverse DNS lookup before chroot/setuid. The result is cached,
* so the later client_name() call will use this cached value. This
* ensures hostname-based ACLs work even when DNS is unavailable
* after chroot.
*
* "reverse lookup" can be set globally OR per-module, so we also
* scan each module: a deployment with "reverse lookup = no" in the
* global section but "reverse lookup = yes" in a specific module
* still triggers a post-chroot lookup at access-check time
* (rsync_module() in this file), which would also fail in the
* chroot and turn hostname-based deny rules into silent bypasses. */
{
int need_reverse = lp_reverse_lookup(-1);
int j, num_modules = lp_num_modules();
for (j = 0; !need_reverse && j < num_modules; j++) {
if (lp_reverse_lookup(j))
need_reverse = 1;
}
if (need_reverse)
(void)client_name(client_addr(f_in));
}
p = lp_daemon_chroot();
if (*p) {
log_init(0); /* Make use we've initialized syslog before chrooting. */
tzset();
if (chroot(p) < 0) {
rsyserr(FLOG, errno, "daemon chroot(\"%s\") failed", p);
return -1;
}
/* Deliberately do NOT set am_chrooted here. am_chrooted
* gates the per-module symlink-race defenses
* (secure_relative_open() and the do_*_at() wrappers in
* syscall.c) and means "the kernel is enforcing path
* confinement at the module boundary". The daemon chroot
* confines path resolution to the daemon-chroot directory,
* not to any individual module path -- modules sharing the
* daemon chroot are still distinguishable filesystem
* subtrees and a sender-controlled symlink in module A
* could redirect a syscall to module B (or to other files
* inside the daemon chroot) without the per-module
* defenses. Leave am_chrooted=0 here so secure_relative_open()
* still fires for "use chroot = no" modules. */
if (chdir("/") < 0) {
rsyserr(FLOG, errno, "daemon chdir(\"/\") failed");
return -1;

View File

@@ -52,7 +52,6 @@ extern int need_messages_from_generator;
extern int delete_mode, delete_before, delete_during, delete_after;
extern int do_compression;
extern int do_compression_level;
extern int do_compression_threads;
extern int saw_stderr_opt;
extern int msgs2stderr;
extern char *shell_cmd;
@@ -132,7 +131,7 @@ static const char *client_info;
* of that protocol for it to be advertised as available. */
static void check_sub_protocol(void)
{
const char *dot;
char *dot;
int their_protocol, their_sub;
int our_sub = get_subprotocol_version();
@@ -415,7 +414,7 @@ static const char *getenv_nstr(int ntype)
env_str = ntype == NSTR_COMPRESS ? "zlib" : protocol_version >= 30 ? "md5" : "md4";
if (am_server && env_str) {
const char *cp = strchr(env_str, '&');
char *cp = strchr(env_str, '&');
if (cp)
env_str = cp + 1;
}

View File

@@ -331,28 +331,6 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]], [[return 0;]])],
CFLAGS="$OLD_CFLAGS"
AC_SUBST(NOEXECSTACK)
dnl Only define HAVE_OPENAT2 when both the <linux/openat2.h> header and the
dnl SYS_openat2 syscall number are present. syscall.c uses openat2(RESOLVE_BENEATH)
dnl for the secure resolver on Linux 5.6+; defining it unconditionally broke the
dnl build on older kernels/headers that lack the header (#924, #905, #900).
AC_CACHE_CHECK([for openat2],rsync_cv_HAVE_OPENAT2,[
AC_COMPILE_IFELSE([
AC_LANG_PROGRAM([[
#include <sys/syscall.h>
#include <linux/openat2.h>
]], [[
struct open_how how;
how.resolve = RESOLVE_BENEATH;
return SYS_openat2 + (int)how.resolve;
]])
],
[rsync_cv_HAVE_OPENAT2=yes], [rsync_cv_HAVE_OPENAT2=no])
])
if test x"$rsync_cv_HAVE_OPENAT2" = x"yes"; then
AC_DEFINE([HAVE_OPENAT2], 1,
[Define to use Linux openat2(RESOLVE_BENEATH) in secure_relative_open where available.])
fi
# arrgh. libc in some old debian version screwed up the largefile
# stuff, getting byte range locking wrong
AC_CACHE_CHECK([for broken largefile support],rsync_cv_HAVE_BROKEN_LARGEFILE,[
@@ -910,7 +888,7 @@ AC_FUNC_UTIME_NULL
AC_FUNC_ALLOCA
AC_CHECK_FUNCS(waitpid wait4 getcwd chown chmod lchmod mknod mkfifo \
fchmod fstat ftruncate strchr readlink link utime utimes lutimes strftime \
chflags getattrlist mktime innetgr linkat mknodat mkfifoat \
chflags getattrlist mktime innetgr linkat \
memmove lchown vsnprintf snprintf vasprintf asprintf setsid strpbrk \
strlcat strlcpy stpcpy strtol mallinfo mallinfo2 getgroups setgroups geteuid getegid \
setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
@@ -1414,7 +1392,7 @@ else
AC_DEFINE(HAVE_LINUX_XATTRS, 1, [True if you have Linux xattrs (or equivalent)])
AC_DEFINE(SUPPORT_XATTRS, 1)
AC_DEFINE(NO_SYMLINK_USER_XATTRS, 1, [True if symlinks do not support user xattrs])
AC_SEARCH_LIBS(getxattr,attr)
AC_CHECK_LIB(attr,getxattr)
;;
darwin*)
AC_MSG_RESULT(Using OS X xattrs)

View File

@@ -6,7 +6,7 @@
BEGIN {
heading = "/* DO NOT EDIT THIS FILE! It is auto-generated from a list of values in " ARGV[1] "! */\n\n"
sect = psect = defines = accessors = prior_ptype = ""
parms = "\nstatic const struct parm_struct parm_table[] = {"
parms = "\nstatic struct parm_struct parm_table[] = {"
comment_fmt = "\n/********** %s **********/\n"
tdstruct = "typedef struct {"
}

View File

@@ -98,7 +98,7 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
strlcpy(p, fp->basename, remainder);
if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
do_chmod_at(fname, fp->mode | S_IWUSR);
do_chmod(fname, fp->mode | S_IWUSR);
/* Save stack by recursing to ourself directly. */
if (S_ISDIR(fp->mode)) {
if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
@@ -139,7 +139,7 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
}
if (flags & DEL_NO_UID_WRITE)
do_chmod_at(fbuf, mode | S_IWUSR);
do_chmod(fbuf, mode | S_IWUSR);
if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
/* This only happens on the first call to delete_item() since
@@ -160,7 +160,7 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
if (S_ISDIR(mode)) {
what = "rmdir";
ok = do_rmdir_at(fbuf) == 0;
ok = do_rmdir(fbuf) == 0;
} else {
if (make_backups > 0 && !(flags & DEL_FOR_BACKUP) && (backup_dir || !is_backup_file(fbuf))) {
what = "make_backup";

View File

@@ -904,7 +904,7 @@ static int rule_matches(const char *fname, filter_rule *ex, int name_flags)
{
int slash_handling, str_cnt = 0, anchored_match = 0;
int ret_match = ex->rflags & FILTRULE_NEGATE ? 0 : 1;
const char *p, *pattern = ex->pattern;
char *p, *pattern = ex->pattern;
const char *strings[16]; /* more than enough */
const char *name = fname + (*fname == '/');

67
flist.c
View File

@@ -132,18 +132,6 @@ static int64 tmp_dev = -1, tmp_ino;
#endif
static char tmp_sum[MAX_DIGEST_LEN];
#ifdef ST_MTIME_NSEC
/* Return st_mtim nsec if it is in the wire-valid range, else 0. */
static inline uint32 wire_mtime_nsec_from_stat(const STRUCT_STAT *stp)
{
unsigned long nsec = (unsigned long)stp->ST_MTIME_NSEC;
if (nsec > MAX_WIRE_NSEC)
return 0;
return (uint32)nsec;
}
#endif
static char empty_sum[MAX_DIGEST_LEN];
static int flist_count_offset; /* for --delete --progress */
static int show_filelist_progress;
@@ -852,9 +840,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
}
if (xflags & XMIT_MOD_NSEC)
#ifndef CAN_SET_NSEC
(void)read_varint_bounded(f, 0, MAX_WIRE_NSEC, "modtime_nsec");
(void)read_varint(f);
#else
modtime_nsec = read_varint_bounded(f, 0, MAX_WIRE_NSEC, "modtime_nsec");
modtime_nsec = read_varint(f);
else
modtime_nsec = 0;
#endif
@@ -873,24 +861,8 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
#endif
}
#endif
if (!(xflags & XMIT_SAME_MODE)) {
if (!(xflags & XMIT_SAME_MODE))
mode = from_wire_mode(read_int(f));
/* Reject modes whose type bits are not one of the standard
* file types; otherwise garbage mode values propagate through
* the file-type checks below unpredictably. mode 0 is the one
* legitimate exception: --delete-missing-args (missing_args==2)
* sends a missing arg as a mode-0 entry (IS_MISSING_FILE), the
* generator's delete signal (#910). */
if (mode != 0 || missing_args != 2) {
if (!S_ISREG(mode) && !S_ISDIR(mode) && !S_ISLNK(mode)
&& !S_ISCHR(mode) && !S_ISBLK(mode)
&& !S_ISFIFO(mode) && !S_ISSOCK(mode)) {
rprintf(FERROR, "invalid file mode 0%o for %s [%s]\n",
(unsigned)mode, lastname, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
}
}
if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME)) {
atime = read_varlong(f, 4);
#if SIZEOF_TIME_T < SIZEOF_INT64
@@ -1267,7 +1239,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
int extra_len = file_extra_cnt * EXTRA_LEN;
const char *basename;
alloc_pool_t *pool;
STRUCT_STAT st = {0};
STRUCT_STAT st;
char *bp;
if (strlcpy(thisname, fname, sizeof thisname) >= sizeof thisname) {
@@ -1429,12 +1401,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
}
#ifdef ST_MTIME_NSEC
{
uint32 nsec = wire_mtime_nsec_from_stat(&st);
if (nsec && protocol_version >= 31)
extra_len += EXTRA_LEN;
}
if (st.ST_MTIME_NSEC && protocol_version >= 31)
extra_len += EXTRA_LEN;
#endif
#if SIZEOF_CAPITAL_OFF_T >= 8
if (st.st_size > 0xFFFFFFFFu && S_ISREG(st.st_mode))
@@ -1489,13 +1457,9 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
file->flags = flags;
file->modtime = st.st_mtime;
#ifdef ST_MTIME_NSEC
{
uint32 nsec = wire_mtime_nsec_from_stat(&st);
if (nsec && protocol_version >= 31) {
file->flags |= FLAG_MOD_NSEC;
F_MOD_NSEC(file) = nsec;
}
if (st.ST_MTIME_NSEC && protocol_version >= 31) {
file->flags |= FLAG_MOD_NSEC;
F_MOD_NSEC(file) = st.ST_MTIME_NSEC;
}
#endif
file->len32 = (uint32)st.st_size;
@@ -2095,9 +2059,10 @@ static void send1extra(int f, struct file_struct *file, struct file_list *flist)
}
if (name_type != NORMAL_NAME) {
STRUCT_STAT st = {0};
if (name_type != MISSING_NAME && link_stat(fbuf, &st, 1) != 0) {
STRUCT_STAT st;
if (name_type == MISSING_NAME)
memset(&st, 0, sizeof st);
else if (link_stat(fbuf, &st, 1) != 0) {
interpret_stat_error(fbuf, True);
continue;
}
@@ -2229,7 +2194,7 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
static const char *lastdir;
static int lastdir_len = -1;
int len, dirlen;
STRUCT_STAT st = {0};
STRUCT_STAT st;
char *p, *dir;
struct file_list *flist;
struct timeval start_tv, end_tv;
@@ -3202,8 +3167,8 @@ static void output_flist(struct file_list *flist)
} else
*uidbuf = '\0';
if (gid_ndx) {
static const char parens[] = "(\0)\0\0\0";
const char *pp = parens + (file->flags & FLAG_SKIP_GROUP ? 0 : 3);
static char parens[] = "(\0)\0\0\0";
char *pp = parens + (file->flags & FLAG_SKIP_GROUP ? 0 : 3);
snprintf(gidbuf, sizeof gidbuf, " gid=%s%u%s",
pp, F_GROUP(file), pp + 2);
} else

View File

@@ -66,7 +66,6 @@ extern int inplace;
extern int append_mode;
extern int make_backups;
extern int csum_length;
extern int xfer_sum_len;
extern int ignore_times;
extern int size_only;
extern OFF_T max_size;
@@ -230,13 +229,11 @@ static int read_delay_line(char *buf, int *flags_p)
*flags_p = 0;
if (sscanf(bp, "%x ", &mode) != 1) {
goto invalid_data;
invalid_data:
rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n");
return -1;
}
past_space = strchr(bp, ' ');
if (!past_space) {
goto invalid_data;
}
past_space++;
past_space = strchr(bp, ' ') + 1;
len = j - read_pos - (past_space - bp) + 1; /* count the '\0' */
read_pos = j + 1;
@@ -250,10 +247,6 @@ static int read_delay_line(char *buf, int *flags_p)
memcpy(buf, past_space, len);
return mode;
invalid_data:
rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n");
return -1;
}
static void do_delayed_deletions(char *delbuf)
@@ -698,11 +691,6 @@ static void sum_sizes_sqroot(struct sum_struct *sum, int64 len)
{
int32 blength;
int s2length;
/* The strong sum can be no longer than the negotiated checksum digest:
* a short checksum (e.g. xxh64 = 8 bytes, when xxh128/xxh3 are absent)
* makes xfer_sum_len < SUM_LENGTH, and the sender rejects an s2length
* larger than xfer_sum_len (io.c). */
int max_s2length = MIN(SUM_LENGTH, xfer_sum_len);
int64 l;
if (len < 0) {
@@ -737,7 +725,7 @@ static void sum_sizes_sqroot(struct sum_struct *sum, int64 len)
if (protocol_version < 27) {
s2length = csum_length;
} else if (csum_length == SUM_LENGTH) {
s2length = max_s2length;
s2length = SUM_LENGTH;
} else {
int32 c;
int b = BLOCKSUM_BIAS;
@@ -746,7 +734,7 @@ static void sum_sizes_sqroot(struct sum_struct *sum, int64 len)
/* add a bit, subtract rollsum, round up. */
s2length = (b + 1 - 32 + 7) / 8; /* --optimize in compiler-- */
s2length = MAX(s2length, csum_length);
s2length = MIN(s2length, max_s2length);
s2length = MIN(s2length, SUM_LENGTH);
}
sum->flength = len;
@@ -996,7 +984,7 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
if (find_exact_for_existing) {
if (alt_dest_type == LINK_DEST && real_st.st_dev == sxp->st.st_dev && real_st.st_ino == sxp->st.st_ino)
return -1;
if (do_unlink_at(fname) < 0 && errno != ENOENT)
if (do_unlink(fname) < 0 && errno != ENOENT)
goto got_nothing_for_ya;
}
#ifdef SUPPORT_HARD_LINKS
@@ -1124,7 +1112,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
&& !IS_SPECIAL(file->mode) && !IS_DEVICE(file->mode)
#endif
&& !S_ISDIR(file->mode)) {
if (do_link_at(cmpbuf, fname) < 0) {
if (do_link(cmpbuf, fname) < 0) {
rsyserr(FERROR_XFER, errno,
"failed to hard-link %s with %s",
cmpbuf, fname);
@@ -1327,7 +1315,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
}
}
if (relative_paths && !implied_dirs && file->mode != 0
&& do_stat_at(dn, &sx.st) < 0) {
&& do_stat(dn, &sx.st) < 0) {
if (dry_run)
goto parent_is_dry_missing;
if (make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
@@ -1439,7 +1427,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
&& (stype == FT_DIR
|| delete_item(fname, sx.st.st_mode, del_opts | DEL_FOR_DIR) != 0))
goto cleanup; /* Any errors get reported later. */
if (do_mkdir_at(fname, (file->mode|added_perms) & 0700) == 0)
if (do_mkdir(fname, (file->mode|added_perms) & 0700) == 0)
file->flags |= FLAG_DIR_CREATED;
goto cleanup;
}
@@ -1481,10 +1469,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
itemize(fnamecmp, file, ndx, statret, &sx,
statret ? ITEM_LOCAL_CHANGE : 0, 0, NULL);
}
if (real_ret != 0 && do_mkdir_at(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
if (real_ret != 0 && do_mkdir(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
if (!relative_paths || errno != ENOENT
|| make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0
|| (do_mkdir_at(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
|| (do_mkdir(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
rsyserr(FERROR_XFER, errno,
"recv_generator: mkdir %s failed",
full_fname(fname));
@@ -1511,7 +1499,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
#ifdef HAVE_CHMOD
if (!am_root && (file->mode & S_IRWXU) != S_IRWXU && dir_tweaking) {
mode_t mode = file->mode | S_IRWXU;
if (do_chmod_at(fname, mode) < 0) {
if (do_chmod(fname, mode) < 0) {
rsyserr(FERROR_XFER, errno,
"failed to modify permissions on %s",
full_fname(fname));
@@ -1718,8 +1706,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
goto cleanup;
}
if (update_only > 0 && statret == 0 && stype == ftype
&& file->modtime - sx.st.st_mtime < modify_window) {
if (update_only > 0 && statret == 0 && file->modtime - sx.st.st_mtime < modify_window) {
if (INFO_GTE(SKIP, 1))
rprintf(FINFO, "%s is newer\n", fname);
#ifdef SUPPORT_HARD_LINKS
@@ -1821,7 +1808,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
;
else if (quick_check_ok(FT_REG, fnamecmp, file, &sx.st)) {
if (partialptr) {
do_unlink_at(partialptr);
do_unlink(partialptr);
handle_partial_dir(partialptr, PDIR_DELETE);
}
set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT | maybe_ATTRS_ACCURATE_TIME);
@@ -1909,7 +1896,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
back_file = NULL;
goto cleanup;
}
if ((f_copy = do_open_at(backupptr, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) {
if ((f_copy = do_open(backupptr, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) {
rsyserr(FERROR_XFER, errno, "open %s", full_fname(backupptr));
unmake_file(back_file);
back_file = NULL;
@@ -2029,7 +2016,7 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
if (slnk) {
#ifdef SUPPORT_LINKS
if (do_symlink_at(slnk, create_name) < 0) {
if (do_symlink(slnk, create_name) < 0) {
rsyserr(FERROR_XFER, errno, "symlink %s -> \"%s\" failed",
full_fname(create_name), slnk);
return 0;
@@ -2045,7 +2032,7 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
return 0;
#endif
} else {
if (do_mknod_at(create_name, file->mode, rdev) < 0) {
if (do_mknod(create_name, file->mode, rdev) < 0) {
rsyserr(FERROR_XFER, errno, "mknod %s failed",
full_fname(create_name));
return 0;
@@ -2053,14 +2040,10 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
}
if (!skip_atomic) {
if (do_rename_at(tmpname, fname) < 0) {
char *full_tmpname = strdup(full_fname(tmpname));
if (full_tmpname == NULL)
out_of_memory("atomic_create");
if (do_rename(tmpname, fname) < 0) {
rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\" failed",
full_tmpname, full_fname(fname));
free(full_tmpname);
do_unlink_at(tmpname);
full_fname(tmpname), full_fname(fname));
do_unlink(tmpname);
return 0;
}
}
@@ -2124,7 +2107,7 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
continue;
fname = f_name(file, NULL);
if (fix_dir_perms)
do_chmod_at(fname, file->mode);
do_chmod(fname, file->mode);
if (need_retouch_dir_times) {
STRUCT_STAT st;
if (link_stat(fname, &st, 0) == 0 && mtime_differs(&st, file)) {
@@ -2159,8 +2142,6 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo)
if (send_failed)
ndx = get_hlink_num();
flist = flist_for_ndx(ndx, "check_for_finished_files.1");
if (ndx < flist->ndx_start)
exit_cleanup(RERR_PROTOCOL);
file = flist->files[ndx - flist->ndx_start];
assert(file->flags & FLAG_HLINKED);
if (send_failed)
@@ -2189,8 +2170,6 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo)
flist = cur_flist;
cur_flist = flist_for_ndx(ndx, "check_for_finished_files.2");
if (ndx < cur_flist->ndx_start)
exit_cleanup(RERR_PROTOCOL);
file = cur_flist->files[ndx - cur_flist->ndx_start];
if (solo_file)
@@ -2391,7 +2370,7 @@ void generate_files(int f_out, const char *local_name)
write_ndx(f_out, NDX_DONE);
if (protocol_version >= 31 && EARLY_DELETE_DONE_MSG()) {
if (delete_mode || force_delete || read_batch)
if ((INFO_GTE(STATS, 2) && (delete_mode || force_delete)) || read_batch)
write_del_stats(f_out);
if (EARLY_DELAY_DONE_MSG()) /* Can't send this before delay */
write_ndx(f_out, NDX_DONE);
@@ -2436,7 +2415,7 @@ void generate_files(int f_out, const char *local_name)
if (protocol_version >= 31) {
if (!EARLY_DELETE_DONE_MSG()) {
if (delete_mode || force_delete || read_batch)
if (INFO_GTE(STATS, 2) || read_batch)
write_del_stats(f_out);
write_ndx(f_out, NDX_DONE);
}

View File

@@ -454,7 +454,7 @@ int hard_link_check(struct file_struct *file, int ndx, char *fname,
int hard_link_one(struct file_struct *file, const char *fname,
const char *oldname, int terse)
{
if (do_link_at(oldname, fname) < 0) {
if (do_link(oldname, fname) < 0) {
enum logcode code;
if (terse) {
if (!INFO_GTE(NAME, 1))

83
io.c
View File

@@ -117,7 +117,7 @@ static int active_filecnt = 0;
static OFF_T active_bytecnt = 0;
static int first_message = 1;
static const char int_byte_extra[64] = {
static char int_byte_extra[64] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* (00 - 3F)/4 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* (40 - 7F)/4 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* (80 - BF)/4 */
@@ -1090,9 +1090,6 @@ static void got_flist_entry_status(enum festatus status, int ndx)
{
struct file_list *flist = flist_for_ndx(ndx, "got_flist_entry_status");
if (ndx < flist->ndx_start)
exit_cleanup(RERR_PROTOCOL);
if (remove_source_files) {
active_filecnt--;
active_bytecnt -= F_LENGTH(flist->files[ndx - flist->ndx_start]);
@@ -1161,8 +1158,8 @@ void set_io_timeout(int secs)
static void check_for_d_option_error(const char *msg)
{
static const char rsync263_opts[] = "BCDHIKLPRSTWabceghlnopqrtuvxz";
const char *colon;
static char rsync263_opts[] = "BCDHIKLPRSTWabceghlnopqrtuvxz";
char *colon;
int saw_d = 0;
if (*msg != 'r'
@@ -1292,21 +1289,8 @@ int read_line(int fd, char *buf, size_t bufsiz, int flags)
return s - buf;
}
/* Reverse safe_arg()'s backslash escaping of a daemon option arg, the way a
* remote shell un-escapes args for the ssh transport. In place; \X -> X. */
static void unbackslash_arg(char *s)
{
char *f = s, *t = s;
while (*f) {
if (*f == '\\' && f[1])
f++;
*t++ = *f++;
}
*t = '\0';
}
void read_args(int f_in, char *mod_name, char *buf, size_t bufsiz, int rl_nulls,
int unescape, char ***argv_p, int *argc_p, char **request_p)
char ***argv_p, int *argc_p, char **request_p)
{
int maxargs = MAX_ARGS;
int dot_pos = 0, argc = 0, request_len = 0;
@@ -1348,11 +1332,6 @@ void read_args(int f_in, char *mod_name, char *buf, size_t bufsiz, int rl_nulls,
glob_expand(buf, &argv, &argc, &maxargs);
} else {
p = strdup(buf);
/* An option arg the client escaped with safe_arg() (no
* remote shell un-escapes it for a daemon). File args
* after the dot are handled by glob_expand() below. */
if (unescape)
unbackslash_arg(p);
argv[argc++] = p;
if (*p == '.' && p[1] == '\0')
dot_pos = argc;
@@ -1886,45 +1865,6 @@ int64 read_varlong(int f, uchar min_bytes)
return u.x;
}
/* Read an int32 and verify lo <= v <= hi. On out-of-range, abort with a
* protocol error naming "what". The bound is co-located with the read so it
* cannot be forgotten by a downstream user. */
int32 read_int_bounded(int f, int32 lo, int32 hi, const char *what)
{
int32 v = read_int(f);
if (v < lo || v > hi) {
rprintf(FERROR, "wire value %s out of range: %ld not in [%ld,%ld] [%s]\n",
what, (long)v, (long)lo, (long)hi, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
return v;
}
/* As read_int_bounded but for varint-encoded values. */
int32 read_varint_bounded(int f, int32 lo, int32 hi, const char *what)
{
int32 v = read_varint(f);
if (v < lo || v > hi) {
rprintf(FERROR, "wire value %s out of range: %ld not in [%ld,%ld] [%s]\n",
what, (long)v, (long)lo, (long)hi, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
return v;
}
/* Read a varint that will be used as a size_t. Rejects negative values
* (which would wrap to ~SIZE_MAX) and values exceeding the supplied max. */
size_t read_varint_size(int f, size_t max, const char *what)
{
int32 v = read_varint(f);
if (v < 0 || (size_t)v > max) {
rprintf(FERROR, "wire size %s out of range: %ld > %lu [%s]\n",
what, (long)v, (unsigned long)max, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
return (size_t)v;
}
int64 read_longint(int f)
{
#if SIZEOF_INT64 >= 8
@@ -2031,21 +1971,6 @@ void read_sum_head(int f, struct sum_struct *sum)
(long)sum->count, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
/* Guard against integer overflow in downstream allocations sized by
* count*element_size. my_alloc uses divide-not-multiply so it is
* already wraparound-safe, but checking here gives a clearer error
* and also covers the (size_t)count * xfer_sum_len arithmetic that
* is performed *before* reaching my_alloc. */
if (xfer_sum_len > 0 && (size_t)sum->count > SIZE_MAX / (size_t)xfer_sum_len) {
rprintf(FERROR, "Invalid checksum count %ld (too large) [%s]\n",
(long)sum->count, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
if ((size_t)sum->count > SIZE_MAX / sizeof(struct sum_buf)) {
rprintf(FERROR, "Invalid checksum count %ld (sum_buf overflow) [%s]\n",
(long)sum->count, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
sum->blength = read_int(f);
if (sum->blength < 0 || sum->blength > max_blength) {
rprintf(FERROR, "Invalid block length %ld [%s]\n",

View File

@@ -1 +1 @@
#define LATEST_YEAR "2026"
#define LATEST_YEAR "2025"

View File

@@ -197,7 +197,7 @@ void md5_update(md_context *ctx, const uchar *input, uint32 length)
memcpy(ctx->buffer + left, input, length);
}
static const uchar md5_padding[CSUM_CHUNK] = { 0x80 };
static uchar md5_padding[CSUM_CHUNK] = { 0x80 };
void md5_result(md_context *ctx, uchar digest[MD5_DIGEST_LEN])
{

View File

@@ -126,18 +126,9 @@ ssize_t sys_llistxattr(const char *path, char *list, size_t size)
unsigned char keylen;
ssize_t off, len = extattr_list_link(path, EXTATTR_NAMESPACE_USER, list, size);
if (len <= 0 || size == 0)
if (len <= 0 || (size_t)len > size)
return len;
if ((size_t)len >= size) {
/* FreeBSD extattr_list_xx() returns 'size' as 'len' in case there are
more data available, truncating the output, we solve this by signalling
ERANGE in case len == size so that the code in xattrs.c will retry with
a bigger buffer */
errno = ERANGE;
return -1;
}
/* FreeBSD puts a single-byte length before each string, with no '\0'
* terminator. We need to change this into a series of null-terminted
* strings. Since the size is the same, we can simply transform the
@@ -145,7 +136,7 @@ ssize_t sys_llistxattr(const char *path, char *list, size_t size)
for (off = 0; off < len; off += keylen + 1) {
keylen = ((unsigned char*)list)[off];
if (off + keylen >= len) {
/* Should be impossible, but bugs happen! */
/* Should be impossible, but kernel bugs happen! */
errno = EINVAL;
return -1;
}

View File

@@ -65,7 +65,7 @@ typedef enum {
struct enum_list {
int value;
const char *name;
char *name;
};
struct parm_struct {
@@ -73,7 +73,7 @@ struct parm_struct {
parm_type type;
parm_class class;
void *ptr;
const struct enum_list *enum_list;
struct enum_list *enum_list;
unsigned flags;
};
@@ -95,7 +95,7 @@ static item_list section_list = EMPTY_ITEM_LIST;
static int iSectionIndex = -1;
static BOOL bInGlobalSection = True;
static const struct enum_list enum_syslog_facility[] = {
static struct enum_list enum_syslog_facility[] = {
#ifdef LOG_AUTH
{ LOG_AUTH, "auth" },
#endif
@@ -178,7 +178,7 @@ static char *expand_vars(const char *str)
for (t = buf, f = str; bufsize && *f; ) {
if (*f == '%' && isUpper(f+1)) {
const char *percent = strchr(f+1, '%');
char *percent = strchr(f+1, '%');
if (percent && percent - f < bufsize) {
char *val;
strlcpy(t, f+1, percent - f);

12
log.c
View File

@@ -456,17 +456,11 @@ void rsyserr(enum logcode code, int errcode, const char *format, ...)
char buf[BIGPATHBUFLEN];
size_t len;
/* snprintf returns the would-have-been length on truncation, so
* each cumulative call must be guarded; if not, sizeof buf - len
* can underflow when promoted to size_t and the next call writes
* past the buffer. */
len = snprintf(buf, sizeof buf, RSYNC_NAME ": [%s] ", who_am_i());
if (len < sizeof buf) {
va_start(ap, format);
len += vsnprintf(buf + len, sizeof buf - len, format, ap);
va_end(ap);
}
va_start(ap, format);
len += vsnprintf(buf + len, sizeof buf - len, format, ap);
va_end(ap);
if (len < sizeof buf) {
len += snprintf(buf + len, sizeof buf - len,

42
main.c
View File

@@ -239,11 +239,11 @@ void write_del_stats(int f)
void read_del_stats(int f)
{
stats.deleted_files = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_files");
stats.deleted_files += stats.deleted_dirs = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_dirs");
stats.deleted_files += stats.deleted_symlinks = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_symlinks");
stats.deleted_files += stats.deleted_devices = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_devices");
stats.deleted_files += stats.deleted_specials = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_specials");
stats.deleted_files = read_varint(f);
stats.deleted_files += stats.deleted_dirs = read_varint(f);
stats.deleted_files += stats.deleted_symlinks = read_varint(f);
stats.deleted_files += stats.deleted_devices = read_varint(f);
stats.deleted_files += stats.deleted_specials = read_varint(f);
}
static void become_copy_as_user()
@@ -386,7 +386,7 @@ static void handle_stats(int f)
static void output_itemized_counts(const char *prefix, int *counts)
{
static char *const labels[] = { "reg", "dir", "link", "dev", "special" };
static char *labels[] = { "reg", "dir", "link", "dev", "special" };
char buf[1024], *pre = " (";
int j, len = 0;
int total = counts[0];
@@ -394,18 +394,9 @@ static void output_itemized_counts(const char *prefix, int *counts)
counts[0] -= counts[1] + counts[2] + counts[3] + counts[4];
for (j = 0; j < 5; j++) {
if (counts[j]) {
/* snprintf can return more than its size arg
* on truncation; keep len <= sizeof buf - 2 so
* the closing ')' and trailing NUL always
* have room and the next iteration's
* sizeof buf - len - 2 cannot underflow. */
if (len >= (int)sizeof buf - 2)
break;
len += snprintf(buf+len, sizeof buf - len - 2,
"%s%s: %s",
pre, labels[j], comma_num(counts[j]));
if (len > (int)sizeof buf - 2)
len = (int)sizeof buf - 2;
pre = ", ";
}
}
@@ -832,16 +823,7 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
dest_path = "/";
*cp = '\0';
if (dry_run && mkpath_dest_arg && do_stat(dest_path, &st) < 0) {
/* --mkpath would have created this parent dir, but a dry run did
* not, so don't chdir into it; flag the destination as not yet
* present (as the dir-creation path above does) so the generator
* doesn't try to compare against the missing tree (#880). Only
* the missing-parent case is touched, so an ordinary file-to-file
* dry run still itemizes against an existing destination. */
dry_run++;
change_dir(dest_path, CD_SKIP_CHDIR);
} else if (!change_dir(dest_path, CD_NORMAL)) {
if (!change_dir(dest_path, CD_NORMAL)) {
rsyserr(FERROR, errno, "change_dir#3 %s failed",
full_fname(dest_path));
exit_cleanup(RERR_FILESELECT);
@@ -1577,10 +1559,6 @@ static int start_client(int argc, char *argv[])
shell_user = shell_machine;
shell_machine = p+1;
}
if (*shell_machine == '-') {
rprintf(FERROR, "Invalid remote host: hostnames may not start with '-'.\n");
exit_cleanup(RERR_SYNTAX);
}
}
if (DEBUG_GTE(CMD, 2)) {
@@ -1765,9 +1743,7 @@ int main(int argc,char *argv[])
our_gid = MY_GID();
am_root = our_uid == ROOT_UID;
// DISPLAY should not be emptied unconditionally
if (!getenv("SSH_ASKPASS"))
unset_env_var("DISPLAY");
unset_env_var("DISPLAY");
#if defined USE_OPENSSL && defined SET_OPENSSL_CONF
#define TO_STR2(x) #x
@@ -1849,7 +1825,7 @@ int main(int argc,char *argv[])
if (am_server && protect_args) {
char buf[MAXPATHLEN];
protect_args = 2;
read_args(STDIN_FILENO, NULL, buf, sizeof buf, 1, 0, &argv, &argc, NULL);
read_args(STDIN_FILENO, NULL, buf, sizeof buf, 1, &argv, &argc, NULL);
if (!parse_arguments(&argc, (const char ***) &argv)) {
option_error();
exit_cleanup(RERR_SYNTAX);

View File

@@ -120,7 +120,6 @@ TZ_RE = re.compile(r'^#define\s+MAINTAINER_TZ_OFFSET\s+(-?\d+(\.\d+)?)', re.M)
VAR_REF_RE = re.compile(r'\$\{(\w+)\}')
VERSION_RE = re.compile(r' (\d[.\d]+)[, ]')
BIN_CHARS_RE = re.compile(r'[\1-\7]+')
LONG_OPT_DASH_RE = re.compile(r'(--\w[-\w]+)')
SPACE_DOUBLE_DASH_RE = re.compile(r'\s--(\s)')
NON_SPACE_SINGLE_DASH_RE = re.compile(r'(^|\W)-')
WHITESPACE_RE = re.compile(r'\s')
@@ -248,9 +247,6 @@ def find_man_substitutions():
env_subs['date'] = time.strftime('%d %b %Y', time.gmtime(mtime + tz_offset)).lstrip('0')
if 'SOURCE_DATE_EPOCH' in os.environ:
env_subs['date'] = time.strftime('%d %b %Y', time.gmtime(int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))))
def html_via_commonmark(txt):
return commonmark.HtmlRenderer().render(commonmark.Parser().parse(txt))
@@ -544,7 +540,6 @@ class TransformHtml(HTMLParser):
if st.in_pre:
html = htmlify(txt)
else:
txt = LONG_OPT_DASH_RE.sub(lambda x: x.group(1).replace('-', NBR_DASH[0]), txt)
txt = SPACE_DOUBLE_DASH_RE.sub(NBR_SPACE[0] + r'--\1', txt).replace('--', NBR_DASH[0]*2)
txt = NON_SPACE_SINGLE_DASH_RE.sub(r'\1' + NBR_DASH[0], txt)
html = htmlify(txt)

View File

@@ -86,7 +86,6 @@ int sparse_files = 0;
int preallocate_files = 0;
int do_compression = 0;
int do_compression_level = CLVL_NOT_SPECIFIED;
int do_compression_threads = 0; /*n = 0 use rsync thread, n >= 1 spawn n threads for compression */
int am_root = 0; /* 0 = normal, 1 = root, 2 = --super, -1 = --fake-super */
int am_server = 0;
int am_sender = 0;
@@ -114,20 +113,11 @@ int mkpath_dest_arg = 0;
int allow_inc_recurse = 1;
int xfer_dirs = -1;
int am_daemon = 0;
/* Set after a successful per-module chroot ("use chroot = yes") in
* clientserver.c. NOT set for the daemon-level "daemon chroot = /X"
* chroot: that confines path resolution to /X, but module paths
* /X/modA, /X/modB, etc. are not chroot boundaries, so the per-module
* symlink-race defenses (secure_relative_open() / do_*_at() in
* syscall.c, gated by `am_daemon && !am_chrooted`) must still fire
* even when the daemon is inside a daemon chroot. */
int am_chrooted = 0;
int connect_timeout = 0;
int keep_partial = 0;
int safe_symlinks = 0;
int copy_unsafe_links = 0;
int munge_symlinks = 0;
int use_secure_symlinks = 0;
int size_only = 0;
int daemon_bwlimit = 0;
int bwlimit = 0;
@@ -235,7 +225,7 @@ char *iconv_opt =
struct chmod_mode_struct *chmod_modes = NULL;
static const char *const debug_verbosity[] = {
static const char *debug_verbosity[] = {
/*0*/ NULL,
/*1*/ NULL,
/*2*/ "BIND,CMD,CONNECT,DEL,DELTASUM,DUP,FILTER,FLIST,ICONV",
@@ -246,7 +236,7 @@ static const char *const debug_verbosity[] = {
#define MAX_VERBOSITY ((int)(sizeof debug_verbosity / sizeof debug_verbosity[0]) - 1)
static const char *const info_verbosity[1+MAX_VERBOSITY] = {
static const char *info_verbosity[1+MAX_VERBOSITY] = {
/*0*/ "NONREG",
/*1*/ "COPY,DEL,FLIST,MISC,NAME,STATS,SYMSAFE",
/*2*/ "BACKUP,MISC2,MOUNT,NAME2,REMOVE,SKIP",
@@ -484,7 +474,7 @@ static void parse_output_words(struct output_struct *words, short *levels, const
static void output_item_help(struct output_struct *words)
{
short *levels = words == info_words ? info_levels : debug_levels;
const char *const*verbosity = words == info_words ? info_verbosity : debug_verbosity;
const char **verbosity = words == info_words ? info_verbosity : debug_verbosity;
char buf[128], *opt, *fmt = "%-10s %s\n";
int j;
@@ -766,8 +756,6 @@ static struct poptOption long_options[] = {
{"skip-compress", 0, POPT_ARG_STRING, &skip_compress, 0, 0, 0 },
{"compress-level", 0, POPT_ARG_INT, &do_compression_level, 0, 0, 0 },
{"zl", 0, POPT_ARG_INT, &do_compression_level, 0, 0, 0 },
{"compress-threads", 0, POPT_ARG_INT, &do_compression_threads, 0, 0, 0 },
{"zt", 0, POPT_ARG_INT, &do_compression_threads, 0, 0, 0 },
{0, 'P', POPT_ARG_NONE, 0, 'P', 0, 0 },
{"progress", 0, POPT_ARG_VAL, &do_progress, 1, 0, 0 },
{"no-progress", 0, POPT_ARG_VAL, &do_progress, 0, 0, 0 },
@@ -856,7 +844,7 @@ static struct poptOption long_options[] = {
{0,0,0,0, 0, 0, 0}
};
static const struct poptOption long_daemon_options[] = {
static struct poptOption long_daemon_options[] = {
/* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
{"address", 0, POPT_ARG_STRING, &bind_address, 0, 0, 0 },
{"bwlimit", 0, POPT_ARG_INT, &daemon_bwlimit, 0, 0, 0 },
@@ -1168,7 +1156,7 @@ static time_t parse_time(const char *arg)
{
const char *cp;
time_t val, now = time(NULL);
struct tm t, tmp, *today = localtime_r(&now, &tmp);
struct tm t, *today = localtime(&now);
int in_date, old_mday, n;
memset(&t, 0, sizeof t);
@@ -1381,10 +1369,6 @@ int parse_arguments(int *argc_p, const char ***argv_p)
/* TODO: Call poptReadDefaultConfig; handle errors. */
pc = poptGetContext(RSYNC_NAME, argc, argv, long_options, 0);
if (pc == NULL) {
strlcpy(err_buf, "poptGetContext returned NULL\n", sizeof err_buf);
return 0;
}
if (!am_server) {
poptReadDefaultConfig(pc, 0);
popt_unalias(pc, "--daemon");
@@ -2022,8 +2006,6 @@ int parse_arguments(int *argc_p, const char ***argv_p)
create_refuse_error(refused_compress);
goto cleanup;
}
if (do_compression_threads < 0)
do_compression_threads = 0;
}
#ifdef HAVE_SETVBUF

174
packaging/branch-from-patch Executable file
View File

@@ -0,0 +1,174 @@
#!/usr/bin/env -S python3 -B
# This script turns one or more diff files in the patches dir (which is
# expected to be a checkout of the rsync-patches git repo) into a branch
# in the main rsync git checkout. This allows the applied patch to be
# merged with the latest rsync changes and tested. To update the diff
# with the resulting changes, see the patch-update script.
import os, sys, re, argparse, glob
sys.path = ['packaging'] + sys.path
from pkglib import *
def main():
global created, info, local_branch
cur_branch, args.base_branch = check_git_state(args.base_branch, not args.skip_check, args.patches_dir)
local_branch = get_patch_branches(args.base_branch)
if args.delete_local_branches:
for name in sorted(local_branch):
branch = f"patch/{args.base_branch}/{name}"
cmd_chk(['git', 'branch', '-D', branch])
local_branch = set()
if args.add_missing:
for fn in sorted(glob.glob(f"{args.patches_dir}/*.diff")):
name = re.sub(r'\.diff$', '', re.sub(r'.+/', '', fn))
if name not in local_branch and fn not in args.patch_files:
args.patch_files.append(fn)
if not args.patch_files:
return
for fn in args.patch_files:
if not fn.endswith('.diff'):
die(f"Filename is not a .diff file: {fn}")
if not os.path.isfile(fn):
die(f"File not found: {fn}")
scanned = set()
info = { }
patch_list = [ ]
for fn in args.patch_files:
m = re.match(r'^(?P<dir>.*?)(?P<name>[^/]+)\.diff$', fn)
patch = argparse.Namespace(**m.groupdict())
if patch.name in scanned:
continue
patch.fn = fn
lines = [ ]
commit_hash = None
with open(patch.fn, 'r', encoding='utf-8') as fh:
for line in fh:
m = re.match(r'^based-on: (\S+)', line)
if m:
commit_hash = m[1]
break
if (re.match(r'^index .*\.\..* \d', line)
or re.match(r'^diff --git ', line)
or re.match(r'^--- (old|a)/', line)):
break
lines.append(re.sub(r'\s*\Z', "\n", line, 1))
info_txt = ''.join(lines).strip() + "\n"
lines = None
parent = args.base_branch
patches = re.findall(r'patch -p1 <%s/(\S+)\.diff' % args.patches_dir, info_txt)
if patches:
last = patches.pop()
if last != patch.name:
warn(f"No identity patch line in {patch.fn}")
patches.append(last)
if patches:
parent = patches.pop()
if parent not in scanned:
diff_fn = patch.dir + parent + '.diff'
if not os.path.isfile(diff_fn):
die(f"Failed to find parent of {patch.fn}: {parent}")
# Add parent to args.patch_files so that we will look for the
# parent's parent. Any duplicates will be ignored.
args.patch_files.append(diff_fn)
else:
warn(f"No patch lines found in {patch.fn}")
info[patch.name] = [ parent, info_txt, commit_hash ]
patch_list.append(patch)
created = set()
for patch in patch_list:
create_branch(patch)
cmd_chk(['git', 'checkout', args.base_branch])
def create_branch(patch):
if patch.name in created:
return
created.add(patch.name)
parent, info_txt, commit_hash = info[patch.name]
parent = argparse.Namespace(dir=patch.dir, name=parent, fn=patch.dir + parent + '.diff')
if parent.name == args.base_branch:
parent_branch = commit_hash if commit_hash else args.base_branch
else:
create_branch(parent)
parent_branch = '/'.join(['patch', args.base_branch, parent.name])
branch = '/'.join(['patch', args.base_branch, patch.name])
print("\n" + '=' * 64)
print(f"Processing {branch} ({parent_branch})")
if patch.name in local_branch:
cmd_chk(['git', 'branch', '-D', branch])
cmd_chk(['git', 'checkout', '-b', branch, parent_branch])
info_fn = 'PATCH.' + patch.name
with open(info_fn, 'w', encoding='utf-8') as fh:
fh.write(info_txt)
cmd_chk(['git', 'add', info_fn])
with open(patch.fn, 'r', encoding='utf-8') as fh:
patch_txt = fh.read()
cmd_run('patch -p1'.split(), input=patch_txt)
for fn in glob.glob('*.orig') + glob.glob('*/*.orig'):
os.unlink(fn)
pos = 0
new_file_re = re.compile(r'\nnew file mode (?P<mode>\d+)\s+--- /dev/null\s+\+\+\+ b/(?P<fn>.+)')
while True:
m = new_file_re.search(patch_txt, pos)
if not m:
break
os.chmod(m['fn'], int(m['mode'], 8))
cmd_chk(['git', 'add', m['fn']])
pos = m.end()
while True:
cmd_chk('git status'.split())
ans = input('Press Enter to commit, Ctrl-C to abort, or type a wild-name to add a new file: ')
if ans == '':
break
cmd_chk("git add " + ans, shell=True)
while True:
s = cmd_run(['git', 'commit', '-a', '-m', f"Creating branch from {patch.name}.diff."])
if not s.returncode:
break
s = cmd_run([os.environ.get('SHELL', '/bin/sh')])
if s.returncode:
die('Aborting due to shell error code')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Create a git patch branch from an rsync patch file.", add_help=False)
parser.add_argument('--branch', '-b', dest='base_branch', metavar='BASE_BRANCH', default='master', help="The branch the patch is based on. Default: master.")
parser.add_argument('--add-missing', '-a', action='store_true', help="Add a branch for every patches/*.diff that doesn't have a branch.")
parser.add_argument('--skip-check', action='store_true', help="Skip the check that ensures starting with a clean branch.")
parser.add_argument('--delete', dest='delete_local_branches', action='store_true', help="Delete all the local patch/BASE/* branches, not just the ones that are being recreated.")
parser.add_argument('--patches-dir', '-p', metavar='DIR', default='patches', help="Override the location of the rsync-patches dir. Default: patches.")
parser.add_argument('patch_files', metavar='patches/DIFF_FILE', nargs='*', help="Specify what patch diff files to process. Default: all of them.")
parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
args = parser.parse_args()
main()
# vim: sw=4 et ft=python

View File

@@ -1,6 +1,6 @@
Summary: A fast, versatile, remote (and local) file-copying tool
Name: rsync
Version: 3.4.4
Version: 3.4.0
%define fullversion %{version}
Release: 1
%define srcdir src
@@ -79,5 +79,5 @@ rm -rf $RPM_BUILD_ROOT
%dir /etc/rsync-ssl/certs
%changelog
* Mon Jun 08 2026 Rsync Project <rsync.project@gmail.com>
Released 3.4.4.
* Wed Jan 15 2025 Rsync Project <rsync.project@gmail.com>
Released 3.4.0.

244
packaging/patch-update Executable file
View File

@@ -0,0 +1,244 @@
#!/usr/bin/env -S python3 -B
# This script is used to turn one or more of the "patch/BASE/*" branches
# into one or more diffs in the "patches" directory. Pass the option
# --gen if you want generated files in the diffs. Pass the name of
# one or more diffs if you want to just update a subset of all the
# diffs.
import os, sys, re, argparse, time, shutil
sys.path = ['packaging'] + sys.path
from pkglib import *
MAKE_GEN_CMDS = [
'./prepare-source'.split(),
'cd build && if test -f config.status ; then ./config.status ; else ../configure ; fi',
'make -C build gen'.split(),
]
TMP_DIR = "patches.gen"
os.environ['GIT_MERGE_AUTOEDIT'] = 'no'
def main():
global master_commit, parent_patch, description, completed, last_touch
if not os.path.isdir(args.patches_dir):
die(f'No "{args.patches_dir}" directory was found.')
if not os.path.isdir('.git'):
die('No ".git" directory present in the current dir.')
starting_branch, args.base_branch = check_git_state(args.base_branch, not args.skip_check, args.patches_dir)
master_commit = latest_git_hash(args.base_branch)
if cmd_txt_chk(['packaging/prep-auto-dir']).out == '':
die('You must setup an auto-build-save dir to use this script.')
if args.gen:
if os.path.lexists(TMP_DIR):
die(f'"{TMP_DIR}" must not exist in the current directory.')
gen_files = get_gen_files()
os.mkdir(TMP_DIR, 0o700)
for cmd in MAKE_GEN_CMDS:
cmd_chk(cmd)
cmd_chk(['rsync', '-a', *gen_files, f'{TMP_DIR}/master/'])
last_touch = int(time.time())
# Start by finding all patches so that we can load all possible parents.
patches = sorted(list(get_patch_branches(args.base_branch)))
parent_patch = { }
description = { }
for patch in patches:
branch = f"patch/{args.base_branch}/{patch}"
desc = ''
proc = cmd_pipe(['git', 'diff', '-U1000', f"{args.base_branch}...{branch}", '--', f"PATCH.{patch}"])
in_diff = False
for line in proc.stdout:
if in_diff:
if not re.match(r'^[ +]', line):
continue
line = line[1:]
m = re.search(r'patch -p1 <patches/(\S+)\.diff', line)
if m and m[1] != patch:
parpat = parent_patch[patch] = m[1]
if not parpat in patches:
die(f"Parent of {patch} is not a local branch: {parpat}")
desc += line
elif re.match(r'^@@ ', line):
in_diff = True
description[patch] = desc
proc.communicate()
if args.patch_files: # Limit the list of patches to actually process
valid_patches = patches
patches = [ ]
for fn in args.patch_files:
name = re.sub(r'\.diff$', '', re.sub(r'.+/', '', fn))
if name not in valid_patches:
die(f"Local branch not available for patch: {name}")
patches.append(name)
completed = set()
for patch in patches:
if patch in completed:
continue
if not update_patch(patch):
break
if args.gen:
shutil.rmtree(TMP_DIR)
while last_touch >= int(time.time()):
time.sleep(1)
cmd_chk(['git', 'checkout', starting_branch])
cmd_chk(['packaging/prep-auto-dir'], discard='output')
def update_patch(patch):
global last_touch
completed.add(patch) # Mark it as completed early to short-circuit any (bogus) dependency loops.
parent = parent_patch.get(patch, None)
if parent:
if parent not in completed:
if not update_patch(parent):
return 0
based_on = parent = f"patch/{args.base_branch}/{parent}"
else:
parent = args.base_branch
based_on = master_commit
print(f"======== {patch} ========")
while args.gen and last_touch >= int(time.time()):
time.sleep(1)
branch = f"patch/{args.base_branch}/{patch}"
s = cmd_run(['git', 'checkout', branch])
if s.returncode != 0:
return 0
s = cmd_run(['git', 'merge', based_on])
ok = s.returncode == 0
skip_shell = False
if not ok or args.cmd or args.make or args.shell:
cmd_chk(['packaging/prep-auto-dir'], discard='output')
if not ok:
print(f'"git merge {based_on}" incomplete -- please fix.')
if not run_a_shell(parent, patch):
return 0
if not args.make and not args.cmd:
skip_shell = True
if args.make:
if cmd_run(['packaging/smart-make']).returncode != 0:
if not run_a_shell(parent, patch):
return 0
if not args.cmd:
skip_shell = True
if args.cmd:
if cmd_run(args.cmd).returncode != 0:
if not run_a_shell(parent, patch):
return 0
skip_shell = True
if args.shell and not skip_shell:
if not run_a_shell(parent, patch):
return 0
with open(f"{args.patches_dir}/{patch}.diff", 'w', encoding='utf-8') as fh:
fh.write(description[patch])
fh.write(f"\nbased-on: {based_on}\n")
if args.gen:
gen_files = get_gen_files()
for cmd in MAKE_GEN_CMDS:
cmd_chk(cmd)
cmd_chk(['rsync', '-a', *gen_files, f"{TMP_DIR}/{patch}/"])
else:
gen_files = [ ]
last_touch = int(time.time())
proc = cmd_pipe(['git', 'diff', based_on])
skipping = False
for line in proc.stdout:
if skipping:
if not re.match(r'^diff --git a/', line):
continue
skipping = False
elif re.match(r'^diff --git a/PATCH', line):
skipping = True
continue
if not re.match(r'^index ', line):
fh.write(line)
proc.communicate()
if args.gen:
e_tmp_dir = re.escape(TMP_DIR)
diff_re = re.compile(r'^(diff -Nurp) %s/[^/]+/(.*?) %s/[^/]+/(.*)' % (e_tmp_dir, e_tmp_dir))
minus_re = re.compile(r'^\-\-\- %s/[^/]+/([^\t]+)\t.*' % e_tmp_dir)
plus_re = re.compile(r'^\+\+\+ %s/[^/]+/([^\t]+)\t.*' % e_tmp_dir)
if parent == args.base_branch:
parent_dir = 'master'
else:
m = re.search(r'([^/]+)$', parent)
parent_dir = m[1]
proc = cmd_pipe(['diff', '-Nurp', f"{TMP_DIR}/{parent_dir}", f"{TMP_DIR}/{patch}"])
for line in proc.stdout:
line = diff_re.sub(r'\1 a/\2 b/\3', line)
line = minus_re.sub(r'--- a/\1', line)
line = plus_re.sub(r'+++ b/\1', line)
fh.write(line)
proc.communicate()
return 1
def run_a_shell(parent, patch):
m = re.search(r'([^/]+)$', parent)
parent_dir = m[1]
os.environ['PS1'] = f"[{parent_dir}] {patch}: "
while True:
s = cmd_run([os.environ.get('SHELL', '/bin/sh')])
if s.returncode != 0:
ans = input("Abort? [n/y] ")
if re.match(r'^y', ans, flags=re.I):
return False
continue
cur_branch, is_clean, status_txt = check_git_status(0)
if is_clean:
break
print(status_txt, end='')
cmd_run('rm -f build/*.o build/*/*.o')
return True
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Turn a git branch back into a diff files in the patches dir.", add_help=False)
parser.add_argument('--branch', '-b', dest='base_branch', metavar='BASE_BRANCH', default='master', help="The branch the patch is based on. Default: master.")
parser.add_argument('--skip-check', action='store_true', help="Skip the check that ensures starting with a clean branch.")
parser.add_argument('--make', '-m', action='store_true', help="Run the smart-make script in every patch branch.")
parser.add_argument('--cmd', '-c', help="Run a command in every patch branch.")
parser.add_argument('--shell', '-s', action='store_true', help="Launch a shell for every patch/BASE/* branch updated, not just when a conflict occurs.")
parser.add_argument('--gen', metavar='DIR', nargs='?', const='', help='Include generated files. Optional DIR value overrides the default of using the "patches" dir.')
parser.add_argument('--patches-dir', '-p', metavar='DIR', default='patches', help="Override the location of the rsync-patches dir. Default: patches.")
parser.add_argument('patch_files', metavar='patches/DIFF_FILE', nargs='*', help="Specify what patch diff files to process. Default: all of them.")
parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
args = parser.parse_args()
if args.gen == '':
args.gen = args.patches_dir
elif args.gen is not None:
args.patches_dir = args.gen
main()
# vim: sw=4 et ft=python

414
packaging/release-rsync Executable file
View File

@@ -0,0 +1,414 @@
#!/usr/bin/env -S python3 -B
# This script expects the directory ~/samba-rsync-ftp to exist and to be a
# copy of the /home/ftp/pub/rsync dir on samba.org. When the script is done,
# the git repository in the current directory will be updated, and the local
# ~/samba-rsync-ftp dir will be ready to be rsynced to samba.org. See the
# script samba-rsync for an easy way to initialize the local ftp copy and to
# thereafter update the remote files from your local copy.
# This script also expects to be able to gpg sign the resulting tar files
# using your default gpg key. Make sure that the html download.html file
# has a link to the relevant keys that are authorized to sign the tar files
# and also make sure that the following commands work as expected:
#
# touch TeMp
# gpg --sign TeMp
# gpg --verify TeMp.gpg
# gpg --sign TeMp
# rm TeMp*
#
# The second time you sign the file it should NOT prompt you for your password
# (unless the timeout period has passed). It will prompt about overriding the
# existing TeMp.gpg file, though.
import os, sys, re, argparse, glob, shutil, signal
from datetime import datetime
from getpass import getpass
sys.path = ['packaging'] + sys.path
from pkglib import *
os.environ['LESS'] = 'mqeiXR'; # Make sure that -F is turned off and -R is turned on.
dest = os.environ['HOME'] + '/samba-rsync-ftp'
ORIGINAL_PATH = os.environ['PATH']
def main():
if not os.path.isfile('packaging/release-rsync'):
die('You must run this script from the top of your rsync checkout.')
now = datetime.now()
cl_today = now.strftime('* %a %b %d %Y')
year = now.strftime('%Y')
ztoday = now.strftime('%d %b %Y')
today = ztoday.lstrip('0')
curdir = os.getcwd()
signal.signal(signal.SIGINT, signal_handler)
if cmd_txt_chk(['packaging/prep-auto-dir']).out == '':
die('You must setup an auto-build-save dir to use this script.');
auto_dir, gen_files = get_gen_files(True)
gen_pathnames = [ os.path.join(auto_dir, fn) for fn in gen_files ]
dash_line = '=' * 74
print(f"""\
{dash_line}
== This will release a new version of rsync onto an unsuspecting world. ==
{dash_line}
""")
with open('build/rsync.1') as fh:
for line in fh:
if line.startswith(r'.\" prefix='):
doc_prefix = line.split('=')[1].strip()
if doc_prefix != '/usr':
warn(f"*** The documentation was built with prefix {doc_prefix} instead of /usr ***")
die("*** Read the md2man script for a way to override this. ***")
break
if line.startswith('.P'):
die("Failed to find the prefix comment at the start of the rsync.1 manpage.")
if not os.path.isdir(dest):
die(dest, "dest does not exist")
if not os.path.isdir('.git'):
die("There is no .git dir in the current directory.")
if os.path.lexists('a'):
die('"a" must not exist in the current directory.')
if os.path.lexists('b'):
die('"b" must not exist in the current directory.')
if os.path.lexists('patches.gen'):
die('"patches.gen" must not exist in the current directory.')
check_git_state(args.master_branch, True, 'patches')
curversion = get_rsync_version()
# All version values are strings!
lastversion, last_protocol_version, pdate = get_NEWS_version_info()
protocol_version, subprotocol_version = get_protocol_versions()
version = curversion
m = re.search(r'pre(\d+)', version)
if m:
version = re.sub(r'pre\d+', 'pre' + str(int(m[1]) + 1), version)
else:
version = version.replace('dev', 'pre1')
ans = input(f"Please enter the version number of this release: [{version}] ")
if ans == '.':
version = re.sub(r'pre\d+', '', version)
elif ans != '':
version = ans
if not re.match(r'^[\d.]+(pre\d+)?$', version):
die(f'Invalid version: "{version}"')
v_ver = 'v' + version
rsync_ver = 'rsync-' + version
if os.path.lexists(rsync_ver):
die(f'"{rsync_ver}" must not exist in the current directory.')
out = cmd_txt_chk(['git', 'tag', '-l', v_ver]).out
if out != '':
print(f"Tag {v_ver} already exists.")
ans = input("\nDelete tag or quit? [Q/del] ")
if not re.match(r'^del', ans, flags=re.I):
die("Aborted")
cmd_chk(['git', 'tag', '-d', v_ver])
if os.path.isdir('patches/.git'):
cmd_chk(f"cd patches && git tag -d '{v_ver}'")
version = re.sub(r'[-.]*pre[-.]*', 'pre', version)
if 'pre' in version and not curversion.endswith('dev'):
lastversion = curversion
ans = input(f"Enter the previous version to produce a patch against: [{lastversion}] ")
if ans != '':
lastversion = ans
lastversion = re.sub(r'[-.]*pre[-.]*', 'pre', lastversion)
rsync_lastver = 'rsync-' + lastversion
if os.path.lexists(rsync_lastver):
die(f'"{rsync_lastver}" must not exist in the current directory.')
m = re.search(r'(pre\d+)', version)
pre = m[1] if m else ''
release = '0.1' if pre else '1'
ans = input(f"Please enter the RPM release number of this release: [{release}] ")
if ans != '':
release = ans
if pre:
release += '.' + pre
finalversion = re.sub(r'pre\d+', '', version)
proto_changed = protocol_version != last_protocol_version
if proto_changed:
if finalversion in pdate:
proto_change_date = pdate[finalversion]
else:
while True:
ans = input("On what date did the protocol change to {protocol_version} get checked in? (dd Mmm yyyy) ")
if re.match(r'^\d\d \w\w\w \d\d\d\d$', ans):
break
proto_change_date = ans
else:
proto_change_date = ' ' * 11
if 'pre' in lastversion:
if not pre:
die("You should not diff a release version against a pre-release version.")
srcdir = srcdiffdir = lastsrcdir = 'src-previews'
skipping = ' ** SKIPPING **'
elif pre:
srcdir = srcdiffdir = 'src-previews'
lastsrcdir = 'src'
skipping = ' ** SKIPPING **'
else:
srcdir = lastsrcdir = 'src'
srcdiffdir = 'src-diffs'
skipping = ''
print(f"""
{dash_line}
version is "{version}"
lastversion is "{lastversion}"
dest is "{dest}"
curdir is "{curdir}"
srcdir is "{srcdir}"
srcdiffdir is "{srcdiffdir}"
lastsrcdir is "{lastsrcdir}"
release is "{release}"
About to:
- tweak SUBPROTOCOL_VERSION in rsync.h, if needed
- tweak the version in version.h and the spec files
- tweak NEWS.md to ensure header values are correct
- generate configure.sh, config.h.in, and proto.h
- page through the differences
""")
ans = input("<Press Enter to continue> ")
specvars = {
'Version:': finalversion,
'Release:': release,
'%define fullversion': f'%{{version}}{pre}',
'Released': version + '.',
'%define srcdir': srcdir,
}
tweak_files = 'version.h rsync.h'.split()
tweak_files += glob.glob('packaging/*.spec')
tweak_files += glob.glob('packaging/*/*.spec')
for fn in tweak_files:
with open(fn, 'r', encoding='utf-8') as fh:
old_txt = txt = fh.read()
if fn == 'version.h':
x_re = re.compile(r'^(#define RSYNC_VERSION).*', re.M)
msg = f"Unable to update RSYNC_VERSION in {fn}"
txt = replace_or_die(x_re, r'\1 "%s"' % version, txt, msg)
elif '.spec' in fn:
for var, val in specvars.items():
x_re = re.compile(r'^%s .*' % re.escape(var), re.M)
txt = replace_or_die(x_re, var + ' ' + val, txt, f"Unable to update {var} in {fn}")
x_re = re.compile(r'^\* \w\w\w \w\w\w \d\d \d\d\d\d (.*)', re.M)
txt = replace_or_die(x_re, r'%s \1' % cl_today, txt, f"Unable to update ChangeLog header in {fn}")
elif fn == 'rsync.h':
x_re = re.compile('(#define\s+SUBPROTOCOL_VERSION)\s+(\d+)')
repl = lambda m: m[1] + ' ' + ('0' if not pre or not proto_changed else '1' if m[2] == '0' else m[2])
txt = replace_or_die(x_re, repl, txt, f"Unable to find SUBPROTOCOL_VERSION define in {fn}")
elif fn == 'NEWS.md':
efv = re.escape(finalversion)
x_re = re.compile(r'^# NEWS for rsync %s \(UNRELEASED\)\s+## Changes in this version:\n' % efv
+ r'(\n### PROTOCOL NUMBER:\s+- The protocol number was changed to \d+\.\n)?')
rel_day = 'UNRELEASED' if pre else today
repl = (f'# NEWS for rsync {finalversion} ({rel_day})\n\n'
+ '## Changes in this version:\n')
if proto_changed:
repl += f'\n### PROTOCOL NUMBER:\n\n - The protocol number was changed to {protocol_version}.\n'
good_top = re.sub(r'\(.*?\)', '(UNRELEASED)', repl, 1)
msg = f"The top lines of {fn} are not in the right format. It should be:\n" + good_top
txt = replace_or_die(x_re, repl, txt, msg)
x_re = re.compile(r'^(\| )(\S{2} \S{3} \d{4})(\s+\|\s+%s\s+\| ).{11}(\s+\| )\S{2}(\s+\|+)$' % efv, re.M)
repl = lambda m: m[1] + (m[2] if pre else ztoday) + m[3] + proto_change_date + m[4] + protocol_version + m[5]
txt = replace_or_die(x_re, repl, txt, f'Unable to find "| ?? ??? {year} | {finalversion} | ... |" line in {fn}')
else:
die(f"Unrecognized file in tweak_files: {fn}")
if txt != old_txt:
print(f"Updating {fn}")
with open(fn, 'w', encoding='utf-8') as fh:
fh.write(txt)
cmd_chk(['packaging/year-tweak'])
print(dash_line)
cmd_run("git diff".split())
srctar_name = f"{rsync_ver}.tar.gz"
pattar_name = f"rsync-patches-{version}.tar.gz"
diff_name = f"{rsync_lastver}-{version}.diffs.gz"
srctar_file = os.path.join(dest, srcdir, srctar_name)
pattar_file = os.path.join(dest, srcdir, pattar_name)
diff_file = os.path.join(dest, srcdiffdir, diff_name)
lasttar_file = os.path.join(dest, lastsrcdir, rsync_lastver + '.tar.gz')
print(f"""\
{dash_line}
About to:
- git commit all changes
- run a full build, ensuring that the manpages & configure.sh are up-to-date
- merge the {args.master_branch} branch into the patch/{args.master_branch}/* branches
- update the files in the "patches" dir and OPTIONALLY (if you type 'y') to
run patch-update with the --make option (which opens a shell on error)
""")
ans = input("<Press Enter OR 'y' to continue> ")
s = cmd_run(['git', 'commit', '-a', '-m', f'Preparing for release of {version} [buildall]'])
if s.returncode:
die('Aborting')
cmd_chk('touch configure.ac && packaging/smart-make && make gen')
print('Creating any missing patch branches.')
s = cmd_run(f'packaging/branch-from-patch --branch={args.master_branch} --add-missing')
if s.returncode:
die('Aborting')
print('Updating files in "patches" dir ...')
s = cmd_run(f'packaging/patch-update --branch={args.master_branch}')
if s.returncode:
die('Aborting')
if re.match(r'^y', ans, re.I):
print(f'\nRunning smart-make on all "patch/{args.master_branch}/*" branches ...')
cmd_run(f"packaging/patch-update --branch={args.master_branch} --skip-check --make")
if os.path.isdir('patches/.git'):
s = cmd_run(f"cd patches && git commit -a -m 'The patches for {version}.'")
if s.returncode:
die('Aborting')
print(f"""\
{dash_line}
About to:
- create signed tag for this release: {v_ver}
- create release diffs, "{diff_name}"
- create release tar, "{srctar_name}"
- generate {rsync_ver}/patches/* files
- create patches tar, "{pattar_name}"
- update top-level README.md, NEWS.md, TODO, and ChangeLog
- update top-level rsync*.html manpages
- gpg-sign the release files
- update hard-linked top-level release files{skipping}
""")
ans = input("<Press Enter to continue> ")
# TODO: is there a better way to ensure that our passphrase is in the agent?
cmd_run("touch TeMp; gpg --sign TeMp; rm TeMp*")
out = cmd_txt(f"git tag -s -m 'Version {version}.' {v_ver}", capture='combined').out
print(out, end='')
if 'bad passphrase' in out or 'failed' in out:
die('Aborting')
if os.path.isdir('patches/.git'):
out = cmd_txt(f"cd patches && git tag -s -m 'Version {version}.' {v_ver}", capture='combined').out
print(out, end='')
if 'bad passphrase' in out or 'failed' in out:
die('Aborting')
os.environ['PATH'] = ORIGINAL_PATH
# Extract the generated files from the old tar.
tweaked_gen_files = [ os.path.join(rsync_lastver, fn) for fn in gen_files ]
cmd_run(['tar', 'xzf', lasttar_file, *tweaked_gen_files])
os.rename(rsync_lastver, 'a')
print(f"Creating {diff_file} ...")
cmd_chk(['rsync', '-a', *gen_pathnames, 'b/'])
sed_script = r's:^((---|\+\+\+) [ab]/[^\t]+)\t.*:\1:' # CAUTION: must not contain any single quotes!
cmd_chk(f"(git diff v{lastversion} {v_ver} -- ':!.github'; diff -upN a b | sed -r '{sed_script}') | gzip -9 >{diff_file}")
shutil.rmtree('a')
os.rename('b', rsync_ver)
print(f"Creating {srctar_file} ...")
cmd_chk(f"git archive --format=tar --prefix={rsync_ver}/ {v_ver} | tar xf -")
cmd_chk(f"support/git-set-file-times --quiet --prefix={rsync_ver}/")
cmd_chk(['fakeroot', 'tar', 'czf', srctar_file, '--exclude=.github', rsync_ver])
shutil.rmtree(rsync_ver)
print(f'Updating files in "{rsync_ver}/patches" dir ...')
os.mkdir(rsync_ver, 0o755)
os.mkdir(f"{rsync_ver}/patches", 0o755)
cmd_chk(f"packaging/patch-update --skip-check --branch={args.master_branch} --gen={rsync_ver}/patches".split())
print(f"Creating {pattar_file} ...")
cmd_chk(['fakeroot', 'tar', 'chzf', pattar_file, rsync_ver + '/patches'])
shutil.rmtree(rsync_ver)
print(f"Updating the other files in {dest} ...")
md_files = 'README.md NEWS.md INSTALL.md'.split()
html_files = [ fn for fn in gen_pathnames if fn.endswith('.html') ]
cmd_chk(['rsync', '-a', *md_files, *html_files, dest])
cmd_chk(["./md-convert", "--dest", dest, *md_files])
cmd_chk(f"git log --name-status | gzip -9 >{dest}/ChangeLog.gz")
for fn in (srctar_file, pattar_file, diff_file):
asc_fn = fn + '.asc'
if os.path.lexists(asc_fn):
os.unlink(asc_fn)
res = cmd_run(['gpg', '--batch', '-ba', fn])
if res.returncode != 0 and res.returncode != 2:
die("gpg signing failed")
if not pre:
for find in f'{dest}/rsync-*.gz {dest}/rsync-*.asc {dest}/src-previews/rsync-*diffs.gz*'.split():
for fn in glob.glob(find):
os.unlink(fn)
top_link = [
srctar_file, f"{srctar_file}.asc",
pattar_file, f"{pattar_file}.asc",
diff_file, f"{diff_file}.asc",
]
for fn in top_link:
os.link(fn, re.sub(r'/src(-\w+)?/', '/', fn))
print(f"""\
{dash_line}
Local changes are done. When you're satisfied, push the git repository
and rsync the release files. Remember to announce the release on *BOTH*
rsync-announce@lists.samba.org and rsync@lists.samba.org (and the web)!
""")
def replace_or_die(regex, repl, txt, die_msg):
m = regex.search(txt)
if not m:
die(die_msg)
return regex.sub(repl, txt, 1)
def signal_handler(sig, frame):
die("\nAborting due to SIGINT.")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Prepare a new release of rsync in the git repo & ftp dir.", add_help=False)
parser.add_argument('--branch', '-b', dest='master_branch', default='master', help="The branch to release. Default: master.")
parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
args = parser.parse_args()
main()
# vim: sw=4 et ft=python

View File

@@ -1,703 +0,0 @@
#!/usr/bin/env python3
# Step-based release script for rsync. Each step is a separate invocation
# selected by a --step-N-XX option, so the maintainer drives the release
# manually one piece at a time.
#
# All persistent state and working files live in ../release/ (a sibling of
# the rsync git checkout):
#
# ../release/rsync-ftp/ mirror of samba.org:/home/ftp/pub/rsync
# ../release/rsync-html/ git checkout of rsync-web (the html site)
# ../release/work/ scratch space for tarball / diff staging
# ../release/release-state.json info shared between steps
#
# The rsync-patches archive is no longer maintained and has been dropped.
#
# Run "packaging/release.py --list" to see the step list.
import os, sys, re, argparse, glob, shutil, json, signal, subprocess
from datetime import datetime
sys.path = ['packaging'] + sys.path
from pkglib import (
warn, die, cmd_run, cmd_chk, cmd_txt, cmd_txt_chk, cmd_pipe,
check_git_state, get_rsync_version,
get_NEWS_version_info, get_protocol_versions,
)
# ---------- Paths ----------
RELEASE_DIR = os.path.realpath('../release')
FTP_DIR = os.path.join(RELEASE_DIR, 'rsync-ftp')
HTML_DIR = os.path.join(RELEASE_DIR, 'rsync-html')
WORK_DIR = os.path.join(RELEASE_DIR, 'work')
STATE_FILE = os.path.join(RELEASE_DIR, 'release-state.json')
# Local rsync-web checkout (sibling of rsync-git) is the source-of-truth for
# the git-tracked html content. The maintainer pulls/commits/pushes there;
# step-1-fetch just snapshots it into HTML_DIR for the release flow.
HTML_SRC = os.path.realpath('../rsync-web')
FTP_REMOTE_PATH = '/home/ftp/pub/rsync'
HTML_REMOTE_PATH = '/home/httpd/html/rsync'
# Files that ./configure + make produce and that the release tarball / diff
# need to bundle alongside the git-tracked source. Mirrors the GENFILES
# definition in Makefile.in (with rrsync.1{,.html} since we always configure
# --with-rrsync in --step-4-build).
GEN_FILES = [
'configure.sh',
'aclocal.m4',
'config.h.in',
'rsync.1', 'rsync.1.html',
'rsync-ssl.1', 'rsync-ssl.1.html',
'rsyncd.conf.5', 'rsyncd.conf.5.html',
'rrsync.1', 'rrsync.1.html',
]
# ---------- Step registry ----------
STEPS = [
('step-1-fetch', 'mirror ../release/rsync-ftp from samba.org and snapshot ../release/rsync-html from ../rsync-web'),
('step-2-prepare', 'gather release info interactively and write release-state.json'),
('step-3-tweak', 'update version.h, rsync.h, NEWS.md, and packaging/*.spec'),
('step-4-build', 'run smart-make + make gen'),
('step-5-commit', 'git commit -a (commit the prepared release changes)'),
('step-6-tag', 'create the gpg-signed git tag'),
('step-7-tarball', 'build the source tarball and diffs.gz against the previous release'),
('step-8-update-ftp', 'refresh README/NEWS/INSTALL/html in the ftp dir, regen ChangeLog.gz, gpg-sign tarballs'),
('step-9-toplinks', 'hard-link top-level release files (final releases only)'),
('step-10-push-ftp', 'rsync ../release/rsync-ftp/ to samba.org'),
('step-11-push-html', 'rsync ../release/rsync-html/ to samba.org (after any manual edits)'),
('step-12-push-git', 'print the git push commands for you to run'),
]
STEP_FLAGS = [s[0] for s in STEPS]
DASH_LINE = '=' * 74
# ---------- State helpers ----------
def load_state():
if not os.path.isfile(STATE_FILE):
die(f"{STATE_FILE} not found. Run --step-2-prepare first.")
with open(STATE_FILE, 'r', encoding='utf-8') as fh:
return json.load(fh)
def save_state(state):
os.makedirs(RELEASE_DIR, exist_ok=True)
with open(STATE_FILE, 'w', encoding='utf-8') as fh:
json.dump(state, fh, indent=2, sort_keys=True)
fh.write('\n')
def require_samba_host():
host = os.environ.get('RSYNC_SAMBA_HOST', '')
if not host.endswith('.samba.org'):
die("Set RSYNC_SAMBA_HOST in your environment to the samba hostname (e.g. hr3.samba.org).")
return host
def require_top_of_checkout():
if not os.path.isfile('packaging/release.py'):
die("Run this script from the top of your rsync checkout.")
if not os.path.isdir('.git'):
die("There is no .git dir in the current directory.")
def replace_or_die(regex, repl, txt, die_msg):
m = regex.search(txt)
if not m:
die(die_msg)
return regex.sub(repl, txt, 1)
def section(title):
print(f"\n{DASH_LINE}\n== {title}\n{DASH_LINE}")
def confirm(prompt, default_no=True):
suffix = '[n] ' if default_no else '[y] '
ans = input(f"{prompt} {suffix}").strip().lower()
if default_no:
return ans.startswith('y')
return ans == '' or ans.startswith('y')
# ---------- Step 1: fetch ftp + html ----------
def step_1_fetch(args):
host = require_samba_host()
os.makedirs(RELEASE_DIR, exist_ok=True)
os.makedirs(WORK_DIR, exist_ok=True)
section(f"Fetching ftp dir into {FTP_DIR}")
if not os.path.isdir(FTP_DIR):
os.makedirs(FTP_DIR)
# The .filt file lives in the ftp dir on the server; mirror down using the
# transmitted filter, falling back to no filter on the very first pull.
filt = os.path.join(FTP_DIR, '.filt')
if os.path.exists(filt):
opts = ['-aivOHP', f'-f:_{filt}']
else:
opts = ['-aivOHP']
cmd_chk(['rsync', *opts, f'{host}:{FTP_REMOTE_PATH}/', f'{FTP_DIR}/'])
section(f"Snapshotting html dir from {HTML_SRC} into {HTML_DIR}")
if not os.path.isdir(HTML_SRC):
die(f"{HTML_SRC} not found. Clone the rsync-web repo there first.")
if not os.path.isdir(os.path.join(HTML_SRC, '.git')):
die(f"{HTML_SRC} exists but is not a git checkout.")
print(f"(Make sure {HTML_SRC} is up to date — this script does not 'git pull' for you.)")
os.makedirs(HTML_DIR, exist_ok=True)
cmd_chk(['rsync', '-aiv', '--exclude=/.git',
f'{HTML_SRC}/', f'{HTML_DIR}/'])
# Then mirror non-git html content from the server (mirroring samba-rsync's
# behavior: skip files that the html git already provides).
filt = os.path.join(HTML_DIR, 'filt')
if os.path.exists(filt):
tmp_filt = os.path.join(HTML_DIR, 'tmp-filt')
cmd_chk(f"sed -n -e 's/[-P]/H/p' '{filt}' >'{tmp_filt}'")
cmd_chk(['rsync', '-aivOHP', f'-f._{tmp_filt}',
f'{host}:{HTML_REMOTE_PATH}/', f'{HTML_DIR}/'])
os.unlink(tmp_filt)
print(f"\nFetch complete. Local dirs are now in {RELEASE_DIR}.")
# ---------- Step 2: prepare ----------
def step_2_prepare(args):
require_top_of_checkout()
os.makedirs(RELEASE_DIR, exist_ok=True)
if not os.path.isdir(FTP_DIR):
die(f"{FTP_DIR} does not exist. Run --step-1-fetch first.")
now = datetime.now().astimezone()
cl_today = now.strftime('* %a %b %d %Y')
year = now.strftime('%Y')
ztoday = now.strftime('%d %b %Y')
today = ztoday.lstrip('0')
tz_now = now.strftime('%z')
tz_num = tz_now[0:1].replace('+', '') + str(float(tz_now[1:3]) + float(tz_now[3:]) / 60)
curversion = get_rsync_version()
lastversion, last_protocol_version, pdate = get_NEWS_version_info()
protocol_version, subprotocol_version = get_protocol_versions()
# Default next version: bump preN, or move dev -> pre1.
version = curversion
m = re.search(r'pre(\d+)', version)
if m:
version = re.sub(r'pre\d+', 'pre' + str(int(m[1]) + 1), version)
else:
version = version.replace('dev', 'pre1')
print(f"\nCurrent version (version.h): {curversion}")
print(f"Last released version (NEWS.md): {lastversion}")
print(f"Current protocol version: {protocol_version} (last released: {last_protocol_version})")
ans = input(f"\nVersion to release [{version}, '.' to drop the preN suffix]: ").strip()
if ans == '.':
version = re.sub(r'pre\d+', '', version)
elif ans:
version = ans
if not re.match(r'^[\d.]+(pre\d+)?$', version):
die(f'Invalid version: "{version}"')
version = re.sub(r'[-.]*pre[-.]*', 'pre', version)
if 'pre' in version and not curversion.endswith('dev'):
lastversion = curversion
ans = input(f"Previous version to diff against [{lastversion}]: ").strip()
if ans:
lastversion = ans
lastversion = re.sub(r'[-.]*pre[-.]*', 'pre', lastversion)
m = re.search(r'(pre\d+)', version)
pre = m[1] if m else ''
finalversion = re.sub(r'pre\d+', '', version)
release = '0.1' if pre else '1'
ans = input(f"RPM release number [{release}]: ").strip()
if ans:
release = ans
if pre:
release += '.' + pre
proto_changed = protocol_version != last_protocol_version
if proto_changed:
if finalversion in pdate:
proto_change_date = pdate[finalversion]
else:
while True:
ans = input(f"Date the protocol changed to {protocol_version} (dd Mmm yyyy): ").strip()
if re.match(r'^\d\d \w\w\w \d\d\d\d$', ans):
break
proto_change_date = ans
else:
proto_change_date = ' ' * 11
if 'pre' in lastversion:
if not pre:
die("Refusing to diff a release version against a pre-release version.")
srcdir = srcdiffdir = lastsrcdir = 'src-previews'
elif pre:
srcdir = srcdiffdir = 'src-previews'
lastsrcdir = 'src'
else:
srcdir = lastsrcdir = 'src'
srcdiffdir = 'src-diffs'
state = {
'version': version,
'lastversion': lastversion,
'finalversion': finalversion,
'pre': pre,
'release': release,
'protocol_version': protocol_version,
'subprotocol_version': subprotocol_version,
'proto_changed': proto_changed,
'proto_change_date': proto_change_date,
'srcdir': srcdir,
'srcdiffdir': srcdiffdir,
'lastsrcdir': lastsrcdir,
'today': today,
'ztoday': ztoday,
'cl_today': cl_today,
'year': year,
'tz_num': tz_num,
'master_branch': args.master_branch,
}
save_state(state)
section("Release info")
for k in ('version', 'lastversion', 'release', 'srcdir', 'srcdiffdir', 'lastsrcdir',
'protocol_version', 'proto_changed', 'proto_change_date'):
print(f" {k}: {state[k]}")
print(f"\nWrote {STATE_FILE}. Re-run --step-2-prepare to change anything.")
# ---------- Step 3: tweak version files ----------
def step_3_tweak(args):
require_top_of_checkout()
state = load_state()
version = state['version']
finalversion = state['finalversion']
pre = state['pre']
release = state['release']
today = state['today']
ztoday = state['ztoday']
cl_today = state['cl_today']
year = state['year']
tz_num = state['tz_num']
proto_changed = state['proto_changed']
proto_change_date = state['proto_change_date']
protocol_version = state['protocol_version']
srcdir = state['srcdir']
specvars = {
'Version:': finalversion,
'Release:': release,
'%define fullversion': f'%{{version}}{pre}',
'Released': version + '.',
'%define srcdir': srcdir,
}
tweak_files = ['version.h', 'rsync.h', 'NEWS.md']
tweak_files += glob.glob('packaging/*.spec')
tweak_files += glob.glob('packaging/*/*.spec')
for fn in tweak_files:
with open(fn, 'r', encoding='utf-8') as fh:
old_txt = txt = fh.read()
if fn == 'version.h':
x_re = re.compile(r'^(#define RSYNC_VERSION).*', re.M)
txt = replace_or_die(x_re, r'\1 "%s"' % version, txt,
f"Unable to update RSYNC_VERSION in {fn}")
x_re = re.compile(r'^(#define MAINTAINER_TZ_OFFSET).*', re.M)
txt = replace_or_die(x_re, r'\1 ' + tz_num, txt,
f"Unable to update MAINTAINER_TZ_OFFSET in {fn}")
elif fn == 'rsync.h':
x_re = re.compile(r'(#define\s+SUBPROTOCOL_VERSION)\s+(\d+)')
repl = lambda m: m[1] + ' ' + (
'0' if not pre or not proto_changed
else '1' if m[2] == '0'
else m[2])
txt = replace_or_die(x_re, repl, txt,
f"Unable to find SUBPROTOCOL_VERSION in {fn}")
elif fn == 'NEWS.md':
efv = re.escape(finalversion)
x_re = re.compile(
r'^# NEWS for rsync %s \(UNRELEASED\)\s+## Changes in this version:\n' % efv
+ r'(\n### PROTOCOL NUMBER:\s+- The protocol number was changed to \d+\.\n)?')
rel_day = 'UNRELEASED' if pre else today
repl = (f'# NEWS for rsync {finalversion} ({rel_day})\n\n'
+ '## Changes in this version:\n')
if proto_changed:
repl += f'\n### PROTOCOL NUMBER:\n\n - The protocol number was changed to {protocol_version}.\n'
good_top = re.sub(r'\(.*?\)', '(UNRELEASED)', repl, 1)
msg = (f"The top of {fn} is not in the right format. It should be:\n" + good_top)
txt = replace_or_die(x_re, repl, txt, msg)
x_re = re.compile(
r'^(\| )(\S{2} \S{3} \d{4})(\s+\|\s+%s\s+\| ).{11}(\s+\| )\S{2}(\s+\|+)$' % efv,
re.M)
repl = lambda m: (m[1] + (m[2] if pre else ztoday) + m[3]
+ proto_change_date + m[4] + protocol_version + m[5])
txt = replace_or_die(x_re, repl, txt,
f'Unable to find "| ?? ??? {year} | {finalversion} | ... |" line in {fn}')
elif '.spec' in fn:
for var, val in specvars.items():
x_re = re.compile(r'^%s .*' % re.escape(var), re.M)
txt = replace_or_die(x_re, var + ' ' + val, txt,
f"Unable to update {var} in {fn}")
x_re = re.compile(r'^\* \w\w\w \w\w\w \d\d \d\d\d\d (.*)', re.M)
txt = replace_or_die(x_re, r'%s \1' % cl_today, txt,
f"Unable to update ChangeLog header in {fn}")
else:
die(f"Unrecognized file in tweak_files: {fn}")
if txt != old_txt:
print(f"Updating {fn}")
with open(fn, 'w', encoding='utf-8') as fh:
fh.write(txt)
cmd_chk(['packaging/year-tweak'])
section("git diff after tweaks")
cmd_run(['git', '--no-pager', 'diff'])
# ---------- Step 4: build ----------
def step_4_build(args):
require_top_of_checkout()
load_state() # just to ensure we've prepared
section("Running prepare-source + configure --prefix=/usr --with-rrsync + make + make gen")
# Always re-prepare so configure.sh is current; we run configure ourselves
# with the release-required flags rather than relying on the cached
# config.status (which may have been produced with different options).
if os.path.isfile('.fetch'):
cmd_chk(['./prepare-source', 'fetch'])
else:
cmd_chk(['./prepare-source'])
cmd_chk(['./configure', '--prefix=/usr', '--with-rrsync'])
cmd_chk(['make'])
cmd_chk(['make', 'gen'])
# ---------- Step 5: commit ----------
def step_5_commit(args):
require_top_of_checkout()
state = load_state()
version = state['version']
section("git status")
cmd_run(['git', 'status'])
if not confirm("Commit all current changes with the release message?"):
die("Aborted.")
cmd_chk(['git', 'commit', '-a', '-m', f'Preparing for release of {version} [buildall]'])
# ---------- Step 6: tag ----------
def step_6_tag(args):
require_top_of_checkout()
state = load_state()
version = state['version']
v_ver = 'v' + version
out = cmd_txt_chk(['git', 'tag', '-l', v_ver]).out
if out.strip():
if not confirm(f"Tag {v_ver} already exists. Delete and recreate?"):
die("Aborted.")
cmd_chk(['git', 'tag', '-d', v_ver])
# Prime the gpg agent so the actual tag signing won't prompt.
section("Priming gpg agent")
cmd_run("touch TeMp; gpg --sign TeMp; rm -f TeMp TeMp.gpg")
section(f"Creating signed tag {v_ver}")
out = cmd_txt(['git', 'tag', '-s', '-m', f'Version {version}.', v_ver],
capture='combined').out
print(out, end='')
if 'bad passphrase' in out.lower() or 'failed' in out.lower():
die("Tag creation failed.")
# ---------- Step 7: tarball + diff ----------
def step_7_tarball(args):
require_top_of_checkout()
state = load_state()
version = state['version']
lastversion = state['lastversion']
pre = state['pre']
srcdir = state['srcdir']
srcdiffdir = state['srcdiffdir']
lastsrcdir = state['lastsrcdir']
rsync_ver = 'rsync-' + version
rsync_lastver = 'rsync-' + lastversion
v_ver = 'v' + version
srctar_name = f"{rsync_ver}.tar.gz"
diff_name = f"{rsync_lastver}-{version}.diffs.gz"
srctar_file = os.path.join(FTP_DIR, srcdir, srctar_name)
diff_file = os.path.join(FTP_DIR, srcdiffdir, diff_name)
lasttar_file = os.path.join(FTP_DIR, lastsrcdir, rsync_lastver + '.tar.gz')
for d in (os.path.dirname(srctar_file), os.path.dirname(diff_file)):
os.makedirs(d, exist_ok=True)
if not os.path.isfile(lasttar_file):
die(f"Previous tarball not found: {lasttar_file}")
# Stage in ../release/work to keep the source checkout clean.
if os.path.isdir(WORK_DIR):
shutil.rmtree(WORK_DIR)
os.makedirs(WORK_DIR)
a_dir = os.path.join(WORK_DIR, 'a')
b_dir = os.path.join(WORK_DIR, 'b')
# Extract gen files from the previous tarball into work/a/.
tweaked_gen_files = [os.path.join(rsync_lastver, fn) for fn in GEN_FILES]
cmd_chk(['tar', '-C', WORK_DIR, '-xzf', lasttar_file, *tweaked_gen_files])
os.rename(os.path.join(WORK_DIR, rsync_lastver), a_dir)
# Copy current gen files (built in the top-level checkout) into work/b/.
os.makedirs(b_dir)
cmd_chk(['rsync', '-a', *GEN_FILES, b_dir + '/'])
section(f"Creating {diff_file}")
sed_script = r's:^((---|\+\+\+) [ab]/[^\t]+)\t.*:\1:' # no single quotes!
cmd_chk(
f"(git diff v{lastversion} {v_ver} -- ':!.github'; "
f"diff -upN {a_dir} {b_dir} | sed -r '{sed_script}') | gzip -9 >{diff_file}")
section(f"Creating {srctar_file}")
# Reuse work/b/ (which already holds the fresh gen files) as the release
# staging dir, then let "git archive" overlay the git-tracked source files
# on top. That way the tarball ends up with both gen files and source.
rsync_ver_dir = os.path.join(WORK_DIR, rsync_ver)
shutil.rmtree(a_dir)
os.rename(b_dir, rsync_ver_dir)
cmd_chk(f"git archive --format=tar --prefix={rsync_ver}/ {v_ver} | "
f"tar -C {WORK_DIR} -xf -")
cmd_chk(f"support/git-set-file-times --quiet --prefix={rsync_ver_dir}/")
cmd_chk(['fakeroot', 'tar', '-C', WORK_DIR, '-czf', srctar_file,
'--exclude=.github', rsync_ver])
# Leave staging in place; --step-8-update-ftp does its own thing.
print(f"\nCreated:\n {srctar_file}\n {diff_file}")
# ---------- Step 8: update ftp ----------
def step_8_update_ftp(args):
require_top_of_checkout()
state = load_state()
version = state['version']
lastversion = state['lastversion']
srcdir = state['srcdir']
srcdiffdir = state['srcdiffdir']
rsync_ver = 'rsync-' + version
rsync_lastver = 'rsync-' + lastversion
srctar_file = os.path.join(FTP_DIR, srcdir, f"{rsync_ver}.tar.gz")
diff_file = os.path.join(FTP_DIR, srcdiffdir,
f"{rsync_lastver}-{version}.diffs.gz")
section(f"Refreshing top-of-tree files in {FTP_DIR}")
md_files = ['README.md', 'NEWS.md', 'INSTALL.md']
html_files = [fn for fn in GEN_FILES if fn.endswith('.html')]
cmd_chk(['rsync', '-a', *md_files, *html_files, FTP_DIR + '/'])
cmd_chk(['./md-convert', '--dest', FTP_DIR, *md_files])
section(f"Regenerating {FTP_DIR}/ChangeLog.gz")
cmd_chk(f"git log --name-status | gzip -9 >{FTP_DIR}/ChangeLog.gz")
# Prime gpg agent and then sign the tar + diff.
section("Priming gpg agent")
cmd_run("touch TeMp; gpg --sign TeMp; rm -f TeMp TeMp.gpg")
for fn in (srctar_file, diff_file):
if not os.path.isfile(fn):
die(f"Missing file to sign: {fn}. Did --step-7-tarball run successfully?")
asc_fn = fn + '.asc'
if os.path.lexists(asc_fn):
os.unlink(asc_fn)
section(f"GPG-signing {fn}")
res = cmd_run(['gpg', '--batch', '-ba', fn])
if res.returncode not in (0, 2):
die("gpg signing failed.")
# ---------- Step 9: top-level hard links ----------
def step_9_toplinks(args):
require_top_of_checkout()
state = load_state()
pre = state['pre']
if pre:
print("Skipping: pre-releases do not get top-level hard links.")
return
version = state['version']
lastversion = state['lastversion']
srcdir = state['srcdir']
srcdiffdir = state['srcdiffdir']
rsync_ver = 'rsync-' + version
rsync_lastver = 'rsync-' + lastversion
srctar_file = os.path.join(FTP_DIR, srcdir, f"{rsync_ver}.tar.gz")
diff_file = os.path.join(FTP_DIR, srcdiffdir,
f"{rsync_lastver}-{version}.diffs.gz")
section("Removing stale top-level rsync-* files")
for find in [f'{FTP_DIR}/rsync-*.gz',
f'{FTP_DIR}/rsync-*.asc',
f'{FTP_DIR}/src-previews/rsync-*diffs.gz*']:
for fn in glob.glob(find):
os.unlink(fn)
top_link = [
srctar_file, srctar_file + '.asc',
diff_file, diff_file + '.asc',
]
for fn in top_link:
target = re.sub(r'/src(-\w+)?/', '/', fn)
if os.path.lexists(target):
os.unlink(target)
os.link(fn, target)
print(f" linked {target}")
# ---------- Step 10: push ftp ----------
def step_10_push_ftp(args):
host = require_samba_host()
if not os.path.isdir(FTP_DIR):
die(f"{FTP_DIR} does not exist. Run --step-1-fetch first.")
section(f"rsync ftp dir to {host}")
rsync_with_confirm(['-aivOHP', '--chown=:rsync', '--del',
f'-f._{os.path.join(FTP_DIR, ".filt")}',
f'{FTP_DIR}/', f'{host}:{FTP_REMOTE_PATH}/'])
# ---------- Step 11: push html ----------
def step_11_push_html(args):
host = require_samba_host()
if not os.path.isdir(HTML_DIR):
die(f"{HTML_DIR} does not exist. Run --step-1-fetch first.")
section(f"rsync html dir to {host}")
filt = os.path.join(HTML_DIR, 'filt')
rsync_with_confirm(['-aivOHP', '--chown=:rsync', '--del',
f'-f._{filt}',
f'{HTML_DIR}/', f'{host}:{HTML_REMOTE_PATH}/'])
# ---------- Step 12: print push-git instructions ----------
def step_12_push_git(args):
state = load_state()
version = state['version']
master_branch = state['master_branch']
v_ver = 'v' + version
print(f"""\
{DASH_LINE}
Run these from the rsync-git checkout (this script does not push for you):
git push origin {master_branch}
git push origin {v_ver}
If you have a 'samba' remote configured (git.samba.org:/data/git/rsync.git):
git push samba {master_branch}
git push samba {v_ver}
Then upload the tarball + .asc to the GitHub release for {v_ver}, run
packaging/send-news (when convenient), and announce on rsync-announce@,
rsync@, and Discord.
""")
# ---------- shared rsync-with-confirm ----------
def rsync_with_confirm(rsync_args):
"""Run an rsync command in dry-run mode, then ask before running for real."""
cmd_run(['rsync', '--dry-run', *rsync_args])
if confirm("Run without --dry-run?"):
cmd_run(['rsync', *rsync_args])
# ---------- dispatch ----------
STEP_FUNCS = {
'step-1-fetch': step_1_fetch,
'step-2-prepare': step_2_prepare,
'step-3-tweak': step_3_tweak,
'step-4-build': step_4_build,
'step-5-commit': step_5_commit,
'step-6-tag': step_6_tag,
'step-7-tarball': step_7_tarball,
'step-8-update-ftp': step_8_update_ftp,
'step-9-toplinks': step_9_toplinks,
'step-10-push-ftp': step_10_push_ftp,
'step-11-push-html': step_11_push_html,
'step-12-push-git': step_12_push_git,
}
def signal_handler(sig, frame):
die("\nAborting due to SIGINT.")
def main():
parser = argparse.ArgumentParser(
description="Step-based release script for rsync.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="Run --list to see the steps. Each invocation runs exactly one --step-* option.")
parser.add_argument('--branch', '-b', dest='master_branch', default='master',
help="The branch to release (default: master).")
parser.add_argument('--list', action='store_true',
help="List all release steps and exit.")
grp = parser.add_mutually_exclusive_group()
for flag, descr in STEPS:
grp.add_argument('--' + flag, dest='step', action='store_const',
const=flag, help=descr)
args = parser.parse_args()
if args.list:
print("Release steps:")
for flag, descr in STEPS:
print(f" --{flag:18s} {descr}")
return
if not args.step:
parser.error("pick one --step-N-XX option (or --list to see them).")
signal.signal(signal.SIGINT, signal_handler)
os.environ['LESS'] = 'mqeiXR'
STEP_FUNCS[args.step](args)
if __name__ == '__main__':
main()
# vim: sw=4 et ft=python

View File

@@ -121,4 +121,4 @@ fi
cd "$SRC_DIR" || exit 1
echo "Copying files from $SRC_DIR to $RSYNC_SAMBA_HOST ..."
do_rsync -aivOHP --chown=:rsync --del -f._$FILT . "$RSYNC_SAMBA_HOST:$DEST_DIR/"
do_rsync -aivOHP --del -f._$FILT . "$RSYNC_SAMBA_HOST:$DEST_DIR/"

View File

@@ -7,6 +7,9 @@
import sys, os, re, argparse, subprocess
from datetime import datetime
MAINTAINER_NAME = 'Wayne Davison'
MAINTAINER_SUF = ' ' + MAINTAINER_NAME + "\n"
def main():
latest_year = '2000'
@@ -19,6 +22,10 @@ def main():
m = argparse.Namespace(**m.groupdict())
if m.year > latest_year:
latest_year = m.year
if m.fn.startswith('zlib/') or m.fn.startswith('popt/'):
continue
if re.search(r'\.(c|h|sh|test)$', m.fn):
maybe_edit_copyright_year(m.fn, m.year)
proc.communicate()
fn = 'latest-year.h'
@@ -32,8 +39,55 @@ def main():
fh.write(txt)
def maybe_edit_copyright_year(fn, year):
opening_lines = [ ]
copyright_line = None
with open(fn, 'r', encoding='utf-8') as fh:
for lineno, line in enumerate(fh):
opening_lines.append(line)
if lineno > 3 and not re.search(r'\S', line):
break
m = re.match(r'^(?P<pre>.*Copyright\s+\S+\s+)(?P<year>\d\d\d\d(?:-\d\d\d\d)?(,\s+\d\d\d\d)*)(?P<suf>.+)', line)
if not m:
continue
copyright_line = argparse.Namespace(**m.groupdict())
copyright_line.lineno = len(opening_lines)
copyright_line.is_maintainer_line = MAINTAINER_NAME in copyright_line.suf
copyright_line.txt = line
if copyright_line.is_maintainer_line:
break
if not copyright_line:
return
if copyright_line.is_maintainer_line:
cyears = copyright_line.year.split('-')
if year == cyears[0]:
cyears = [ year ]
else:
cyears = [ cyears[0], year ]
txt = copyright_line.pre + '-'.join(cyears) + MAINTAINER_SUF
if txt == copyright_line.txt:
return
opening_lines[copyright_line.lineno - 1] = txt
else:
if fn.startswith('lib/') or fn.startswith('testsuite/'):
return
txt = copyright_line.pre + year + MAINTAINER_SUF
opening_lines[copyright_line.lineno - 1] += txt
remaining_txt = fh.read()
print(f"Updating {fn} with year {year}")
with open(fn, 'w', encoding='utf-8') as fh:
fh.write(''.join(opening_lines))
fh.write(remaining_txt)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Grab the year of the last mod for our c & h files and make sure the LATEST_YEAR value is accurate.")
parser = argparse.ArgumentParser(description="Grab the year of last mod for our c & h files and make sure the Copyright comment is up-to-date.")
args = parser.parse_args()
main()

55
popt/findme.c Normal file
View File

@@ -0,0 +1,55 @@
/** \ingroup popt
* \file popt/findme.c
*/
/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING
file accompanying popt source distributions, available from
ftp://ftp.rpm.org/pub/rpm/dist. */
#include "system.h"
#include "findme.h"
const char * findProgramPath(const char * argv0)
{
char * path = getenv("PATH");
char * pathbuf;
char * start, * chptr;
char * buf;
size_t bufsize;
if (argv0 == NULL) return NULL; /* XXX can't happen */
/* If there is a / in the argv[0], it has to be an absolute path */
if (strchr(argv0, '/'))
return xstrdup(argv0);
if (path == NULL) return NULL;
bufsize = strlen(path) + 1;
start = pathbuf = alloca(bufsize);
if (pathbuf == NULL) return NULL; /* XXX can't happen */
strlcpy(pathbuf, path, bufsize);
bufsize += sizeof "/" - 1 + strlen(argv0);
buf = malloc(bufsize);
if (buf == NULL) return NULL; /* XXX can't happen */
chptr = NULL;
/*@-branchstate@*/
do {
if ((chptr = strchr(start, ':')))
*chptr = '\0';
snprintf(buf, bufsize, "%s/%s", start, argv0);
if (!access(buf, X_OK))
return buf;
if (chptr)
start = chptr + 1;
else
start = NULL;
} while (start && *start);
/*@=branchstate@*/
free(buf);
return NULL;
}

20
popt/findme.h Normal file
View File

@@ -0,0 +1,20 @@
/** \ingroup popt
* \file popt/findme.h
*/
/* (C) 1998-2000 Red Hat, Inc. -- Licensing details are in the COPYING
file accompanying popt source distributions, available from
ftp://ftp.rpm.org/pub/rpm/dist. */
#ifndef H_FINDME
#define H_FINDME
/**
* Return absolute path to executable by searching PATH.
* @param argv0 name of executable
* @return (malloc'd) absolute path to executable (or NULL)
*/
/*@null@*/ const char * findProgramPath(/*@null@*/ const char * argv0)
/*@*/;
#endif

View File

@@ -70,7 +70,6 @@ extern int fuzzy_basis;
extern struct name_num_item *xfer_sum_nni;
extern int xfer_sum_len;
extern int use_secure_symlinks;
static struct bitbag *delayed_bits = NULL;
static int phase = 0, redoing = 0;
@@ -83,65 +82,6 @@ static int updating_basis_or_equiv;
#define MAX_UNIQUE_NUMBER 999999
#define MAX_UNIQUE_LOOP 100
/* Open a basis/output path that may legitimately be an operator-trusted
* ABSOLUTE path -- e.g. an absolute --partial-dir ("a directory reserved for
* partial-dir work") or --backup-dir. secure_relative_open() deliberately
* rejects an absolute relpath, so feeding it the whole absolute partialptr
* (with a NULL basedir) returns EINVAL: the basis fd is then -1, no basis is
* mapped, and receive_data() omits every matched block from the whole-file
* verification checksum -> a spurious "failed verification" that strands the
* (correct) data in the partial-dir forever.
*
* The operator's directory is trusted; only the leaf basename is peer-supplied.
* So when basedir is NULL and relpath is absolute, split it into its directory
* (trusted) and leaf and confine just the leaf -- exactly how secure_relative_
* open already trusts an absolute basedir while O_NOFOLLOW-confining the leaf.
* Anything else is a straight pass-through that preserves the strict contract. */
static int secure_basis_open(const char *basedir, const char *relpath, int flags, mode_t mode)
{
extern int am_daemon, am_chrooted;
/* The confined resolver is only needed for the sanitizing daemon
* (am_daemon && !am_chrooted, i.e. use_secure_symlinks). Local /
* remote-shell mode has no module boundary, and "use chroot = yes" makes
* the kernel root the boundary, so there an alt-dest basis like
* --link-dest=../01 must resolve against the cwd as a bare open did before
* the hardening (confining it would reject the legitimate sibling "..",
* #915). */
if (!am_daemon || am_chrooted) {
if (basedir) {
char fullpath[MAXPATHLEN];
if (pathjoin(fullpath, sizeof fullpath, basedir, relpath) >= sizeof fullpath) {
errno = ENAMETOOLONG;
return -1;
}
return do_open(fullpath, flags, mode);
}
return do_open(relpath, flags, mode);
}
if (!basedir && relpath && *relpath == '/') {
const char *slash = strrchr(relpath, '/');
const char *leaf = slash + 1;
char dirbuf[MAXPATHLEN];
const char *dir;
if (slash == relpath)
dir = "/";
else {
size_t dlen = slash - relpath;
if (dlen >= sizeof dirbuf) {
errno = ENAMETOOLONG;
return -1;
}
memcpy(dirbuf, relpath, dlen);
dirbuf[dlen] = '\0';
dir = dirbuf;
}
return secure_relative_open(dir, leaf, flags, mode);
}
return secure_relative_open(basedir, relpath, flags, mode);
}
/* get_tmpname() - create a tmp filename for a given filename
*
* If a tmpdir is defined, use that as the directory to put it in. Otherwise,
@@ -274,12 +214,7 @@ int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file)
* access to ensure that there is no race condition. They will be
* correctly updated after the right owner and group info is set.
* (Thanks to snabb@epipe.fi for pointing this out.) */
/* When use_secure_symlinks is on (non-chroot daemon with munge_symlinks),
* use secure_mkstemp to prevent symlink race attacks on parent directories. */
if (use_secure_symlinks)
fd = secure_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
else
fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
#if 0
/* In most cases parent directories will already exist because their
@@ -377,12 +312,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
}
}
while (1) {
data = NULL;
i = recv_token(f_in, &data);
if (i == 0)
break;
while ((i = recv_token(f_in, &data)) != 0) {
if (INFO_GTE(PROGRESS, 1))
show_progress(offset, total_size);
@@ -390,10 +320,6 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH | MSK_ACTIVE_RECEIVER);
if (i > 0) {
if (!data) {
rprintf(FERROR, "Invalid literal token with no data [%s]\n", who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
if (DEBUG_GTE(DELTASUM, 3)) {
rprintf(FINFO,"data recv %d at %s\n",
i, big_num(offset));
@@ -411,11 +337,6 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
}
i = -(i+1);
if (i < 0 || i >= sum.count) {
rprintf(FERROR, "Invalid block index %d (count=%ld) [%s]\n",
i, (long)sum.count, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
offset2 = i * (OFF_T)sum.blength;
len = sum.blength;
if (i == (int)sum.count-1 && sum.remainder != 0)
@@ -423,34 +344,6 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
stats.matched_data += len;
/* A block match with no mapped basis is a protocol inconsistency
* ONLY when we are actually producing output (fd != -1): the
* generator told the sender a basis existed but the receiver could
* not open it, so honoring the match would silently omit these
* bytes from the verification checksum (a spurious failure) or
* leave a hole in the output. Fail cleanly in that case.
*
* On the DISCARD path (fd == -1, fname == NULL) there is no output
* and no verification: discard_receive_data() deliberately drains a
* delta the receiver never intends to write (basis fstat failed,
* basis is a directory, output open failed, batch skip, ...). The
* sender does not know the data is being discarded and streams an
* ordinary delta, so a match token here is NORMAL protocol, not
* malformed. Absorb it benignly (advance the offset and continue),
* as the pre-existing "if (mapbuf)" guards did before this check was
* added in 31fbb17d -- erroring would wrongly break legitimate
* transfers, and full_fname(fname) with fname==NULL would
* dereference NULL (a receiver crash on a normal transfer). */
if (!mapbuf) {
if (fd != -1) {
rprintf(FERROR, "got a block match with no basis file for %s [%s]\n",
full_fname(fname), who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
offset += len;
continue;
}
if (DEBUG_GTE(DELTASUM, 3)) {
rprintf(FINFO,
"chunk[%d] of size %ld at %s offset=%s%s\n",
@@ -543,7 +436,7 @@ static void handle_delayed_updates(char *local_name)
}
/* We don't use robust_rename() here because the
* partial-dir must be on the same drive. */
if (do_rename_at(partialptr, fname) < 0) {
if (do_rename(partialptr, fname) < 0) {
rsyserr(FERROR_XFER, errno,
"rename failed for %s (from %s)",
full_fname(fname), partialptr);
@@ -559,10 +452,7 @@ static void handle_delayed_updates(char *local_name)
static void no_batched_update(int ndx, BOOL is_redo)
{
struct file_list *flist = flist_for_ndx(ndx, "no_batched_update");
struct file_struct *file;
if (ndx < flist->ndx_start)
exit_cleanup(RERR_PROTOCOL);
file = flist->files[ndx - flist->ndx_start];
struct file_struct *file = flist->files[ndx - flist->ndx_start];
rprintf(FERROR_XFER, "(No batched update for%s \"%s\")\n",
is_redo ? " resend of" : "", f_name(file, NULL));
@@ -699,8 +589,6 @@ int recv_files(int f_in, int f_out, char *local_name)
if (ndx - cur_flist->ndx_start >= 0)
file = cur_flist->files[ndx - cur_flist->ndx_start];
else if (cur_flist->parent_ndx < 0)
exit_cleanup(RERR_PROTOCOL);
else
file = dir_flist->files[cur_flist->parent_ndx];
fname = local_name ? local_name : f_name(file, fbuf);
@@ -880,9 +768,8 @@ int recv_files(int f_in, int f_out, char *local_name)
fnamecmp = fname;
}
/* open the file (secure_basis_open tolerates an operator-trusted
* absolute fnamecmp, e.g. an absolute --partial-dir basis) */
fd1 = secure_basis_open(basedir, fnamecmp, O_RDONLY, 0);
/* open the file */
fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);
if (fd1 == -1 && protocol_version < 29) {
if (fnamecmp != fname) {
@@ -896,7 +783,7 @@ int recv_files(int f_in, int f_out, char *local_name)
basedir = basis_dir[0];
fnamecmp = fname;
fnamecmp_type = FNAMECMP_BASIS_DIR_LOW;
fd1 = secure_basis_open(basedir, fnamecmp, O_RDONLY, 0);
fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);
}
}
@@ -967,21 +854,11 @@ int recv_files(int f_in, int f_out, char *local_name)
/* We now check to see if we are writing the file "inplace" */
if (inplace || one_inplace) {
fnametmp = one_inplace ? partialptr : fname;
/* When use_secure_symlinks is on (non-chroot daemon),
* use secure open to prevent symlink race attacks where an
* attacker could switch a directory to a symlink between
* path validation and file open. */
if (use_secure_symlinks)
fd2 = secure_basis_open(NULL, fnametmp, O_WRONLY|O_CREAT, 0600);
else
fd2 = do_open(fnametmp, O_WRONLY|O_CREAT, 0600);
fd2 = do_open(fnametmp, O_WRONLY|O_CREAT, 0600);
#ifdef linux
if (fd2 == -1 && errno == EACCES) {
/* Maybe the error was due to protected_regular setting? */
if (use_secure_symlinks)
fd2 = secure_relative_open(NULL, fname, O_WRONLY, 0600);
else
fd2 = do_open(fname, O_WRONLY, 0600);
fd2 = do_open(fname, O_WRONLY, 0600);
}
#endif
if (fd2 == -1) {
@@ -1033,7 +910,7 @@ int recv_files(int f_in, int f_out, char *local_name)
recv_ok = -1;
else if (fnamecmp == partialptr) {
if (!one_inplace)
do_unlink_at(partialptr);
do_unlink(partialptr);
handle_partial_dir(partialptr, PDIR_DELETE);
}
} else if (keep_partial && partialptr && (!one_inplace || delay_updates)) {
@@ -1042,7 +919,7 @@ int recv_files(int f_in, int f_out, char *local_name)
"Unable to create partial-dir for %s -- discarding %s.\n",
local_name ? local_name : f_name(file, NULL),
recv_ok ? "completed file" : "partial file");
do_unlink_at(fnametmp);
do_unlink(fnametmp);
recv_ok = -1;
} else if (!finish_transfer(partialptr, fnametmp, fnamecmp, NULL,
file, recv_ok, !partial_dir))
@@ -1053,7 +930,7 @@ int recv_files(int f_in, int f_out, char *local_name)
} else
partialptr = NULL;
} else if (!one_inplace)
do_unlink_at(fnametmp);
do_unlink(fnametmp);
cleanup_disable();

View File

@@ -513,7 +513,6 @@ has its own detailed description later in this manpage.
--compress, -z compress file data during the transfer
--compress-choice=STR choose the compression algorithm (aka --zc)
--compress-level=NUM explicitly set compression level (aka --zl)
--compress-threads=NUM explicitly set compression threads (aka --zt)
--skip-compress=LIST skip compressing files with suffix in LIST
--cvs-exclude, -C auto-ignore files in the same way CVS does
--filter=RULE, -f add a file-filtering RULE
@@ -2818,22 +2817,6 @@ expand it.
report something like "`Client compress: zstd (level 3)`" (along with the
checksum choice in effect).
0. `--compress-threads=NUM`, `--zt=NUM`
Set the number of threads to spawn when compressing data. Setting this
option to 1 or more will instruct the compression library to spawn 1 or
more threads for compression. Ideally, increasing the number of threads
will increase transfer speed if the transfer is CPU bound on the sender.
This option does not affect decompression.
Compression algorithms that allow threading:
- `zstd` (only when libzstd is compiled with threading support)
This option is ignored if one of the above alogithms is not selected as the
`--compression-choice` or if compression not enabled.
0. `--skip-compress=LIST`
**NOTE:** no compression method currently supports per-file compression

View File

@@ -547,7 +547,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
if (am_root >= 0) {
uid_t uid = change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid;
gid_t gid = change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid;
if (do_lchown_at(fname, uid, gid) != 0) {
if (do_lchown(fname, uid, gid) != 0) {
/* We shouldn't have attempted to change uid
* or gid unless have the privilege. */
rsyserr(FERROR_XFER, errno, "%s %s failed",
@@ -657,7 +657,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
#ifdef HAVE_CHMOD
if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
int ret = am_root < 0 ? 0 : do_chmod_at(fname, new_mode);
int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
if (ret < 0) {
rsyserr(FERROR_XFER, errno,
"failed to set permissions on %s",
@@ -758,7 +758,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
full_fname(fnametmp), fname);
if (!partialptr || (ret == -2 && temp_copy_name)
|| robust_rename(fnametmp, partialptr, NULL, file->mode) < 0)
do_unlink_at(fnametmp);
do_unlink(fnametmp);
return 0;
}
if (ret == 0) {
@@ -774,7 +774,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME);
if (temp_copy_name) {
if (do_rename_at(fnametmp, fname) < 0) {
if (do_rename(fnametmp, fname) < 0) {
rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\"",
full_fname(fnametmp), fname);
return 0;

25
rsync.h
View File

@@ -84,6 +84,7 @@
#define FLAG_DUPLICATE (1<<4) /* sender */
#define FLAG_MISSING_DIR (1<<4) /* generator */
#define FLAG_HLINKED (1<<5) /* receiver/generator (checked on all types) */
#define FLAG_GOT_DIR_FLIST (1<<5)/* sender/receiver/generator - dir_flist only */
#define FLAG_HLINK_FIRST (1<<6) /* receiver/generator (w/FLAG_HLINKED) */
#define FLAG_IMPLIED_DIR (1<<6) /* sender/receiver/generator (dirs only) */
#define FLAG_HLINK_LAST (1<<7) /* receiver/generator */
@@ -92,7 +93,6 @@
#define FLAG_SKIP_GROUP (1<<10) /* receiver/generator */
#define FLAG_TIME_FAILED (1<<11)/* generator */
#define FLAG_MOD_NSEC (1<<12) /* sender/receiver/generator */
#define FLAG_GOT_DIR_FLIST (1<<13)/* sender/receiver/generator - dir_flist only */
/* These flags are passed to functions but not stored. */
@@ -163,29 +163,6 @@
/* For compatibility with older rsyncs */
#define OLD_MAX_BLOCK_SIZE ((int32)1 << 29)
/* Policy ceilings on attacker-controlled wire values. Picked well above any
* legitimate filesystem / protocol traffic but well below sizes that could
* cause integer overflow or DoS-grade allocations. See input_checking.txt.
*
* Note on MAX_WIRE_XATTR_DATALEN: xattr datum size is bounded only by the
* wire-format maximum (signed int32 varint, ~2GB). macOS resource forks
* are transferred as the com.apple.ResourceFork xattr and can legitimately
* be many GB; --max-alloc (default 1GB, configurable) is the real
* allocation cap. read_varint_size() still rejects negative values so a
* hostile peer cannot wrap to ~SIZE_MAX. */
#define MAX_WIRE_XATTR_COUNT 65536
#define MAX_WIRE_XATTR_NAMELEN 4096
#define MAX_WIRE_XATTR_DATALEN ((int32)0x7fffffff)
#define MAX_WIRE_ACL_COUNT 65536
#define MAX_WIRE_NSEC 999999999
/* MAX_WIRE_DEL_STAT is the per-category cap for read_del_stats() in main.c,
* which accumulates 5 wire-supplied counts into the int32 stats.deleted_files
* accumulator. Capped at 2^28 so 5 * 2^28 = 1.34 GB stays under INT32_MAX
* (2.15 GB) with margin -- a higher cap (e.g. 2^30) would let a hostile peer
* supplying 3+ max-sized counts overflow the accumulator, which is signed-int
* UB. 2^28 is still well above any plausible real transfer's deletion count. */
#define MAX_WIRE_DEL_STAT ((int32)1 << 28)
#define ROUND_UP_1024(siz) ((siz) & (1024-1) ? ((siz) | (1024-1)) + 1 : (siz))
#define IOERR_GENERAL (1<<0) /* For backward compatibility, this must == 1 */

View File

@@ -1073,16 +1073,6 @@ in the values of parameters. See that section for details.
**system()** call's default shell), and use RSYNC_NO_XFER_EXEC to disable
both options completely.
0. `temp dir`
Specifies a directory that rsync should use for temporary files created
during the transfer of updated files. If that directory is on a different
partition, after transfer file is being copied instead of unlinked.
This parameter equals with `--temp-dir` option, so please consult rsync
manpage for further information.
## CONFIG DIRECTIVES
There are currently two config directives available that allow a config file to

View File

@@ -1,485 +0,0 @@
#!/usr/bin/env python3
# Copyright (C) 2001, 2002 by Martin Pool <mbp@samba.org>
# Copyright (C) 2003-2022 Wayne Davison
# Copyright (C) 2026 Andrew Tridgell
#
# Rewrite of runtests.sh in Python (runtests.sh is now deprecated).
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version
# 2 as published by the Free Software Foundation.
"""rsync test runner.
Invokes test scripts from testsuite/ and reports results.
Can be called by 'make check' or directly.
Usage:
./runtests.py [options] [TEST ...]
Each TEST is a test name (e.g. 'delete') or glob pattern (e.g. 'xattr*').
If no tests are specified, all tests are run.
"""
import argparse
import concurrent.futures
import glob
import os
import subprocess
import sys
import threading
def parse_args():
p = argparse.ArgumentParser(description='Run rsync test suite')
p.add_argument('tests', nargs='*', metavar='TEST',
help='Test names or patterns to run (default: all)')
p.add_argument('-j', '--parallel', type=int, default=1, metavar='N',
help='Run up to N tests in parallel (default: 1)')
p.add_argument('--valgrind', action='store_true',
help='Run rsync under valgrind (logs to per-process files)')
p.add_argument('--valgrind-opts', default='', metavar='OPTS',
help='Extra valgrind options (e.g. "--leak-check=full")')
p.add_argument('--preserve-scratch', action='store_true',
help='Keep scratch directories after tests complete')
p.add_argument('--log-level', type=int, default=1, metavar='N',
help='Verbosity level 1-10 (default: 1)')
p.add_argument('--always-log', action='store_true',
help='Show test logs even for passing tests')
p.add_argument('--stop-on-fail', action='store_true',
help='Stop after first test failure')
p.add_argument('--timeout', type=int, default=300, metavar='SECS',
help='Per-test timeout in seconds (default: 300)')
p.add_argument('--rsync-bin', default=None, metavar='PATH',
help='Path to rsync binary (default: ./rsync)')
p.add_argument('--tooldir', default=None, metavar='DIR',
help='Tool/build directory (default: cwd)')
p.add_argument('--srcdir', default=None, metavar='DIR',
help='Source directory (default: script directory)')
p.add_argument('--protocol', type=int, default=None, metavar='VER',
help='Force protocol version (adds --protocol=VER to rsync)')
p.add_argument('--expect-skipped', default=None, metavar='LIST',
help='Comma-separated list of expected-skipped tests')
return p.parse_args()
def find_setfacl_nodef(scratchbase):
"""Determine the setfacl command to remove default ACLs."""
for cmd in [
['setacl', '-k', 'u::7,g::5,o:5', scratchbase],
['setfacl', '-k', scratchbase],
['setfacl', '-s', 'u::7,g::5,o:5', scratchbase],
]:
try:
subprocess.run(cmd, capture_output=True, timeout=5)
return cmd[:2] if cmd[0] == 'setacl' else cmd[:2]
except (FileNotFoundError, subprocess.TimeoutExpired):
continue
try:
r = subprocess.run(['setfacl', '--help'], capture_output=True, text=True, timeout=5)
if '-k,' in r.stdout or '-k,' in r.stderr:
return ['setfacl', '-k']
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
return None
def get_tls_args(config_h):
"""Determine TLS_ARGS from config.h."""
args = ''
try:
with open(config_h) as f:
text = f.read()
if '#define HAVE_LUTIMES 1' in text:
args += ' -l'
if '#undef CHOWN_MODIFIES_SYMLINK' in text:
args += ' -L'
except FileNotFoundError:
pass
return args.strip()
def read_shconfig(path):
"""Read shell config variables from shconfig."""
env = {}
try:
with open(path) as f:
for line in f:
line = line.strip()
if line.startswith('#') or line.startswith('export') or not line:
continue
if '=' in line:
k, _, v = line.partition('=')
env[k.strip()] = v.strip().strip('"')
except FileNotFoundError:
pass
return env
def get_testuser():
"""Determine the current test user."""
for cmd in ['/usr/bin/whoami', '/usr/ucb/whoami', '/bin/whoami']:
if os.path.isfile(cmd):
try:
return subprocess.check_output([cmd], text=True).strip()
except subprocess.CalledProcessError:
pass
try:
return subprocess.check_output(['id', '-un'], text=True).strip()
except (FileNotFoundError, subprocess.CalledProcessError):
return os.environ.get('LOGNAME', os.environ.get('USER', 'UNKNOWN'))
def prep_scratch(scratchdir, srcdir, tooldir, setfacl_nodef):
"""Prepare a scratch directory for a test."""
if os.path.isdir(scratchdir):
subprocess.run(['chmod', '-R', 'u+rwX', scratchdir], capture_output=True)
subprocess.run(['rm', '-rf', scratchdir], capture_output=True)
os.makedirs(scratchdir, exist_ok=True)
if setfacl_nodef:
subprocess.run(setfacl_nodef + [scratchdir], capture_output=True)
try:
os.chmod(scratchdir, os.stat(scratchdir).st_mode & ~0o2000) # clear setgid
except OSError:
pass
src_link = os.path.join(scratchdir, 'src')
if not os.path.exists(src_link):
if os.path.isabs(srcdir):
os.symlink(srcdir, src_link)
else:
os.symlink(os.path.join(tooldir, srcdir), src_link)
def collect_tests(suitedir, patterns):
"""Collect test scripts matching the given patterns."""
if not patterns:
tests = sorted(glob.glob(os.path.join(suitedir, '*.test')))
else:
tests = []
for pat in patterns:
if not pat.endswith('.test'):
pat = pat + '.test'
matches = sorted(glob.glob(os.path.join(suitedir, pat)))
tests.extend(matches)
return tests
def build_rsync_cmd(rsync_bin, args, scratchbase):
"""Build the RSYNC command string for tests."""
parts = []
if args.valgrind:
vlog = os.path.join(scratchbase, 'valgrind.%p.log')
vopts = f'--log-file={vlog}'
if args.valgrind_opts:
vopts += ' ' + args.valgrind_opts
parts.append(f'valgrind {vopts}')
parts.append(rsync_bin)
if args.protocol is not None:
parts.append(f'--protocol={args.protocol}')
return ' '.join(parts)
class TestResult:
"""Result of a single test execution."""
__slots__ = ('testbase', 'result', 'output', 'skipped_reason')
def __init__(self, testbase, result, output='', skipped_reason=''):
self.testbase = testbase
self.result = result
self.output = output
self.skipped_reason = skipped_reason
def run_one_test(testscript, testbase, scratchdir, base_env, timeout,
srcdir, tooldir, setfacl_nodef, always_log):
"""Run a single test. Returns a TestResult.
This function is safe to call from multiple threads — it uses only
per-test state (unique scratchdir, copy of env).
"""
prep_scratch(scratchdir, srcdir, tooldir, setfacl_nodef)
env = base_env.copy()
env['scratchdir'] = scratchdir
logfile = os.path.join(scratchdir, 'test.log')
try:
with open(logfile, 'w') as log:
proc = subprocess.run(
['sh', '-e', testscript],
stdout=log, stderr=subprocess.STDOUT,
env=env, timeout=timeout,
cwd=env.get('TOOLDIR', '.')
)
result = proc.returncode
except subprocess.TimeoutExpired:
result = 1
with open(logfile, 'a') as log:
log.write(f"\nTIMEOUT: test took over {timeout} seconds\n")
# Build output text
output_parts = []
show_log = always_log or (result not in (0, 77, 78))
if show_log:
output_parts.append(f'----- {testbase} log follows')
try:
with open(logfile) as f:
output_parts.append(f.read().rstrip())
except FileNotFoundError:
pass
output_parts.append(f'----- {testbase} log ends')
rsyncd_log = os.path.join(scratchdir, 'rsyncd.log')
if os.path.isfile(rsyncd_log):
output_parts.append(f'----- {testbase} rsyncd.log follows')
with open(rsyncd_log) as f:
output_parts.append(f.read().rstrip())
output_parts.append(f'----- {testbase} rsyncd.log ends')
skipped_reason = ''
if result == 0:
output_parts.append(f'PASS {testbase}')
elif result == 77:
whyfile = os.path.join(scratchdir, 'whyskipped')
try:
with open(whyfile) as f:
skipped_reason = f.read().strip()
except FileNotFoundError:
pass
output_parts.append(f'SKIP {testbase} ({skipped_reason})')
elif result == 78:
output_parts.append(f'XFAIL {testbase}')
else:
output_parts.append(f'FAIL {testbase}')
return TestResult(testbase, result, '\n'.join(output_parts), skipped_reason)
# Lock for serializing output in parallel mode
_print_lock = threading.Lock()
def main():
args = parse_args()
# Also accept legacy environment variables
if args.preserve_scratch or os.environ.get('preserve_scratch') == 'yes':
args.preserve_scratch = True
if args.log_level == 1:
args.log_level = int(os.environ.get('loglevel', '1'))
if args.expect_skipped is None:
args.expect_skipped = os.environ.get('RSYNC_EXPECT_SKIPPED', 'IGNORE')
if os.environ.get('whichtests'):
args.tests = [os.environ['whichtests']]
# Determine directories
tooldir = args.tooldir or os.environ.get('TOOLDIR') or os.getcwd()
script_path = os.path.dirname(os.path.abspath(__file__))
srcdir = args.srcdir or script_path
if not srcdir or srcdir == '.':
srcdir = tooldir
rsync_bin = args.rsync_bin or os.environ.get('rsync_bin') or os.path.join(tooldir, 'rsync')
suitedir = os.path.join(srcdir, 'testsuite')
scratchbase = os.path.join(os.environ.get('scratchbase', tooldir), 'testtmp')
os.makedirs(scratchbase, exist_ok=True)
shconfig = read_shconfig(os.path.join(tooldir, 'shconfig'))
tls_args = get_tls_args(os.path.join(tooldir, 'config.h'))
setfacl_nodef = find_setfacl_nodef(scratchbase)
rsync_cmd = build_rsync_cmd(rsync_bin, args, scratchbase)
if not os.path.isfile(rsync_bin):
sys.stderr.write(f"rsync_bin {rsync_bin} is not a file\n")
sys.exit(2)
if not os.path.isdir(srcdir):
sys.stderr.write(f"srcdir {srcdir} is not a directory\n")
sys.exit(2)
# Helper programs the test scripts invoke directly. Missing any of these
# would cause many tests to fail with confusing "not found" errors, so
# check up front and point the user at the make target that builds them.
required_helpers = ['tls', 'trimslash', 't_unsafe', 't_chmod_secure',
't_secure_relpath',
'wildtest', 'getgroups', 'getfsdev']
missing = [h for h in required_helpers
if not os.path.isfile(os.path.join(tooldir, h))]
if missing:
sys.stderr.write(
f"runtests.py: missing test helper program(s) in {tooldir}: "
f"{', '.join(missing)}\n"
f"Build them with: make {' '.join(missing)}\n"
f"or run the full test target: make check\n"
)
sys.exit(2)
testuser = get_testuser()
# Print header
print('=' * 60)
print(f'{sys.argv[0]} running in {tooldir}')
print(f' rsync_bin={rsync_cmd}')
print(f' srcdir={srcdir}')
print(f' TLS_ARGS={tls_args}')
print(f' testuser={testuser}')
print(f' os={subprocess.check_output(["uname", "-a"], text=True).strip()}')
print(f' preserve_scratch={"yes" if args.preserve_scratch else "no"}')
if args.valgrind:
print(f' valgrind=enabled (logs in valgrind.*.log)')
if args.parallel > 1:
print(f' parallel={args.parallel}')
print(f' scratchbase={scratchbase}')
# Build base environment for test scripts
path = os.environ.get('PATH', '')
if os.path.isdir('/usr/xpg4/bin'):
path = '/usr/xpg4/bin:' + path
base_env = os.environ.copy()
base_env.update({
'PATH': path,
'POSIXLY_CORRECT': '1',
'TOOLDIR': tooldir,
'srcdir': srcdir,
'RSYNC': rsync_cmd,
'TLS_ARGS': tls_args,
'RUNSHFLAGS': '-e',
'scratchbase': scratchbase,
'suitedir': suitedir,
'TESTRUN_TIMEOUT': str(args.timeout),
'HOME': scratchbase,
})
for k, v in shconfig.items():
if v:
base_env[k] = v
if setfacl_nodef:
base_env['setfacl_nodef'] = ' '.join(setfacl_nodef)
else:
base_env['setfacl_nodef'] = 'true'
if args.log_level > 8:
base_env['RUNSHFLAGS'] = '-e -x'
# Collect tests
tests = collect_tests(suitedir, args.tests)
full_run = len(args.tests) == 0
# Record test order for consistent skipped-list output
test_order = {os.path.basename(t).replace('.test', ''): i for i, t in enumerate(tests)}
passed = 0
failed = 0
skipped = 0
skipped_list = []
def process_result(tr):
"""Process a TestResult and update counters. Returns True if test failed."""
nonlocal passed, failed, skipped
with _print_lock:
if tr.output:
print(tr.output)
scratchdir = os.path.join(scratchbase, tr.testbase)
if tr.result == 0:
passed += 1
if not args.preserve_scratch and os.path.isdir(scratchdir):
subprocess.run(['rm', '-rf', scratchdir], capture_output=True)
return False
elif tr.result == 77:
skipped_list.append(tr.testbase)
skipped += 1
if not args.preserve_scratch and os.path.isdir(scratchdir):
subprocess.run(['rm', '-rf', scratchdir], capture_output=True)
return False
elif tr.result == 78:
failed += 1
return True
else:
failed += 1
return True
if args.parallel > 1:
# Parallel execution
with concurrent.futures.ThreadPoolExecutor(max_workers=args.parallel) as executor:
futures = {}
for testscript in tests:
testbase = os.path.basename(testscript).replace('.test', '')
scratchdir = os.path.join(scratchbase, testbase)
timeout = 600 if 'hardlinks' in testbase else args.timeout
f = executor.submit(
run_one_test, testscript, testbase, scratchdir,
base_env, timeout, srcdir, tooldir, setfacl_nodef,
args.always_log
)
futures[f] = testbase
for f in concurrent.futures.as_completed(futures):
tr = f.result()
is_fail = process_result(tr)
if is_fail and args.stop_on_fail:
# Cancel pending futures
for pending in futures:
pending.cancel()
break
else:
# Sequential execution
for testscript in tests:
testbase = os.path.basename(testscript).replace('.test', '')
scratchdir = os.path.join(scratchbase, testbase)
timeout = 600 if 'hardlinks' in testbase else args.timeout
tr = run_one_test(
testscript, testbase, scratchdir,
base_env, timeout, srcdir, tooldir, setfacl_nodef,
args.always_log
)
is_fail = process_result(tr)
if is_fail and args.stop_on_fail:
break
# Check valgrind logs for errors
vg_errors = 0
if args.valgrind:
for vlog in sorted(glob.glob(os.path.join(scratchbase, 'valgrind.*.log'))):
try:
with open(vlog) as f:
content = f.read()
for line in content.splitlines():
if 'ERROR SUMMARY:' in line and 'ERROR SUMMARY: 0 errors' not in line:
vg_errors += 1
print(f'----- valgrind errors in {os.path.basename(vlog)}:')
print(content)
break
except FileNotFoundError:
pass
# Summary
print('-' * 60)
print('----- overall results:')
print(f' {passed} passed')
if failed > 0:
print(f' {failed} failed')
if skipped > 0:
print(f' {skipped} skipped')
if vg_errors > 0:
print(f' {vg_errors} valgrind error(s) found (see logs in {scratchbase})')
skipped_str = ','.join(sorted(skipped_list, key=lambda x: test_order.get(x, 0)))
if full_run and args.expect_skipped != 'IGNORE':
print('----- skipped results:')
print(f' expected: {args.expect_skipped}')
print(f' got: {skipped_str}')
else:
skipped_str = ''
args.expect_skipped = ''
print('-' * 60)
exit_code = failed + vg_errors
if exit_code == 0 and skipped_str != args.expect_skipped:
exit_code = 1
print(f'overall result is {exit_code}')
sys.exit(exit_code)
if __name__ == '__main__':
main()

360
runtests.sh Executable file
View File

@@ -0,0 +1,360 @@
#! /bin/sh
# Copyright (C) 2001, 2002 by Martin Pool <mbp@samba.org>
# Copyright (C) 2003-2022 Wayne Davison
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version
# 2 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# -------------------------------------------------------------------------
# rsync top-level test script -- this invokes all the other more
# detailed tests in order. This script can either be called by `make
# check' or `make installcheck'. `check' runs against the copies of
# the program and other files in the build directory, and
# `installcheck' against the installed copy of the program.
# It can also be called on a single test file using a run like this:
#
# preserve_scratch=yes whichtests=itemize.test ./runtests.sh
# In either case we need to also be able to find the source directory,
# since we read test scripts and possibly other information from
# there.
# Whenever possible, informational messages are written to stdout and
# error messages to stderr. They're separated out by the build farm
# display scripts.
# According to the GNU autoconf manual, the only valid place to set up
# directory locations is through Make, since users are allowed to (try
# to) change their mind on the Make command line. So, Make has to
# pass in all the values we need.
# For other configured settings we read ./config.sh, which tells us
# about shell commands on this machine and similar things.
# rsync_bin gives the location of the rsync binary. This is either
# builddir/rsync if we're testing an uninstalled copy, or
# install_prefix/bin/rsync if we're testing an installed copy. On the
# build farm rsync will be installed, but into a scratch /usr.
# srcdir gives the location of the source tree, which lets us find the
# build scripts. At the moment we assume we are invoked from the
# source directory.
# This script must be invoked from the build directory.
# A scratch directory, 'testtmp', is used in the build directory to
# hold per-test subdirectories.
# This script also uses the $loglevel environment variable. 1 is the
# default value, and 10 the most verbose. You can set this from the
# Make command line. It's also set by the build farm to give more
# detail for failing builds.
# -------------------------------------------------------------------------
# NOTES FOR TEST CASES:
# Each test case runs in its own shell.
# Exit codes from tests:
# 1 tests failed
# 2 error in starting tests
# 77 this test skipped (random value unlikely to happen by chance, same as
# automake)
# HOWEVER, the overall exit code to the farm is different: we return
# the *number of tests that failed*, so that it will show up nicely in
# the overall summary.
# rsync.fns contains some general setup functions and definitions.
# -------------------------------------------------------------------------
# NOTES ON PORTABILITY:
# Both this script and the Makefile have to be pretty conservative
# about which Unix features they use.
# We cannot count on Make exporting variables to commands, unless
# they're explicitly given on the command line.
# Also, we can't count on 'cp -a' or 'mkdir -p', although they're
# pretty handy (see function makepath for the latter).
# I think some of the GNU documentation suggests that we shouldn't
# rely on shell functions. However, the Bash manual seems to say that
# they're in POSIX 1003.2, and since the build farm relies on them
# they're probably working on most machines we really care about.
# You cannot use "function foo {" syntax, but must instead say "foo()
# {", or it breaks on FreeBSD.
# BSD machines tend not to have "head" or "seq".
# You cannot do "export VAR=VALUE" all on one line; the export must be
# separate from the assignment. (SCO SysV)
# Don't rely on grep -q, as that doesn't work everywhere -- just redirect
# stdout to /dev/null to keep it quiet.
# -------------------------------------------------------------------------
# STILL TO DO:
# We need a good protection against tests that hang indefinitely.
# Perhaps some combination of starting them in the background, wait,
# and kill?
# Perhaps we need a common way to cleanup tests. At the moment just
# clobbering the directory when we're done should be enough.
# If any of the targets fail, then (GNU?) Make returns 2, instead of
# the return code from the failing command. This is fine, but it
# means that the build farm just shows "2" for failed tests, not the
# number of tests that actually failed. For more details we might
# need to grovel through the log files to find a line saying how many
# failed.
set -e
. "./shconfig"
RUNSHFLAGS='-e'
export RUNSHFLAGS
# for Solaris
if [ -d /usr/xpg4/bin ]; then
PATH="/usr/xpg4/bin/:$PATH"
export PATH
fi
if [ "x$loglevel" != x ] && [ "$loglevel" -gt 8 ]; then
if set -x; then
# If it doesn't work the first time, don't keep trying.
RUNSHFLAGS="$RUNSHFLAGS -x"
fi
fi
POSIXLY_CORRECT=1
if test x"$TOOLDIR" = x; then
TOOLDIR=`pwd`
fi
srcdir=`dirname $0`
if test x"$srcdir" = x || test x"$srcdir" = x.; then
srcdir="$TOOLDIR"
fi
if test x"$rsync_bin" = x; then
rsync_bin="$TOOLDIR/rsync"
fi
# This allows the user to specify extra rsync options -- use carefully!
RSYNC="$rsync_bin $*"
#RSYNC="valgrind $rsync_bin $*"
TLS_ARGS=''
if grep -E '^#define HAVE_LUTIMES 1' config.h >/dev/null; then
TLS_ARGS="$TLS_ARGS -l"
fi
if grep -E '#undef CHOWN_MODIFIES_SYMLINK' config.h >/dev/null; then
TLS_ARGS="$TLS_ARGS -L"
fi
export POSIXLY_CORRECT TOOLDIR srcdir RSYNC TLS_ARGS
echo "============================================================"
echo "$0 running in $TOOLDIR"
echo " rsync_bin=$RSYNC"
echo " srcdir=$srcdir"
echo " TLS_ARGS=$TLS_ARGS"
if [ -f /usr/bin/whoami ]; then
testuser=`/usr/bin/whoami`
elif [ -f /usr/ucb/whoami ]; then
testuser=`/usr/ucb/whoami`
elif [ -f /bin/whoami ]; then
testuser=`/bin/whoami`
else
testuser=`id -un 2>/dev/null || echo ${LOGNAME:-${USERNAME:-${USER:-'UNKNOWN'}}}`
fi
echo " testuser=$testuser"
echo " os=`uname -a`"
# It must be "yes", not just nonnull
if [ "x$preserve_scratch" = xyes ]; then
echo " preserve_scratch=yes"
else
echo " preserve_scratch=no"
fi
# Check if setacl/setfacl is around and if it supports the -k or -s option.
if setacl -k u::7,g::5,o:5 testsuite 2>/dev/null; then
setfacl_nodef='setacl -k'
elif setfacl --help 2>&1 | grep ' -k,\|\[-[a-z]*k' >/dev/null; then
setfacl_nodef='setfacl -k'
elif setfacl -s u::7,g::5,o:5 testsuite 2>/dev/null; then
setfacl_nodef='setfacl -s u::7,g::5,o:5'
else
# The "true" command runs successfully, but does nothing.
setfacl_nodef=true
fi
export setfacl_nodef
if [ ! -f "$rsync_bin" ]; then
echo "rsync_bin $rsync_bin is not a file" >&2
exit 2
fi
if [ ! -d "$srcdir" ]; then
echo "srcdir $srcdir is not a directory" >&2
exit 2
fi
expect_skipped="${RSYNC_EXPECT_SKIPPED-IGNORE}"
skipped_list=''
skipped=0
missing=0
passed=0
failed=0
# Directory that holds the other test subdirs. We create separate dirs
# inside for each test case, so that they can be left behind in case of
# failure to aid investigation. We don't remove the testtmp subdir at
# the end so that it can be configured as a symlink to a filesystem that
# has ACLs and xattr support enabled (if desired).
scratchbase="${scratchbase:-$TOOLDIR}"/testtmp
echo " scratchbase=$scratchbase"
[ -d "$scratchbase" ] || mkdir "$scratchbase"
suitedir="$srcdir/testsuite"
TESTRUN_TIMEOUT=300
export scratchdir suitedir TESTRUN_TIMEOUT
prep_scratch() {
[ -d "$scratchdir" ] && chmod -R u+rwX "$scratchdir" && rm -rf "$scratchdir"
mkdir "$scratchdir"
# Get rid of default ACLs and dir-setgid to avoid confusing some tests.
$setfacl_nodef "$scratchdir" 2>/dev/null || true
chmod g-s "$scratchdir"
case "$srcdir" in
/*) ln -s "$srcdir" "$scratchdir/src" ;;
*) ln -s "$TOOLDIR/$srcdir" "$scratchdir/src" ;;
esac
return 0
}
maybe_discard_scratch() {
[ x"$preserve_scratch" != xyes ] && [ -d "$scratchdir" ] && rm -rf "$scratchdir"
return 0
}
if [ "x$whichtests" = x ]; then
whichtests="*.test"
full_run=yes
else
full_run=no
fi
for testscript in $suitedir/$whichtests; do
testbase=`echo $testscript | sed -e 's!.*/!!' -e 's/.test\$//'`
scratchdir="$scratchbase/$testbase"
prep_scratch
case "$testscript" in
*hardlinks*) TESTRUN_TIMEOUT=600 ;;
*) TESTRUN_TIMEOUT=300 ;;
esac
set +e
"$TOOLDIR/"testrun $RUNSHFLAGS "$testscript" >"$scratchdir/test.log" 2>&1
result=$?
set -e
if [ "x$always_log" = xyes ] || ( [ $result != 0 ] && [ $result != 77 ] && [ $result != 78 ] )
then
echo "----- $testbase log follows"
cat "$scratchdir/test.log"
echo "----- $testbase log ends"
if [ -f "$scratchdir/rsyncd.log" ]; then
echo "----- $testbase rsyncd.log follows"
cat "$scratchdir/rsyncd.log"
echo "----- $testbase rsyncd.log ends"
fi
fi
case $result in
0)
echo "PASS $testbase"
passed=`expr $passed + 1`
maybe_discard_scratch
;;
77)
# backticks will fill the whole file onto one line, which is a feature
whyskipped=`cat "$scratchdir/whyskipped"`
echo "SKIP $testbase ($whyskipped)"
skipped_list="$skipped_list,$testbase"
skipped=`expr $skipped + 1`
maybe_discard_scratch
;;
78)
# It failed, but we expected that. don't dump out error logs,
# because most users won't want to see them. But do leave
# the working directory around.
echo "XFAIL $testbase"
failed=`expr $failed + 1`
;;
*)
echo "FAIL $testbase"
failed=`expr $failed + 1`
if [ "x$nopersist" = xyes ]; then
exit 1
fi
esac
done
echo '------------------------------------------------------------'
echo "----- overall results:"
echo " $passed passed"
[ "$failed" -gt 0 ] && echo " $failed failed"
[ "$skipped" -gt 0 ] && echo " $skipped skipped"
[ "$missing" -gt 0 ] && echo " $missing missing"
if [ "$full_run" = yes ] && [ "$expect_skipped" != IGNORE ]; then
skipped_list=`echo "$skipped_list" | sed 's/^,//'`
echo "----- skipped results:"
echo " expected: $expect_skipped"
echo " got: $skipped_list"
else
skipped_list=''
expect_skipped=''
fi
echo '------------------------------------------------------------'
# OK, so expr exits with 0 if the result is neither null nor zero; and
# 1 if the expression is null or zero. This is the opposite of what
# we want, and if we just call expr then this script will always fail,
# because -e is set.
result=`expr $failed + $missing || true`
if [ "$result" = 0 ] && [ "$skipped_list" != "$expect_skipped" ]; then
result=1
fi
echo "overall result is $result"
exit $result

View File

@@ -48,8 +48,6 @@ extern int make_backups;
extern int inplace;
extern int inplace_partial;
extern int batch_fd;
extern int use_secure_symlinks;
extern char *module_dir;
extern int write_batch;
extern int file_old_total;
extern BOOL want_progress_now;
@@ -140,8 +138,6 @@ void successful_send(int ndx)
return;
flist = flist_for_ndx(ndx, "successful_send");
if (ndx < flist->ndx_start)
exit_cleanup(RERR_PROTOCOL);
file = flist->files[ndx - flist->ndx_start];
if (!change_pathname(file, NULL, 0))
return;
@@ -266,8 +262,6 @@ void send_files(int f_in, int f_out)
if (ndx - cur_flist->ndx_start >= 0)
file = cur_flist->files[ndx - cur_flist->ndx_start];
else if (cur_flist->parent_ndx < 0)
exit_cleanup(RERR_PROTOCOL);
else
file = dir_flist->files[cur_flist->parent_ndx];
if (F_PATHNAME(file)) {
@@ -356,32 +350,7 @@ void send_files(int f_in, int f_out)
exit_cleanup(RERR_PROTOCOL);
}
if (use_secure_symlinks) {
/* Open from module root to prevent TOCTOU race where
* change_pathname's chdir follows a directory symlink.
* Reconstruct the full path relative to module_dir
* from F_PATHNAME (path) and f_name (fname). */
char secure_path[MAXPATHLEN];
const char *relp;
int slen = snprintf(secure_path, sizeof secure_path, "%s%s%s", path, slash, fname);
if (slen >= (int)sizeof secure_path) {
io_error |= IOERR_GENERAL;
rprintf(FERROR_XFER, "path too long: %s%s%s\n", path, slash, fname);
free_sums(s);
if (protocol_version >= 30)
send_msg_int(MSG_NO_SEND, ndx);
continue;
}
/* A module with `path = /` makes F_PATHNAME absolute, so the
* joined path starts with '/'; strip leading slashes to a
* module-relative path that secure_relative_open accepts (#897). */
relp = secure_path;
while (*relp == '/')
relp++;
fd = secure_relative_open(module_dir, relp, O_RDONLY, 0);
} else {
fd = do_open_checklinks(fname);
}
fd = do_open_checklinks(fname);
if (fd == -1) {
if (errno == ENOENT) {
enum logcode c = am_daemon && protocol_version < 28 ? FERROR : FWARNING;

View File

@@ -347,7 +347,8 @@ __attribute__ ((target("avx2"))) MVSTATIC int32 get_checksum1_avx2_64(schar* buf
__m128i tmp = _mm_load_si128((__m128i*) mul_t1_buf);
__m256i mul_t1 = _mm256_cvtepu8_epi16(tmp);
__m256i mul_const = _mm256_broadcastd_epi32(_mm_cvtsi32_si128(4 | (3 << 8) | (2 << 16) | (1 << 24)));
__m256i mul_one = _mm256_set1_epi8(1);
__m256i mul_one;
mul_one = _mm256_abs_epi8(_mm256_cmpeq_epi16(mul_one,mul_one)); // set all vector elements to 1
for (; i < (len-64); i+=64) {
// Load ... 4*[int8*16]
@@ -547,118 +548,6 @@ int main() {
#pragma clang optimize on
#endif /* BENCHMARK_SIMD_CHECKSUM1 */
#ifdef TEST_SIMD_CHECKSUM1
static uint32 checksum_via_default(char *buf, int32 len)
{
uint32 s1 = 0, s2 = 0;
get_checksum1_default_1((schar*)buf, len, 0, &s1, &s2);
return (s1 & 0xffff) + (s2 << 16);
}
static uint32 checksum_via_sse2(char *buf, int32 len)
{
int32 i;
uint32 s1 = 0, s2 = 0;
i = get_checksum1_sse2_32((schar*)buf, len, 0, &s1, &s2);
get_checksum1_default_1((schar*)buf, len, i, &s1, &s2);
return (s1 & 0xffff) + (s2 << 16);
}
static uint32 checksum_via_ssse3(char *buf, int32 len)
{
int32 i;
uint32 s1 = 0, s2 = 0;
i = get_checksum1_ssse3_32((schar*)buf, len, 0, &s1, &s2);
get_checksum1_default_1((schar*)buf, len, i, &s1, &s2);
return (s1 & 0xffff) + (s2 << 16);
}
static uint32 checksum_via_avx2(char *buf, int32 len)
{
int32 i;
uint32 s1 = 0, s2 = 0;
#ifdef USE_ROLL_ASM
i = get_checksum1_avx2_asm((schar*)buf, len, 0, &s1, &s2);
#else
i = get_checksum1_avx2_64((schar*)buf, len, 0, &s1, &s2);
#endif
get_checksum1_default_1((schar*)buf, len, i, &s1, &s2);
return (s1 & 0xffff) + (s2 << 16);
}
int main()
{
static const int sizes[] = {1, 4, 31, 32, 33, 63, 64, 65, 128, 129, 256, 700, 1024, 4096, 65536};
int num_sizes = sizeof(sizes) / sizeof(sizes[0]);
int max_size = sizes[num_sizes - 1];
int failures = 0;
/* Allocate with extra bytes for unaligned test */
unsigned char *raw = (unsigned char *)malloc(max_size + 64 + 1);
if (!raw) {
fprintf(stderr, "malloc failed\n");
return 1;
}
/* Fill with deterministic data */
for (int i = 0; i < max_size + 64 + 1; i++)
raw[i] = (i + (i % 3) + (i % 11)) % 256;
/* Test with aligned buffer (64-byte aligned) */
unsigned char *aligned = raw + (64 - ((uintptr_t)raw % 64));
/* Test with unaligned buffer (+1 byte offset) */
unsigned char *unaligned = aligned + 1;
struct { const char *name; unsigned char *buf; } buffers[] = {
{"aligned", aligned},
{"unaligned", unaligned},
};
for (int b = 0; b < 2; b++) {
char *buf = (char *)buffers[b].buf;
const char *bname = buffers[b].name;
for (int s = 0; s < num_sizes; s++) {
int32 len = sizes[s];
uint32 ref = checksum_via_default(buf, len);
uint32 cs_sse2 = checksum_via_sse2(buf, len);
uint32 cs_ssse3 = checksum_via_ssse3(buf, len);
uint32 cs_avx2 = checksum_via_avx2(buf, len);
uint32 cs_auto = get_checksum1(buf, len);
if (cs_sse2 != ref) {
printf("FAIL %-9s size=%5d: SSE2=%08x ref=%08x\n", bname, len, cs_sse2, ref);
failures++;
}
if (cs_ssse3 != ref) {
printf("FAIL %-9s size=%5d: SSSE3=%08x ref=%08x\n", bname, len, cs_ssse3, ref);
failures++;
}
if (cs_avx2 != ref) {
printf("FAIL %-9s size=%5d: AVX2=%08x ref=%08x\n", bname, len, cs_avx2, ref);
failures++;
}
if (cs_auto != ref) {
printf("FAIL %-9s size=%5d: auto=%08x ref=%08x\n", bname, len, cs_auto, ref);
failures++;
}
}
}
free(raw);
if (failures) {
printf("%d checksum mismatches!\n", failures);
return 1;
}
printf("All SIMD checksum tests passed.\n");
return 0;
}
#endif /* TEST_SIMD_CHECKSUM1 */
#endif /* } USE_ROLL_SIMD */
#endif /* } __cplusplus */
#endif /* } __x86_64__ */

View File

@@ -47,23 +47,21 @@ static struct sigaction sigact;
static int sock_exec(const char *prog);
#define PROXY_BUF_SIZE 1024
/* Establish a proxy connection on an open socket to a web proxy by using the
* CONNECT method. If proxy_user and proxy_pass are not NULL, they are used to
* authenticate to the proxy using the "Basic" proxy-authorization protocol. */
static int establish_proxy_connection(int fd, char *host, int port, char *proxy_user, char *proxy_pass)
{
char *cp, buffer[PROXY_BUF_SIZE + 1];
char *authhdr, authbuf[PROXY_BUF_SIZE + 1];
char *cp, buffer[1024];
char *authhdr, authbuf[1024];
int len;
if (proxy_user && proxy_pass) {
stringjoin(buffer, PROXY_BUF_SIZE,
stringjoin(buffer, sizeof buffer,
proxy_user, ":", proxy_pass, NULL);
len = strlen(buffer);
if ((len*8 + 5) / 6 >= PROXY_BUF_SIZE - 3) {
if ((len*8 + 5) / 6 >= (int)sizeof authbuf - 3) {
rprintf(FERROR,
"authentication information is too long\n");
return -1;
@@ -76,14 +74,14 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
authhdr = "";
}
len = snprintf(buffer, PROXY_BUF_SIZE, "CONNECT %s:%d HTTP/1.0%s%s\r\n\r\n", host, port, authhdr, authbuf);
assert(len > 0 && len < PROXY_BUF_SIZE);
len = snprintf(buffer, sizeof buffer, "CONNECT %s:%d HTTP/1.0%s%s\r\n\r\n", host, port, authhdr, authbuf);
assert(len > 0 && len < (int)sizeof buffer);
if (write(fd, buffer, len) != len) {
rsyserr(FERROR, errno, "failed to write to proxy");
return -1;
}
for (cp = buffer; cp < &buffer[PROXY_BUF_SIZE - 1]; cp++) {
for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
if (read(fd, cp, 1) != 1) {
rsyserr(FERROR, errno, "failed to read from proxy");
return -1;
@@ -92,13 +90,11 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
break;
}
if (cp == &buffer[PROXY_BUF_SIZE - 1]) {
rprintf(FERROR, "proxy response line too long\n");
return -1;
}
*cp = '\0';
if (cp > buffer && cp[-1] == '\r')
cp[-1] = '\0';
if (*cp != '\n')
cp++;
*cp-- = '\0';
if (*cp == '\r')
*cp = '\0';
if (strncmp(buffer, "HTTP/", 5) != 0) {
rprintf(FERROR, "bad response from proxy -- %s\n",
buffer);
@@ -114,7 +110,7 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
}
/* throw away the rest of the HTTP header */
while (1) {
for (cp = buffer; cp < &buffer[PROXY_BUF_SIZE]; cp++) {
for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
if (read(fd, cp, 1) != 1) {
rsyserr(FERROR, errno,
"failed to read from proxy");

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
import os, re, argparse, subprocess
from datetime import datetime, UTC
from datetime import datetime
NULL_COMMIT_RE = re.compile(r'\0\0commit [a-f0-9]{40}$|\0$')
@@ -74,7 +74,7 @@ def print_line(fn, mtime, commit_time):
if args.list > 1:
ts = str(commit_time).rjust(10)
else:
ts = datetime.fromtimestamp(commit_time, UTC).strftime("%Y-%m-%d %H:%M:%S")
ts = datetime.utcfromtimestamp(commit_time).strftime("%Y-%m-%d %H:%M:%S")
chg = '.' if mtime == commit_time else '*'
print(chg, ts, fn)

View File

@@ -46,7 +46,6 @@ long_opts = {
'compare-dest': 2,
'compress-choice': 1,
'compress-level': 1,
'compress-threads': 1,
'copy-dest': 2,
'copy-devices': -1,
'copy-unsafe-links': 0,
@@ -60,7 +59,6 @@ long_opts = {
'delete-during': 0,
'delete-excluded': 0,
'delete-missing-args': 0,
'dirs': 0,
'existing': 0,
'fake-super': 0,
'files-from': 3,
@@ -302,7 +300,6 @@ def validated_arg(opt, arg, typ=3, wild=False):
if arg.startswith('./'):
arg = arg[1:]
arg = arg.replace('//', '/')
arg = arg.lstrip('/')
if args.dir != '/':
if HAS_DOT_DOT_RE.search(arg):
die("do not use .. in", opt, "(anchor the path at the root of your restricted dir)")

View File

@@ -2,7 +2,7 @@
REAL_RSYNC=/usr/bin/rsync
IGNOREEXIT=24
IGNOREOUT='^((file|directory) has vanished: |rsync warning: some files vanished before they could be transferred)'
IGNOREOUT='^(file has vanished: |rsync warning: some files vanished before they could be transferred)'
# If someone installs this as "rsync", make sure we don't affect a server run.
for arg in "${@}"; do

1365
syscall.c
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,117 +0,0 @@
/*
* Test harness for do_chmod_at(). Confirms the symlink-TOCTOU
* primitive used by CVE-2026-29518 (and its incomplete-fix follow-up
* for chmod) is closed by do_chmod_at(): a parent directory component
* being a symlink that escapes the receiver's confinement must be
* rejected, while a parent symlink that resolves *within* the tree
* must still work (so legitimate dir-symlinks are not regressed).
*
* Not linked into rsync itself.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "rsync.h"
#include <sys/stat.h>
int dry_run = 0;
int am_root = 0;
int am_sender = 0;
int read_only = 0;
int list_only = 0;
int copy_links = 0;
int copy_unsafe_links = 0;
extern int am_daemon, am_chrooted;
short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
static int errs = 0;
static void check(const char *label, int actual_rc, int expect_ok,
const char *path, mode_t expected_mode)
{
struct stat st;
int got_ok = (actual_rc == 0);
if (got_ok != expect_ok) {
fprintf(stderr, "FAIL [%s]: rc=%d errno=%d (%s), expected %s\n",
label, actual_rc, errno, strerror(errno),
expect_ok ? "success" : "rejection");
errs++;
return;
}
if (path && stat(path, &st) < 0) {
fprintf(stderr, "FAIL [%s]: stat(%s) failed: %s\n",
label, path, strerror(errno));
errs++;
return;
}
if (path && (st.st_mode & 07777) != expected_mode) {
fprintf(stderr,
"FAIL [%s]: %s mode is 0%o, expected 0%o\n",
label, path, st.st_mode & 07777, expected_mode);
errs++;
return;
}
fprintf(stderr, "OK [%s]\n", label);
}
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "usage: %s <module-dir>\n", argv[0]);
return 2;
}
if (chdir(argv[1]) < 0) {
perror("chdir");
return 2;
}
/* Simulate the daemon-without-chroot deployment that do_chmod_at()
* defends. With am_daemon=0 or am_chrooted=1 the wrapper falls
* through to plain do_chmod() and the symlink-race test would be
* meaningless. */
am_daemon = 1;
am_chrooted = 0;
/* Test layout (all inside the directory we just chdir'd to):
*
* ./realdir/sentinel -- regular target file
* ./inside_link -> realdir -- legitimate dir-symlink within the tree
* ./escape_link -> ../trap -- attacker swap, target outside tree
* ../trap/sentinel -- the file the attacker wants to alter
*
* The shell wrapper that calls this helper has set both sentinel
* files to mode 0600 so we have a clean baseline to compare.
*/
/* Scenario A: legitimate parent dir-symlink, chmod must succeed. */
int rc = do_chmod_at("inside_link/sentinel", 0640);
check("A: legit dir-symlink within tree",
rc, 1, "realdir/sentinel", 0640);
/* Scenario B: parent symlink escapes the tree -- chmod must be
* rejected and the outside file's mode must be unchanged. */
rc = do_chmod_at("escape_link/sentinel", 0666);
check("B: parent symlink escapes tree (the attack)",
rc, 0, "../trap/sentinel", 0600);
/* Scenario C: plain relative path with no symlink components,
* regression check that the safe wrapper doesn't break the
* normal case. */
rc = do_chmod_at("realdir/sentinel", 0644);
check("C: plain relative path (regression check)",
rc, 1, "realdir/sentinel", 0644);
/* Scenario D: top-level file, no parent directory component.
* Falls back to do_chmod(); should succeed. */
rc = do_chmod_at("topfile", 0640);
check("D: top-level file, no parent component",
rc, 1, "topfile", 0640);
if (errs)
fprintf(stderr, "%d failure(s)\n", errs);
return errs ? 1 : 0;
}

View File

@@ -1,151 +0,0 @@
/*
* Test harness for secure_relative_open()'s front-door input
* validation. Codex audit Finding 5 noted that the existing check
*
* if (strncmp(relpath, "../", 3) == 0 || strstr(relpath, "/../"))
*
* catches "../foo" and "foo/../bar" but misses bare ".." (an actual
* one-level escape on platforms that fall back to the per-component
* walk), as well as "a/..", "foo/..", and any other form that
* decomposes to a ".." component when split on "/". The kernel-
* enforced RESOLVE_BENEATH (Linux 5.6+) and O_RESOLVE_BENEATH
* (FreeBSD 13+, macOS 15+) reject these in-kernel; the per-
* component fallback used on NetBSD, OpenBSD, Solaris, Cygwin and
* pre-5.6 Linux does not, so the validation must happen at the
* front door.
*
* This helper invokes secure_relative_open() with each suspect
* input and checks both the failure (rc < 0) and the errno
* (EINVAL means "rejected at the front door"). Pre-fix, the kernel
* may reject with a different errno (EXDEV from RESOLVE_BENEATH);
* post-fix, the front-door check catches every variant up front
* with a consistent EINVAL across platforms.
*
* Not linked into rsync itself.
*/
#include "rsync.h"
#include <sys/stat.h>
int dry_run = 0;
int am_root = 0;
int am_sender = 0;
int read_only = 0;
int list_only = 0;
int copy_links = 0;
int copy_unsafe_links = 0;
extern int am_daemon, am_chrooted;
short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
static int errs = 0;
static void check_relpath(const char *relpath)
{
int fd;
int saved_errno;
errno = 0;
fd = secure_relative_open(NULL, relpath, O_RDONLY | O_DIRECTORY, 0);
saved_errno = errno;
if (fd >= 0) {
fprintf(stderr,
"FAIL [relpath=%-12s]: returned valid fd %d (escape) -- expected -1 EINVAL\n",
relpath, fd);
close(fd);
errs++;
return;
}
if (saved_errno != EINVAL) {
fprintf(stderr,
"FAIL [relpath=%-12s]: rejected but errno=%d (%s), expected EINVAL\n",
relpath, saved_errno, strerror(saved_errno));
errs++;
return;
}
fprintf(stderr, "OK [relpath=%-12s]: rejected with EINVAL\n", relpath);
}
static void check_basedir(const char *basedir)
{
int fd;
int saved_errno;
errno = 0;
fd = secure_relative_open(basedir, "ok", O_RDONLY | O_DIRECTORY, 0);
saved_errno = errno;
if (fd >= 0) {
fprintf(stderr,
"FAIL [basedir=%-12s]: returned valid fd %d -- expected -1 EINVAL\n",
basedir, fd);
close(fd);
errs++;
return;
}
if (saved_errno != EINVAL) {
fprintf(stderr,
"FAIL [basedir=%-12s]: rejected but errno=%d (%s), expected EINVAL\n",
basedir, saved_errno, strerror(saved_errno));
errs++;
return;
}
fprintf(stderr, "OK [basedir=%-12s]: rejected with EINVAL\n", basedir);
}
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "usage: %s <test-dir>\n", argv[0]);
return 2;
}
if (chdir(argv[1]) < 0) {
perror("chdir");
return 2;
}
/* secure_relative_open's daemon-only confinement protections only
* fire when am_daemon && !am_chrooted (the threat model is the
* daemon-no-chroot deployment), but the front-door input
* validation runs unconditionally. We set am_daemon anyway so the
* helper exercises the same code shape the receiver does. */
am_daemon = 1;
am_chrooted = 0;
mkdir("subdir", 0755);
/* Each of these relpaths must be rejected with EINVAL at the
* secure_relative_open() front door. ".." is the actual one-level
* escape; the others ("subdir/..", "subdir/../subdir") resolve
* back to the start dir on systems that allow them, but we still
* reject them as defence-in-depth: a path containing a ".." token
* is suspicious and the caller should normalise before passing
* it in. The "../foo" / "foo/../bar" / "/foo" / "/" cases are
* regression checks for the existing checks. */
check_relpath("..");
check_relpath("../foo");
check_relpath("subdir/..");
check_relpath("subdir/../subdir");
check_relpath("foo/../bar");
check_relpath("/foo");
check_relpath("/");
/* Same checks against basedir (which the codex Finding 2 fix
* routes through the same RESOLVE_BENEATH-equivalent). Absolute
* basedirs are operator-trusted and intentionally not validated
* here. */
check_basedir("..");
check_basedir("../subdir");
check_basedir("subdir/..");
check_basedir("foo/../bar");
if (errs)
fprintf(stderr, "\n%d failure(s)\n", errs);
return errs ? 1 : 0;
}

View File

@@ -23,8 +23,6 @@
int do_fsync = 0;
int inplace = 0;
int am_daemon = 0;
int am_chrooted = 0;
int modify_window = 0;
int preallocate_files = 0;
int protect_args = 0;
@@ -36,14 +34,9 @@ int preserve_perms = 0;
int preserve_executability = 0;
int omit_link_times = 0;
int open_noatime = 0;
size_t max_alloc = (size_t)-1; /* unlimited: helpers link util2.o, where 0 makes
* every my_alloc()/my_strdup() abort with
* "exceeded --max-alloc=0" (hit on the
* secure_relative_open() fallback path). */
size_t max_alloc = 0; /* max_alloc is needed when combined with util2.o */
char *partial_dir;
char *module_dir;
/* curr_dir[]/curr_dir_len (read by secure_relative_open) are defined in
* syscall.c, which every helper links -- no stub needed here. */
filter_rule_list daemon_filter_list;
void rprintf(UNUSED(enum logcode code), const char *format, ...)

View File

@@ -1,113 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for the basedir-confinement gap in
# secure_relative_open(). The function opens basedir with a plain
# openat(AT_FDCWD, basedir, O_RDONLY | O_DIRECTORY), without
# RESOLVE_BENEATH or a per-component O_NOFOLLOW walk, so a parent
# symlink ON basedir is followed unrestrictedly. RESOLVE_BENEATH is
# then applied only to relpath, anchored at the wrong directory.
#
# The receiver's basis-file lookup at receiver.c passes
# basis_dir[fnamecmp_type] (from --copy-dest / --link-dest /
# --compare-dest -- all sender-controllable in daemon mode) as
# basedir. A daemon-module attacker with write access can plant a
# symlink at module/cd -> /outside, then run --link-dest=cd to
# make the daemon's basis-file lookup resolve into /outside,
# leaking the contents of daemon-readable files via the rsync
# delta-rolling read-disclosure primitive.
#
# We detect the escape by leveraging --link-dest: when basis
# matches source exactly (content + mtime + mode), --link-dest
# hard-links the destination to the basis file. With the bug, the
# destination ends up as a hard link to the outside-the-module
# file (same inode). With the fix, no basis is found and the
# destination is a fresh copy (different inode).
#
# The vulnerable code path is the same on every platform
# (including the per-component fallback on systems without
# RESOLVE_BENEATH), so this test is not platform-gated.
. "$suitedir/rsync.fns"
mod="$scratchdir/module"
outside="$scratchdir/outside"
src="$scratchdir/src"
conf="$scratchdir/test-rsyncd.conf"
rm -rf "$mod" "$outside" "$src"
mkdir -p "$mod" "$outside" "$src"
# Portable inode-number helper (GNU coreutils stat -c, BSD stat -f).
file_inode() {
stat -c %i "$1" 2>/dev/null || stat -f %i "$1"
}
# Outside-the-module file an attacker would like the daemon to
# treat as a basis.
echo "OUTSIDE_SECRET_DATA" > "$outside/target.txt"
chmod 0644 "$outside/target.txt"
# The symlink trap planted in the module by the local attacker.
ln -s "$outside" "$mod/cd"
# Source file matches outside/target.txt exactly (content + mtime
# + mode) so --link-dest will hard-link the destination to the
# basis file iff the daemon's basedir lookup reaches outside/.
echo "OUTSIDE_SECRET_DATA" > "$src/target.txt"
touch -r "$outside/target.txt" "$src/target.txt"
chmod 0644 "$src/target.txt"
# When running as root the daemon would drop to "nobody" by
# default, which can't write into the test scratch dir. Force the
# daemon to keep our uid/gid in that case so the basis-link
# transfer can actually create the destination file. (Non-root
# can't specify uid/gid in rsyncd.conf -- comment them out then.)
my_uid=`get_testuid`
root_uid=`get_rootuid`
root_gid=`get_rootgid`
uid_setting="uid = $root_uid"
gid_setting="gid = $root_gid"
if test x"$my_uid" != x"$root_uid"; then
uid_setting="#$uid_setting"
gid_setting="#$gid_setting"
fi
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload]
path = $mod
use chroot = no
read only = no
EOF
# Recursive --link-dest push directly into the module root. We
# avoid pushing into a destination subdir because the receiver
# would chdir into it before resolving --link-dest, making the
# relative basedir "cd" resolve in the wrong CWD and masking the
# bug. The realistic attack pushes into the module root (or the
# attacker uses a basedir path that resolves correctly from
# whichever subdir the receiver chdirs into).
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -rtp --link-dest=cd "$src/" rsync://localhost/upload/ \
>/dev/null 2>&1 || true
if [ ! -f "$mod/target.txt" ]; then
test_fail "destination file was not created -- daemon transfer failed before the test could observe the basedir behaviour"
fi
outside_inode=$(file_inode "$outside/target.txt")
dst_inode=$(file_inode "$mod/target.txt")
if [ "$outside_inode" = "$dst_inode" ]; then
test_fail "basedir-escape: --link-dest hard-linked module/target.txt to outside/target.txt (inode $outside_inode); daemon's basis-file lookup followed the parent symlink on the basedir"
fi
exit 0

View File

@@ -1,206 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for codex audit Findings 3b and 3c:
#
# 3b: generator.c:1905 -- the in-place backup creation opens
# backupptr via bare do_open(O_WRONLY|O_CREAT|O_TRUNC|O_EXCL).
# With --backup-dir set to an attacker-planted parent symlink,
# the backup file is written outside the module under the
# daemon's authority.
#
# 3c-symlink: syscall.c:207 -- do_symlink_at falls through to bare
# do_symlink for am_root < 0 (fake-super), which then opens
# the destination path with bare open() (final-component
# fake-super file). A parent symlink on the destination path
# redirects the file creation outside the module.
#
# 3c-mknod: syscall.c:506 -- do_mknod_at falls through to bare
# do_mknod for am_root < 0, same path-based open(). For
# FIFOs/sockets/devices the bare path is also used.
#
# Each scenario plants a "secret" file outside the module at a
# location the symlink trap points to. The check is that the
# outside file's content and mode are unchanged after the attack
# attempt.
. "$suitedir/rsync.fns"
# All three scenarios depend on receiver-side daemon code paths
# that are only secured on platforms with a working
# secure_relative_open. The chdir/chmod tests already skip the
# same set; mirror that.
case "$(uname -s)" in
SunOS|OpenBSD|NetBSD|CYGWIN*)
test_skipped "secure_relative_open relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
;;
esac
mod="$scratchdir/module"
outside="$scratchdir/outside"
src="$scratchdir/src"
conf="$scratchdir/test-rsyncd.conf"
# Portable inode-and-mode helpers.
file_mode() {
stat -c %a "$1" 2>/dev/null || stat -f %Lp "$1"
}
setup() {
rm -rf "$mod" "$outside" "$src"
mkdir -p "$mod" "$outside" "$src"
echo "OUTSIDE_PROTECTED_DATA" > "$outside/target.txt"
chmod 0644 "$outside/target.txt"
outside_pristine="$scratchdir/outside-pristine.txt"
cp -p "$outside/target.txt" "$outside_pristine"
ln -s "$outside" "$mod/cd"
}
verify_outside_unchanged() {
label="$1"
mode=$(file_mode "$outside/target.txt")
case "$mode" in
644|0644) ;;
*) test_fail "$label: outside/target.txt mode changed from 644 to $mode" ;;
esac
if ! cmp -s "$outside/target.txt" "$outside_pristine"; then
test_fail "$label: outside/target.txt content changed -- daemon followed the cd symlink"
fi
}
verify_outside_unchanged_or_absent() {
label="$1"
target="$2" # specific file under outside/ to check absence of
if [ -e "$outside/$target" ]; then
test_fail "$label: outside/$target was created -- daemon followed the cd symlink"
fi
}
# When running as root the daemon would drop to "nobody" by default
# and fail to write into the test scratch dir. Force it to keep our
# uid/gid in that case so the receiver actually runs the code paths
# we want to test.
my_uid=`get_testuid`
root_uid=`get_rootuid`
root_gid=`get_rootgid`
uid_setting="uid = $root_uid"
gid_setting="gid = $root_gid"
if test x"$my_uid" != x"$root_uid"; then
uid_setting="#$uid_setting"
gid_setting="#$gid_setting"
fi
############################################################
# Scenario 3b: --inplace --backup --backup-dir=cd
#
# Pre-create module/target.txt so the receiver enters the in-place
# update path; a backup of the existing content must be made
# before the update. With --backup-dir=cd, backupptr resolves to
# "cd/target.txt"; with the bug, robust_unlink and the bare
# do_open at generator.c:1905 both follow the cd symlink, the
# unlink deletes outside/target.txt and the create writes the
# pre-existing module/target.txt content there.
############################################################
setup
echo "EXISTING_MODULE_DATA" > "$mod/target.txt"
chmod 0666 "$mod/target.txt"
echo "NEW_DATA_FROM_SENDER" > "$src/target.txt"
chmod 0644 "$src/target.txt"
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload]
path = $mod
use chroot = no
read only = no
EOF
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC --inplace --backup --backup-dir=cd "$src/target.txt" \
rsync://localhost/upload/target.txt >/dev/null 2>&1 || true
verify_outside_unchanged "3b inplace+backup-dir=cd"
############################################################
# Scenario 3c-symlink: fake-super symlink push to a path with a
# symlinked parent
#
# With "fake super = yes" set on the module, the receiver
# represents symlinks as fake-super files (regular files with the
# link target written to them). The path-based open() in
# do_symlink's fake-super branch follows parent symlinks. We push
# a single symlink to the destination path "cd/sym" so the
# receiver's create-file call lands at "cd/sym" relative to the
# module root, where cd is the symlink trap.
############################################################
setup
mkdir -p "$src/cd"
ln -s /etc/passwd "$src/cd/sym"
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload_fake]
path = $mod
use chroot = no
read only = no
fake super = yes
EOF
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -rl "$src/" rsync://localhost/upload_fake/ >/dev/null 2>&1 || true
verify_outside_unchanged_or_absent "3c-symlink fake-super symlink push" "sym"
############################################################
# Scenario 3c-mknod: fake-super FIFO push to a path with a
# symlinked parent
#
# Similar to 3c-symlink but for special files. mkfifo works
# without root; we push a FIFO and verify the receiver doesn't
# create a fake-super file at outside/fifo.
############################################################
setup
mkdir -p "$src/cd"
mkfifo "$src/cd/fifo" 2>/dev/null
if [ ! -p "$src/cd/fifo" ]; then
test_skipped "mkfifo unavailable; cannot exercise 3c-mknod"
fi
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload_fake]
path = $mod
use chroot = no
read only = no
fake super = yes
EOF
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -rD "$src/" rsync://localhost/upload_fake/ >/dev/null 2>&1 || true
verify_outside_unchanged_or_absent "3c-mknod fake-super FIFO push" "fifo"
exit 0

View File

@@ -1,135 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for the symlink-TOCTOU class of bug at the receiver's
# chdir(). After the CVE-2026-29518 fix to secure_relative_open(), an
# attack remained where the receiver's chdir() into a destination
# subdirectory followed an attacker-planted symlink, escaping the
# module. Every subsequent path-relative syscall (open, chmod, lchown,
# utimes, etc.) inherited the escape -- secure_relative_open's
# RESOLVE_BENEATH anchor itself was outside the module by then, so it
# stopped protecting against anything.
#
# This test runs an actual rsync daemon (via RSYNC_CONNECT_PROG to
# avoid the network) configured with "use chroot = no", plants a
# symlink at module/subdir -> ../outside, and runs four flavours of
# rsync transfer that previously all reached files in ../outside:
#
# 1. single-file dest = subdir/target.txt (the original poc_chmod)
# 2. -r src/subdir/ to upload/subdir/ (the chdir-escape case)
# 3. -r src/subdir/ to upload/subdir/ (no --size-only: forces basis read+write)
# 4. -r src/ to upload/ (was already protected by the
# original CVE-2026-29518 fix;
# regression-checked here)
#
# All four must leave the outside-the-module sentinel file's mode AND
# content unchanged.
. "$suitedir/rsync.fns"
case "$(uname -s)" in
SunOS|OpenBSD|NetBSD|CYGWIN*)
test_skipped "secure chdir relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
;;
esac
mod="$scratchdir/module"
outside="$scratchdir/outside"
src="$scratchdir/src"
conf="$scratchdir/test-rsyncd.conf"
rm -rf "$mod" "$outside" "$src"
mkdir -p "$mod" "$outside" "$src" "$src/subdir"
# Portable octal-mode helper -- macOS and FreeBSD's stat use -f, GNU
# coreutils stat uses -c.
file_mode() {
stat -c %a "$1" 2>/dev/null || stat -f %Lp "$1"
}
# The "secret" file outside the module the attacker is trying to alter.
# Save a pristine copy alongside it so we can compare with cmp(1) rather
# than depending on sha1sum/shasum/sha1, which differ across platforms.
echo "OUTSIDE_SECRET_DATA" > "$outside/target.txt"
chmod 0600 "$outside/target.txt"
outside_pristine="$scratchdir/outside-pristine.txt"
cp -p "$outside/target.txt" "$outside_pristine"
# Symlink trap planted in the module by the local attacker.
ln -s "$outside" "$mod/subdir"
# Source files the sender will push: same size as the outside target,
# 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"
chmod 0666 "$src/target.txt" "$src/subdir/target.txt"
cat > "$conf" <<EOF
use chroot = no
log file = $scratchdir/rsyncd.log
[upload]
path = $mod
use chroot = no
read only = no
EOF
reset_outside() {
chmod 0600 "$outside/target.txt"
echo "OUTSIDE_SECRET_DATA" > "$outside/target.txt"
}
verify_unchanged() {
label="$1"
mode=$(file_mode "$outside/target.txt")
case "$mode" in
600|0600) ;;
*) test_fail "$label: outside file mode changed from 600 to $mode (chmod escape)" ;;
esac
if ! cmp -s "$outside/target.txt" "$outside_pristine"; then
test_fail "$label: outside file content changed (write escape)"
fi
}
run_attack() {
label="$1"; shift
reset_outside
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC "$@" >/dev/null 2>&1 || true
verify_unchanged "$label"
}
# 1. The original poc_chmod scenario: single file, dest path with
# the symlinked subdir as a path component. With --size-only the
# receiver normally skips the basis open and goes straight to chmod
# -- only the chdir-escape blocks the chmod from reaching outside.
run_attack "single-file --size-only" \
-tp --size-only \
"$src/target.txt" rsync://localhost/upload/subdir/target.txt
# 2. -r push into the symlinked subdir: receiver chdir's into "subdir",
# follows the symlink, ends up in outside.
run_attack "-r --size-only into subdir/" \
-rtp --size-only \
"$src/subdir/" rsync://localhost/upload/subdir/
# 3. Same but no --size-only -- forces the basis-file open and a real
# rename, so this exercises the read-disclosure and write-escape
# paths together.
run_attack "-r without --size-only into subdir/" \
-rtp \
"$src/subdir/" rsync://localhost/upload/subdir/
# 4. -r src/ to upload/ -- this case was already covered by the
# original CVE-2026-29518 fix because the receiver stays at module
# root and operates on slashed paths. Regression check.
run_attack "-r --size-only into upload/ root" \
-rtp --size-only \
"$src/" rsync://localhost/upload/
exit 0

View File

@@ -1,68 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for the symlink-TOCTOU class of bug applied to
# chmod() on the receiver side. The CVE-2026-29518 fix used
# secure_relative_open() for the basis-file open, but every other
# path-based syscall the receiver runs on sender-controllable paths
# is vulnerable to the same primitive: a local attacker swaps a
# symlink into one of the parent directory components between the
# receiver's check and its act, and the syscall escapes the module.
#
# This test exercises the new do_chmod_at() wrapper via the
# t_chmod_secure helper. The helper sets up two scenarios:
# - a parent dir-symlink that resolves WITHIN the module tree
# (legitimate -K-style use, must continue to work)
# - a parent dir-symlink that escapes the module tree (the
# attack, must be rejected)
# plus two regression scenarios (plain relative path, top-level
# file) that just confirm the safe wrapper doesn't break the
# normal case.
#
# The kernel-enforced "stay below dirfd" path resolution is
# only available on Linux 5.6+, FreeBSD 13+, and macOS 15+.
# Skip on platforms that fall back to per-component O_NOFOLLOW
# (Solaris, OpenBSD, NetBSD, Cygwin); the per-component fallback
# would also reject the attack but the legitimate dir-symlink
# scenario would fail there.
. "$suitedir/rsync.fns"
case "$(uname -s)" in
SunOS|OpenBSD|NetBSD|CYGWIN*)
test_skipped "do_chmod_at relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
;;
esac
mod="$scratchdir/module"
trap_outside="$scratchdir/trap"
rm -rf "$mod" "$trap_outside"
mkdir -p "$mod/realdir" "$trap_outside"
# Set up the four file-system objects the helper expects:
echo bystander > "$mod/realdir/sentinel"
chmod 0600 "$mod/realdir/sentinel"
echo target > "$trap_outside/sentinel"
chmod 0600 "$trap_outside/sentinel"
ln -s realdir "$mod/inside_link"
ln -s ../trap "$mod/escape_link"
echo top > "$mod/topfile"
chmod 0600 "$mod/topfile"
"$TOOLDIR/t_chmod_secure" "$mod" || \
test_fail "t_chmod_secure reported failures (see stderr above)"
# Sanity-check from the shell side too: the outside file's mode must
# still be 0600 -- the helper checked this, but a second look from
# the shell guards against a helper-internal stat() bug.
mode=$(stat -c '%a' "$trap_outside/sentinel" 2>/dev/null \
|| stat -f '%Lp' "$trap_outside/sentinel" 2>/dev/null)
if [ "$mode" != "600" ]; then
test_fail "outside sentinel mode changed from 600 to $mode -- chmod escaped the module"
fi
exit 0

View File

@@ -1,27 +0,0 @@
#!/bin/sh
# clean-fname-underflow.test
# Ensure clean_fname() does not read-before-buffer when collapsing "..".
# This exercises the --server path where a crafted merge filename hits clean_fname().
. "$suitedir/rsync.fns"
workdir="$scratchdir/workdir"
mkdir -p "$workdir/mod"
cd "$workdir"
rsync_bin=`echo $RSYNC | sed 's/ .*//'`
# Invoke the server-side path. We don't need a real transfer; we just want to
# ensure clean_fname() doesn't crash when given "a/../test" via --filter=merge.
if $rsync_bin --server --sender -vlr --filter='merge a/../test' . mod/ >/dev/null 2>&1; then
: # success
else
status=$?
# Non-zero exit is expected for bogus input; ensure it wasn't a signal/crash.
if [ $status -ge 128 ]; then
test_fail "rsync exited due to a signal (status=$status)"
fi
fi
echo "OK: clean_fname() handled 'a/../test' without crashing"
exit 0

View File

@@ -1,98 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for codex audit Finding 3a: copy_file()'s source
# open in copy_altdest_file() is via do_open_nofollow(), which only
# refuses a final-component symlink. Parent components are still
# resolved with normal symlink-following. A daemon module attacker
# who plants a parent symlink at module/cd -> /outside, then runs
# --copy-dest=cd against a source file matching the size+mtime of
# /outside/target.txt, drives the receiver to:
#
# 1. Find a match-level >= 2 basis at "cd/target.txt"
# 2. Call copy_altdest_file -> copy_file(src="cd/target.txt", ...)
# 3. do_open_nofollow follows the "cd" parent symlink and reads
# the contents of /outside/target.txt under the daemon's
# authority
# 4. Copy that content into the module destination
#
# Result: outside/target.txt content lands at module/target.txt,
# accessible to the attacker on a subsequent pull.
#
# We detect by content: src/target.txt and outside/target.txt have
# identical metadata (size + mtime + mode) but different content.
# After the transfer, module/target.txt should match src (no
# basedir escape) -- if it matches outside, the bug copied across
# the symlink boundary.
. "$suitedir/rsync.fns"
mod="$scratchdir/module"
outside="$scratchdir/outside"
src="$scratchdir/src"
conf="$scratchdir/test-rsyncd.conf"
rm -rf "$mod" "$outside" "$src"
mkdir -p "$mod" "$outside" "$src"
# Outside-the-module file the daemon should not read on the
# attacker's behalf.
echo "OUTSIDE_LEAKED_DATA!" > "$outside/target.txt"
chmod 0644 "$outside/target.txt"
# The symlink trap.
ln -s "$outside" "$mod/cd"
# Source: same size, same mtime, same mode as outside -- so the
# generator's link_stat + quick_check_ok finds a match-level >= 2
# basis and calls copy_altdest_file.
echo "ATTACKER_KNOWN_DATA!" > "$src/target.txt"
touch -r "$outside/target.txt" "$src/target.txt"
chmod 0644 "$src/target.txt"
# When running as root the daemon would drop to "nobody" by
# default and fail to mkstemp in the scratch dir; force it to
# keep our uid/gid in that case.
my_uid=`get_testuid`
root_uid=`get_rootuid`
root_gid=`get_rootgid`
uid_setting="uid = $root_uid"
gid_setting="gid = $root_gid"
if test x"$my_uid" != x"$root_uid"; then
uid_setting="#$uid_setting"
gid_setting="#$gid_setting"
fi
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload]
path = $mod
use chroot = no
read only = no
EOF
# --copy-dest push to module root.
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -rtp --copy-dest=cd "$src/" rsync://localhost/upload/ \
>/dev/null 2>&1 || true
if [ ! -f "$mod/target.txt" ]; then
test_fail "destination file was not created -- daemon transfer failed before the test could observe the basedir behaviour"
fi
if cmp -s "$mod/target.txt" "$outside/target.txt"; then
test_fail "basedir-escape via copy_file source: module/target.txt now contains the contents of outside/target.txt -- daemon read /outside via the cd symlink and copied it into the module"
fi
if ! cmp -s "$mod/target.txt" "$src/target.txt"; then
test_fail "destination doesn't match source content (and isn't outside content either): unexpected state"
fi
exit 0

View File

@@ -1,111 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for GHSA-rjfm-3w2m-jf4f: a hostname-based "hosts deny"
# rule must still match when the daemon performs a 'daemon chroot' and
# the chroot does not contain the NSS files glibc needs for reverse DNS.
#
# Pre-fix, reverse DNS happened *after* the daemon chroot. With an empty
# chroot the NSS lookup failed, client_name() returned "UNKNOWN", and a
# deny rule referring to the connecting hostname silently failed to
# match.
#
# Two scenarios are exercised so we can distinguish the case the fix
# definitely covers from the per-module path that may still be
# vulnerable:
# A. global "reverse lookup = yes" (covered by b6abdb4c)
# B. only module "reverse lookup = yes" (gap to verify)
. "$suitedir/rsync.fns"
case `uname -s` in
Linux*) ;;
*) test_skipped "test is Linux-specific (uses chroot+unshare)" ;;
esac
# We need CAP_SYS_CHROOT. Re-exec under a user namespace if not root.
if ! chroot / /bin/true 2>/dev/null; then
if [ -z "$RSYNC_UNSHARED" ] && unshare --user --map-root-user true 2>/dev/null; then
echo "Re-running under unshare --user --map-root-user..."
RSYNC_UNSHARED=1 exec unshare --user --map-root-user "$SHELL_PATH" $RUNSHFLAGS "$0"
fi
test_skipped "need CAP_SYS_CHROOT (root or unshare --user --map-root-user)"
fi
# We need 127.0.0.1 to reverse-resolve to a real hostname while NSS is
# still working (i.e. before the daemon's chroot). The daemon will
# look that name up itself as part of its hostname-based ACL check;
# we then deny that name and assert the connection is rejected.
client_hostname=`getent hosts 127.0.0.1 2>/dev/null | awk 'NR==1 {print $2}'`
if [ -z "$client_hostname" ] || [ "$client_hostname" = "127.0.0.1" ]; then
test_skipped "no reverse DNS for 127.0.0.1"
fi
chrootdir="$scratchdir/chroot"
rm -rf "$chrootdir"
mkdir -p "$chrootdir/modroot"
echo "from chroot" > "$chrootdir/modroot/file1"
conf="$scratchdir/test-rsyncd.conf"
logfile="$scratchdir/rsyncd.log"
write_conf() {
cat >"$conf" <<EOF
use chroot = no
log file = $logfile
daemon chroot = $chrootdir
reverse lookup = $1
hosts deny = $client_hostname
max verbosity = 4
[chrootmod]
path = /modroot
read only = yes
reverse lookup = $2
EOF
}
# Run a transfer and return 0 if the daemon refused with @ERROR access
# denied (the expected outcome when the deny rule matches).
run_check() {
label="$1"
rm -f "$logfile"
rm -rf "$todir"
mkdir -p "$todir"
out="$scratchdir/run.out"
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -av localhost::chrootmod/ "$todir/" >"$out" 2>&1
rc=$?
echo "----- $label (rsync exit $rc):"
cat "$out"
echo "----- daemon log:"
[ -f "$logfile" ] && cat "$logfile"
echo "-----"
grep -q '@ERROR.*access denied' "$out"
}
# Scenario A: global reverse lookup. Covered by b6abdb4c.
write_conf yes yes
if ! run_check "Scenario A (global reverse lookup = yes)"; then
test_fail "Scenario A: hostname deny rule was bypassed"
fi
# Scenario B: only the per-module reverse-lookup setting is enabled.
# The b6abdb4c fix only pre-warms client_name()'s cache when the
# global setting is on, so the post-chroot lookup in this path may
# still produce "UNKNOWN" and bypass the deny rule.
write_conf no yes
if ! run_check "Scenario B (per-module reverse lookup only)"; then
test_fail "Scenario B: hostname deny rule was bypassed (per-module reverse lookup with daemon chroot still has the bypass)"
fi
exit 0

View File

@@ -1,51 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Test that a daemon module configured with "refuse options = compress"
# rejects clients that ask for compression and still serves the same
# transfer when the client does not.
. "$suitedir/rsync.fns"
build_rsyncd_conf
# Append a module that refuses --compress (-z).
cat >>"$conf" <<EOF
[no-compress]
path = $fromdir
read only = yes
refuse options = compress
EOF
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon"
export RSYNC_CONNECT_PROG
hands_setup
# Build a reference tree mirroring the daemon's global exclude rule.
$RSYNC -av --exclude=foobar.baz "$fromdir/" "$chkdir/"
# A compressed transfer must be refused.
errlog="$scratchdir/refuse.err"
if $RSYNC -avz localhost::no-compress/ "$todir/" >/dev/null 2>"$errlog"; then
cat "$errlog" >&2
test_fail "compressed transfer was not refused"
fi
grep -- '--compress' "$errlog" >/dev/null || {
cat "$errlog" >&2
test_fail "expected refuse error mentioning --compress"
}
# The same transfer without -z must succeed.
rm -rf "$todir"
mkdir "$todir"
checkit "$RSYNC -av localhost::no-compress/ '$todir/'" "$chkdir" "$todir"
# The script would have aborted on error, so getting here means we've won.
exit 0

View File

@@ -77,12 +77,5 @@ rm -rf "$todir"
$RSYNC -aHivv --debug=HLINK5 "$name1" "$todir/"
diff $diffopt "$name1" "$todir" || test_fail "solo copy of name1 failed"
# Make sure there's nothing wrong with sending a single directory with -H
# enabled (this has broken in 3.4.0 so far, so we need this test).
rm -rf "$fromdir" "$todir"
makepath "$fromdir/sym" "$todir"
$RSYNC -aH "$fromdir/sym" "$todir"
diff $diffopt "$fromdir" "$todir" || test_fail "solo copy of sym failed"
# The script would have aborted on error, so getting here means we've won.
exit 0

View File

@@ -16,9 +16,9 @@ makepath "$longdir" || test_skipped "unable to create long directory"
touch "$longdir/1" || test_skipped "unable to create files in long directory"
date > "$longdir/1"
if [ -r /etc ]; then
ls -la /etc >"$longdir/2" || [ $? -eq 1 ]
ls -la /etc >"$longdir/2"
else
ls -la / >"$longdir/2" || [ $? -eq 1 ]
ls -la / >"$longdir/2"
fi
checkit "$RSYNC --delete -avH '$fromdir/' '$todir'" "$fromdir/" "$todir"

View File

@@ -1,32 +0,0 @@
#!/bin/sh
# Test rsync --open-noatime option keeps source atimes intact
. "$suitedir/rsync.fns"
$RSYNC -VV | grep '"atimes": true' >/dev/null || test_skipped "Rsync is configured without atimes support"
# O_NOATIME is Linux-specific; skip on other platforms
case `uname` in
Linux) ;;
*) test_skipped "O_NOATIME is only supported on Linux" ;;
esac
mkdir "$fromdir"
# --open-noatime did not work properly on files with size > 0
echo content > "$fromdir/foo"
touch -a -t 200102031717.42 "$fromdir/foo"
TLS_ARGS=--atimes
"$TOOLDIR/tls" $TLS_ARGS "$fromdir/foo" > "$tmpdir/atime-from-before"
# Do not use checkit because it uses "diff" which breaks atimes
$RSYNC --open-noatime --archive --recursive --times --atimes -vvv "$fromdir/" "$todir/"
"$TOOLDIR/tls" $TLS_ARGS "$fromdir/foo" > "$tmpdir/atime-from-after"
diff "$tmpdir/atime-from-before" "$tmpdir/atime-from-after"
# The script would have aborted on error, so getting here means we've won.
exit 0

View File

@@ -10,28 +10,22 @@
. "$suitedir/rsync.fns"
test -f /proc/sys/fs/protected_regular || test_skipped "Can't find protected_regular setting (only available on Linux)"
pr_lvl=`cat /proc/sys/fs/protected_regular 2>/dev/null` || test_skipped "Can't check if fs.protected_regular is enabled"
pr_lvl=`cat /proc/sys/fs/protected_regular 2>/dev/null` || test_skipped "Can't check if fs.protected_regular is enabled (probably need root)"
test "$pr_lvl" != 0 || test_skipped "fs.protected_regular is not enabled"
workdir="$tmpdir/files"
mkdir -p "$workdir"
mkdir "$workdir"
chmod 1777 "$workdir"
echo "Source" > "$workdir/src"
echo "" > "$workdir/dst"
chown 5001 "$workdir/dst" || test_skipped "Can't chown (probably need root)"
if ! chown 5001 "$workdir/dst" 2>/dev/null; then
# Not root - try re-running under unshare with UID mapping
if [ -z "$RSYNC_UNSHARED" ] && unshare --user --map-root-user --map-users 5001:100000:1 true 2>/dev/null; then
echo "Re-running under unshare with UID mapping..."
RSYNC_UNSHARED=1 exec unshare --user --map-root-user --map-users 5001:100000:1 "$SHELL_PATH" $RUNSHFLAGS "$0"
fi
test_skipped "Can't chown (need root or unshare with uidmap)"
fi
# Output is only shown in case of an error
echo "Contents of $workdir:"
ls -al "$workdir"
$RSYNC --inplace "$workdir/src" "$workdir/dst" || test_fail
# The script would have aborted on error, so getting here means we've won.
exit 0

View File

@@ -1,128 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for the off-by-one stack OOB write in
# establish_proxy_connection() in socket.c when a malicious or
# man-in-the-middle HTTP proxy returns a first response line of
# 1023+ bytes without a '\n' terminator.
#
# Pre-fix: the read loop walked buffer[0..sizeof-2] one byte at a
# time, then post-loop logic did "if (*cp != '\n') cp++; *cp-- =
# '\0';". If no newline arrived before the loop filled the buffer,
# cp was left at &buffer[sizeof-1] (never written by the loop),
# *cp held stale stack bytes, and cp++ pushed cp one past the array.
# The null-termination then wrote one byte out of bounds on the
# stack. AddressSanitizer reports stack-buffer-overflow at the
# null-termination site.
#
# Post-fix: the bound-exhaustion case is detected by position and
# rejected with an "proxy response line too long" message, so no
# OOB write occurs and rsync exits with a non-signal status.
. "$suitedir/rsync.fns"
command -v python3 >/dev/null 2>&1 || test_skipped "python3 not available"
workdir="$scratchdir/workdir"
mkdir -p "$workdir"
cd "$workdir"
port_file="$workdir/port"
proxy_log="$workdir/proxy.log"
# A minimal TCP listener: binds to an ephemeral port on 127.0.0.1,
# writes the chosen port to $port_file *before* accept() so the test
# can synchronise without a sleep, accepts one connection, reads
# until end-of-headers or 64 KiB, sends exactly 1023 bytes of 'X'
# with no '\n', then closes.
python3 - "$port_file" >"$proxy_log" 2>&1 <<'PYEOF' &
import socket, sys, os
port_file = sys.argv[1]
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("127.0.0.1", 0))
port = s.getsockname()[1]
tmp = port_file + ".tmp"
with open(tmp, "w") as fp:
fp.write("%d\n" % port)
os.rename(tmp, port_file) # atomic visibility to the shell side
s.listen(1)
conn, _ = s.accept()
conn.settimeout(5)
try:
data = b""
while b"\r\n\r\n" not in data and len(data) < 65536:
chunk = conn.recv(8192)
if not chunk:
break
data += chunk
except socket.timeout:
pass
conn.sendall(b"X" * 1023) # exactly the buffer-1 trigger size
try:
conn.shutdown(socket.SHUT_RDWR)
except OSError:
pass
conn.close()
s.close()
PYEOF
proxy_pid=$!
# Wait up to ~10s for the listener to publish its port.
i=0
while [ ! -s "$port_file" ] && [ $i -lt 10 ]; do
sleep 1
i=$((i + 1))
done
if [ ! -s "$port_file" ]; then
kill "$proxy_pid" 2>/dev/null
cat "$proxy_log" >&2 2>/dev/null
test_fail "proxy listener never published a port"
fi
port=`cat "$port_file"`
case "$port" in
*[!0-9]*|"") kill "$proxy_pid" 2>/dev/null; test_fail "bogus port from listener: '$port'" ;;
esac
# Run rsync through the malicious proxy. Any rsync:// URL works:
# the proxy intercepts the CONNECT and never forwards anywhere.
rsync_err="$workdir/rsync.err"
# rsync MUST exit non-zero here (the proxy is misbehaving).
# Use `|| status=$?` so we capture the real exit code under `sh -e`;
# `if ! cmd; then status=$?` would only ever see 0 because the `!`
# is the last command before `$?`.
status=0
RSYNC_PROXY="127.0.0.1:$port" \
$RSYNC rsync://example.invalid:873/whatever/ "$workdir/out/" \
>/dev/null 2>"$rsync_err" || status=$?
# Reap the listener.
wait "$proxy_pid" 2>/dev/null || true
# 1. rsync must not have crashed (SIGSEGV/SIGABRT report >= 128).
if [ "$status" -ge 128 ]; then
cat "$rsync_err" >&2
test_fail "rsync killed by signal (status=$status) -- possible stack OOB regression"
fi
# 2. rsync must have actually exited non-zero (i.e. saw the bad proxy).
if [ "$status" -eq 0 ]; then
cat "$rsync_err" >&2
test_fail "rsync returned success despite malformed proxy response"
fi
# 3. The new error message must appear.
if ! grep -q "proxy response line too long" "$rsync_err"; then
cat "$rsync_err" >&2
test_fail "expected 'proxy response line too long' in rsync stderr"
fi
echo "OK: over-long proxy response line rejected cleanly without crashing"
exit 0

View File

@@ -97,7 +97,7 @@ printmsg() {
}
rsync_ls_lR() {
find "$@" -name .git -prune -o -name auto-build-save -prune -o -name testtmp -prune -o -print | \
find "$@" -name .git -prune -o -name auto-build-save -prune -o -print | \
sort | sed 's/ /\\ /g' | xargs "$TOOLDIR/tls" $TLS_ARGS
}
@@ -195,15 +195,15 @@ hands_setup() {
echo some data > "$fromdir/dir/subdir/foobar.baz"
mkdir "$fromdir/dir/subdir/subsubdir"
if [ -r /etc ]; then
ls -ltr /etc > "$fromdir/dir/subdir/subsubdir/etc-ltr-list" || [ $? -eq 1 ]
ls -ltr /etc > "$fromdir/dir/subdir/subsubdir/etc-ltr-list"
else
ls -ltr / > "$fromdir/dir/subdir/subsubdir/etc-ltr-list" || [ $? -eq 1 ]
ls -ltr / > "$fromdir/dir/subdir/subsubdir/etc-ltr-list"
fi
mkdir "$fromdir/dir/subdir/subsubdir2"
if [ -r /bin ]; then
ls -lt /bin > "$fromdir/dir/subdir/subsubdir2/bin-lt-list" || [ $? -eq 1 ]
ls -lt /bin > "$fromdir/dir/subdir/subsubdir2/bin-lt-list"
else
ls -lt / > "$fromdir/dir/subdir/subsubdir2/bin-lt-list" || [ $? -eq 1 ]
ls -lt / > "$fromdir/dir/subdir/subsubdir2/bin-lt-list"
fi
# echo testing head:

View File

@@ -1,34 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for codex audit Finding 5: secure_relative_open()'s
# front-door input check rejects "../foo" and "foo/../bar" but
# misses bare "..", "subdir/..", and other variants whose "/"-split
# components contain a literal "..". The kernel-enforced
# RESOLVE_BENEATH (Linux 5.6+) and O_RESOLVE_BENEATH
# (FreeBSD 13+, macOS 15+) reject these in-kernel; the per-component
# walk fallback used on NetBSD, OpenBSD, Solaris, Cygwin and pre-5.6
# Linux does not -- so the validation must happen at the front door.
#
# This test invokes the t_secure_relpath helper, which calls
# secure_relative_open() with each suspect input and verifies the
# return value is -1 with errno == EINVAL. EINVAL is the marker
# that the front-door rejected the input, not the kernel; pre-fix
# the kernel returns -1 with EXDEV (or, on the per-component
# fallback, may return a valid fd at all -- "escape").
. "$suitedir/rsync.fns"
testdir="$scratchdir/relpath-test"
rm -rf "$testdir"
mkdir -p "$testdir"
if ! "$TOOLDIR/t_secure_relpath" "$testdir"; then
test_fail "t_secure_relpath rejected one or more inputs incorrectly (see stderr above for the specific case)"
fi
exit 0

View File

@@ -1,90 +0,0 @@
#!/bin/sh
# Copyright (C) 2026 by Andrew Tridgell
# This program is distributable under the terms of the GNU GPL (see
# COPYING).
# Regression test for codex re-check finding: the sender-side file-
# list generator can still follow an attacker-planted symlink out of
# the module via change_pathname() -> change_dir(...,CD_SKIP_CHDIR)
# followed by change_dir(...,CD_NORMAL). The CD_SKIP_CHDIR sets
# skipped_chdir=1, and the next CD_NORMAL call's secure-branch in
# util1.c is gated on `!skipped_chdir`, so the secure path is
# bypassed and a raw chdir(curr_dir) follows attacker-controlled
# symlinks during flist generation.
#
# Reach: rsync daemon module with `use chroot = no`. A local
# attacker plants module/cd -> /outside. A client (innocent or
# malicious) pulls rsync://<daemon>/<module>/cd/. The daemon, as
# sender, enumerates files in /outside and ships their metadata
# (names, sizes, modes, mtimes) to the client. The actual content
# transfer fails later at the secure_relative_open step with EXDEV,
# but by then the metadata has already leaked.
#
# We detect by running a dry-run pull of the symlinked subdir and
# checking whether the client's --list-only output mentions any
# file from /outside. With the bug, /outside/secret.txt appears in
# the list with its size; with the fix, the daemon's chdir into
# the symlinked subdir is rejected and no /outside file is listed.
. "$suitedir/rsync.fns"
case "$(uname -s)" in
SunOS|OpenBSD|NetBSD|CYGWIN*)
test_skipped "secure change_dir relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
;;
esac
mod="$scratchdir/module"
outside="$scratchdir/outside"
listfile="$scratchdir/listed.txt"
conf="$scratchdir/test-rsyncd.conf"
rm -rf "$mod" "$outside"
mkdir -p "$mod" "$outside"
# Outside-the-module file the daemon should NOT enumerate to clients.
# A distinctive name + non-trivial size makes the leak easy to spot.
echo "OUTSIDE_PROTECTED_FILE_USED_AS_LEAK_DETECTOR" > "$outside/leak_marker.txt"
chmod 0644 "$outside/leak_marker.txt"
# The symlink trap planted by the local attacker.
ln -s "$outside" "$mod/cd"
my_uid=`get_testuid`
root_uid=`get_rootuid`
root_gid=`get_rootgid`
uid_setting="uid = $root_uid"
gid_setting="gid = $root_gid"
if test x"$my_uid" != x"$root_uid"; then
uid_setting="#$uid_setting"
gid_setting="#$gid_setting"
fi
cat > "$conf" <<EOF
use chroot = no
$uid_setting
$gid_setting
log file = $scratchdir/rsyncd.log
[upload]
path = $mod
use chroot = no
read only = no
EOF
# Pull recursively into the symlinked subdir with dry-run + verbose,
# capturing the daemon's flist (file list) on stdout. If the daemon
# enumerates /outside, leak_marker.txt will appear in the listing.
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
$RSYNC -nrv rsync://localhost/upload/cd/ "$scratchdir/dst/" \
> "$listfile" 2>&1 || true
if grep -q "leak_marker\.txt" "$listfile"; then
echo "----- leaked listing follows" >&2
sed 's/^/ /' "$listfile" >&2
echo "----- leaked listing ends" >&2
test_fail "sender flist leak: outside/leak_marker.txt was enumerated to the client (daemon's chdir followed the cd symlink during flist generation)"
fi
exit 0

View File

@@ -1,11 +0,0 @@
#!/bin/sh
# Test SIMD checksum implementations against the C reference
. "$suitedir/rsync.fns"
if ! test -x "$TOOLDIR/simdtest"; then
test_skipped "simdtest not built (SIMD not available)"
fi
"$TOOLDIR/simdtest"

View File

@@ -6,7 +6,7 @@
# This program is distributable under the terms of the GNU GPL (see
# COPYING)
# This script tests ssh, if possible. It's called by runtests.py
# This script tests ssh, if possible. It's called by runtests.sh
. "$suitedir/rsync.fns"

View File

@@ -1,259 +0,0 @@
#!/bin/sh
# Test that updating a file through a directory symlink works when using
# -K (--copy-dirlinks). This is a regression test for:
# https://github.com/RsyncProject/rsync/issues/715
#
# The CVE fix in commit c35e283 introduced secure_relative_open() which
# uses O_NOFOLLOW on all path components, breaking legitimate directory
# symlinks on the receiver side. The fix splits the path into basedir
# (dirname, symlinks followed) and basename (O_NOFOLLOW) so that
# directory symlinks are traversed while the final file component is
# still protected.
#
# The regression only manifests when delta matching is triggered (i.e.,
# the sender finds matching blocks in the old file). Small files with
# completely different content are transferred in full and don't trigger
# the bug. We use a large file with a small modification to ensure
# delta transfer is used.
#
# In addition to the original regression, this test covers edge cases
# in the fix itself:
# - --backup with directory symlinks (finish_transfer pointer identity)
# - --partial-dir with protocol < 29 (fnamecmp != partialptr guard)
# - --inplace with directory symlinks (updating_basis_or_equiv check)
# - Files without a dirname (top-level files, no split needed)
. "$suitedir/rsync.fns"
# secure_relative_open() uses kernel-enforced "stay below dirfd" via
# openat2(RESOLVE_BENEATH) on Linux 5.6+ and openat(O_RESOLVE_BENEATH)
# on FreeBSD 13+. Other platforms fall back to a per-component
# O_NOFOLLOW walk that rejects every symlink including legitimate
# directory symlinks -- the very case this test exercises. Skip on
# those rather than report a known failure.
case "$(uname -s)" in
SunOS|OpenBSD|NetBSD|CYGWIN*)
test_skipped "secure_relative_open lacks RESOLVE_BENEATH equivalent on $(uname -s); issue #715 still affects this platform"
;;
esac
RSYNC_RSH="$scratchdir/src/support/lsh.sh"
export RSYNC_RSH
# $HOME is set to $scratchdir by rsync.fns
# localhost: destination will cd to $HOME (i.e., $scratchdir)
# Helper: create a large file suitable for delta transfers.
# ~32KB is large enough for rsync's block matching to find matches.
make_testfile() {
dd if=/dev/urandom of="$1" bs=1024 count=32 2>/dev/null \
|| test_fail "failed to create test file $1"
}
# Set up source tree
srcbase="$tmpdir/src"
######################################################################
# Test 1: Basic directory symlink update (the original issue #715)
######################################################################
mkdir -p "$HOME/real-dir"
ln -s real-dir "$HOME/dir"
mkdir -p "$srcbase/dir"
make_testfile "$srcbase/dir/file"
# First transfer (initial): should create the file through the symlink
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 1: initial transfer failed"
if [ ! -f "$HOME/real-dir/file" ]; then
test_fail "test 1: initial transfer did not create file through symlink"
fi
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 1: initial transfer content mismatch"
# Small modification to trigger delta transfer
echo "appended update" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
# Second transfer (update): was failing with "failed verification"
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 1: update through directory symlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 1: update transfer content mismatch"
######################################################################
# Test 2: Compression (-z) as in the original reproducer
######################################################################
echo "another line" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptzv --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 2: compressed update through directory symlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 2: compressed update content mismatch"
######################################################################
# Test 3: Nested directory symlinks (nested/sub/data.txt where
# "nested" is a symlink to "nested_real")
######################################################################
mkdir -p "$HOME/nested_real/sub"
ln -s nested_real "$HOME/nested"
mkdir -p "$srcbase/nested/sub"
make_testfile "$srcbase/nested/sub/data.txt"
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" nested/sub/data.txt localhost:) \
|| test_fail "test 3: initial nested transfer failed"
echo "appended nested" >> "$srcbase/nested/sub/data.txt"
sleep 1
touch "$srcbase/nested/sub/data.txt"
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" nested/sub/data.txt localhost:) \
|| test_fail "test 3: update through nested directory symlink failed"
diff "$srcbase/nested/sub/data.txt" "$HOME/nested_real/sub/data.txt" >/dev/null \
|| test_fail "test 3: nested update content mismatch"
######################################################################
# Test 4: --backup with directory symlinks
#
# Exercises the finish_transfer() "fnamecmp == fname" pointer
# comparison that determines whether to update fnamecmp to the
# backup name. If broken, --backup would reference a renamed file
# for xattr handling.
######################################################################
# Reset destination
rm -f "$HOME/real-dir/file" "$HOME/real-dir/file~"
make_testfile "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 4: initial transfer for backup test failed"
echo "backup update" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --backup --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 4: update with --backup through directory symlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 4: backup update content mismatch"
if [ ! -f "$HOME/real-dir/file~" ]; then
test_fail "test 4: backup file was not created"
fi
######################################################################
# Test 5: --inplace with directory symlinks
#
# Exercises the updating_basis_or_equiv check which uses
# "fnamecmp == fname". With --inplace, rsync writes directly to
# the destination file instead of a temp file.
######################################################################
rm -f "$HOME/real-dir/file" "$HOME/real-dir/file~"
make_testfile "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --inplace --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 5: initial inplace transfer failed"
echo "inplace update" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --inplace --rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 5: inplace update through directory symlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 5: inplace update content mismatch"
######################################################################
# Test 6: Top-level file (no dirname, no split needed)
#
# Ensures the dirname/basename split is not attempted for files
# at the top level (file->dirname is NULL).
######################################################################
make_testfile "$srcbase/topfile"
mkdir -p "$HOME"
(cd "$srcbase" && $RSYNC -Rlptv --rsync-path="$RSYNC" topfile localhost:) \
|| test_fail "test 6: initial top-level transfer failed"
echo "toplevel update" >> "$srcbase/topfile"
sleep 1
touch "$srcbase/topfile"
(cd "$srcbase" && $RSYNC -Rlptv --rsync-path="$RSYNC" topfile localhost:) \
|| test_fail "test 6: top-level update failed"
diff "$srcbase/topfile" "$HOME/topfile" >/dev/null \
|| test_fail "test 6: top-level update content mismatch"
######################################################################
# Test 7: --partial-dir with protocol < 29
#
# For protocol < 29, fnamecmp_type stays FNAMECMP_FNAME even when
# fnamecmp is set to partialptr. The dirname/basename split must
# NOT trigger in this case (guarded by "fnamecmp == fname").
######################################################################
rm -f "$HOME/real-dir/file"
make_testfile "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 --partial-dir=.rsync-partial \
--rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 7: initial proto28 partial-dir transfer failed"
echo "partial-dir update" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 --partial-dir=.rsync-partial \
--rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 7: proto28 partial-dir update through dirlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 7: proto28 partial-dir update content mismatch"
######################################################################
# Test 8: Protocol < 29 basic directory symlink update
#
# Exercises the protocol < 29 code path and its fallback logic
# (clearing basedir on retry).
######################################################################
rm -f "$HOME/real-dir/file"
make_testfile "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 \
--rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 8: initial proto28 transfer failed"
echo "proto28 update" >> "$srcbase/dir/file"
sleep 1
touch "$srcbase/dir/file"
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 \
--rsync-path="$RSYNC" dir/file localhost:) \
|| test_fail "test 8: proto28 update through directory symlink failed"
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|| test_fail "test 8: proto28 update content mismatch"
# The script would have aborted on error, so getting here means we've won.
exit 0

View File

@@ -38,10 +38,7 @@ EOF
xls() {
for fn in "${@}"; do
runat "$fn" "$SHELL_PATH" <<EOF
for x in *; do
case "\$x" in SUNWattr_*) continue;; esac
echo "\$x=\`cat \$x\`"
done
for x in *; do echo "\$x=\`cat \$x\`"; done
EOF
done
}

4
tls.c
View File

@@ -127,7 +127,7 @@ static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
{
if (t) {
int len;
struct tm tmp, *mt = gmtime_r(&t, &tmp);
struct tm *mt = gmtime(&t);
len = snprintf(dest, destsize,
" %04d-%02d-%02d %02d:%02d:%02d",
@@ -230,7 +230,7 @@ static void list_file(const char *fname)
mtimebuf, atimebuf, crtimebuf, fname, linkbuf);
}
static const struct poptOption long_options[] = {
static struct poptOption long_options[] = {
/* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
{"atimes", 'U', POPT_ARG_NONE, &display_atimes, 0, 0, 0},
#ifdef SUPPORT_CRTIMES

154
token.c
View File

@@ -33,7 +33,6 @@ extern int do_compression;
extern int protocol_version;
extern int module_id;
extern int do_compression_level;
extern int do_compression_threads;
extern char *skip_compress;
#ifndef Z_INSERT_ONLY
@@ -292,14 +291,6 @@ static int32 simple_recv_token(int f, char **data)
int32 i = read_int(f);
if (i <= 0)
return i;
/* simple_send_token caps each literal chunk at CHUNK_SIZE;
* reject anything larger so a hostile peer cannot drive the
* read_buf below past our static CHUNK_SIZE buffer. */
if (i > CHUNK_SIZE) {
rprintf(FERROR, "invalid uncompressed token length %ld [%s]\n",
(long)i, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
residue = i;
}
@@ -481,29 +472,14 @@ send_deflated_token(int f, int32 token, struct map_struct *buf, OFF_T offset, in
tx_strm.avail_in = n1;
if (protocol_version >= 31) /* Newer protocols avoid a data-duplicating bug */
offset += n1;
/* With our bundled zlib's Z_INSERT_ONLY this produces no
* output and consumes the input in one call. A build
* against a system zlib lacks Z_INSERT_ONLY and falls back
* to Z_SYNC_FLUSH (see top of file), which emits a flush
* block we discard -- and for an incompressible token that
* block can exceed obuf. Loop, resetting the output buffer,
* until all the input is consumed so a large token can't
* overflow obuf and abort the transfer (#951). Drain until
* avail_out != 0 too: a full output buffer can leave pending
* bytes that would otherwise leak into the next real deflate
* send and corrupt the stream (same condition as the data loop
* above). The discarded output is not sent: the receiver
* rebuilds the matching history itself in see_deflate_token(). */
do {
tx_strm.next_out = (Bytef *) obuf;
tx_strm.avail_out = AVAIL_OUT_SIZE(CHUNK_SIZE);
r = deflate(&tx_strm, Z_INSERT_ONLY);
if (r != Z_OK) {
rprintf(FERROR, "deflate on token returned %d (%d bytes left)\n",
r, tx_strm.avail_in);
exit_cleanup(RERR_STREAMIO);
}
} while (tx_strm.avail_in != 0 || tx_strm.avail_out == 0);
tx_strm.next_out = (Bytef *) obuf;
tx_strm.avail_out = AVAIL_OUT_SIZE(CHUNK_SIZE);
r = deflate(&tx_strm, Z_INSERT_ONLY);
if (r != Z_OK || tx_strm.avail_in != 0) {
rprintf(FERROR, "deflate on token returned %d (%d bytes left)\n",
r, tx_strm.avail_in);
exit_cleanup(RERR_STREAMIO);
}
} while (toklen > 0);
}
}
@@ -517,52 +493,9 @@ static char *cbuf;
static char *dbuf;
/* for decoding runs of tokens */
#define MAX_TOKEN_INDEX ((int32)0x7ffffffe)
static int32 rx_token;
static int32 rx_run;
static NORETURN void invalid_compressed_token(void)
{
rprintf(FERROR, "invalid token number in compressed stream\n");
exit_cleanup(RERR_PROTOCOL);
}
static int32 recv_compressed_token_num(int f, int32 flag)
{
if (flag & TOKEN_REL) {
int32 incr = flag & 0x3f;
if (rx_token > MAX_TOKEN_INDEX - incr)
invalid_compressed_token();
rx_token += incr;
flag >>= 6;
} else {
rx_token = read_int(f);
if (rx_token < 0 || rx_token > MAX_TOKEN_INDEX)
invalid_compressed_token();
}
if (flag & 1) {
rx_run = read_byte(f);
rx_run += read_byte(f) << 8;
if (rx_run <= 0 || rx_token > MAX_TOKEN_INDEX - rx_run)
invalid_compressed_token();
recv_state = r_running;
}
return -1 - rx_token;
}
static int32 recv_compressed_token_run(void)
{
if (rx_run <= 0 || rx_token >= MAX_TOKEN_INDEX)
invalid_compressed_token();
++rx_token;
if (--rx_run == 0)
recv_state = r_idle;
return -1 - rx_token;
}
/* Receive a deflated token and inflate it */
static int32 recv_deflated_token(int f, char **data)
{
@@ -653,7 +586,17 @@ static int32 recv_deflated_token(int f, char **data)
}
/* here we have a token of some kind */
return recv_compressed_token_num(f, flag);
if (flag & TOKEN_REL) {
rx_token += flag & 0x3f;
flag >>= 6;
} else
rx_token = read_int(f);
if (flag & 1) {
rx_run = read_byte(f);
rx_run += read_byte(f) << 8;
recv_state = r_running;
}
return -1 - rx_token;
case r_inflating:
rx_strm.next_out = (Bytef *)dbuf;
@@ -673,7 +616,10 @@ static int32 recv_deflated_token(int f, char **data)
break;
case r_running:
return recv_compressed_token_run();
++rx_token;
if (--rx_run == 0)
recv_state = r_idle;
return -1 - rx_token;
}
}
}
@@ -746,8 +692,6 @@ static void send_zstd_token(int f, int32 token, struct map_struct *buf, OFF_T of
obuf = new_array(char, OBUF_SIZE);
ZSTD_CCtx_setParameter(zstd_cctx, ZSTD_c_compressionLevel, do_compression_level);
ZSTD_CCtx_setParameter(zstd_cctx, ZSTD_c_nbWorkers, do_compression_threads);
zstd_out_buff.dst = obuf + 2;
comp_init_done = 1;
@@ -785,11 +729,12 @@ static void send_zstd_token(int f, int32 token, struct map_struct *buf, OFF_T of
zstd_in_buff.src = map_ptr(buf, offset, nb);
zstd_in_buff.size = nb;
zstd_in_buff.pos = 0;
int finished;
do {
zstd_out_buff.size = MAX_DATA_COUNT;
zstd_out_buff.pos = 0;
if (zstd_out_buff.size == 0) {
zstd_out_buff.size = MAX_DATA_COUNT;
zstd_out_buff.pos = 0;
}
/* File ended, flush */
if (token != -2)
@@ -807,21 +752,20 @@ static void send_zstd_token(int f, int32 token, struct map_struct *buf, OFF_T of
* state and send a smaller buffer so that the remote side can
* finish the file.
*/
finished = (flush == ZSTD_e_flush) ? (r == 0) : (zstd_in_buff.pos == zstd_in_buff.size);
if (zstd_out_buff.pos != 0) {
if (zstd_out_buff.pos == zstd_out_buff.size || flush == ZSTD_e_flush) {
n = zstd_out_buff.pos;
obuf[0] = DEFLATED_DATA + (n >> 8);
obuf[1] = n;
write_buf(f, obuf, n+2);
zstd_out_buff.size = 0;
}
/*
* Loop while the input buffer isn't full consumed or the
* internal state isn't fully flushed.
*/
} while (!finished);
} while (zstd_in_buff.pos < zstd_in_buff.size || r > 0);
flush_pending = token == -2;
}
@@ -884,7 +828,17 @@ static int32 recv_zstd_token(int f, char **data)
return 0;
}
/* here we have a token of some kind */
return recv_compressed_token_num(f, flag);
if (flag & TOKEN_REL) {
rx_token += flag & 0x3f;
flag >>= 6;
} else
rx_token = read_int(f);
if (flag & 1) {
rx_run = read_byte(f);
rx_run += read_byte(f) << 8;
recv_state = r_running;
}
return -1 - rx_token;
case r_inflated: /* zstd doesn't get into this state */
break;
@@ -915,7 +869,10 @@ static int32 recv_zstd_token(int f, char **data)
break;
case r_running:
return recv_compressed_token_run();
++rx_token;
if (--rx_run == 0)
recv_state = r_idle;
return -1 - rx_token;
}
}
}
@@ -1035,7 +992,17 @@ static int32 recv_compressed_token(int f, char **data)
}
/* here we have a token of some kind */
return recv_compressed_token_num(f, flag);
if (flag & TOKEN_REL) {
rx_token += flag & 0x3f;
flag >>= 6;
} else
rx_token = read_int(f);
if (flag & 1) {
rx_run = read_byte(f);
rx_run += read_byte(f) << 8;
recv_state = r_running;
}
return -1 - rx_token;
case r_inflating:
avail_out = LZ4_decompress_safe(next_in, dbuf, avail_in, size);
@@ -1051,7 +1018,10 @@ static int32 recv_compressed_token(int f, char **data)
break;
case r_running:
return recv_compressed_token_run();
++rx_token;
if (--rx_run == 0)
recv_state = r_idle;
return -1 - rx_token;
}
}
}

122
util1.c
View File

@@ -41,8 +41,8 @@ extern filter_rule_list daemon_filter_list;
int sanitize_paths = 0;
extern char curr_dir[MAXPATHLEN]; /* defined in syscall.c */
extern unsigned int curr_dir_len;
char curr_dir[MAXPATHLEN];
unsigned int curr_dir_len;
int curr_dir_depth; /* This is only set for a sanitizing daemon. */
/* Set a fd into nonblocking mode. */
@@ -141,7 +141,7 @@ int set_times(const char *fname, STRUCT_STAT *stp)
#ifdef HAVE_UTIMENSAT
#include "case_N.h"
if (do_utimensat_at(fname, stp) == 0)
if (do_utimensat(fname, stp) == 0)
break;
if (errno != ENOSYS)
return -1;
@@ -336,13 +336,7 @@ static int unlink_and_reopen(const char *dest, mode_t mode)
mode |= S_IWUSR;
#endif
mode &= INITACCESSPERMS;
/* Use do_open_at so the create/truncate goes through a secure
* parent dirfd in the daemon-no-chroot deployment. Otherwise
* an attacker could swap a parent component with a symlink in
* the window between robust_unlink (which uses do_unlink_at,
* already secure) and the create here, and redirect the new
* file outside the module. */
if ((ofd = do_open_at(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
int save_errno = errno;
rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(dest));
errno = save_errno;
@@ -366,23 +360,12 @@ static int unlink_and_reopen(const char *dest, mode_t mode)
* --copy-dest options. */
int copy_file(const char *source, const char *dest, int tmpfilefd, mode_t mode)
{
extern int am_daemon, am_chrooted;
int ifd, ofd;
char buf[1024 * 8];
int len; /* Number of bytes read into `buf'. */
OFF_T prealloc_len = 0, offset = 0;
/* On a daemon without chroot, route the source open through
* secure_relative_open so a parent-symlink on the source path
* (e.g. --copy-dest=cd where cd is a symlink to an outside
* directory) cannot redirect the read to a file the daemon can
* see but the attacker should not. Plain do_open_nofollow only
* refuses a final-component symlink; parents are still followed. */
if (am_daemon && !am_chrooted && source && *source && source[0] != '/')
ifd = secure_relative_open(NULL, source, O_RDONLY | O_NOFOLLOW, 0);
else
ifd = do_open_nofollow(source, O_RDONLY);
if (ifd < 0) {
if ((ifd = do_open_nofollow(source, O_RDONLY)) < 0) {
int save_errno = errno;
rsyserr(FERROR_XFER, errno, "open %s", full_fname(source));
errno = save_errno;
@@ -496,13 +479,13 @@ int copy_file(const char *source, const char *dest, int tmpfilefd, mode_t mode)
int robust_unlink(const char *fname)
{
#ifndef ETXTBSY
return do_unlink_at(fname);
return do_unlink(fname);
#else
static int counter = 1;
int rc, pos, start;
char path[MAXPATHLEN];
rc = do_unlink_at(fname);
rc = do_unlink(fname);
if (rc == 0 || errno != ETXTBSY)
return rc;
@@ -532,7 +515,7 @@ int robust_unlink(const char *fname)
}
/* maybe we should return rename()'s exit status? Nah. */
if (do_rename_at(fname, path) != 0) {
if (do_rename(fname, path) != 0) {
errno = ETXTBSY;
return -1;
}
@@ -555,7 +538,7 @@ int robust_rename(const char *from, const char *to, const char *partialptr,
return 0;
while (tries--) {
if (do_rename_at(from, to) == 0)
if (do_rename(from, to) == 0)
return 0;
switch (errno) {
@@ -576,7 +559,7 @@ int robust_rename(const char *from, const char *to, const char *partialptr,
}
if (copy_file(from, to, -1, mode) != 0)
return -2;
do_unlink_at(from);
do_unlink(from);
return 1;
default:
return -1;
@@ -959,7 +942,7 @@ int count_dir_elements(const char *p)
* resulting name would be empty, returns ".". */
int clean_fname(char *name, int flags)
{
char *limit = name, *t = name, *f = name;
char *limit = name - 1, *t = name, *f = name;
int anchored;
if (!name)
@@ -1004,13 +987,9 @@ int clean_fname(char *name, int flags)
f += 2;
continue;
}
/* backing up for ".." — avoid reading before 'name' */
while (s > limit && s[-1] != '/')
s--;
/* If found prior '/', or we reached the start, adjust t. */
if (s != t - 1 && (s <= name || *s == '/')) {
t = (s == name) ? name : s + 1;
while (s > limit && *--s != '/') {}
if (s != t - 1 && (s < name || *s == '/')) {
t = s + 1;
f += 2;
continue;
}
@@ -1133,7 +1112,6 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth, i
* Also cleans the path using the clean_fname() function. */
int change_dir(const char *dir, int set_path_only)
{
extern int am_daemon, am_chrooted;
static int initialised, skipped_chdir;
unsigned int len;
@@ -1172,57 +1150,10 @@ int change_dir(const char *dir, int set_path_only)
curr_dir[curr_dir_len++] = '/';
memcpy(curr_dir + curr_dir_len, dir, len + 1);
if (!set_path_only) {
int chdir_failed;
/* In the daemon-without-chroot deployment we must not
* follow a symlink in any component of the chdir
* target -- otherwise CWD escapes the module and
* every subsequent path-relative syscall (open,
* chmod, lchown, ...) inherits the escape, which
* defeats secure_relative_open's RESOLVE_BENEATH
* anchor and re-opens the CVE-2026-29518 class of
* symlink TOCTOU attacks. Use the secure resolver
* to get a confined dirfd, then fchdir() to it.
*
* If skipped_chdir is set, a previous CD_SKIP_CHDIR
* call buffered an absolute prefix in curr_dir
* (e.g. change_pathname's CD_SKIP_CHDIR to orig_dir)
* without syncing the kernel's CWD. Resolve `dir`
* relative to that prefix as basedir so the secure
* branch still anchors at the operator-trusted
* directory rather than wherever the kernel CWD
* happens to be. */
if (am_daemon && !am_chrooted) {
const char *basedir = NULL;
char prefix[MAXPATHLEN];
int dfd;
if (skipped_chdir) {
if (save_dir_len >= sizeof prefix) {
errno = ENAMETOOLONG;
chdir_failed = 1;
goto chdir_cleanup;
}
memcpy(prefix, curr_dir, save_dir_len);
prefix[save_dir_len] = '\0';
basedir = prefix;
}
dfd = secure_relative_open(basedir, dir,
O_RDONLY | O_DIRECTORY, 0);
if (dfd < 0) {
chdir_failed = 1;
} else {
chdir_failed = fchdir(dfd) != 0;
close(dfd);
}
} else {
chdir_failed = chdir(curr_dir) != 0;
}
chdir_cleanup:
if (chdir_failed) {
curr_dir_len = save_dir_len;
curr_dir[curr_dir_len] = '\0';
return 0;
}
if (!set_path_only && chdir(curr_dir)) {
curr_dir_len = save_dir_len;
curr_dir[curr_dir_len] = '\0';
return 0;
}
skipped_chdir = set_path_only;
}
@@ -1350,20 +1281,20 @@ int handle_partial_dir(const char *fname, int create)
dir = partial_fname;
if (create) {
STRUCT_STAT st;
int statret = do_lstat_at(dir, &st);
int statret = do_lstat(dir, &st);
if (statret == 0 && !S_ISDIR(st.st_mode)) {
if (do_unlink_at(dir) < 0) {
if (do_unlink(dir) < 0) {
*fn = '/';
return 0;
}
statret = -1;
}
if (statret < 0 && do_mkdir_at(dir, 0700) < 0) {
if (statret < 0 && do_mkdir(dir, 0700) < 0) {
*fn = '/';
return 0;
}
} else
do_rmdir_at(dir);
do_rmdir(dir);
*fn = '/';
return 1;
@@ -1458,13 +1389,8 @@ char *timestring(time_t t)
static int ndx = 0;
static char buffers[4][20]; /* We support 4 simultaneous timestring results. */
char *TimeBuf = buffers[ndx = (ndx + 1) % 4];
struct tm tmp, *tm = localtime_r(&t, &tmp);
int len;
if (!tm) {
strlcpy(TimeBuf, "(time out of range)", sizeof buffers[0]);
return TimeBuf;
}
len = snprintf(TimeBuf, sizeof buffers[0], "%4d/%02d/%02d %02d:%02d:%02d",
struct tm *tm = localtime(&t);
int len = snprintf(TimeBuf, sizeof buffers[0], "%4d/%02d/%02d %02d:%02d:%02d",
(int)tm->tm_year + 1900, (int)tm->tm_mon + 1, (int)tm->tm_mday,
(int)tm->tm_hour, (int)tm->tm_min, (int)tm->tm_sec);
assert(len > 0); /* Silence gcc warning */

View File

@@ -1,2 +1,2 @@
#define RSYNC_VERSION "3.4.4"
#define MAINTAINER_TZ_OFFSET 10.0
#define RSYNC_VERSION "3.4.0"
#define MAINTAINER_TZ_OFFSET -7.0

View File

@@ -32,9 +32,7 @@ int fnmatch_errors = 0;
int wildmatch_errors = 0;
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 202311L
typedef char bool;
#endif
int output_iterations = 0;
int explode_mod = 0;
@@ -42,7 +40,7 @@ int empties_mod = 0;
int empty_at_start = 0;
int empty_at_end = 0;
static const struct poptOption long_options[] = {
static struct poptOption long_options[] = {
/* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
{"iterations", 'i', POPT_ARG_NONE, &output_iterations, 0, 0, 0},
{"empties", 'e', POPT_ARG_STRING, 0, 'e', 0, 0},

View File

@@ -697,13 +697,6 @@ int recv_xattr_request(struct file_struct *file, int f_in)
rxa = lst->items;
num = 0;
while ((rel_pos = read_varint(f_in)) != 0) {
/* Detect signed overflow before the accumulating add. A hostile
* peer could otherwise wrap 'num' to land on an arbitrary value. */
if ((rel_pos > 0 && num > INT_MAX - rel_pos)
|| (rel_pos < 0 && num < INT_MIN - rel_pos)) {
rprintf(FERROR, "xattr rel_pos accumulation overflow [%s]\n", who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
num += rel_pos;
if (am_sender) {
/* The sender-related num values are only in order on the sender.
@@ -749,7 +742,7 @@ int recv_xattr_request(struct file_struct *file, int f_in)
}
old_datum = rxa->datum;
rxa->datum_len = read_varint_size(f_in, MAX_WIRE_XATTR_DATALEN, "xattr datum_len");
rxa->datum_len = read_varint(f_in);
if (SIZE_MAX - rxa->name_len < rxa->datum_len)
overflow_exit("recv_xattr_request");
@@ -790,8 +783,7 @@ void receive_xattr(int f, struct file_struct *file)
return;
}
count = read_varint_bounded(f, 0, MAX_WIRE_XATTR_COUNT, "xattr count");
if (count != 0) {
if ((count = read_varint(f)) != 0) {
(void)EXPAND_ITEM_LIST(&temp_xattr, rsync_xa, count);
temp_xattr.count = 0;
}
@@ -799,8 +791,8 @@ void receive_xattr(int f, struct file_struct *file)
for (num = 1; num <= count; num++) {
char *ptr, *name;
rsync_xa *rxa;
size_t name_len = read_varint_size(f, MAX_WIRE_XATTR_NAMELEN, "xattr name_len");
size_t datum_len = read_varint_size(f, MAX_WIRE_XATTR_DATALEN, "xattr datum_len");
size_t name_len = read_varint(f);
size_t datum_len = read_varint(f);
size_t dget_len = datum_len > MAX_FULL_DATUM ? 1 + (size_t)xattr_sum_len : datum_len;
size_t extra_len = MIGHT_NEED_RPRE ? RPRE_LEN : 0;
if (SIZE_MAX - dget_len < extra_len || SIZE_MAX - dget_len - extra_len < name_len)
@@ -868,8 +860,8 @@ void receive_xattr(int f, struct file_struct *file)
rxa->num = num;
}
if (need_sort && temp_xattr.count > 1)
qsort(temp_xattr.items, temp_xattr.count, sizeof (rsync_xa), rsync_xal_compare_names);
if (need_sort && count > 1)
qsort(temp_xattr.items, count, sizeof (rsync_xa), rsync_xal_compare_names);
ndx = rsync_xal_store(&temp_xattr); /* adds item to rsync_xal_l */
@@ -1094,7 +1086,7 @@ int set_xattr(const char *fname, const struct file_struct *file, const char *fna
&& !S_ISLNK(sxp->st.st_mode)
#endif
&& access(fname, W_OK) < 0
&& do_chmod_at(fname, (sxp->st.st_mode & CHMOD_BITS) | S_IWUSR) == 0)
&& do_chmod(fname, (sxp->st.st_mode & CHMOD_BITS) | S_IWUSR) == 0)
added_write_perm = 1;
ndx = F_XATTR(file);
@@ -1102,7 +1094,7 @@ int set_xattr(const char *fname, const struct file_struct *file, const char *fna
lst = &glst->xa_items;
int return_value = rsync_xal_set(fname, lst, fnamecmp, sxp);
if (added_write_perm) /* remove the temporary write permission */
do_chmod_at(fname, sxp->st.st_mode);
do_chmod(fname, sxp->st.st_mode);
return return_value;
}
@@ -1219,7 +1211,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
| (S_ISDIR(fst.st_mode) ? 0700 : 0600);
if (fst.st_mode != mode)
do_chmod_at(fname, mode);
do_chmod(fname, mode);
if (!IS_DEVICE(fst.st_mode))
fst.st_rdev = 0; /* just in case */
@@ -1257,12 +1249,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
{
/* Use the *_at variants so that on a daemon-no-chroot deployment
* the metadata read goes through a secure parent dirfd instead
* of bare path resolution. The *_at wrappers fall through to
* plain do_stat outside the daemon-no-chroot context, so this
* change is transparent for non-daemon use. */
int ret = do_stat_at(fname, fst);
int ret = do_stat(fname, fst);
if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
xst->st_mode = 0;
return ret;
@@ -1270,7 +1257,7 @@ int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
int x_lstat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
{
int ret = do_lstat_at(fname, fst);
int ret = do_lstat(fname, fst);
if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
xst->st_mode = 0;
return ret;

View File

@@ -62,7 +62,10 @@ local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2));
#endif
/* ========================================================================= */
uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len)
uLong ZEXPORT adler32(adler, buf, len)
uLong adler;
const Bytef *buf;
uInt len;
{
unsigned long sum2;
unsigned n;
@@ -130,7 +133,10 @@ uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len)
}
/* ========================================================================= */
local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2)
local uLong adler32_combine_(adler1, adler2, len2)
uLong adler1;
uLong adler2;
z_off64_t len2;
{
unsigned long sum1;
unsigned long sum2;
@@ -156,12 +162,18 @@ local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2)
}
/* ========================================================================= */
uLong ZEXPORT adler32_combine(uLong adler1, uLong adler2, z_off_t len2)
uLong ZEXPORT adler32_combine(adler1, adler2, len2)
uLong adler1;
uLong adler2;
z_off_t len2;
{
return adler32_combine_(adler1, adler2, len2);
}
uLong ZEXPORT adler32_combine64(uLong adler1, uLong adler2, z_off64_t len2)
uLong ZEXPORT adler32_combine64(adler1, adler2, len2)
uLong adler1;
uLong adler2;
z_off64_t len2;
{
return adler32_combine_(adler1, adler2, len2);
}

View File

@@ -19,7 +19,12 @@
memory, Z_BUF_ERROR if there was not enough room in the output buffer,
Z_STREAM_ERROR if the level parameter is invalid.
*/
int ZEXPORT compress2 (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen, int level)
int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
uLong sourceLen;
int level;
{
z_stream stream;
int err;
@@ -54,7 +59,11 @@ int ZEXPORT compress2 (Bytef *dest, uLongf *destLen, const Bytef *source, uLong
/* ===========================================================================
*/
int ZEXPORT compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)
int ZEXPORT compress (dest, destLen, source, sourceLen)
Bytef *dest;
uLongf *destLen;
const Bytef *source;
uLong sourceLen;
{
return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION);
}
@@ -63,7 +72,8 @@ int ZEXPORT compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong s
If the default memLevel or windowBits for deflateInit() is changed, then
this function needs to be updated.
*/
uLong ZEXPORT compressBound (uLong sourceLen)
uLong ZEXPORT compressBound (sourceLen)
uLong sourceLen;
{
return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
(sourceLen >> 25) + 13;

View File

@@ -87,7 +87,7 @@ local void make_crc_table OF((void));
allow for word-at-a-time CRC calculation for both big-endian and little-
endian machines, where a word is four bytes.
*/
local void make_crc_table(void)
local void make_crc_table()
{
z_crc_t c;
int n, k;
@@ -164,7 +164,9 @@ local void make_crc_table(void)
}
#ifdef MAKECRCH
local void write_table(FILE *out, const z_crc_t FAR *table)
local void write_table(out, table)
FILE *out;
const z_crc_t FAR *table;
{
int n;
@@ -185,7 +187,7 @@ local void write_table(FILE *out, const z_crc_t FAR *table)
/* =========================================================================
* This function can be used by asm versions of crc32()
*/
const z_crc_t FAR * ZEXPORT get_crc_table(void)
const z_crc_t FAR * ZEXPORT get_crc_table()
{
#ifdef DYNAMIC_CRC_TABLE
if (crc_table_empty)
@@ -199,7 +201,10 @@ const z_crc_t FAR * ZEXPORT get_crc_table(void)
#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1
/* ========================================================================= */
unsigned long ZEXPORT crc32(unsigned long crc, const unsigned char FAR *buf, uInt len)
unsigned long ZEXPORT crc32(crc, buf, len)
unsigned long crc;
const unsigned char FAR *buf;
uInt len;
{
if (buf == Z_NULL) return 0UL;
@@ -239,7 +244,10 @@ unsigned long ZEXPORT crc32(unsigned long crc, const unsigned char FAR *buf, uIn
#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4
/* ========================================================================= */
local unsigned long crc32_little(unsigned long crc, const unsigned char FAR *buf, unsigned len)
local unsigned long crc32_little(crc, buf, len)
unsigned long crc;
const unsigned char FAR *buf;
unsigned len;
{
register z_crc_t c;
register const z_crc_t FAR *buf4;
@@ -276,7 +284,10 @@ local unsigned long crc32_little(unsigned long crc, const unsigned char FAR *buf
#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4
/* ========================================================================= */
local unsigned long crc32_big(unsigned long crc, const unsigned char FAR *buf, unsigned len)
local unsigned long crc32_big(crc, buf, len)
unsigned long crc;
const unsigned char FAR *buf;
unsigned len;
{
register z_crc_t c;
register const z_crc_t FAR *buf4;
@@ -311,7 +322,9 @@ local unsigned long crc32_big(unsigned long crc, const unsigned char FAR *buf, u
#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */
/* ========================================================================= */
local unsigned long gf2_matrix_times(unsigned long *mat, unsigned long vec)
local unsigned long gf2_matrix_times(mat, vec)
unsigned long *mat;
unsigned long vec;
{
unsigned long sum;
@@ -326,7 +339,9 @@ local unsigned long gf2_matrix_times(unsigned long *mat, unsigned long vec)
}
/* ========================================================================= */
local void gf2_matrix_square(unsigned long *square, unsigned long *mat)
local void gf2_matrix_square(square, mat)
unsigned long *square;
unsigned long *mat;
{
int n;
@@ -335,7 +350,10 @@ local void gf2_matrix_square(unsigned long *square, unsigned long *mat)
}
/* ========================================================================= */
local uLong crc32_combine_(uLong crc1, uLong crc2, z_off64_t len2)
local uLong crc32_combine_(crc1, crc2, len2)
uLong crc1;
uLong crc2;
z_off64_t len2;
{
int n;
unsigned long row;
@@ -388,12 +406,18 @@ local uLong crc32_combine_(uLong crc1, uLong crc2, z_off64_t len2)
}
/* ========================================================================= */
uLong ZEXPORT crc32_combine(uLong crc1, uLong crc2, z_off_t len2)
uLong ZEXPORT crc32_combine(crc1, crc2, len2)
uLong crc1;
uLong crc2;
z_off_t len2;
{
return crc32_combine_(crc1, crc2, len2);
}
uLong ZEXPORT crc32_combine64(uLong crc1, uLong crc2, z_off64_t len2)
uLong ZEXPORT crc32_combine64(crc1, crc2, len2)
uLong crc1;
uLong crc2;
z_off64_t len2;
{
return crc32_combine_(crc1, crc2, len2);
}

View File

@@ -200,8 +200,11 @@ struct static_tree_desc_s {int dummy;}; /* for buggy compilers */
zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head));
/* ========================================================================= */
int ZEXPORT deflateInit_(z_streamp strm, int level, const char *version,
int stream_size)
int ZEXPORT deflateInit_(strm, level, version, stream_size)
z_streamp strm;
int level;
const char *version;
int stream_size;
{
return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL,
Z_DEFAULT_STRATEGY, version, stream_size);
@@ -209,8 +212,16 @@ int ZEXPORT deflateInit_(z_streamp strm, int level, const char *version,
}
/* ========================================================================= */
int ZEXPORT deflateInit2_(z_streamp strm, int level, int method, int windowBits,
int memLevel, int strategy, const char *version, int stream_size)
int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
version, stream_size)
z_streamp strm;
int level;
int method;
int windowBits;
int memLevel;
int strategy;
const char *version;
int stream_size;
{
deflate_state *s;
int wrap = 1;
@@ -348,8 +359,10 @@ int ZEXPORT deflateInit2_(z_streamp strm, int level, int method, int windowBits,
}
/* ========================================================================= */
int ZEXPORT deflateSetDictionary (z_streamp strm, const Bytef *dictionary,
uInt dictLength)
int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
z_streamp strm;
const Bytef *dictionary;
uInt dictLength;
{
deflate_state *s;
uInt str, n;
@@ -415,7 +428,8 @@ int ZEXPORT deflateSetDictionary (z_streamp strm, const Bytef *dictionary,
}
/* ========================================================================= */
int ZEXPORT deflateResetKeep (z_streamp strm)
int ZEXPORT deflateResetKeep (strm)
z_streamp strm;
{
deflate_state *s;
@@ -449,7 +463,8 @@ int ZEXPORT deflateResetKeep (z_streamp strm)
}
/* ========================================================================= */
int ZEXPORT deflateReset (z_streamp strm)
int ZEXPORT deflateReset (strm)
z_streamp strm;
{
int ret;
@@ -460,7 +475,9 @@ int ZEXPORT deflateReset (z_streamp strm)
}
/* ========================================================================= */
int ZEXPORT deflateSetHeader (z_streamp strm, gz_headerp head)
int ZEXPORT deflateSetHeader (strm, head)
z_streamp strm;
gz_headerp head;
{
if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
if (strm->state->wrap != 2) return Z_STREAM_ERROR;
@@ -469,7 +486,10 @@ int ZEXPORT deflateSetHeader (z_streamp strm, gz_headerp head)
}
/* ========================================================================= */
int ZEXPORT deflatePending (z_streamp strm, unsigned *pending, int *bits)
int ZEXPORT deflatePending (strm, pending, bits)
unsigned *pending;
int *bits;
z_streamp strm;
{
if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
if (pending != Z_NULL)
@@ -480,7 +500,10 @@ int ZEXPORT deflatePending (z_streamp strm, unsigned *pending, int *bits)
}
/* ========================================================================= */
int ZEXPORT deflatePrime (z_streamp strm, int bits, int value)
int ZEXPORT deflatePrime (strm, bits, value)
z_streamp strm;
int bits;
int value;
{
deflate_state *s;
int put;
@@ -503,7 +526,10 @@ int ZEXPORT deflatePrime (z_streamp strm, int bits, int value)
}
/* ========================================================================= */
int ZEXPORT deflateParams(z_streamp strm, int level, int strategy)
int ZEXPORT deflateParams(strm, level, strategy)
z_streamp strm;
int level;
int strategy;
{
deflate_state *s;
compress_func func;
@@ -541,8 +567,12 @@ int ZEXPORT deflateParams(z_streamp strm, int level, int strategy)
}
/* ========================================================================= */
int ZEXPORT deflateTune(z_streamp strm, int good_length, int max_lazy,
int nice_length, int max_chain)
int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain)
z_streamp strm;
int good_length;
int max_lazy;
int nice_length;
int max_chain;
{
deflate_state *s;
@@ -572,7 +602,9 @@ int ZEXPORT deflateTune(z_streamp strm, int good_length, int max_lazy,
* upper bound of about 14% expansion does not seem onerous for output buffer
* allocation.
*/
uLong ZEXPORT deflateBound(z_streamp strm, uLong sourceLen)
uLong ZEXPORT deflateBound(strm, sourceLen)
z_streamp strm;
uLong sourceLen;
{
deflate_state *s;
uLong complen, wraplen;
@@ -632,7 +664,9 @@ uLong ZEXPORT deflateBound(z_streamp strm, uLong sourceLen)
* IN assertion: the stream state is correct and there is enough room in
* pending_buf.
*/
local void putShortMSB (deflate_state *s, uInt b)
local void putShortMSB (s, b)
deflate_state *s;
uInt b;
{
put_byte(s, (Byte)(b >> 8));
put_byte(s, (Byte)(b & 0xff));
@@ -644,7 +678,8 @@ local void putShortMSB (deflate_state *s, uInt b)
* to avoid allocating a large strm->next_out buffer and copying into it.
* (See also read_buf()).
*/
local void flush_pending(z_streamp strm)
local void flush_pending(strm)
z_streamp strm;
{
unsigned len;
deflate_state *s = strm->state;
@@ -666,7 +701,9 @@ local void flush_pending(z_streamp strm)
}
/* ========================================================================= */
int ZEXPORT deflate (z_streamp strm, int flush)
int ZEXPORT deflate (strm, flush)
z_streamp strm;
int flush;
{
int old_flush; /* value of flush param for previous deflate call */
deflate_state *s;
@@ -978,7 +1015,8 @@ int ZEXPORT deflate (z_streamp strm, int flush)
}
/* ========================================================================= */
int ZEXPORT deflateEnd (z_streamp strm)
int ZEXPORT deflateEnd (strm)
z_streamp strm;
{
int status;
@@ -1012,7 +1050,9 @@ int ZEXPORT deflateEnd (z_streamp strm)
* To simplify the source, this is not supported for 16-bit MSDOS (which
* doesn't have enough memory anyway to duplicate compression states).
*/
int ZEXPORT deflateCopy (z_streamp dest, z_streamp source)
int ZEXPORT deflateCopy (dest, source)
z_streamp dest;
z_streamp source;
{
#ifdef MAXSEG_64K
return Z_STREAM_ERROR;
@@ -1069,7 +1109,10 @@ int ZEXPORT deflateCopy (z_streamp dest, z_streamp source)
* allocating a large strm->next_in buffer and copying from it.
* (See also flush_pending()).
*/
local int read_buf(z_streamp strm, Bytef *buf, unsigned size)
local int read_buf(strm, buf, size)
z_streamp strm;
Bytef *buf;
unsigned size;
{
unsigned len = strm->avail_in;
@@ -1096,7 +1139,8 @@ local int read_buf(z_streamp strm, Bytef *buf, unsigned size)
/* ===========================================================================
* Initialize the "longest match" routines for a new zlib stream
*/
local void lm_init (deflate_state *s)
local void lm_init (s)
deflate_state *s;
{
s->window_size = (ulg)2L*s->w_size;
@@ -1137,7 +1181,9 @@ local void lm_init (deflate_state *s)
/* For 80x86 and 680x0, an optimized version will be provided in match.asm or
* match.S. The code will be functionally equivalent.
*/
local uInt longest_match(deflate_state *s, IPos cur_match)
local uInt longest_match(s, cur_match)
deflate_state *s;
IPos cur_match; /* current match */
{
unsigned chain_length = s->max_chain_length;/* max hash chain length */
register Bytef *scan = s->window + s->strstart; /* current string */
@@ -1284,7 +1330,9 @@ local uInt longest_match(deflate_state *s, IPos cur_match)
/* ---------------------------------------------------------------------------
* Optimized version for FASTEST only
*/
local uInt longest_match(deflate_state *s, IPos cur_match)
local uInt longest_match(s, cur_match)
deflate_state *s;
IPos cur_match; /* current match */
{
register Bytef *scan = s->window + s->strstart; /* current string */
register Bytef *match; /* matched string */
@@ -1341,7 +1389,10 @@ local uInt longest_match(deflate_state *s, IPos cur_match)
/* ===========================================================================
* Check that the match at match_start is indeed a match.
*/
local void check_match(deflate_state *s, IPos start, IPos match, int length)
local void check_match(s, start, match, length)
deflate_state *s;
IPos start, match;
int length;
{
/* check that the match is indeed a match */
if (zmemcmp(s->window + match,
@@ -1372,7 +1423,8 @@ local void check_match(deflate_state *s, IPos start, IPos match, int length)
* performed for at least two bytes (required for the zip translate_eol
* option -- not supported here).
*/
local void fill_window(deflate_state *s)
local void fill_window(s)
deflate_state *s;
{
register unsigned n, m;
register Posf *p;
@@ -1545,7 +1597,9 @@ local void fill_window(deflate_state *s)
* NOTE: this function should be optimized to avoid extra copying from
* window to pending_buf.
*/
local block_state deflate_stored(deflate_state *s, int flush)
local block_state deflate_stored(s, flush)
deflate_state *s;
int flush;
{
/* Stored blocks are limited to 0xffff bytes, pending_buf is limited
* to pending_buf_size, and each stored block has a 5 byte header:
@@ -1616,7 +1670,9 @@ local block_state deflate_stored(deflate_state *s, int flush)
* new strings in the dictionary only for unmatched strings or for short
* matches. It is used only for the fast compression options.
*/
local block_state deflate_fast(deflate_state *s, int flush)
local block_state deflate_fast(s, flush)
deflate_state *s;
int flush;
{
IPos hash_head; /* head of the hash chain */
int bflush; /* set if current block must be flushed */
@@ -1726,7 +1782,9 @@ local block_state deflate_fast(deflate_state *s, int flush)
* evaluation for matches: a match is finally adopted only if there is
* no better match at the next window position.
*/
local block_state deflate_slow(deflate_state *s, int flush)
local block_state deflate_slow(s, flush)
deflate_state *s;
int flush;
{
IPos hash_head; /* head of hash chain */
int bflush; /* set if current block must be flushed */
@@ -1865,7 +1923,9 @@ local block_state deflate_slow(deflate_state *s, int flush)
* one. Do not maintain a hash table. (It will be regenerated if this run of
* deflate switches away from Z_RLE.)
*/
local block_state deflate_rle(deflate_state *s, int flush)
local block_state deflate_rle(s, flush)
deflate_state *s;
int flush;
{
int bflush; /* set if current block must be flushed */
uInt prev; /* byte at distance one to match */
@@ -1936,7 +1996,9 @@ local block_state deflate_rle(deflate_state *s, int flush)
* For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table.
* (It will be regenerated if this run of deflate switches away from Huffman.)
*/
local block_state deflate_huff(deflate_state *s, int flush)
local block_state deflate_huff(s, flush)
deflate_state *s;
int flush;
{
int bflush; /* set if current block must be flushed */

View File

@@ -45,7 +45,9 @@
requires strm->avail_out >= 258 for each loop to avoid checking for
output space.
*/
void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start)
void ZLIB_INTERNAL inflate_fast(strm, start)
z_streamp strm;
unsigned start; /* inflate()'s starting value for strm->avail_out */
{
struct inflate_state FAR *state;
z_const unsigned char FAR *in; /* local strm->next_in */

View File

@@ -101,7 +101,8 @@ local int updatewindow OF((z_streamp strm, const unsigned char FAR *end,
local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf,
unsigned len));
int ZEXPORT inflateResetKeep(z_streamp strm)
int ZEXPORT inflateResetKeep(strm)
z_streamp strm;
{
struct inflate_state FAR *state;
@@ -125,7 +126,8 @@ int ZEXPORT inflateResetKeep(z_streamp strm)
return Z_OK;
}
int ZEXPORT inflateReset(z_streamp strm)
int ZEXPORT inflateReset(strm)
z_streamp strm;
{
struct inflate_state FAR *state;
@@ -137,7 +139,9 @@ int ZEXPORT inflateReset(z_streamp strm)
return inflateResetKeep(strm);
}
int ZEXPORT inflateReset2(z_streamp strm, int windowBits)
int ZEXPORT inflateReset2(strm, windowBits)
z_streamp strm;
int windowBits;
{
int wrap;
struct inflate_state FAR *state;
@@ -173,7 +177,11 @@ int ZEXPORT inflateReset2(z_streamp strm, int windowBits)
return inflateReset(strm);
}
int ZEXPORT inflateInit2_(z_streamp strm, int windowBits, const char *version, int stream_size)
int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size)
z_streamp strm;
int windowBits;
const char *version;
int stream_size;
{
int ret;
struct inflate_state FAR *state;
@@ -211,12 +219,18 @@ int ZEXPORT inflateInit2_(z_streamp strm, int windowBits, const char *version, i
return ret;
}
int ZEXPORT inflateInit_(z_streamp strm, const char *version, int stream_size)
int ZEXPORT inflateInit_(strm, version, stream_size)
z_streamp strm;
const char *version;
int stream_size;
{
return inflateInit2_(strm, DEF_WBITS, version, stream_size);
}
int ZEXPORT inflatePrime(z_streamp strm, int bits, int value)
int ZEXPORT inflatePrime(strm, bits, value)
z_streamp strm;
int bits;
int value;
{
struct inflate_state FAR *state;
@@ -244,7 +258,8 @@ int ZEXPORT inflatePrime(z_streamp strm, int bits, int value)
used for threaded applications, since the rewriting of the tables and virgin
may not be thread-safe.
*/
local void fixedtables(struct inflate_state FAR *state)
local void fixedtables(state)
struct inflate_state FAR *state;
{
#ifdef BUILDFIXED
static int virgin = 1;
@@ -361,7 +376,10 @@ void makefixed()
output will fall in the output data, making match copies simpler and faster.
The advantage may be dependent on the size of the processor's data caches.
*/
local int updatewindow(z_streamp strm, const Bytef *end, unsigned copy)
local int updatewindow(strm, end, copy)
z_streamp strm;
const Bytef *end;
unsigned copy;
{
struct inflate_state FAR *state;
unsigned dist;
@@ -584,7 +602,9 @@ local int updatewindow(z_streamp strm, const Bytef *end, unsigned copy)
will return Z_BUF_ERROR if it has not reached the end of the stream.
*/
int ZEXPORT inflate(z_streamp strm, int flush)
int ZEXPORT inflate(strm, flush)
z_streamp strm;
int flush;
{
struct inflate_state FAR *state;
z_const unsigned char FAR *next; /* next input */
@@ -1254,7 +1274,8 @@ int ZEXPORT inflate(z_streamp strm, int flush)
return ret;
}
int ZEXPORT inflateEnd(z_streamp strm)
int ZEXPORT inflateEnd(strm)
z_streamp strm;
{
struct inflate_state FAR *state;
if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0)
@@ -1267,7 +1288,10 @@ int ZEXPORT inflateEnd(z_streamp strm)
return Z_OK;
}
int ZEXPORT inflateGetDictionary(z_streamp strm, Bytef *dictionary, uInt *dictLength)
int ZEXPORT inflateGetDictionary(strm, dictionary, dictLength)
z_streamp strm;
Bytef *dictionary;
uInt *dictLength;
{
struct inflate_state FAR *state;
@@ -1287,7 +1311,10 @@ int ZEXPORT inflateGetDictionary(z_streamp strm, Bytef *dictionary, uInt *dictLe
return Z_OK;
}
int ZEXPORT inflateSetDictionary(z_streamp strm, const Bytef *dictionary, uInt dictLength)
int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength)
z_streamp strm;
const Bytef *dictionary;
uInt dictLength;
{
struct inflate_state FAR *state;
unsigned long dictid;
@@ -1319,7 +1346,9 @@ int ZEXPORT inflateSetDictionary(z_streamp strm, const Bytef *dictionary, uInt d
return Z_OK;
}
int ZEXPORT inflateGetHeader(z_streamp strm, gz_headerp head)
int ZEXPORT inflateGetHeader(strm, head)
z_streamp strm;
gz_headerp head;
{
struct inflate_state FAR *state;
@@ -1345,7 +1374,10 @@ int ZEXPORT inflateGetHeader(z_streamp strm, gz_headerp head)
called again with more data and the *have state. *have is initialized to
zero for the first call.
*/
local unsigned syncsearch(unsigned FAR *have, const unsigned char FAR *buf, unsigned len)
local unsigned syncsearch(have, buf, len)
unsigned FAR *have;
const unsigned char FAR *buf;
unsigned len;
{
unsigned got;
unsigned next;
@@ -1365,7 +1397,8 @@ local unsigned syncsearch(unsigned FAR *have, const unsigned char FAR *buf, unsi
return next;
}
int ZEXPORT inflateSync(z_streamp strm)
int ZEXPORT inflateSync(strm)
z_streamp strm;
{
unsigned len; /* number of bytes to look at or looked at */
unsigned long in, out; /* temporary to save total_in and total_out */
@@ -1415,7 +1448,8 @@ int ZEXPORT inflateSync(z_streamp strm)
block. When decompressing, PPP checks that at the end of input packet,
inflate is waiting for these length bytes.
*/
int ZEXPORT inflateSyncPoint(z_streamp strm)
int ZEXPORT inflateSyncPoint(strm)
z_streamp strm;
{
struct inflate_state FAR *state;
@@ -1424,7 +1458,9 @@ int ZEXPORT inflateSyncPoint(z_streamp strm)
return state->mode == STORED && state->bits == 0;
}
int ZEXPORT inflateCopy(z_streamp dest, z_streamp source)
int ZEXPORT inflateCopy(dest, source)
z_streamp dest;
z_streamp source;
{
struct inflate_state FAR *state;
struct inflate_state FAR *copy;
@@ -1469,7 +1505,9 @@ int ZEXPORT inflateCopy(z_streamp dest, z_streamp source)
return Z_OK;
}
int ZEXPORT inflateUndermine(z_streamp strm, int subvert)
int ZEXPORT inflateUndermine(strm, subvert)
z_streamp strm;
int subvert;
{
struct inflate_state FAR *state;
@@ -1484,7 +1522,8 @@ int ZEXPORT inflateUndermine(z_streamp strm, int subvert)
#endif
}
long ZEXPORT inflateMark(z_streamp strm)
long ZEXPORT inflateMark(strm)
z_streamp strm;
{
struct inflate_state FAR *state;

View File

@@ -29,9 +29,13 @@ const char inflate_copyright[] =
table index bits. It will differ if the request is greater than the
longest code or if it is less than the shortest code.
*/
int ZLIB_INTERNAL inflate_table(codetype type, unsigned short FAR *lens,
unsigned codes, code FAR * FAR *table, unsigned FAR *bits,
unsigned short FAR *work)
int ZLIB_INTERNAL inflate_table(type, lens, codes, table, bits, work)
codetype type;
unsigned short FAR *lens;
unsigned codes;
code FAR * FAR *table;
unsigned FAR *bits;
unsigned short FAR *work;
{
unsigned len; /* a code's length in bits */
unsigned sym; /* index of code symbols */

View File

@@ -185,7 +185,10 @@ local void gen_trees_header OF((void));
#ifdef DEBUG
local void send_bits OF((deflate_state *s, int value, int length));
local void send_bits(deflate_state *s, int value, int length)
local void send_bits(s, value, length)
deflate_state *s;
int value; /* value to send */
int length; /* number of bits */
{
Tracevv((stderr," l %2d v %4x ", length, value));
Assert(length > 0 && length <= 15, "invalid length");
@@ -228,7 +231,7 @@ local void send_bits(deflate_state *s, int value, int length)
/* ===========================================================================
* Initialize the various 'constant' tables.
*/
local void tr_static_init(void)
local void tr_static_init()
{
#if defined(GEN_TREES_H) || !defined(STDC)
static int static_init_done = 0;
@@ -322,7 +325,7 @@ local void tr_static_init(void)
((i) == (last)? "\n};\n\n" : \
((i) % (width) == (width)-1 ? ",\n" : ", "))
void gen_trees_header(void)
void gen_trees_header()
{
FILE *header = fopen("trees.h", "w");
int i;
@@ -375,7 +378,8 @@ void gen_trees_header(void)
/* ===========================================================================
* Initialize the tree data structures for a new zlib stream.
*/
void ZLIB_INTERNAL _tr_init(deflate_state *s)
void ZLIB_INTERNAL _tr_init(s)
deflate_state *s;
{
tr_static_init();
@@ -402,7 +406,8 @@ void ZLIB_INTERNAL _tr_init(deflate_state *s)
/* ===========================================================================
* Initialize a new block.
*/
local void init_block(deflate_state *s)
local void init_block(s)
deflate_state *s;
{
int n; /* iterates over tree elements */
@@ -445,7 +450,10 @@ local void init_block(deflate_state *s)
* when the heap property is re-established (each father smaller than its
* two sons).
*/
local void pqdownheap(deflate_state *s, ct_data *tree, int k)
local void pqdownheap(s, tree, k)
deflate_state *s;
ct_data *tree; /* the tree to restore */
int k; /* node to move down */
{
int v = s->heap[k];
int j = k << 1; /* left son of k */
@@ -477,7 +485,9 @@ local void pqdownheap(deflate_state *s, ct_data *tree, int k)
* The length opt_len is updated; static_len is also updated if stree is
* not null.
*/
local void gen_bitlen(deflate_state *s, tree_desc *desc)
local void gen_bitlen(s, desc)
deflate_state *s;
tree_desc *desc; /* the tree descriptor */
{
ct_data *tree = desc->dyn_tree;
int max_code = desc->max_code;
@@ -562,7 +572,10 @@ local void gen_bitlen(deflate_state *s, tree_desc *desc)
* OUT assertion: the field code is set for all tree elements of non
* zero code length.
*/
local void gen_codes(ct_data *tree, int max_code, ushf *bl_count)
local void gen_codes (tree, max_code, bl_count)
ct_data *tree; /* the tree to decorate */
int max_code; /* largest code with non zero frequency */
ushf *bl_count; /* number of codes at each bit length */
{
ush next_code[MAX_BITS+1]; /* next code value for each bit length */
ush code = 0; /* running code value */
@@ -601,7 +614,9 @@ local void gen_codes(ct_data *tree, int max_code, ushf *bl_count)
* and corresponding code. The length opt_len is updated; static_len is
* also updated if stree is not null. The field max_code is set.
*/
local void build_tree(deflate_state *s, tree_desc *desc)
local void build_tree(s, desc)
deflate_state *s;
tree_desc *desc; /* the tree descriptor */
{
ct_data *tree = desc->dyn_tree;
const ct_data *stree = desc->stat_desc->static_tree;
@@ -687,7 +702,10 @@ local void build_tree(deflate_state *s, tree_desc *desc)
* Scan a literal or distance tree to determine the frequencies of the codes
* in the bit length tree.
*/
local void scan_tree(deflate_state *s, ct_data *tree, int max_code)
local void scan_tree (s, tree, max_code)
deflate_state *s;
ct_data *tree; /* the tree to be scanned */
int max_code; /* and its largest code of non zero frequency */
{
int n; /* iterates over all tree elements */
int prevlen = -1; /* last emitted length */
@@ -729,7 +747,10 @@ local void scan_tree(deflate_state *s, ct_data *tree, int max_code)
* Send a literal or distance tree in compressed form, using the codes in
* bl_tree.
*/
local void send_tree(deflate_state *s, ct_data *tree, int max_code)
local void send_tree (s, tree, max_code)
deflate_state *s;
ct_data *tree; /* the tree to be scanned */
int max_code; /* and its largest code of non zero frequency */
{
int n; /* iterates over all tree elements */
int prevlen = -1; /* last emitted length */
@@ -777,7 +798,8 @@ local void send_tree(deflate_state *s, ct_data *tree, int max_code)
* Construct the Huffman tree for the bit lengths and return the index in
* bl_order of the last bit length code to send.
*/
local int build_bl_tree(deflate_state *s)
local int build_bl_tree(s)
deflate_state *s;
{
int max_blindex; /* index of last bit length code of non zero freq */
@@ -811,7 +833,9 @@ local int build_bl_tree(deflate_state *s)
* lengths of the bit length codes, the literal tree and the distance tree.
* IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
*/
local void send_all_trees(deflate_state *s, int lcodes, int dcodes, int blcodes)
local void send_all_trees(s, lcodes, dcodes, blcodes)
deflate_state *s;
int lcodes, dcodes, blcodes; /* number of codes for each tree */
{
int rank; /* index in bl_order */
@@ -838,7 +862,11 @@ local void send_all_trees(deflate_state *s, int lcodes, int dcodes, int blcodes)
/* ===========================================================================
* Send a stored block
*/
void ZLIB_INTERNAL _tr_stored_block(deflate_state *s, charf *buf, ulg stored_len, int last)
void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last)
deflate_state *s;
charf *buf; /* input block */
ulg stored_len; /* length of input block */
int last; /* one if this is the last block for a file */
{
send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */
#ifdef DEBUG
@@ -851,7 +879,8 @@ void ZLIB_INTERNAL _tr_stored_block(deflate_state *s, charf *buf, ulg stored_len
/* ===========================================================================
* Flush the bits in the bit buffer to pending output (leaves at most 7 bits)
*/
void ZLIB_INTERNAL _tr_flush_bits(deflate_state *s)
void ZLIB_INTERNAL _tr_flush_bits(s)
deflate_state *s;
{
bi_flush(s);
}
@@ -860,7 +889,8 @@ void ZLIB_INTERNAL _tr_flush_bits(deflate_state *s)
* Send one empty static block to give enough lookahead for inflate.
* This takes 10 bits, of which 7 may remain in the bit buffer.
*/
void ZLIB_INTERNAL _tr_align(deflate_state *s)
void ZLIB_INTERNAL _tr_align(s)
deflate_state *s;
{
send_bits(s, STATIC_TREES<<1, 3);
send_code(s, END_BLOCK, static_ltree);
@@ -874,7 +904,11 @@ void ZLIB_INTERNAL _tr_align(deflate_state *s)
* Determine the best encoding for the current block: dynamic trees, static
* trees or store, and output the encoded block to the zip file.
*/
void ZLIB_INTERNAL _tr_flush_block(deflate_state *s, charf *buf, ulg stored_len, int last)
void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
deflate_state *s;
charf *buf; /* input block, or NULL if too old */
ulg stored_len; /* length of input block */
int last; /* one if this is the last block for a file */
{
ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */
int max_blindex = 0; /* index of last bit length code of non zero freq */
@@ -973,7 +1007,10 @@ void ZLIB_INTERNAL _tr_flush_block(deflate_state *s, charf *buf, ulg stored_len,
* Save the match info and tally the frequency counts. Return true if
* the current block must be flushed.
*/
int ZLIB_INTERNAL _tr_tally(deflate_state *s, unsigned dist, unsigned lc)
int ZLIB_INTERNAL _tr_tally (s, dist, lc)
deflate_state *s;
unsigned dist; /* distance of matched string */
unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */
{
s->sym_buf[s->sym_next++] = dist;
s->sym_buf[s->sym_next++] = dist >> 8;
@@ -998,7 +1035,10 @@ int ZLIB_INTERNAL _tr_tally(deflate_state *s, unsigned dist, unsigned lc)
/* ===========================================================================
* Send the block data compressed using the given Huffman trees
*/
local void compress_block(deflate_state *s, const ct_data *ltree, const ct_data *dtree)
local void compress_block(s, ltree, dtree)
deflate_state *s;
const ct_data *ltree; /* literal tree */
const ct_data *dtree; /* distance tree */
{
unsigned dist; /* distance of matched string */
int lc; /* match length or unmatched char (if dist == 0) */
@@ -1055,7 +1095,8 @@ local void compress_block(deflate_state *s, const ct_data *ltree, const ct_data
* (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
* IN assertion: the fields Freq of dyn_ltree are set.
*/
local int detect_data_type(deflate_state *s)
local int detect_data_type(s)
deflate_state *s;
{
/* black_mask is the bit mask of black-listed bytes
* set bits 0..6, 14..25, and 28..31
@@ -1088,7 +1129,9 @@ local int detect_data_type(deflate_state *s)
* method would use a table)
* IN assertion: 1 <= len <= 15
*/
local unsigned bi_reverse(unsigned code, int len)
local unsigned bi_reverse(code, len)
unsigned code; /* the value to invert */
int len; /* its bit length */
{
register unsigned res = 0;
do {
@@ -1101,7 +1144,8 @@ local unsigned bi_reverse(unsigned code, int len)
/* ===========================================================================
* Flush the bit buffer, keeping at most 7 bits in it.
*/
local void bi_flush(deflate_state *s)
local void bi_flush(s)
deflate_state *s;
{
if (s->bi_valid == 16) {
put_short(s, s->bi_buf);
@@ -1117,7 +1161,8 @@ local void bi_flush(deflate_state *s)
/* ===========================================================================
* Flush the bit buffer and align the output on a byte boundary
*/
local void bi_windup(deflate_state *s)
local void bi_windup(s)
deflate_state *s;
{
if (s->bi_valid > 8) {
put_short(s, s->bi_buf);
@@ -1135,7 +1180,11 @@ local void bi_windup(deflate_state *s)
* Copy a stored block, storing first the length and its
* one's complement if requested.
*/
local void copy_block(deflate_state *s, charf *buf, unsigned len, int header)
local void copy_block(s, buf, len, header)
deflate_state *s;
charf *buf; /* the input data */
unsigned len; /* its length */
int header; /* true if block header must be written */
{
bi_windup(s); /* align on byte boundary */

View File

@@ -27,12 +27,12 @@ z_const char * const z_errmsg[10] = {
""};
const char * ZEXPORT zlibVersion(void)
const char * ZEXPORT zlibVersion()
{
return ZLIB_VERSION;
}
uLong ZEXPORT zlibCompileFlags(void)
uLong ZEXPORT zlibCompileFlags()
{
uLong flags;
@@ -122,7 +122,8 @@ uLong ZEXPORT zlibCompileFlags(void)
# endif
int ZLIB_INTERNAL z_verbose = verbose;
void ZLIB_INTERNAL z_error (char *m)
void ZLIB_INTERNAL z_error (m)
char *m;
{
fprintf(stderr, "%s\n", m);
exit(1);
@@ -132,7 +133,8 @@ void ZLIB_INTERNAL z_error (char *m)
/* exported to allow conversion of error code to string for compress() and
* uncompress()
*/
const char * ZEXPORT zError(int err)
const char * ZEXPORT zError(err)
int err;
{
return ERR_MSG(err);
}
@@ -147,7 +149,10 @@ const char * ZEXPORT zError(int err)
#ifndef HAVE_MEMCPY
void ZLIB_INTERNAL zmemcpy(Bytef* dest, const Bytef* source, uInt len)
void ZLIB_INTERNAL zmemcpy(dest, source, len)
Bytef* dest;
const Bytef* source;
uInt len;
{
if (len == 0) return;
do {
@@ -155,7 +160,10 @@ void ZLIB_INTERNAL zmemcpy(Bytef* dest, const Bytef* source, uInt len)
} while (--len != 0);
}
int ZLIB_INTERNAL zmemcmp(const Bytef* s1, const Bytef* s2, uInt len)
int ZLIB_INTERNAL zmemcmp(s1, s2, len)
const Bytef* s1;
const Bytef* s2;
uInt len;
{
uInt j;
@@ -165,7 +173,9 @@ int ZLIB_INTERNAL zmemcmp(const Bytef* s1, const Bytef* s2, uInt len)
return 0;
}
void ZLIB_INTERNAL zmemzero(Bytef* dest, uInt len)
void ZLIB_INTERNAL zmemzero(dest, len)
Bytef* dest;
uInt len;
{
if (len == 0) return;
do {
@@ -291,14 +301,19 @@ extern voidp calloc OF((uInt items, uInt size));
extern void free OF((voidpf ptr));
#endif
voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
voidpf opaque;
unsigned items;
unsigned size;
{
if (opaque) items += size - size; /* make compiler happy */
return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) :
(voidpf)calloc(items, size);
}
void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
void ZLIB_INTERNAL zcfree (opaque, ptr)
voidpf opaque;
voidpf ptr;
{
free(ptr);
if (opaque) return; /* make compiler happy */