mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-05-28 16:57:47 -04:00
Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c7777aaa6 | ||
|
|
6af41d2357 | ||
|
|
a0b9a8e989 | ||
|
|
ac692b199c | ||
|
|
147e9bea8c | ||
|
|
a5fc5ebe7a | ||
|
|
c79cb81a4f | ||
|
|
650643109e | ||
|
|
4cf08983e8 | ||
|
|
8112445318 | ||
|
|
c38f20c5ff | ||
|
|
0cf200ecbb | ||
|
|
e4c681fefd | ||
|
|
c44c90e946 | ||
|
|
fc592a8e25 | ||
|
|
40a6e13071 | ||
|
|
3cc6a9e8cd | ||
|
|
30656c5e35 | ||
|
|
15d2964256 | ||
|
|
862fe4eeaf | ||
|
|
859d44fa4f | ||
|
|
f1c24ab03b | ||
|
|
b9cc0c6176 | ||
|
|
c60550bff9 | ||
|
|
67f1dcf604 | ||
|
|
79fd7d5885 | ||
|
|
dfdcd8f851 | ||
|
|
04e2fc2c76 | ||
|
|
7f60ec001a | ||
|
|
4fa7156ccd | ||
|
|
dcf364dac5 | ||
|
|
d1eff8f0dc | ||
|
|
8f727166d9 | ||
|
|
5bcb3deb2f | ||
|
|
de3cc03b03 | ||
|
|
006ee327d6 | ||
|
|
9b6363fa10 | ||
|
|
9e2f0fe9ae | ||
|
|
4f6e4ea64a | ||
|
|
567c40935f | ||
|
|
8e11f0c169 | ||
|
|
e9dbc8d66d | ||
|
|
bd2dbd2f32 | ||
|
|
350e295d1c | ||
|
|
066156fcd9 | ||
|
|
a5bbe859db | ||
|
|
d046525de3 | ||
|
|
bb0a8118c2 | ||
|
|
d1df0aaf70 | ||
|
|
15d8e49a64 | ||
|
|
b905ab23af | ||
|
|
aa142f08ef | ||
|
|
236417cf35 | ||
|
|
2a97d81e99 | ||
|
|
359e539a72 | ||
|
|
9e0898460d | ||
|
|
185520a141 | ||
|
|
c98f9d1f68 | ||
|
|
1f9ce2fcbe | ||
|
|
797e17fc4a | ||
|
|
c2db921890 | ||
|
|
77be09aaed | ||
|
|
0d0f615240 | ||
|
|
b6457bbc83 | ||
|
|
1807ce485a | ||
|
|
9c175ac9ef | ||
|
|
a84b79ea58 | ||
|
|
d4c4f6754e | ||
|
|
a4b926dcdc | ||
|
|
0973d0e380 | ||
|
|
e405cfc073 | ||
|
|
b78a841bb0 | ||
|
|
f7a2b8a3fa | ||
|
|
d941807915 | ||
|
|
992e10efaf | ||
|
|
1c5ebdc4e5 | ||
|
|
9994933c8c | ||
|
|
23d9ead5af | ||
|
|
fcfdd36054 | ||
|
|
89b847393f | ||
|
|
788ecbe5ea | ||
|
|
353506bc51 | ||
|
|
7cff121ec8 | ||
|
|
14f33837dc | ||
|
|
3305a7a063 | ||
|
|
494879b819 | ||
|
|
8d6da040e5 | ||
|
|
68e9add76a | ||
|
|
dc34990b2e | ||
|
|
81ead9e70c | ||
|
|
996af4a79f | ||
|
|
dacadd53a9 | ||
|
|
a6312e60c9 | ||
|
|
e3ee0e7319 | ||
|
|
0fd29b6bcb | ||
|
|
7f79682732 | ||
|
|
870b7d96dc | ||
|
|
9dc31473ba | ||
|
|
536ae3f4ef | ||
|
|
0590b09d9a | ||
|
|
407c71c7ce | ||
|
|
344327385f | ||
|
|
688f5c379a | ||
|
|
9f86ddc965 | ||
|
|
c35e28331f | ||
|
|
b4a27ca25d | ||
|
|
8ad4b5d912 | ||
|
|
589b0691e5 | ||
|
|
36212021f0 | ||
|
|
2b38542e0d | ||
|
|
321dd78f8c | ||
|
|
6f10f12577 | ||
|
|
1a95869dfc | ||
|
|
c9fe6ca304 | ||
|
|
990fa5c1e1 | ||
|
|
07069880a2 | ||
|
|
e55b190f4a | ||
|
|
48d51a1370 | ||
|
|
f654e47691 | ||
|
|
83ad3533d4 | ||
|
|
fa28c5d693 | ||
|
|
62bb9bba02 | ||
|
|
6601510425 | ||
|
|
f7ac7ffd16 | ||
|
|
4320c25fcc | ||
|
|
4490fb8660 | ||
|
|
475ca7d43c | ||
|
|
7c3c54b132 | ||
|
|
bcf0738f98 | ||
|
|
8749ec6436 | ||
|
|
42e2b56c4e | ||
|
|
0902b52f66 | ||
|
|
9615a2492b | ||
|
|
4592aa770d | ||
|
|
8bc363cc9f | ||
|
|
a9a3155756 | ||
|
|
fcc79836b8 | ||
|
|
804411b7fd | ||
|
|
0b1b2a3ff4 | ||
|
|
50bdf9685d | ||
|
|
3f2a38b011 | ||
|
|
5510255f12 | ||
|
|
56a039b04a | ||
|
|
7bc3be2b9e | ||
|
|
411c4789df | ||
|
|
231b239f30 | ||
|
|
4c8683c875 | ||
|
|
85c906f964 | ||
|
|
35f5a21a16 | ||
|
|
99673f937f | ||
|
|
9505ac5945 | ||
|
|
0dd25d4752 | ||
|
|
ae3e13ba99 |
23
.cirrus.yml
23
.cirrus.yml
@@ -1,23 +0,0 @@
|
||||
freebsd_task:
|
||||
name: FreeBSD
|
||||
freebsd_instance:
|
||||
image_family: freebsd-13-1
|
||||
env:
|
||||
PATH: /usr/local/bin:$PATH
|
||||
prep_script:
|
||||
- dd if=/dev/zero of=/tmp/zpool bs=1M count=1024
|
||||
- zpool create -m `pwd`/testtmp zpool /tmp/zpool
|
||||
- pkg install -y bash autotools m4 xxhash zstd liblz4 wget
|
||||
- wget -O git-version.h https://gist.githubusercontent.com/WayneD/c11243fa374fc64d4e42f2855c8e3827/raw/rsync-git-version.h
|
||||
configure_script:
|
||||
- CPPFLAGS=-I/usr/local/include/ LDFLAGS=-L/usr/local/lib/ ./configure --disable-md2man
|
||||
make_script:
|
||||
- make
|
||||
install_script:
|
||||
- make install
|
||||
info_script:
|
||||
- rsync --version
|
||||
test_script:
|
||||
- RSYNC_EXPECT_SKIPPED=acls-default,acls,crtimes,protected-regular make check
|
||||
ssl_file_list_script:
|
||||
- rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
|
||||
77
.github/workflows/almalinux-8-build.yml
vendored
Normal file
77
.github/workflows/almalinux-8-build.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
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 ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/almalinux-8-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
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
|
||||
125
.github/workflows/build.yml
vendored
125
.github/workflows/build.yml
vendored
@@ -1,125 +0,0 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore: [ .cirrus.yml ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore: [ .cirrus.yml ]
|
||||
schedule:
|
||||
- cron: '42 8 * * *'
|
||||
|
||||
jobs:
|
||||
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: prep
|
||||
run: |
|
||||
sudo apt-get install acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev python3-cmarkgfm openssl wget
|
||||
wget -O git-version.h https://gist.githubusercontent.com/WayneD/c11243fa374fc64d4e42f2855c8e3827/raw/rsync-git-version.h
|
||||
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@v3
|
||||
with:
|
||||
name: ubuntu-bin
|
||||
path: |
|
||||
rsync
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
|
||||
macos-build:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: prep
|
||||
run: |
|
||||
brew install automake openssl xxhash zstd lz4 wget
|
||||
sudo pip3 install commonmark
|
||||
wget -O git-version.h https://gist.githubusercontent.com/WayneD/c11243fa374fc64d4e42f2855c8e3827/raw/rsync-git-version.h
|
||||
echo "/usr/local/bin" >>$GITHUB_PATH
|
||||
- name: configure
|
||||
run: CPPFLAGS=-I/usr/local/opt/openssl/include/ LDFLAGS=-L/usr/local/opt/openssl/lib/ ./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=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@v3
|
||||
with:
|
||||
name: macos-bin
|
||||
path: |
|
||||
rsync
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
|
||||
cygwin-build:
|
||||
runs-on: windows-2022
|
||||
if: (github.event_name == 'schedule' || contains(github.event.head_commit.message, '[buildall]'))
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: cygwin
|
||||
run: choco install -y --no-progress cygwin cyg-get
|
||||
- name: prep
|
||||
run: |
|
||||
cyg-get make autoconf automake gcc-core attr libattr-devel python39 python39-pip libzstd-devel liblz4-devel libssl-devel libxxhash0 libxxhash-devel
|
||||
curl.exe -o git-version.h https://gist.githubusercontent.com/WayneD/c11243fa374fc64d4e42f2855c8e3827/raw/rsync-git-version.h
|
||||
echo "C:/tools/cygwin/bin" >>$Env:GITHUB_PATH
|
||||
- name: commonmark
|
||||
run: bash -c 'python3 -mpip install --user commonmark'
|
||||
- name: configure
|
||||
run: bash -c './configure --with-rrsync'
|
||||
- name: make
|
||||
run: bash -c 'make'
|
||||
- name: install
|
||||
run: bash -c 'make install'
|
||||
- name: info
|
||||
run: bash -c '/usr/local/bin/rsync --version'
|
||||
- name: 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
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cygwin-bin
|
||||
path: |
|
||||
rsync.exe
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
56
.github/workflows/cygwin-build.yml
vendored
Normal file
56
.github/workflows/cygwin-build.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Test rsync on Cygwin
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/cygwin-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/cygwin-build.yml'
|
||||
schedule:
|
||||
- cron: '42 8 * * *'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-2022
|
||||
name: Test rsync on Cygwin
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: cygwin
|
||||
run: choco install -y --no-progress cygwin cyg-get
|
||||
- name: prep
|
||||
run: |
|
||||
cyg-get make autoconf automake gcc-core attr libattr-devel python39 python39-pip libzstd-devel liblz4-devel libssl-devel libxxhash0 libxxhash-devel
|
||||
echo "C:/tools/cygwin/bin" >>$Env:GITHUB_PATH
|
||||
- name: commonmark
|
||||
run: bash -c 'python3 -mpip install --user commonmark'
|
||||
- name: configure
|
||||
run: bash -c './configure --with-rrsync'
|
||||
- name: make
|
||||
run: bash -c 'make'
|
||||
- name: install
|
||||
run: bash -c 'make install'
|
||||
- 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'
|
||||
- name: ssl file list
|
||||
run: bash -c 'PATH="/usr/local/bin:$PATH" rsync-ssl --no-motd download.samba.org::rsyncftp/ || true'
|
||||
- name: save artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cygwin-bin
|
||||
path: |
|
||||
rsync.exe
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
50
.github/workflows/freebsd-build.yml
vendored
Normal file
50
.github/workflows/freebsd-build.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Test rsync on FreeBSD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/freebsd-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/freebsd-build.yml'
|
||||
schedule:
|
||||
- cron: '42 8 * * *'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Test rsync on FreeBSD
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Test in FreeBSD VM
|
||||
id: test
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y bash autotools m4 devel/xxhash zstd liblz4 python3 archivers/liblz4 git
|
||||
run: |
|
||||
freebsd-version
|
||||
./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: freebsd-bin
|
||||
path: |
|
||||
rsync
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
58
.github/workflows/macos-build.yml
vendored
Normal file
58
.github/workflows/macos-build.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Test rsync on macOS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/macos-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/macos-build.yml'
|
||||
schedule:
|
||||
- cron: '42 8 * * *'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: macos-latest
|
||||
name: Test rsync on macOS
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: prep
|
||||
run: |
|
||||
brew install automake openssl xxhash zstd lz4
|
||||
pip3 install --user --break-system-packages commonmark
|
||||
echo "$(brew --prefix)/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
|
||||
- name: make
|
||||
run: make
|
||||
- name: install
|
||||
run: sudo make install
|
||||
- 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
|
||||
- name: ssl file list
|
||||
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
|
||||
- name: save artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-bin
|
||||
path: |
|
||||
rsync
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
51
.github/workflows/netbsd-build.yml
vendored
Normal file
51
.github/workflows/netbsd-build.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Test rsync on NetBSD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/netbsd-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
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
|
||||
52
.github/workflows/openbsd-build.yml
vendored
Normal file
52
.github/workflows/openbsd-build.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Test rsync on OpenBSD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/openbsd-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
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
|
||||
50
.github/workflows/solaris-build.yml
vendored
Normal file
50
.github/workflows/solaris-build.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Test rsync on Solaris
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/solaris-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/solaris-build.yml'
|
||||
schedule:
|
||||
- cron: '42 8 * * *'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Test rsync on Solaris
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Test in Solaris VM
|
||||
id: test
|
||||
uses: vmactions/solaris-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install bash automake gnu-m4 pkg://solaris/runtime/python-35 autoconf gcc git
|
||||
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: solaris-bin
|
||||
path: |
|
||||
rsync
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
60
.github/workflows/ubuntu-22.04-build.yml
vendored
Normal file
60
.github/workflows/ubuntu-22.04-build.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
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 ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/ubuntu-22.04-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
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
|
||||
56
.github/workflows/ubuntu-build.yml
vendored
Normal file
56
.github/workflows/ubuntu-build.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Test rsync on Ubuntu
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/ubuntu-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/ubuntu-build.yml'
|
||||
schedule:
|
||||
- cron: '42 8 * * *'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Test rsync on Ubuntu
|
||||
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-bin
|
||||
path: |
|
||||
rsync
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -58,3 +58,4 @@ aclocal.m4
|
||||
/auto-build-save
|
||||
.deps
|
||||
/*.exe
|
||||
*.dSYM/
|
||||
|
||||
@@ -104,6 +104,8 @@ like.
|
||||
> sudo apt install -y liblz4-dev
|
||||
> sudo apt install -y libssl-dev
|
||||
|
||||
Or run support/install_deps_ubuntu.sh
|
||||
|
||||
- For CentOS (use EPEL for python3-pip):
|
||||
|
||||
> sudo yum -y install epel-release
|
||||
@@ -230,6 +232,9 @@ not completely implement the "New Sockets" API.
|
||||
[This site][5] says that Apple started to support IPv6 in 10.2 (Jaguar). If
|
||||
your build fails, try again after running configure with `--disable-ipv6`.
|
||||
|
||||
Apple Silicon macs may install packages in a slightly different location and require flags.
|
||||
CFLAGS="-I /opt/homebrew/include" LDFLAGS="-L /opt/homebrew/lib"
|
||||
|
||||
[5]: http://www.ipv6.org/impl/mac.html
|
||||
|
||||
## IBM AIX notes
|
||||
|
||||
43
Makefile.in
43
Makefile.in
@@ -49,20 +49,21 @@ 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/findme.o popt/popt.o popt/poptconfig.o \
|
||||
popt/popthelp.o popt/poptparse.o
|
||||
popt_OBJS= 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 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) wildtest$(EXEEXT)
|
||||
testrun$(EXEEXT) trimslash$(EXEEXT) t_unsafe$(EXEEXT) t_chmod_secure$(EXEEXT) \
|
||||
t_secure_relpath$(EXEEXT) wildtest$(EXEEXT) simdtest$(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 trimslash.o wildtest.o
|
||||
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
|
||||
|
||||
# note that the -I. is needed to handle config.h when using VPATH
|
||||
.c.o:
|
||||
@@ -178,20 +179,20 @@ T_UNSAFE_OBJ = t_unsafe.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/sn
|
||||
t_unsafe$(EXEEXT): $(T_UNSAFE_OBJ)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_UNSAFE_OBJ) $(LIBS)
|
||||
|
||||
T_CHMOD_SECURE_OBJ = t_chmod_secure.o syscall.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 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
|
||||
|
||||
.PHONY: gen
|
||||
gen: conf proto.h man git-version.h
|
||||
|
||||
.PHONY: gensend
|
||||
gensend: gen
|
||||
if ! diff git-version.h $(srcdir)/gists/rsync-git-version.h >/dev/null; then \
|
||||
./rsync -ai git-version.h $(srcdir)/gists/rsync-git-version.h && \
|
||||
(cd $(srcdir)/gists && git commit --allow-empty-message -m '' rsync-git-version.h && git push) ; \
|
||||
fi
|
||||
rsync -aic $(GENFILES) git-version.h $${SAMBA_HOST-samba.org}:/home/ftp/pub/rsync/generated-files/ || true
|
||||
|
||||
aclocal.m4: $(srcdir)/m4/*.m4
|
||||
aclocal -I $(srcdir)/m4
|
||||
|
||||
@@ -320,20 +321,28 @@ test: check
|
||||
|
||||
.PHONY: check
|
||||
check: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
|
||||
rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh
|
||||
$(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT)
|
||||
|
||||
.PHONY: check29
|
||||
check29: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
|
||||
rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh --protocol=29
|
||||
$(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT) --protocol=29
|
||||
|
||||
.PHONY: check30
|
||||
check30: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
|
||||
rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh --protocol=30
|
||||
$(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT) --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
|
||||
|
||||
@@ -349,7 +358,7 @@ testsuite/xattrs-hlink.test:
|
||||
|
||||
.PHONY: installcheck
|
||||
installcheck: $(CHECK_PROGS) $(CHECK_SYMLINKS)
|
||||
POSIXLY_CORRECT=1 TOOLDIR=`pwd` rsync_bin="$(bindir)/rsync$(EXEEXT)" srcdir="$(srcdir)" $(srcdir)/runtests.sh
|
||||
$(srcdir)/runtests.py --rsync-bin="$(bindir)/rsync$(EXEEXT)" --srcdir="$(srcdir)" --tooldir=`pwd`
|
||||
|
||||
# TODO: Add 'dist' target; need to know which files will be included
|
||||
|
||||
@@ -366,4 +375,4 @@ doxygen:
|
||||
.PHONY: doxygen-upload
|
||||
doxygen-upload:
|
||||
rsync -avzv $(srcdir)/dox/html/ --delete \
|
||||
$${SAMBA_HOST-samba.org}:/home/httpd/html/rsync/doxygen/head/
|
||||
$${RSYNC_SAMBA_HOST-samba.org}:/home/httpd/html/rsync/doxygen/head/
|
||||
|
||||
411
NEWS.md
411
NEWS.md
@@ -1,3 +1,410 @@
|
||||
# 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.
|
||||
|
||||
For more details on the vulnerabilities please see the CERT report
|
||||
https://kb.cert.org/vuls/id/952657
|
||||
|
||||
## Changes in this version:
|
||||
|
||||
### PROTOCOL NUMBER:
|
||||
|
||||
- The protocol number was changed to 32 to make it easier for
|
||||
administrators to check their servers have been updated
|
||||
|
||||
### SECURITY FIXES:
|
||||
|
||||
Many thanks to Simon Scannell, Pedro Gallegos, and Jasiel Spelman at
|
||||
Google Cloud Vulnerability Research and Aleksei Gorban (Loqpa) for
|
||||
discovering these vulnerabilities and working with the rsync project
|
||||
to develop and test fixes.
|
||||
|
||||
- CVE-2024-12084 - Heap Buffer Overflow in Checksum Parsing.
|
||||
|
||||
- CVE-2024-12085 - Info Leak via uninitialized Stack contents defeats ASLR.
|
||||
|
||||
- CVE-2024-12086 - Server leaks arbitrary client files.
|
||||
|
||||
- CVE-2024-12087 - Server can make client write files outside of destination directory using symbolic links.
|
||||
|
||||
- CVE-2024-12088 - --safe-links Bypass.
|
||||
|
||||
- CVE-2024-12747 - symlink race condition.
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
- Fixed the included popt to avoid a memory error on modern gcc versions.
|
||||
|
||||
- Fixed an incorrect extern variable's type that caused an ACL issue on macOS.
|
||||
|
||||
- Fixed IPv6 configure check
|
||||
|
||||
### INTERNAL:
|
||||
|
||||
- Updated included popt to version 1.19.
|
||||
|
||||
### DEVELOPER RELATED:
|
||||
|
||||
- Various improvements to the release scripts and git setup.
|
||||
|
||||
- Improved packaging/var-checker to identify variable type issues.
|
||||
|
||||
- added FreeBSD and Solaris CI builds
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
# NEWS for rsync 3.3.0 (6 Apr 2024)
|
||||
|
||||
## Changes in this version:
|
||||
@@ -4762,6 +5169,10 @@
|
||||
|
||||
| RELEASE DATE | VER. | DATE OF COMMIT\* | PROTOCOL |
|
||||
|--------------|--------|------------------|-------------|
|
||||
| 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 |
|
||||
| 06 Apr 2024 | 3.3.0 | | 31 |
|
||||
| 20 Oct 2022 | 3.2.7 | | 31 |
|
||||
| 09 Sep 2022 | 3.2.6 | | 31 |
|
||||
|
||||
12
README.md
12
README.md
@@ -34,7 +34,7 @@ If you need to build rsync yourself, check out the [INSTALL][1] page for
|
||||
information on what libraries and packages you can use to get the maximum
|
||||
features in your build.
|
||||
|
||||
[1]: https://github.com/WayneD/rsync/blob/master/INSTALL.md
|
||||
[1]: https://github.com/RsyncProject/rsync/blob/master/INSTALL.md
|
||||
|
||||
SETUP
|
||||
-----
|
||||
@@ -112,6 +112,7 @@ page of the web site.
|
||||
|
||||
Alternately, email your bug report to <rsync@lists.samba.org>.
|
||||
|
||||
For security issues please email details of the issue to <rsync.project@gmail.com>.
|
||||
|
||||
GIT REPOSITORY
|
||||
--------------
|
||||
@@ -120,7 +121,7 @@ If you want to get the very latest version of rsync direct from the
|
||||
source code repository, then you will need to use git. The git repo
|
||||
is hosted [on GitHub][6] and [on Samba's site][7].
|
||||
|
||||
[6]: https://github.com/WayneD/rsync
|
||||
[6]: https://github.com/RsyncProject/rsync
|
||||
[7]: https://git.samba.org/?p=rsync.git;a=summary
|
||||
|
||||
See [the download page][8] for full details on all the ways to grab the
|
||||
@@ -132,13 +133,12 @@ source.
|
||||
COPYRIGHT
|
||||
---------
|
||||
|
||||
Rsync was originally written by Andrew Tridgell and is currently
|
||||
maintained by Wayne Davison. It has been improved by many developers
|
||||
from around the world.
|
||||
Rsync was originally written by Andrew Tridgell and Paul Mackerras. Many
|
||||
people from around the world have helped to maintain and improve it.
|
||||
|
||||
Rsync may be used, modified and redistributed only under the terms of
|
||||
the GNU General Public License, found in the file [COPYING][9] in this
|
||||
distribution, or at [the Free Software Foundation][10].
|
||||
|
||||
[9]: https://github.com/WayneD/rsync/blob/master/COPYING
|
||||
[9]: https://github.com/RsyncProject/rsync/blob/master/COPYING
|
||||
[10]: https://www.fsf.org/licenses/gpl.html
|
||||
|
||||
@@ -9,4 +9,5 @@ help backporting fixes into an older release, feel free to ask.
|
||||
|
||||
Email your vulnerability information to rsync's maintainer:
|
||||
|
||||
Wayne Davison <wayne@opencoder.net>
|
||||
Rsync Project <rsync.project@gmail.com>
|
||||
|
||||
|
||||
2
access.c
2
access.c
@@ -99,7 +99,7 @@ static void make_mask(char *mask, int plen, int addrlen)
|
||||
return;
|
||||
}
|
||||
|
||||
static int match_address(const char *addr, const char *tok)
|
||||
static int match_address(const char *addr, char *tok)
|
||||
{
|
||||
char *p;
|
||||
struct addrinfo hints, *resa, *rest;
|
||||
|
||||
9
acls.c
9
acls.c
@@ -28,7 +28,7 @@ extern int dry_run;
|
||||
extern int am_root;
|
||||
extern int read_only;
|
||||
extern int list_only;
|
||||
extern int orig_umask;
|
||||
extern mode_t orig_umask;
|
||||
extern int numeric_ids;
|
||||
extern int inc_recurse;
|
||||
extern int preserve_devices;
|
||||
@@ -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(f);
|
||||
int i, count = read_varint_bounded(f, 0, MAX_WIRE_ACL_COUNT, "ACL count");
|
||||
|
||||
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 && am_root && !numeric_ids)
|
||||
if (inc_recurse && !numeric_ids)
|
||||
id = match_uid(id);
|
||||
} else {
|
||||
if (inc_recurse && (!am_root || !numeric_ids))
|
||||
@@ -765,6 +765,7 @@ static int recv_rsync_acl(int f, item_list *racl_list, SMB_ACL_TYPE_T type, mode
|
||||
/* If we received a superfluous mask, throw it away. */
|
||||
duo_item->racl.mask_obj = NO_ENTRY;
|
||||
(void)mode;
|
||||
(void)computed_mask_bits;
|
||||
#else
|
||||
if (duo_item->racl.names.count && duo_item->racl.mask_obj == NO_ENTRY) {
|
||||
/* Mask must be non-empty with lists. */
|
||||
@@ -981,7 +982,7 @@ static int set_rsync_acl(const char *fname, acl_duo *duo_item,
|
||||
&& !pack_smb_acl(&duo_item->sacl, &duo_item->racl))
|
||||
return -1;
|
||||
#ifdef HAVE_OSX_ACLS
|
||||
mode = 0; /* eliminate compiler warning */
|
||||
(void)mode; /* eliminate compiler warning */
|
||||
#else
|
||||
if (type == SMB_ACL_TYPE_ACCESS) {
|
||||
cur_mode = change_sacl_perms(duo_item->sacl, &duo_item->racl, cur_mode, mode);
|
||||
|
||||
14
backup.c
14
backup.c
@@ -39,7 +39,7 @@ static int validate_backup_dir(void)
|
||||
{
|
||||
STRUCT_STAT st;
|
||||
|
||||
if (do_lstat(backup_dir_buf, &st) < 0) {
|
||||
if (do_lstat_at(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(backup_dir_buf, ACCESSPERMS) < 0) {
|
||||
while (do_mkdir_at(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(from, to) == 0) {
|
||||
if (do_link_at(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(from, to) == 0) {
|
||||
if (do_rename_at(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(buf, &bakst) == 0) {
|
||||
if (do_lstat_at(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(buf, file->mode, sx.st.st_rdev) < 0)
|
||||
if (do_mknod_at(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(sl, buf) < 0)
|
||||
if (do_symlink_at(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);
|
||||
|
||||
2
batch.c
2
batch.c
@@ -75,7 +75,7 @@ static int *flag_ptr[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static char *flag_name[] = {
|
||||
static const char *const flag_name[] = {
|
||||
"--recurse (-r)",
|
||||
"--owner (-o)",
|
||||
"--group (-g)",
|
||||
|
||||
@@ -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 {
|
||||
char *cp = checksum_choice ? strchr(checksum_choice, ',') : NULL;
|
||||
const 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,9 +366,8 @@ void get_checksum2(char *buf, int32 len, char *sum)
|
||||
|
||||
mdfour_begin(&m);
|
||||
|
||||
if (len > len1) {
|
||||
if (buf1)
|
||||
free(buf1);
|
||||
if (len > len1 || !buf1) {
|
||||
free(buf1);
|
||||
buf1 = new_array(char, len+4);
|
||||
len1 = len;
|
||||
}
|
||||
@@ -406,7 +405,7 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum)
|
||||
int32 remainder;
|
||||
int fd;
|
||||
|
||||
fd = do_open(fname, O_RDONLY, 0);
|
||||
fd = do_open_checklinks(fname);
|
||||
if (fd == -1) {
|
||||
memset(sum, 0, file_sum_len);
|
||||
return;
|
||||
|
||||
@@ -198,7 +198,7 @@ NORETURN void _exit_cleanup(int code, const char *file, int line)
|
||||
switch_step++;
|
||||
|
||||
if (cleanup_fname)
|
||||
do_unlink(cleanup_fname);
|
||||
do_unlink_at(cleanup_fname);
|
||||
if (exit_code)
|
||||
kill_all(SIGUSR1);
|
||||
if (cleanup_pid && cleanup_pid == getpid()) {
|
||||
|
||||
@@ -167,7 +167,7 @@ int read_proxy_protocol_header(int fd)
|
||||
char sig[PROXY_V2_SIG_SIZE];
|
||||
char ver_cmd;
|
||||
char fam;
|
||||
char len[2];
|
||||
unsigned char len[2];
|
||||
union {
|
||||
struct {
|
||||
char src_addr[4];
|
||||
|
||||
@@ -30,6 +30,7 @@ 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;
|
||||
@@ -38,6 +39,7 @@ 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;
|
||||
@@ -976,11 +978,14 @@ 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;
|
||||
}
|
||||
|
||||
@@ -1003,6 +1008,15 @@ 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])) {
|
||||
@@ -1298,13 +1312,49 @@ 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;
|
||||
|
||||
5
compat.c
5
compat.c
@@ -52,6 +52,7 @@ 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;
|
||||
@@ -131,7 +132,7 @@ static const char *client_info;
|
||||
* of that protocol for it to be advertised as available. */
|
||||
static void check_sub_protocol(void)
|
||||
{
|
||||
char *dot;
|
||||
const char *dot;
|
||||
int their_protocol, their_sub;
|
||||
int our_sub = get_subprotocol_version();
|
||||
|
||||
@@ -414,7 +415,7 @@ static const char *getenv_nstr(int ntype)
|
||||
env_str = ntype == NSTR_COMPRESS ? "zlib" : protocol_version >= 30 ? "md5" : "md4";
|
||||
|
||||
if (am_server && env_str) {
|
||||
char *cp = strchr(env_str, '&');
|
||||
const char *cp = strchr(env_str, '&');
|
||||
if (cp)
|
||||
env_str = cp + 1;
|
||||
}
|
||||
|
||||
50
configure.ac
50
configure.ac
@@ -392,7 +392,7 @@ AS_HELP_STRING([--disable-ipv6],[disable to omit ipv6 support]),
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
main()
|
||||
int main()
|
||||
{
|
||||
if (socket(AF_INET6, SOCK_STREAM, 0) < 0)
|
||||
exit(1);
|
||||
@@ -424,6 +424,26 @@ case $host_os in
|
||||
* ) AC_MSG_RESULT(no);;
|
||||
esac
|
||||
|
||||
# We default to using our zlib unless --with-included-zlib=no is given.
|
||||
if test x"$with_included_zlib" != x"no"; then
|
||||
with_included_zlib=yes
|
||||
elif test x"$ac_cv_header_zlib_h" != x"yes"; then
|
||||
with_included_zlib=yes
|
||||
fi
|
||||
if test x"$with_included_zlib" != x"yes"; then
|
||||
AC_CHECK_LIB(z, deflateParams, , [with_included_zlib=yes])
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING([whether to use included zlib])
|
||||
if test x"$with_included_zlib" = x"yes"; then
|
||||
AC_MSG_RESULT($srcdir/zlib)
|
||||
BUILD_ZLIB='$(zlib_OBJS)'
|
||||
CFLAGS="-I$srcdir/zlib $CFLAGS"
|
||||
else
|
||||
AC_DEFINE(EXTERNAL_ZLIB, 1, [Define to 1 if using external zlib])
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING([whether to enable use of openssl crypto library])
|
||||
AC_ARG_ENABLE([openssl],
|
||||
AS_HELP_STRING([--disable-openssl],[disable to omit openssl crypto library]))
|
||||
@@ -573,7 +593,7 @@ if test x"$no_lib" != x; then
|
||||
echo ""
|
||||
echo "See the INSTALL file for hints on how to install the missing libraries and/or"
|
||||
echo "how to generate (or fetch) manpages:"
|
||||
echo " https://github.com/WayneD/rsync/blob/master/INSTALL.md"
|
||||
echo " https://github.com/RsyncProject/rsync/blob/master/INSTALL.md"
|
||||
echo ""
|
||||
echo "To disable one or more features, the relevant configure options are:"
|
||||
for lib in $no_lib; do
|
||||
@@ -870,7 +890,7 @@ 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 \
|
||||
memmove lchown vsnprintf snprintf vasprintf asprintf setsid strpbrk \
|
||||
strlcat strlcpy strtol mallinfo mallinfo2 getgroups setgroups geteuid getegid \
|
||||
strlcat strlcpy stpcpy strtol mallinfo mallinfo2 getgroups setgroups geteuid getegid \
|
||||
setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
|
||||
seteuid strerror putenv iconv_open locale_charset nl_langinfo getxattr \
|
||||
extattr_get_link sigaction sigprocmask setattrlist getgrouplist \
|
||||
@@ -1084,6 +1104,8 @@ if test x"$with_included_popt" = x"yes"; then
|
||||
AC_MSG_RESULT($srcdir/popt)
|
||||
BUILD_POPT='$(popt_OBJS)'
|
||||
CFLAGS="-I$srcdir/popt $CFLAGS"
|
||||
AC_DEFINE(POPT_SYSCONFDIR, "/etc", [sysconfig dir for popt])
|
||||
AC_DEFINE(PACKAGE, "rsync", [package name for rsync])
|
||||
if test x"$ALLOCA" != x
|
||||
then
|
||||
# this can be removed when/if we add an included alloca.c;
|
||||
@@ -1094,26 +1116,6 @@ else
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
|
||||
# We default to using our zlib unless --with-included-zlib=no is given.
|
||||
if test x"$with_included_zlib" != x"no"; then
|
||||
with_included_zlib=yes
|
||||
elif test x"$ac_cv_header_zlib_h" != x"yes"; then
|
||||
with_included_zlib=yes
|
||||
fi
|
||||
if test x"$with_included_zlib" != x"yes"; then
|
||||
AC_CHECK_LIB(z, deflateParams, , [with_included_zlib=yes])
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING([whether to use included zlib])
|
||||
if test x"$with_included_zlib" = x"yes"; then
|
||||
AC_MSG_RESULT($srcdir/zlib)
|
||||
BUILD_ZLIB='$(zlib_OBJS)'
|
||||
CFLAGS="-I$srcdir/zlib $CFLAGS"
|
||||
else
|
||||
AC_DEFINE(EXTERNAL_ZLIB, 1, [Define to 1 if using external zlib])
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
|
||||
AC_CACHE_CHECK([for unsigned char],rsync_cv_SIGNED_CHAR_OK,[
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[signed char *s = (signed char *)""]])],[rsync_cv_SIGNED_CHAR_OK=yes],[rsync_cv_SIGNED_CHAR_OK=no])])
|
||||
if test x"$rsync_cv_SIGNED_CHAR_OK" = x"yes"; then
|
||||
@@ -1390,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_CHECK_LIB(attr,getxattr)
|
||||
AC_SEARCH_LIBS(getxattr,attr)
|
||||
;;
|
||||
darwin*)
|
||||
AC_MSG_RESULT(Using OS X xattrs)
|
||||
|
||||
@@ -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 struct parm_struct parm_table[] = {"
|
||||
parms = "\nstatic const struct parm_struct parm_table[] = {"
|
||||
comment_fmt = "\n/********** %s **********/\n"
|
||||
tdstruct = "typedef struct {"
|
||||
}
|
||||
|
||||
6
delete.c
6
delete.c
@@ -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(fname, fp->mode | S_IWUSR);
|
||||
do_chmod_at(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(fbuf, mode | S_IWUSR);
|
||||
do_chmod_at(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(fbuf) == 0;
|
||||
ok = do_rmdir_at(fbuf) == 0;
|
||||
} else {
|
||||
if (make_backups > 0 && !(flags & DEL_FOR_BACKUP) && (backup_dir || !is_backup_file(fbuf))) {
|
||||
what = "make_backup";
|
||||
|
||||
@@ -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;
|
||||
char *p, *pattern = ex->pattern;
|
||||
const char *p, *pattern = ex->pattern;
|
||||
const char *strings[16]; /* more than enough */
|
||||
const char *name = fname + (*fname == '/');
|
||||
|
||||
|
||||
36
flist.c
36
flist.c
@@ -840,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(f);
|
||||
(void)read_varint_bounded(f, 0, MAX_WIRE_NSEC, "modtime_nsec");
|
||||
#else
|
||||
modtime_nsec = read_varint(f);
|
||||
modtime_nsec = read_varint_bounded(f, 0, MAX_WIRE_NSEC, "modtime_nsec");
|
||||
else
|
||||
modtime_nsec = 0;
|
||||
#endif
|
||||
@@ -861,8 +861,19 @@ 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. */
|
||||
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
|
||||
@@ -1390,7 +1401,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
|
||||
|
||||
if (copy_devices && am_sender && IS_DEVICE(st.st_mode)) {
|
||||
if (st.st_size == 0) {
|
||||
int fd = do_open(fname, O_RDONLY, 0);
|
||||
int fd = do_open_checklinks(fname);
|
||||
if (fd >= 0) {
|
||||
st.st_size = get_device_size(fd, fname);
|
||||
close(fd);
|
||||
@@ -2584,6 +2595,19 @@ struct file_list *recv_file_list(int f, int dir_ndx)
|
||||
init_hard_links();
|
||||
#endif
|
||||
|
||||
if (inc_recurse && dir_ndx >= 0) {
|
||||
if (dir_ndx >= dir_flist->used) {
|
||||
rprintf(FERROR_XFER, "rsync: refusing invalid dir_ndx %u >= %u\n", dir_ndx, dir_flist->used);
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
struct file_struct *file = dir_flist->files[dir_ndx];
|
||||
if (file->flags & FLAG_GOT_DIR_FLIST) {
|
||||
rprintf(FERROR_XFER, "rsync: refusing malicious duplicate flist for dir %d\n", dir_ndx);
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
file->flags |= FLAG_GOT_DIR_FLIST;
|
||||
}
|
||||
|
||||
flist = flist_new(0, "recv_file_list");
|
||||
flist_expand(flist, FLIST_START_LARGE);
|
||||
|
||||
@@ -3154,8 +3178,8 @@ static void output_flist(struct file_list *flist)
|
||||
} else
|
||||
*uidbuf = '\0';
|
||||
if (gid_ndx) {
|
||||
static char parens[] = "(\0)\0\0\0";
|
||||
char *pp = parens + (file->flags & FLAG_SKIP_GROUP ? 0 : 3);
|
||||
static const char parens[] = "(\0)\0\0\0";
|
||||
const 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
|
||||
|
||||
56
generator.c
56
generator.c
@@ -229,11 +229,13 @@ static int read_delay_line(char *buf, int *flags_p)
|
||||
*flags_p = 0;
|
||||
|
||||
if (sscanf(bp, "%x ", &mode) != 1) {
|
||||
invalid_data:
|
||||
rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n");
|
||||
return -1;
|
||||
goto invalid_data;
|
||||
}
|
||||
past_space = strchr(bp, ' ') + 1;
|
||||
past_space = strchr(bp, ' ');
|
||||
if (!past_space) {
|
||||
goto invalid_data;
|
||||
}
|
||||
past_space++;
|
||||
len = j - read_pos - (past_space - bp) + 1; /* count the '\0' */
|
||||
read_pos = j + 1;
|
||||
|
||||
@@ -247,6 +249,10 @@ 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)
|
||||
@@ -984,7 +990,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(fname) < 0 && errno != ENOENT)
|
||||
if (do_unlink_at(fname) < 0 && errno != ENOENT)
|
||||
goto got_nothing_for_ya;
|
||||
}
|
||||
#ifdef SUPPORT_HARD_LINKS
|
||||
@@ -1112,7 +1118,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(cmpbuf, fname) < 0) {
|
||||
if (do_link_at(cmpbuf, fname) < 0) {
|
||||
rsyserr(FERROR_XFER, errno,
|
||||
"failed to hard-link %s with %s",
|
||||
cmpbuf, fname);
|
||||
@@ -1315,7 +1321,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
|
||||
}
|
||||
}
|
||||
if (relative_paths && !implied_dirs && file->mode != 0
|
||||
&& do_stat(dn, &sx.st) < 0) {
|
||||
&& do_stat_at(dn, &sx.st) < 0) {
|
||||
if (dry_run)
|
||||
goto parent_is_dry_missing;
|
||||
if (make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
|
||||
@@ -1427,7 +1433,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(fname, (file->mode|added_perms) & 0700) == 0)
|
||||
if (do_mkdir_at(fname, (file->mode|added_perms) & 0700) == 0)
|
||||
file->flags |= FLAG_DIR_CREATED;
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -1469,10 +1475,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(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
|
||||
if (real_ret != 0 && do_mkdir_at(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(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
|
||||
|| (do_mkdir_at(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
|
||||
rsyserr(FERROR_XFER, errno,
|
||||
"recv_generator: mkdir %s failed",
|
||||
full_fname(fname));
|
||||
@@ -1499,7 +1505,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(fname, mode) < 0) {
|
||||
if (do_chmod_at(fname, mode) < 0) {
|
||||
rsyserr(FERROR_XFER, errno,
|
||||
"failed to modify permissions on %s",
|
||||
full_fname(fname));
|
||||
@@ -1798,7 +1804,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
|
||||
|
||||
if (write_devices && IS_DEVICE(sx.st.st_mode) && sx.st.st_size == 0) {
|
||||
/* This early open into fd skips the regular open below. */
|
||||
if ((fd = do_open(fnamecmp, O_RDONLY, 0)) >= 0)
|
||||
if ((fd = do_open_nofollow(fnamecmp, O_RDONLY)) >= 0)
|
||||
real_sx.st.st_size = sx.st.st_size = get_device_size(fd, fnamecmp);
|
||||
}
|
||||
|
||||
@@ -1808,7 +1814,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(partialptr);
|
||||
do_unlink_at(partialptr);
|
||||
handle_partial_dir(partialptr, PDIR_DELETE);
|
||||
}
|
||||
set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT | maybe_ATTRS_ACCURATE_TIME);
|
||||
@@ -1867,7 +1873,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
|
||||
}
|
||||
|
||||
/* open the file */
|
||||
if (fd < 0 && (fd = do_open(fnamecmp, O_RDONLY, 0)) < 0) {
|
||||
if (fd < 0 && (fd = do_open_checklinks(fnamecmp)) < 0) {
|
||||
rsyserr(FERROR, errno, "failed to open %s, continuing",
|
||||
full_fname(fnamecmp));
|
||||
pretend_missing:
|
||||
@@ -1896,7 +1902,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
|
||||
back_file = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
if ((f_copy = do_open(backupptr, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) {
|
||||
if ((f_copy = do_open_at(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;
|
||||
@@ -2016,7 +2022,7 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
|
||||
|
||||
if (slnk) {
|
||||
#ifdef SUPPORT_LINKS
|
||||
if (do_symlink(slnk, create_name) < 0) {
|
||||
if (do_symlink_at(slnk, create_name) < 0) {
|
||||
rsyserr(FERROR_XFER, errno, "symlink %s -> \"%s\" failed",
|
||||
full_fname(create_name), slnk);
|
||||
return 0;
|
||||
@@ -2032,7 +2038,7 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
|
||||
return 0;
|
||||
#endif
|
||||
} else {
|
||||
if (do_mknod(create_name, file->mode, rdev) < 0) {
|
||||
if (do_mknod_at(create_name, file->mode, rdev) < 0) {
|
||||
rsyserr(FERROR_XFER, errno, "mknod %s failed",
|
||||
full_fname(create_name));
|
||||
return 0;
|
||||
@@ -2040,10 +2046,14 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
|
||||
}
|
||||
|
||||
if (!skip_atomic) {
|
||||
if (do_rename(tmpname, fname) < 0) {
|
||||
if (do_rename_at(tmpname, fname) < 0) {
|
||||
char *full_tmpname = strdup(full_fname(tmpname));
|
||||
if (full_tmpname == NULL)
|
||||
out_of_memory("atomic_create");
|
||||
rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\" failed",
|
||||
full_fname(tmpname), full_fname(fname));
|
||||
do_unlink(tmpname);
|
||||
full_tmpname, full_fname(fname));
|
||||
free(full_tmpname);
|
||||
do_unlink_at(tmpname);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -2107,7 +2117,7 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
|
||||
continue;
|
||||
fname = f_name(file, NULL);
|
||||
if (fix_dir_perms)
|
||||
do_chmod(fname, file->mode);
|
||||
do_chmod_at(fname, file->mode);
|
||||
if (need_retouch_dir_times) {
|
||||
STRUCT_STAT st;
|
||||
if (link_stat(fname, &st, 0) == 0 && mtime_differs(&st, file)) {
|
||||
@@ -2142,6 +2152,8 @@ 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)
|
||||
@@ -2170,6 +2182,8 @@ 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)
|
||||
|
||||
4
hlink.c
4
hlink.c
@@ -117,7 +117,7 @@ static void match_gnums(int32 *ndx_list, int ndx_count)
|
||||
struct ht_int32_node *node = NULL;
|
||||
int32 gnum, gnum_next;
|
||||
|
||||
qsort(ndx_list, ndx_count, sizeof ndx_list[0], (int (*)()) hlink_compare_gnum);
|
||||
qsort(ndx_list, ndx_count, sizeof ndx_list[0], (int (*)(const void*, const void*))hlink_compare_gnum);
|
||||
|
||||
for (from = 0; from < ndx_count; from++) {
|
||||
file = hlink_flist->sorted[ndx_list[from]];
|
||||
@@ -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(oldname, fname) < 0) {
|
||||
if (do_link_at(oldname, fname) < 0) {
|
||||
enum logcode code;
|
||||
if (terse) {
|
||||
if (!INFO_GTE(NAME, 1))
|
||||
|
||||
66
io.c
66
io.c
@@ -55,6 +55,7 @@ extern int read_batch;
|
||||
extern int compat_flags;
|
||||
extern int protect_args;
|
||||
extern int checksum_seed;
|
||||
extern int xfer_sum_len;
|
||||
extern int daemon_connection;
|
||||
extern int protocol_version;
|
||||
extern int remove_source_files;
|
||||
@@ -116,7 +117,7 @@ static int active_filecnt = 0;
|
||||
static OFF_T active_bytecnt = 0;
|
||||
static int first_message = 1;
|
||||
|
||||
static char int_byte_extra[64] = {
|
||||
static const 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 */
|
||||
@@ -1089,6 +1090,9 @@ 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]);
|
||||
@@ -1157,8 +1161,8 @@ void set_io_timeout(int secs)
|
||||
|
||||
static void check_for_d_option_error(const char *msg)
|
||||
{
|
||||
static char rsync263_opts[] = "BCDHIKLPRSTWabceghlnopqrtuvxz";
|
||||
char *colon;
|
||||
static const char rsync263_opts[] = "BCDHIKLPRSTWabceghlnopqrtuvxz";
|
||||
const char *colon;
|
||||
int saw_d = 0;
|
||||
|
||||
if (*msg != 'r'
|
||||
@@ -1864,6 +1868,45 @@ 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
|
||||
@@ -1970,6 +2013,21 @@ 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",
|
||||
@@ -1977,7 +2035,7 @@ void read_sum_head(int f, struct sum_struct *sum)
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
sum->s2length = protocol_version < 27 ? csum_length : (int)read_int(f);
|
||||
if (sum->s2length < 0 || sum->s2length > MAX_DIGEST_LEN) {
|
||||
if (sum->s2length < 0 || sum->s2length > xfer_sum_len) {
|
||||
rprintf(FERROR, "Invalid checksum length %d [%s]\n",
|
||||
sum->s2length, who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define LATEST_YEAR "2024"
|
||||
#define LATEST_YEAR "2026"
|
||||
|
||||
@@ -197,7 +197,7 @@ void md5_update(md_context *ctx, const uchar *input, uint32 length)
|
||||
memcpy(ctx->buffer + left, input, length);
|
||||
}
|
||||
|
||||
static uchar md5_padding[CSUM_CHUNK] = { 0x80 };
|
||||
static const uchar md5_padding[CSUM_CHUNK] = { 0x80 };
|
||||
|
||||
void md5_result(md_context *ctx, uchar digest[MD5_DIGEST_LEN])
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ struct alloc_pool
|
||||
size_t size; /* extent size */
|
||||
size_t quantum; /* allocation quantum */
|
||||
struct pool_extent *extents; /* top extent is "live" */
|
||||
void (*bomb)(); /* called if malloc fails */
|
||||
void (*bomb)(const char*, const char*, int); /* called if malloc fails */
|
||||
int flags;
|
||||
|
||||
/* statistical data */
|
||||
@@ -42,6 +42,7 @@ struct align_test {
|
||||
/* Temporarily cast a void* var into a char* var when adding an offset (to
|
||||
* keep some compilers from complaining about the pointer arithmetic). */
|
||||
#define PTR_ADD(b,o) ( (void*) ((char*)(b) + (o)) )
|
||||
#define PTR_SUB(b,o) ( (void*) ((char*)(b) - (o)) )
|
||||
|
||||
alloc_pool_t
|
||||
pool_create(size_t size, size_t quantum, void (*bomb)(const char*, const char*, int), int flags)
|
||||
@@ -100,7 +101,7 @@ pool_destroy(alloc_pool_t p)
|
||||
for (cur = pool->extents; cur; cur = next) {
|
||||
next = cur->next;
|
||||
if (pool->flags & POOL_PREPEND)
|
||||
free(PTR_ADD(cur->start, -sizeof (struct pool_extent)));
|
||||
free(PTR_SUB(cur->start, sizeof (struct pool_extent)));
|
||||
else {
|
||||
free(cur->start);
|
||||
free(cur);
|
||||
@@ -235,7 +236,7 @@ pool_free(alloc_pool_t p, size_t len, void *addr)
|
||||
if (cur->free + cur->bound >= pool->size) {
|
||||
prev->next = cur->next;
|
||||
if (pool->flags & POOL_PREPEND)
|
||||
free(PTR_ADD(cur->start, -sizeof (struct pool_extent)));
|
||||
free(PTR_SUB(cur->start, sizeof (struct pool_extent)));
|
||||
else {
|
||||
free(cur->start);
|
||||
free(cur);
|
||||
@@ -292,7 +293,7 @@ pool_free_old(alloc_pool_t p, void *addr)
|
||||
while ((cur = next) != NULL) {
|
||||
next = cur->next;
|
||||
if (pool->flags & POOL_PREPEND)
|
||||
free(PTR_ADD(cur->start, -sizeof (struct pool_extent)));
|
||||
free(PTR_SUB(cur->start, sizeof (struct pool_extent)));
|
||||
else {
|
||||
free(cur->start);
|
||||
free(cur);
|
||||
|
||||
@@ -126,9 +126,18 @@ 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_t)len > size)
|
||||
if (len <= 0 || size == 0)
|
||||
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
|
||||
@@ -136,7 +145,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 kernel bugs happen! */
|
||||
/* Should be impossible, but bugs happen! */
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ typedef enum {
|
||||
|
||||
struct enum_list {
|
||||
int value;
|
||||
char *name;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
struct parm_struct {
|
||||
@@ -73,7 +73,7 @@ struct parm_struct {
|
||||
parm_type type;
|
||||
parm_class class;
|
||||
void *ptr;
|
||||
struct enum_list *enum_list;
|
||||
const 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 struct enum_list enum_syslog_facility[] = {
|
||||
static const 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)) {
|
||||
char *percent = strchr(f+1, '%');
|
||||
const char *percent = strchr(f+1, '%');
|
||||
if (percent && percent - f < bufsize) {
|
||||
char *val;
|
||||
strlcpy(t, f+1, percent - f);
|
||||
|
||||
12
log.c
12
log.c
@@ -456,11 +456,17 @@ 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());
|
||||
|
||||
va_start(ap, format);
|
||||
len += vsnprintf(buf + len, sizeof buf - len, format, ap);
|
||||
va_end(ap);
|
||||
if (len < sizeof buf) {
|
||||
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,
|
||||
|
||||
31
main.c
31
main.c
@@ -66,7 +66,7 @@ extern int protect_args;
|
||||
extern int relative_paths;
|
||||
extern int sanitize_paths;
|
||||
extern int curr_dir_depth;
|
||||
extern int curr_dir_len;
|
||||
extern unsigned int curr_dir_len;
|
||||
extern int module_id;
|
||||
extern int rsync_port;
|
||||
extern int whole_file;
|
||||
@@ -239,11 +239,11 @@ void write_del_stats(int f)
|
||||
|
||||
void read_del_stats(int f)
|
||||
{
|
||||
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);
|
||||
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");
|
||||
}
|
||||
|
||||
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 *labels[] = { "reg", "dir", "link", "dev", "special" };
|
||||
static char *const labels[] = { "reg", "dir", "link", "dev", "special" };
|
||||
char buf[1024], *pre = " (";
|
||||
int j, len = 0;
|
||||
int total = counts[0];
|
||||
@@ -394,9 +394,18 @@ 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 = ", ";
|
||||
}
|
||||
}
|
||||
@@ -1559,6 +1568,10 @@ 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)) {
|
||||
@@ -1743,7 +1756,9 @@ int main(int argc,char *argv[])
|
||||
our_gid = MY_GID();
|
||||
am_root = our_uid == ROOT_UID;
|
||||
|
||||
unset_env_var("DISPLAY");
|
||||
// DISPLAY should not be emptied unconditionally
|
||||
if (!getenv("SSH_ASKPASS"))
|
||||
unset_env_var("DISPLAY");
|
||||
|
||||
#if defined USE_OPENSSL && defined SET_OPENSSL_CONF
|
||||
#define TO_STR2(x) #x
|
||||
|
||||
11
match.c
11
match.c
@@ -147,6 +147,9 @@ static void hash_search(int f,struct sum_struct *s,
|
||||
int more;
|
||||
schar *map;
|
||||
|
||||
// prevent possible memory leaks
|
||||
memset(sum2, 0, sizeof sum2);
|
||||
|
||||
/* want_i is used to encourage adjacent matches, allowing the RLL
|
||||
* coding of the output to work more efficiently. */
|
||||
want_i = 0;
|
||||
@@ -232,7 +235,7 @@ static void hash_search(int f,struct sum_struct *s,
|
||||
done_csum2 = 1;
|
||||
}
|
||||
|
||||
if (memcmp(sum2,s->sums[i].sum2,s->s2length) != 0) {
|
||||
if (memcmp(sum2, sum2_at(s, i), s->s2length) != 0) {
|
||||
false_alarms++;
|
||||
continue;
|
||||
}
|
||||
@@ -252,7 +255,7 @@ static void hash_search(int f,struct sum_struct *s,
|
||||
if (i != aligned_i) {
|
||||
if (sum != s->sums[aligned_i].sum1
|
||||
|| l != s->sums[aligned_i].len
|
||||
|| memcmp(sum2, s->sums[aligned_i].sum2, s->s2length) != 0)
|
||||
|| memcmp(sum2, sum2_at(s, aligned_i), s->s2length) != 0)
|
||||
goto check_want_i;
|
||||
i = aligned_i;
|
||||
}
|
||||
@@ -271,7 +274,7 @@ static void hash_search(int f,struct sum_struct *s,
|
||||
if (sum != s->sums[i].sum1)
|
||||
goto check_want_i;
|
||||
get_checksum2((char *)map, l, sum2);
|
||||
if (memcmp(sum2, s->sums[i].sum2, s->s2length) != 0)
|
||||
if (memcmp(sum2, sum2_at(s, i), s->s2length) != 0)
|
||||
goto check_want_i;
|
||||
/* OK, we have a re-alignment match. Bump the offset
|
||||
* forward to the new match point. */
|
||||
@@ -290,7 +293,7 @@ static void hash_search(int f,struct sum_struct *s,
|
||||
&& (!updating_basis_file || s->sums[want_i].offset >= offset
|
||||
|| s->sums[want_i].flags & SUMFLG_SAME_OFFSET)
|
||||
&& sum == s->sums[want_i].sum1
|
||||
&& memcmp(sum2, s->sums[want_i].sum2, s->s2length) == 0) {
|
||||
&& memcmp(sum2, sum2_at(s, want_i), s->s2length) == 0) {
|
||||
/* we've found an adjacent match - the RLL coder
|
||||
* will be happy */
|
||||
i = want_i;
|
||||
|
||||
@@ -120,6 +120,7 @@ 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')
|
||||
@@ -247,6 +248,9 @@ 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))
|
||||
@@ -540,6 +544,7 @@ 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)
|
||||
|
||||
30
options.c
30
options.c
@@ -86,6 +86,7 @@ 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;
|
||||
@@ -113,11 +114,20 @@ 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;
|
||||
@@ -225,7 +235,7 @@ char *iconv_opt =
|
||||
|
||||
struct chmod_mode_struct *chmod_modes = NULL;
|
||||
|
||||
static const char *debug_verbosity[] = {
|
||||
static const char *const debug_verbosity[] = {
|
||||
/*0*/ NULL,
|
||||
/*1*/ NULL,
|
||||
/*2*/ "BIND,CMD,CONNECT,DEL,DELTASUM,DUP,FILTER,FLIST,ICONV",
|
||||
@@ -236,7 +246,7 @@ static const char *debug_verbosity[] = {
|
||||
|
||||
#define MAX_VERBOSITY ((int)(sizeof debug_verbosity / sizeof debug_verbosity[0]) - 1)
|
||||
|
||||
static const char *info_verbosity[1+MAX_VERBOSITY] = {
|
||||
static const char *const info_verbosity[1+MAX_VERBOSITY] = {
|
||||
/*0*/ "NONREG",
|
||||
/*1*/ "COPY,DEL,FLIST,MISC,NAME,STATS,SYMSAFE",
|
||||
/*2*/ "BACKUP,MISC2,MOUNT,NAME2,REMOVE,SKIP",
|
||||
@@ -474,7 +484,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 **verbosity = words == info_words ? info_verbosity : debug_verbosity;
|
||||
const char *const*verbosity = words == info_words ? info_verbosity : debug_verbosity;
|
||||
char buf[128], *opt, *fmt = "%-10s %s\n";
|
||||
int j;
|
||||
|
||||
@@ -756,6 +766,8 @@ 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 },
|
||||
@@ -844,7 +856,7 @@ static struct poptOption long_options[] = {
|
||||
{0,0,0,0, 0, 0, 0}
|
||||
};
|
||||
|
||||
static struct poptOption long_daemon_options[] = {
|
||||
static const 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 },
|
||||
@@ -1156,7 +1168,7 @@ static time_t parse_time(const char *arg)
|
||||
{
|
||||
const char *cp;
|
||||
time_t val, now = time(NULL);
|
||||
struct tm t, *today = localtime(&now);
|
||||
struct tm t, tmp, *today = localtime_r(&now, &tmp);
|
||||
int in_date, old_mday, n;
|
||||
|
||||
memset(&t, 0, sizeof t);
|
||||
@@ -1369,6 +1381,10 @@ 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");
|
||||
@@ -2006,6 +2022,8 @@ 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
|
||||
@@ -2563,7 +2581,7 @@ char *safe_arg(const char *opt, const char *arg)
|
||||
if (escape_leading_tilde)
|
||||
*t++ = '\\';
|
||||
while (*f) {
|
||||
if (*f == '\\') {
|
||||
if (*f == '\\') {
|
||||
if (!is_filename_arg || !strchr(WILD_CHARS, f[1]))
|
||||
*t++ = '\\';
|
||||
} else if (strchr(escapes, *f))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
TARGETS := all install install-ssl-daemon install-all install-strip conf gen gensend reconfigure restatus \
|
||||
TARGETS := all install install-ssl-daemon install-all install-strip conf gen reconfigure restatus \
|
||||
proto man clean cleantests distclean test check check29 check30 installcheck splint \
|
||||
doxygen doxygen-upload finddead rrsync
|
||||
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
#!/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(['/bin/zsh'])
|
||||
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
|
||||
@@ -1,6 +1,6 @@
|
||||
Summary: A fast, versatile, remote (and local) file-copying tool
|
||||
Name: rsync
|
||||
Version: 3.3.0
|
||||
Version: 3.4.3
|
||||
%define fullversion %{version}
|
||||
Release: 1
|
||||
%define srcdir src
|
||||
@@ -79,9 +79,5 @@ rm -rf $RPM_BUILD_ROOT
|
||||
%dir /etc/rsync-ssl/certs
|
||||
|
||||
%changelog
|
||||
* Sat Apr 06 2024 Wayne Davison <wayne@opencoder.net>
|
||||
Released 3.3.0.
|
||||
|
||||
* Fri Mar 21 2008 Wayne Davison <wayne@opencoder.net>
|
||||
Added installation of /etc/xinetd.d/rsync file and some commented-out
|
||||
lines that demonstrate how to use the rsync-patches tar file.
|
||||
* Wed May 20 2026 Rsync Project <rsync.project@gmail.com>
|
||||
Released 3.4.3.
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
#!/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
|
||||
@@ -170,17 +170,6 @@ def get_patch_branches(base_branch):
|
||||
return branches
|
||||
|
||||
|
||||
def mandate_gensend_hook():
|
||||
hook = '.git/hooks/pre-push'
|
||||
if not os.path.exists(hook):
|
||||
print('Creating hook file:', hook)
|
||||
cmd_chk(['./rsync', '-a', 'packaging/pre-push', hook])
|
||||
else:
|
||||
ct = cmd_txt(['grep', 'make gensend', hook], discard='output')
|
||||
if ct.rc:
|
||||
die('Please add a "make gensend" into your', hook, 'script.')
|
||||
|
||||
|
||||
# Snag the GENFILES values out of the Makefile file and return them as a list.
|
||||
def get_gen_files(want_dir_plus_list=False):
|
||||
cont_re = re.compile(r'\\\n')
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
cat >/dev/null # Just discard stdin data
|
||||
|
||||
if [[ -f /proc/$PPID/cmdline ]]; then
|
||||
while read -d $'\0' arg ; do
|
||||
if [[ "$arg" == '--tags' ]] ; then
|
||||
exit 0
|
||||
fi
|
||||
done </proc/$PPID/cmdline
|
||||
fi
|
||||
|
||||
branch=`git rev-parse --abbrev-ref HEAD`
|
||||
if [[ "$branch" = master && "$*" == *github* ]]; then
|
||||
make gensend
|
||||
fi
|
||||
@@ -1,399 +0,0 @@
|
||||
#!/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.
|
||||
|
||||
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')
|
||||
|
||||
mandate_gensend_hook()
|
||||
|
||||
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 NEWS.md'.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
|
||||
703
packaging/release.py
Executable file
703
packaging/release.py
Executable file
@@ -0,0 +1,703 @@
|
||||
#!/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
|
||||
124
packaging/samba-rsync
Executable file
124
packaging/samba-rsync
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/bin/bash
|
||||
# This script makes it easy to update the ftp & html directories on the samba.org server.
|
||||
# It expects the 2 *_DEST directories to contain updated files that need to be sent to
|
||||
# the remote server. If these directories don't exist yet, they will be copied from the
|
||||
# remote server (while also making the html dir a git checkout).
|
||||
|
||||
FTP_SRC="$HOME/samba-rsync-ftp"
|
||||
HTML_SRC="$HOME/samba-rsync-html"
|
||||
|
||||
FTP_DEST="/home/ftp/pub/rsync"
|
||||
HTML_DEST="/home/httpd/html/rsync"
|
||||
|
||||
HTML_GIT='git.samba.org:/data/git/rsync-web.git'
|
||||
|
||||
export RSYNC_PARTIAL_DIR=''
|
||||
|
||||
case "$RSYNC_SAMBA_HOST" in
|
||||
*.samba.org) ;;
|
||||
*)
|
||||
echo "You must set RSYNC_SAMBA_HOST in your environment to the samba hostname to use." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
MODE=''
|
||||
REVERSE=''
|
||||
while (( $# )); do
|
||||
case "$1" in
|
||||
-R|--reverse) REVERSE=yes ;;
|
||||
f|ftp) MODE=ftp ;;
|
||||
h|html) MODE=html ;;
|
||||
-h|--help)
|
||||
echo "Usage: [-R] [f|ftp|h|html]"
|
||||
echo "-R --reverse Copy the files from the server to the local host."
|
||||
echo " The default is to update the remote files."
|
||||
echo "-h --help Output this help message."
|
||||
echo " "
|
||||
echo "The script will prompt if ftp or html is not specified on the command line."
|
||||
echo "Only one category can be copied at a time. When pulling html files, a git"
|
||||
echo "checkout will be either created or updated prior to the rsync copy."
|
||||
exit
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
while [ ! "$MODE" ]; do
|
||||
if [ "$REVERSE" = yes ]; then
|
||||
DIRECTION=FROM
|
||||
else
|
||||
DIRECTION=TO
|
||||
fi
|
||||
echo -n "Copy which files $DIRECTION the server? ftp or html? "
|
||||
read ans
|
||||
case "$ans" in
|
||||
f*) MODE=ftp ;;
|
||||
h*) MODE=html ;;
|
||||
'') exit 1 ;;
|
||||
*) echo "You must answer f or h to copy the ftp or html data." ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$MODE" = ftp ]; then
|
||||
SRC_DIR="$FTP_SRC"
|
||||
DEST_DIR="$FTP_DEST"
|
||||
FILT=".filt"
|
||||
else
|
||||
SRC_DIR="$HTML_SRC"
|
||||
DEST_DIR="$HTML_DEST"
|
||||
FILT="filt"
|
||||
fi
|
||||
|
||||
function do_rsync {
|
||||
rsync --dry-run "${@}" | grep -v 'is uptodate$'
|
||||
echo ''
|
||||
echo -n "Run without --dry-run? [n] "
|
||||
read ans
|
||||
case "$ans" in
|
||||
y*) rsync "${@}" | grep -v 'is uptodate$' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [ -d "$SRC_DIR" ]; then
|
||||
REVERSE_RSYNC=do_rsync
|
||||
else
|
||||
echo "The directory $SRC_DIR does not exist yet."
|
||||
echo -n "Do you want to create it? [n] "
|
||||
read ans
|
||||
case "$ans" in
|
||||
y*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
REVERSE=yes
|
||||
REVERSE_RSYNC=rsync
|
||||
fi
|
||||
|
||||
if [ "$REVERSE" = yes ]; then
|
||||
OPTS='-aivOHP'
|
||||
TMP_FILT="$SRC_DIR/tmp-filt"
|
||||
echo "Copying files from $RSYNC_SAMBA_HOST to $SRC_DIR ..."
|
||||
if [ "$MODE" = html ]; then
|
||||
if [ $REVERSE_RSYNC = rsync ]; then
|
||||
git clone "$HTML_GIT" "$SRC_DIR" || exit 1
|
||||
else
|
||||
cd "$SRC_DIR" || exit 1
|
||||
git pull || exit 1
|
||||
fi
|
||||
sed -n -e 's/[-P]/H/p' "$SRC_DIR/$FILT" >"$TMP_FILT"
|
||||
OPTS="${OPTS}f._$TMP_FILT"
|
||||
else
|
||||
OPTS="${OPTS}f:_$FILT"
|
||||
fi
|
||||
$REVERSE_RSYNC "$OPTS" "$RSYNC_SAMBA_HOST:$DEST_DIR/" "$SRC_DIR/"
|
||||
rm -f "$TMP_FILT"
|
||||
exit
|
||||
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/"
|
||||
33
packaging/send-news
Executable file
33
packaging/send-news
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# This script expects the ~/src/rsync directory to contain the rsync
|
||||
# source that has been updated. It also expects the auto-build-save
|
||||
# directory to have been created prior to the running of configure so
|
||||
# that each branch has its own build directory underneath. This supports
|
||||
# the maintainer workflow for the rsync-patches files maintenace.
|
||||
|
||||
FTP_SRC="$HOME/samba-rsync-ftp"
|
||||
FTP_DEST="/home/ftp/pub/rsync"
|
||||
MD_FILES="README.md INSTALL.md NEWS.md"
|
||||
|
||||
case "$RSYNC_SAMBA_HOST" in
|
||||
*.samba.org) ;;
|
||||
*)
|
||||
echo "You must set RSYNC_SAMBA_HOST in your environment to the samba hostname to use." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -d "$FTP_SRC" ]; then
|
||||
packaging/samba-rsync ftp # Ask to initialize the local ftp dir
|
||||
fi
|
||||
|
||||
cd ~/src/rsync
|
||||
|
||||
make man
|
||||
./md-convert --dest="$FTP_SRC" $MD_FILES
|
||||
rsync -aiic $MD_FILES auto-build-save/master/*.?.html "$FTP_SRC"
|
||||
|
||||
cd "$FTP_SRC"
|
||||
|
||||
rsync -aiic README.* INSTALL.* NEWS.* *.?.html "$RSYNC_SAMBA_HOST:$FTP_DEST/"
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
import os, sys, re, argparse, glob
|
||||
|
||||
VARS_RE = re.compile(r'^(?!(?:extern|enum)\s)([a-zA-Z]\S*\s+.*);', re.M)
|
||||
VARS_RE = re.compile(r'^(?!(?:extern|enum)\s)([a-zA-Z][^ \n\t:]*\s+.*);', re.M)
|
||||
EXTERNS_RE = re.compile(r'^extern\s+(.*);', re.M)
|
||||
|
||||
types = { }
|
||||
sizes = { }
|
||||
|
||||
def main():
|
||||
@@ -68,19 +69,44 @@ def parse_vars(fn, lines):
|
||||
for line in lines:
|
||||
line = re.sub(r'\s*\{.*\}', '', line)
|
||||
line = re.sub(r'\s*\(.*\)', '', line)
|
||||
for item in re.split(r'\s*,\s*', line):
|
||||
item = re.sub(r'\s*=.*', '', item)
|
||||
m = re.search(r'(?P<var>\w+)(?P<sz>\[.*?\])?$', item)
|
||||
line = re.sub(r'\s*=\s*[^,]*', '', line)
|
||||
m = re.search(r'^(?:(?:static|extern)\s+)?(?P<type>[^\[,]+?)(?P<vars>\w+([\[,].+)?)$', line)
|
||||
if not m:
|
||||
print(f"Bogus match? ({line})")
|
||||
continue
|
||||
items = m['vars']
|
||||
main_type = m['type'].strip()
|
||||
mt_len = len(main_type)
|
||||
main_type = main_type.rstrip('*')
|
||||
first_stars = '*' * (mt_len - len(main_type))
|
||||
if first_stars:
|
||||
main_type = main_type.rstrip()
|
||||
items = first_stars + items
|
||||
for item in re.split(r'\s*,\s*', items):
|
||||
m = re.search(r'(?P<stars>\*+\s*)?(?P<var>\w+)(?P<sz>\[.*?\])?$', item)
|
||||
if not m:
|
||||
print(f"Bogus match? ({item})")
|
||||
continue
|
||||
if m['sz']:
|
||||
if m['var'] in sizes:
|
||||
if sizes[m['var']] != m['sz']:
|
||||
typ = main_type
|
||||
if m['stars']:
|
||||
typ = typ + m['stars'].strip()
|
||||
chk = [
|
||||
'type', typ, types,
|
||||
'size', m['sz'], sizes,
|
||||
]
|
||||
while chk:
|
||||
label = chk.pop(0)
|
||||
new = chk.pop(0)
|
||||
lst = chk.pop(0)
|
||||
if label == 'type':
|
||||
new = ' '.join(new.split()).replace(' *', '*')
|
||||
if m['var'] in lst:
|
||||
old = lst[m['var']]
|
||||
if new != old:
|
||||
var = m['var']
|
||||
print(fn, f'has inconsistent size for "{var}":', m['sz'], 'vs', sizes[var])
|
||||
print(fn, f'has inconsistent {label} for "{var}":', new, 'vs', old)
|
||||
else:
|
||||
sizes[m['var']] = m['sz']
|
||||
lst[m['var']] = new
|
||||
ret.append(m['var'])
|
||||
return ret
|
||||
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
import sys, os, re, argparse, subprocess
|
||||
from datetime import datetime
|
||||
|
||||
MAINTAINER_NAME = 'Wayne Davison'
|
||||
MAINTAINER_SUF = ' ' + MAINTAINER_NAME + "\n"
|
||||
|
||||
def main():
|
||||
latest_year = '2000'
|
||||
|
||||
@@ -22,10 +19,6 @@ 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'
|
||||
@@ -39,55 +32,8 @@ 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 last mod for our c & h files and make sure the Copyright comment is up-to-date.")
|
||||
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.")
|
||||
args = parser.parse_args()
|
||||
main()
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/** \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;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/** \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
|
||||
959
popt/lookup3.c
Normal file
959
popt/lookup3.c
Normal file
@@ -0,0 +1,959 @@
|
||||
/* -------------------------------------------------------------------- */
|
||||
/*
|
||||
* lookup3.c, by Bob Jenkins, May 2006, Public Domain.
|
||||
*
|
||||
* These are functions for producing 32-bit hashes for hash table lookup.
|
||||
* jlu32w(), jlu32l(), jlu32lpair(), jlu32b(), _JLU3_MIX(), and _JLU3_FINAL()
|
||||
* are externally useful functions. Routines to test the hash are included
|
||||
* if SELF_TEST is defined. You can use this free for any purpose. It's in
|
||||
* the public domain. It has no warranty.
|
||||
*
|
||||
* You probably want to use jlu32l(). jlu32l() and jlu32b()
|
||||
* hash byte arrays. jlu32l() is is faster than jlu32b() on
|
||||
* little-endian machines. Intel and AMD are little-endian machines.
|
||||
* On second thought, you probably want jlu32lpair(), which is identical to
|
||||
* jlu32l() except it returns two 32-bit hashes for the price of one.
|
||||
* You could implement jlu32bpair() if you wanted but I haven't bothered here.
|
||||
*
|
||||
* If you want to find a hash of, say, exactly 7 integers, do
|
||||
* a = i1; b = i2; c = i3;
|
||||
* _JLU3_MIX(a,b,c);
|
||||
* a += i4; b += i5; c += i6;
|
||||
* _JLU3_MIX(a,b,c);
|
||||
* a += i7;
|
||||
* _JLU3_FINAL(a,b,c);
|
||||
* then use c as the hash value. If you have a variable size array of
|
||||
* 4-byte integers to hash, use jlu32w(). If you have a byte array (like
|
||||
* a character string), use jlu32l(). If you have several byte arrays, or
|
||||
* a mix of things, see the comments above jlu32l().
|
||||
*
|
||||
* Why is this so big? I read 12 bytes at a time into 3 4-byte integers,
|
||||
* then mix those integers. This is fast (you can do a lot more thorough
|
||||
* mixing with 12*3 instructions on 3 integers than you can with 3 instructions
|
||||
* on 1 byte), but shoehorning those bytes into integers efficiently is messy.
|
||||
*/
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(_JLU3_SELFTEST)
|
||||
# define _JLU3_jlu32w 1
|
||||
# define _JLU3_jlu32l 1
|
||||
# define _JLU3_jlu32lpair 1
|
||||
# define _JLU3_jlu32b 1
|
||||
#endif
|
||||
|
||||
static const union _dbswap {
|
||||
const uint32_t ui;
|
||||
const unsigned char uc[4];
|
||||
} endian = { .ui = 0x11223344 };
|
||||
# define HASH_LITTLE_ENDIAN (endian.uc[0] == (unsigned char) 0x44)
|
||||
# define HASH_BIG_ENDIAN (endian.uc[0] == (unsigned char) 0x11)
|
||||
|
||||
#ifndef ROTL32
|
||||
# define ROTL32(x, s) (((x) << (s)) | ((x) >> (32 - (s))))
|
||||
#endif
|
||||
|
||||
/* NOTE: The _size parameter should be in bytes. */
|
||||
#define _JLU3_INIT(_h, _size) (0xdeadbeef + ((uint32_t)(_size)) + (_h))
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/*
|
||||
* _JLU3_MIX -- mix 3 32-bit values reversibly.
|
||||
*
|
||||
* This is reversible, so any information in (a,b,c) before _JLU3_MIX() is
|
||||
* still in (a,b,c) after _JLU3_MIX().
|
||||
*
|
||||
* If four pairs of (a,b,c) inputs are run through _JLU3_MIX(), or through
|
||||
* _JLU3_MIX() in reverse, there are at least 32 bits of the output that
|
||||
* are sometimes the same for one pair and different for another pair.
|
||||
* This was tested for:
|
||||
* * pairs that differed by one bit, by two bits, in any combination
|
||||
* of top bits of (a,b,c), or in any combination of bottom bits of
|
||||
* (a,b,c).
|
||||
* * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
|
||||
* the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
|
||||
* is commonly produced by subtraction) look like a single 1-bit
|
||||
* difference.
|
||||
* * the base values were pseudorandom, all zero but one bit set, or
|
||||
* all zero plus a counter that starts at zero.
|
||||
*
|
||||
* Some k values for my "a-=c; a^=ROTL32(c,k); c+=b;" arrangement that
|
||||
* satisfy this are
|
||||
* 4 6 8 16 19 4
|
||||
* 9 15 3 18 27 15
|
||||
* 14 9 3 7 17 3
|
||||
* Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
|
||||
* for "differ" defined as + with a one-bit base and a two-bit delta. I
|
||||
* used http://burtleburtle.net/bob/hash/avalanche.html to choose
|
||||
* the operations, constants, and arrangements of the variables.
|
||||
*
|
||||
* This does not achieve avalanche. There are input bits of (a,b,c)
|
||||
* that fail to affect some output bits of (a,b,c), especially of a. The
|
||||
* most thoroughly mixed value is c, but it doesn't really even achieve
|
||||
* avalanche in c.
|
||||
*
|
||||
* This allows some parallelism. Read-after-writes are good at doubling
|
||||
* the number of bits affected, so the goal of mixing pulls in the opposite
|
||||
* direction as the goal of parallelism. I did what I could. Rotates
|
||||
* seem to cost as much as shifts on every machine I could lay my hands
|
||||
* on, and rotates are much kinder to the top and bottom bits, so I used
|
||||
* rotates.
|
||||
*/
|
||||
/* -------------------------------------------------------------------- */
|
||||
#define _JLU3_MIX(a,b,c) \
|
||||
{ \
|
||||
a -= c; a ^= ROTL32(c, 4); c += b; \
|
||||
b -= a; b ^= ROTL32(a, 6); a += c; \
|
||||
c -= b; c ^= ROTL32(b, 8); b += a; \
|
||||
a -= c; a ^= ROTL32(c,16); c += b; \
|
||||
b -= a; b ^= ROTL32(a,19); a += c; \
|
||||
c -= b; c ^= ROTL32(b, 4); b += a; \
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/**
|
||||
* _JLU3_FINAL -- final mixing of 3 32-bit values (a,b,c) into c
|
||||
*
|
||||
* Pairs of (a,b,c) values differing in only a few bits will usually
|
||||
* produce values of c that look totally different. This was tested for
|
||||
* * pairs that differed by one bit, by two bits, in any combination
|
||||
* of top bits of (a,b,c), or in any combination of bottom bits of
|
||||
* (a,b,c).
|
||||
* * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
|
||||
* the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
|
||||
* is commonly produced by subtraction) look like a single 1-bit
|
||||
* difference.
|
||||
* * the base values were pseudorandom, all zero but one bit set, or
|
||||
* all zero plus a counter that starts at zero.
|
||||
*
|
||||
* These constants passed:
|
||||
* 14 11 25 16 4 14 24
|
||||
* 12 14 25 16 4 14 24
|
||||
* and these came close:
|
||||
* 4 8 15 26 3 22 24
|
||||
* 10 8 15 26 3 22 24
|
||||
* 11 8 15 26 3 22 24
|
||||
*/
|
||||
/* -------------------------------------------------------------------- */
|
||||
#define _JLU3_FINAL(a,b,c) \
|
||||
{ \
|
||||
c ^= b; c -= ROTL32(b,14); \
|
||||
a ^= c; a -= ROTL32(c,11); \
|
||||
b ^= a; b -= ROTL32(a,25); \
|
||||
c ^= b; c -= ROTL32(b,16); \
|
||||
a ^= c; a -= ROTL32(c,4); \
|
||||
b ^= a; b -= ROTL32(a,14); \
|
||||
c ^= b; c -= ROTL32(b,24); \
|
||||
}
|
||||
|
||||
#if defined(_JLU3_jlu32w)
|
||||
uint32_t jlu32w(uint32_t h, const uint32_t *k, size_t size);
|
||||
/* -------------------------------------------------------------------- */
|
||||
/**
|
||||
* This works on all machines. To be useful, it requires
|
||||
* -- that the key be an array of uint32_t's, and
|
||||
* -- that the size be the number of uint32_t's in the key
|
||||
*
|
||||
* The function jlu32w() is identical to jlu32l() on little-endian
|
||||
* machines, and identical to jlu32b() on big-endian machines,
|
||||
* except that the size has to be measured in uint32_ts rather than in
|
||||
* bytes. jlu32l() is more complicated than jlu32w() only because
|
||||
* jlu32l() has to dance around fitting the key bytes into registers.
|
||||
*
|
||||
* @param h the previous hash, or an arbitrary value
|
||||
* @param *k the key, an array of uint32_t values
|
||||
* @param size the size of the key, in uint32_ts
|
||||
* @return the lookup3 hash
|
||||
*/
|
||||
/* -------------------------------------------------------------------- */
|
||||
uint32_t jlu32w(uint32_t h, const uint32_t *k, size_t size)
|
||||
{
|
||||
uint32_t a = _JLU3_INIT(h, (size * sizeof(*k)));
|
||||
uint32_t b = a;
|
||||
uint32_t c = a;
|
||||
|
||||
if (k == NULL)
|
||||
goto exit;
|
||||
|
||||
/*----------------------------------------------- handle most of the key */
|
||||
while (size > 3) {
|
||||
a += k[0];
|
||||
b += k[1];
|
||||
c += k[2];
|
||||
_JLU3_MIX(a,b,c);
|
||||
size -= 3;
|
||||
k += 3;
|
||||
}
|
||||
|
||||
/*----------------------------------------- handle the last 3 uint32_t's */
|
||||
switch (size) {
|
||||
case 3 : c+=k[2];
|
||||
case 2 : b+=k[1];
|
||||
case 1 : a+=k[0];
|
||||
_JLU3_FINAL(a,b,c);
|
||||
/* fallthrough */
|
||||
case 0:
|
||||
break;
|
||||
}
|
||||
/*---------------------------------------------------- report the result */
|
||||
exit:
|
||||
return c;
|
||||
}
|
||||
#endif /* defined(_JLU3_jlu32w) */
|
||||
|
||||
#if defined(_JLU3_jlu32l)
|
||||
uint32_t jlu32l(uint32_t h, const void *key, size_t size);
|
||||
/* -------------------------------------------------------------------- */
|
||||
/*
|
||||
* jlu32l() -- hash a variable-length key into a 32-bit value
|
||||
* h : can be any 4-byte value
|
||||
* k : the key (the unaligned variable-length array of bytes)
|
||||
* size : the size of the key, counting by bytes
|
||||
* Returns a 32-bit value. Every bit of the key affects every bit of
|
||||
* the return value. Two keys differing by one or two bits will have
|
||||
* totally different hash values.
|
||||
*
|
||||
* The best hash table sizes are powers of 2. There is no need to do
|
||||
* mod a prime (mod is sooo slow!). If you need less than 32 bits,
|
||||
* use a bitmask. For example, if you need only 10 bits, do
|
||||
* h = (h & hashmask(10));
|
||||
* In which case, the hash table should have hashsize(10) elements.
|
||||
*
|
||||
* If you are hashing n strings (uint8_t **)k, do it like this:
|
||||
* for (i=0, h=0; i<n; ++i) h = jlu32l(h, k[i], len[i]);
|
||||
*
|
||||
* By Bob Jenkins, 2006. bob_jenkins@burtleburtle.net. You may use this
|
||||
* code any way you wish, private, educational, or commercial. It's free.
|
||||
*
|
||||
* Use for hash table lookup, or anything where one collision in 2^^32 is
|
||||
* acceptable. Do NOT use for cryptographic purposes.
|
||||
*
|
||||
* @param h the previous hash, or an arbitrary value
|
||||
* @param *k the key, an array of uint8_t values
|
||||
* @param size the size of the key
|
||||
* @return the lookup3 hash
|
||||
*/
|
||||
/* -------------------------------------------------------------------- */
|
||||
uint32_t jlu32l(uint32_t h, const void *key, size_t size)
|
||||
{
|
||||
union { const void *ptr; size_t i; } u;
|
||||
uint32_t a = _JLU3_INIT(h, size);
|
||||
uint32_t b = a;
|
||||
uint32_t c = a;
|
||||
|
||||
if (key == NULL)
|
||||
goto exit;
|
||||
|
||||
u.ptr = key;
|
||||
if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
|
||||
const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
|
||||
#ifdef VALGRIND
|
||||
const uint8_t *k8;
|
||||
#endif
|
||||
|
||||
/*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
|
||||
while (size > 12) {
|
||||
a += k[0];
|
||||
b += k[1];
|
||||
c += k[2];
|
||||
_JLU3_MIX(a,b,c);
|
||||
size -= 12;
|
||||
k += 3;
|
||||
}
|
||||
|
||||
/*------------------------- handle the last (probably partial) block */
|
||||
/*
|
||||
* "k[2]&0xffffff" actually reads beyond the end of the string, but
|
||||
* then masks off the part it's not allowed to read. Because the
|
||||
* string is aligned, the masked-off tail is in the same word as the
|
||||
* rest of the string. Every machine with memory protection I've seen
|
||||
* does it on word boundaries, so is OK with this. But VALGRIND will
|
||||
* still catch it and complain. The masking trick does make the hash
|
||||
* noticeably faster for short strings (like English words).
|
||||
*/
|
||||
#ifndef VALGRIND
|
||||
|
||||
switch (size) {
|
||||
case 12: c += k[2]; b+=k[1]; a+=k[0]; break;
|
||||
case 11: c += k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
|
||||
case 10: c += k[2]&0xffff; b+=k[1]; a+=k[0]; break;
|
||||
case 9: c += k[2]&0xff; b+=k[1]; a+=k[0]; break;
|
||||
case 8: b += k[1]; a+=k[0]; break;
|
||||
case 7: b += k[1]&0xffffff; a+=k[0]; break;
|
||||
case 6: b += k[1]&0xffff; a+=k[0]; break;
|
||||
case 5: b += k[1]&0xff; a+=k[0]; break;
|
||||
case 4: a += k[0]; break;
|
||||
case 3: a += k[0]&0xffffff; break;
|
||||
case 2: a += k[0]&0xffff; break;
|
||||
case 1: a += k[0]&0xff; break;
|
||||
case 0: goto exit;
|
||||
}
|
||||
|
||||
#else /* make valgrind happy */
|
||||
|
||||
k8 = (const uint8_t *)k;
|
||||
switch (size) {
|
||||
case 12: c += k[2]; b+=k[1]; a+=k[0] break;
|
||||
case 11: c += ((uint32_t)k8[10])<<16; /* fallthrough */
|
||||
case 10: c += ((uint32_t)k8[9])<<8; /* fallthrough */
|
||||
case 9: c += k8[8]; /* fallthrough */
|
||||
case 8: b += k[1]; a+=k[0]; break;
|
||||
case 7: b += ((uint32_t)k8[6])<<16; /* fallthrough */
|
||||
case 6: b += ((uint32_t)k8[5])<<8; /* fallthrough */
|
||||
case 5: b += k8[4]; /* fallthrough */
|
||||
case 4: a += k[0]; break;
|
||||
case 3: a += ((uint32_t)k8[2])<<16; /* fallthrough */
|
||||
case 2: a += ((uint32_t)k8[1])<<8; /* fallthrough */
|
||||
case 1: a += k8[0]; break;
|
||||
case 0: goto exit;
|
||||
}
|
||||
|
||||
#endif /* !valgrind */
|
||||
|
||||
} else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
|
||||
const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
|
||||
const uint8_t *k8;
|
||||
|
||||
/*----------- all but last block: aligned reads and different mixing */
|
||||
while (size > 12) {
|
||||
a += k[0] + (((uint32_t)k[1])<<16);
|
||||
b += k[2] + (((uint32_t)k[3])<<16);
|
||||
c += k[4] + (((uint32_t)k[5])<<16);
|
||||
_JLU3_MIX(a,b,c);
|
||||
size -= 12;
|
||||
k += 6;
|
||||
}
|
||||
|
||||
/*------------------------- handle the last (probably partial) block */
|
||||
k8 = (const uint8_t *)k;
|
||||
switch (size) {
|
||||
case 12:
|
||||
c += k[4]+(((uint32_t)k[5])<<16);
|
||||
b += k[2]+(((uint32_t)k[3])<<16);
|
||||
a += k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 11:
|
||||
c += ((uint32_t)k8[10])<<16;
|
||||
/* fallthrough */
|
||||
case 10:
|
||||
c += (uint32_t)k[4];
|
||||
b += k[2]+(((uint32_t)k[3])<<16);
|
||||
a += k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 9:
|
||||
c += (uint32_t)k8[8];
|
||||
/* fallthrough */
|
||||
case 8:
|
||||
b += k[2]+(((uint32_t)k[3])<<16);
|
||||
a += k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 7:
|
||||
b += ((uint32_t)k8[6])<<16;
|
||||
/* fallthrough */
|
||||
case 6:
|
||||
b += (uint32_t)k[2];
|
||||
a += k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 5:
|
||||
b += (uint32_t)k8[4];
|
||||
/* fallthrough */
|
||||
case 4:
|
||||
a += k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 3:
|
||||
a += ((uint32_t)k8[2])<<16;
|
||||
/* fallthrough */
|
||||
case 2:
|
||||
a += (uint32_t)k[0];
|
||||
break;
|
||||
case 1:
|
||||
a += (uint32_t)k8[0];
|
||||
break;
|
||||
case 0:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
} else { /* need to read the key one byte at a time */
|
||||
const uint8_t *k = (const uint8_t *)key;
|
||||
|
||||
/*----------- all but the last block: affect some 32 bits of (a,b,c) */
|
||||
while (size > 12) {
|
||||
a += (uint32_t)k[0];
|
||||
a += ((uint32_t)k[1])<<8;
|
||||
a += ((uint32_t)k[2])<<16;
|
||||
a += ((uint32_t)k[3])<<24;
|
||||
b += (uint32_t)k[4];
|
||||
b += ((uint32_t)k[5])<<8;
|
||||
b += ((uint32_t)k[6])<<16;
|
||||
b += ((uint32_t)k[7])<<24;
|
||||
c += (uint32_t)k[8];
|
||||
c += ((uint32_t)k[9])<<8;
|
||||
c += ((uint32_t)k[10])<<16;
|
||||
c += ((uint32_t)k[11])<<24;
|
||||
_JLU3_MIX(a,b,c);
|
||||
size -= 12;
|
||||
k += 12;
|
||||
}
|
||||
|
||||
/*---------------------------- last block: affect all 32 bits of (c) */
|
||||
switch (size) {
|
||||
case 12: c += ((uint32_t)k[11])<<24; /* fallthrough */
|
||||
case 11: c += ((uint32_t)k[10])<<16; /* fallthrough */
|
||||
case 10: c += ((uint32_t)k[9])<<8; /* fallthrough */
|
||||
case 9: c += (uint32_t)k[8]; /* fallthrough */
|
||||
case 8: b += ((uint32_t)k[7])<<24; /* fallthrough */
|
||||
case 7: b += ((uint32_t)k[6])<<16; /* fallthrough */
|
||||
case 6: b += ((uint32_t)k[5])<<8; /* fallthrough */
|
||||
case 5: b += (uint32_t)k[4]; /* fallthrough */
|
||||
case 4: a += ((uint32_t)k[3])<<24; /* fallthrough */
|
||||
case 3: a += ((uint32_t)k[2])<<16; /* fallthrough */
|
||||
case 2: a += ((uint32_t)k[1])<<8; /* fallthrough */
|
||||
case 1: a += (uint32_t)k[0];
|
||||
break;
|
||||
case 0:
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
_JLU3_FINAL(a,b,c);
|
||||
|
||||
exit:
|
||||
return c;
|
||||
}
|
||||
#endif /* defined(_JLU3_jlu32l) */
|
||||
|
||||
#if defined(_JLU3_jlu32lpair)
|
||||
/**
|
||||
* jlu32lpair: return 2 32-bit hash values.
|
||||
*
|
||||
* This is identical to jlu32l(), except it returns two 32-bit hash
|
||||
* values instead of just one. This is good enough for hash table
|
||||
* lookup with 2^^64 buckets, or if you want a second hash if you're not
|
||||
* happy with the first, or if you want a probably-unique 64-bit ID for
|
||||
* the key. *pc is better mixed than *pb, so use *pc first. If you want
|
||||
* a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)".
|
||||
*
|
||||
* @param h the previous hash, or an arbitrary value
|
||||
* @param *key the key, an array of uint8_t values
|
||||
* @param size the size of the key in bytes
|
||||
* @retval *pc, IN: primary initval, OUT: primary hash
|
||||
* *retval *pb IN: secondary initval, OUT: secondary hash
|
||||
*/
|
||||
void jlu32lpair(const void *key, size_t size, uint32_t *pc, uint32_t *pb)
|
||||
{
|
||||
union { const void *ptr; size_t i; } u;
|
||||
uint32_t a = _JLU3_INIT(*pc, size);
|
||||
uint32_t b = a;
|
||||
uint32_t c = a;
|
||||
|
||||
if (key == NULL)
|
||||
goto exit;
|
||||
|
||||
c += *pb; /* Add the secondary hash. */
|
||||
|
||||
u.ptr = key;
|
||||
if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
|
||||
const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
|
||||
#ifdef VALGRIND
|
||||
const uint8_t *k8;
|
||||
#endif
|
||||
|
||||
/*-- all but last block: aligned reads and affect 32 bits of (a,b,c) */
|
||||
while (size > (size_t)12) {
|
||||
a += k[0];
|
||||
b += k[1];
|
||||
c += k[2];
|
||||
_JLU3_MIX(a,b,c);
|
||||
size -= 12;
|
||||
k += 3;
|
||||
}
|
||||
/*------------------------- handle the last (probably partial) block */
|
||||
/*
|
||||
* "k[2]&0xffffff" actually reads beyond the end of the string, but
|
||||
* then masks off the part it's not allowed to read. Because the
|
||||
* string is aligned, the masked-off tail is in the same word as the
|
||||
* rest of the string. Every machine with memory protection I've seen
|
||||
* does it on word boundaries, so is OK with this. But VALGRIND will
|
||||
* still catch it and complain. The masking trick does make the hash
|
||||
* noticeably faster for short strings (like English words).
|
||||
*/
|
||||
#ifndef VALGRIND
|
||||
|
||||
switch (size) {
|
||||
case 12: c += k[2]; b+=k[1]; a+=k[0]; break;
|
||||
case 11: c += k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
|
||||
case 10: c += k[2]&0xffff; b+=k[1]; a+=k[0]; break;
|
||||
case 9: c += k[2]&0xff; b+=k[1]; a+=k[0]; break;
|
||||
case 8: b += k[1]; a+=k[0]; break;
|
||||
case 7: b += k[1]&0xffffff; a+=k[0]; break;
|
||||
case 6: b += k[1]&0xffff; a+=k[0]; break;
|
||||
case 5: b += k[1]&0xff; a+=k[0]; break;
|
||||
case 4: a += k[0]; break;
|
||||
case 3: a += k[0]&0xffffff; break;
|
||||
case 2: a += k[0]&0xffff; break;
|
||||
case 1: a += k[0]&0xff; break;
|
||||
case 0: goto exit;
|
||||
}
|
||||
|
||||
#else /* make valgrind happy */
|
||||
|
||||
k8 = (const uint8_t *)k;
|
||||
switch (size) {
|
||||
case 12: c += k[2]; b+=k[1]; a+=k[0]; break;
|
||||
case 11: c += ((uint32_t)k8[10])<<16; /* fallthrough */
|
||||
case 10: c += ((uint32_t)k8[9])<<8; /* fallthrough */
|
||||
case 9: c += k8[8]; /* fallthrough */
|
||||
case 8: b += k[1]; a+=k[0]; break;
|
||||
case 7: b += ((uint32_t)k8[6])<<16; /* fallthrough */
|
||||
case 6: b += ((uint32_t)k8[5])<<8; /* fallthrough */
|
||||
case 5: b += k8[4]; /* fallthrough */
|
||||
case 4: a += k[0]; break;
|
||||
case 3: a += ((uint32_t)k8[2])<<16; /* fallthrough */
|
||||
case 2: a += ((uint32_t)k8[1])<<8; /* fallthrough */
|
||||
case 1: a += k8[0]; break;
|
||||
case 0: goto exit;
|
||||
}
|
||||
|
||||
#endif /* !valgrind */
|
||||
|
||||
} else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
|
||||
const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
|
||||
const uint8_t *k8;
|
||||
|
||||
/*----------- all but last block: aligned reads and different mixing */
|
||||
while (size > (size_t)12) {
|
||||
a += k[0] + (((uint32_t)k[1])<<16);
|
||||
b += k[2] + (((uint32_t)k[3])<<16);
|
||||
c += k[4] + (((uint32_t)k[5])<<16);
|
||||
_JLU3_MIX(a,b,c);
|
||||
size -= 12;
|
||||
k += 6;
|
||||
}
|
||||
|
||||
/*------------------------- handle the last (probably partial) block */
|
||||
k8 = (const uint8_t *)k;
|
||||
switch (size) {
|
||||
case 12:
|
||||
c += k[4]+(((uint32_t)k[5])<<16);
|
||||
b += k[2]+(((uint32_t)k[3])<<16);
|
||||
a += k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 11:
|
||||
c += ((uint32_t)k8[10])<<16;
|
||||
/* fallthrough */
|
||||
case 10:
|
||||
c += k[4];
|
||||
b += k[2]+(((uint32_t)k[3])<<16);
|
||||
a += k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 9:
|
||||
c += k8[8];
|
||||
/* fallthrough */
|
||||
case 8:
|
||||
b += k[2]+(((uint32_t)k[3])<<16);
|
||||
a += k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 7:
|
||||
b += ((uint32_t)k8[6])<<16;
|
||||
/* fallthrough */
|
||||
case 6:
|
||||
b += k[2];
|
||||
a += k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 5:
|
||||
b += k8[4];
|
||||
/* fallthrough */
|
||||
case 4:
|
||||
a += k[0]+(((uint32_t)k[1])<<16);
|
||||
break;
|
||||
case 3:
|
||||
a += ((uint32_t)k8[2])<<16;
|
||||
/* fallthrough */
|
||||
case 2:
|
||||
a += k[0];
|
||||
break;
|
||||
case 1:
|
||||
a += k8[0];
|
||||
break;
|
||||
case 0:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
} else { /* need to read the key one byte at a time */
|
||||
const uint8_t *k = (const uint8_t *)key;
|
||||
|
||||
/*----------- all but the last block: affect some 32 bits of (a,b,c) */
|
||||
while (size > (size_t)12) {
|
||||
a += k[0];
|
||||
a += ((uint32_t)k[1])<<8;
|
||||
a += ((uint32_t)k[2])<<16;
|
||||
a += ((uint32_t)k[3])<<24;
|
||||
b += k[4];
|
||||
b += ((uint32_t)k[5])<<8;
|
||||
b += ((uint32_t)k[6])<<16;
|
||||
b += ((uint32_t)k[7])<<24;
|
||||
c += k[8];
|
||||
c += ((uint32_t)k[9])<<8;
|
||||
c += ((uint32_t)k[10])<<16;
|
||||
c += ((uint32_t)k[11])<<24;
|
||||
_JLU3_MIX(a,b,c);
|
||||
size -= 12;
|
||||
k += 12;
|
||||
}
|
||||
|
||||
/*---------------------------- last block: affect all 32 bits of (c) */
|
||||
switch (size) {
|
||||
case 12: c += ((uint32_t)k[11])<<24; /* fallthrough */
|
||||
case 11: c += ((uint32_t)k[10])<<16; /* fallthrough */
|
||||
case 10: c += ((uint32_t)k[9])<<8; /* fallthrough */
|
||||
case 9: c += k[8]; /* fallthrough */
|
||||
case 8: b += ((uint32_t)k[7])<<24; /* fallthrough */
|
||||
case 7: b += ((uint32_t)k[6])<<16; /* fallthrough */
|
||||
case 6: b += ((uint32_t)k[5])<<8; /* fallthrough */
|
||||
case 5: b += k[4]; /* fallthrough */
|
||||
case 4: a += ((uint32_t)k[3])<<24; /* fallthrough */
|
||||
case 3: a += ((uint32_t)k[2])<<16; /* fallthrough */
|
||||
case 2: a += ((uint32_t)k[1])<<8; /* fallthrough */
|
||||
case 1: a += k[0];
|
||||
break;
|
||||
case 0:
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
_JLU3_FINAL(a,b,c);
|
||||
|
||||
exit:
|
||||
*pc = c;
|
||||
*pb = b;
|
||||
return;
|
||||
}
|
||||
#endif /* defined(_JLU3_jlu32lpair) */
|
||||
|
||||
#if defined(_JLU3_jlu32b)
|
||||
uint32_t jlu32b(uint32_t h, const void *key, size_t size);
|
||||
/*
|
||||
* jlu32b():
|
||||
* This is the same as jlu32w() on big-endian machines. It is different
|
||||
* from jlu32l() on all machines. jlu32b() takes advantage of
|
||||
* big-endian byte ordering.
|
||||
*
|
||||
* @param h the previous hash, or an arbitrary value
|
||||
* @param *k the key, an array of uint8_t values
|
||||
* @param size the size of the key
|
||||
* @return the lookup3 hash
|
||||
*/
|
||||
uint32_t jlu32b(uint32_t h, const void *key, size_t size)
|
||||
{
|
||||
union { const void *ptr; size_t i; } u;
|
||||
uint32_t a = _JLU3_INIT(h, size);
|
||||
uint32_t b = a;
|
||||
uint32_t c = a;
|
||||
|
||||
if (key == NULL)
|
||||
return h;
|
||||
|
||||
u.ptr = key;
|
||||
if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) {
|
||||
const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
|
||||
#ifdef VALGRIND
|
||||
const uint8_t *k8;
|
||||
#endif
|
||||
|
||||
/*-- all but last block: aligned reads and affect 32 bits of (a,b,c) */
|
||||
while (size > 12) {
|
||||
a += k[0];
|
||||
b += k[1];
|
||||
c += k[2];
|
||||
_JLU3_MIX(a,b,c);
|
||||
size -= 12;
|
||||
k += 3;
|
||||
}
|
||||
|
||||
/*------------------------- handle the last (probably partial) block */
|
||||
/*
|
||||
* "k[2]<<8" actually reads beyond the end of the string, but
|
||||
* then shifts out the part it's not allowed to read. Because the
|
||||
* string is aligned, the illegal read is in the same word as the
|
||||
* rest of the string. Every machine with memory protection I've seen
|
||||
* does it on word boundaries, so is OK with this. But VALGRIND will
|
||||
* still catch it and complain. The masking trick does make the hash
|
||||
* noticeably faster for short strings (like English words).
|
||||
*/
|
||||
#ifndef VALGRIND
|
||||
|
||||
switch (size) {
|
||||
case 12: c += k[2]; b+=k[1]; a+=k[0]; break;
|
||||
case 11: c += k[2]&0xffffff00; b+=k[1]; a+=k[0]; break;
|
||||
case 10: c += k[2]&0xffff0000; b+=k[1]; a+=k[0]; break;
|
||||
case 9: c += k[2]&0xff000000; b+=k[1]; a+=k[0]; break;
|
||||
case 8: b += k[1]; a+=k[0]; break;
|
||||
case 7: b += k[1]&0xffffff00; a+=k[0]; break;
|
||||
case 6: b += k[1]&0xffff0000; a+=k[0]; break;
|
||||
case 5: b += k[1]&0xff000000; a+=k[0]; break;
|
||||
case 4: a += k[0]; break;
|
||||
case 3: a += k[0]&0xffffff00; break;
|
||||
case 2: a += k[0]&0xffff0000; break;
|
||||
case 1: a += k[0]&0xff000000; break;
|
||||
case 0: goto exit;
|
||||
}
|
||||
|
||||
#else /* make valgrind happy */
|
||||
|
||||
k8 = (const uint8_t *)k;
|
||||
switch (size) { /* all the case statements fall through */
|
||||
case 12: c += k[2]; b+=k[1]; a+=k[0]; break;
|
||||
case 11: c += ((uint32_t)k8[10])<<8; /* fallthrough */
|
||||
case 10: c += ((uint32_t)k8[9])<<16; /* fallthrough */
|
||||
case 9: c += ((uint32_t)k8[8])<<24; /* fallthrough */
|
||||
case 8: b += k[1]; a+=k[0]; break;
|
||||
case 7: b += ((uint32_t)k8[6])<<8; /* fallthrough */
|
||||
case 6: b += ((uint32_t)k8[5])<<16; /* fallthrough */
|
||||
case 5: b += ((uint32_t)k8[4])<<24; /* fallthrough */
|
||||
case 4: a += k[0]; break;
|
||||
case 3: a += ((uint32_t)k8[2])<<8; /* fallthrough */
|
||||
case 2: a += ((uint32_t)k8[1])<<16; /* fallthrough */
|
||||
case 1: a += ((uint32_t)k8[0])<<24; break;
|
||||
case 0: goto exit;
|
||||
}
|
||||
|
||||
#endif /* !VALGRIND */
|
||||
|
||||
} else { /* need to read the key one byte at a time */
|
||||
const uint8_t *k = (const uint8_t *)key;
|
||||
|
||||
/*----------- all but the last block: affect some 32 bits of (a,b,c) */
|
||||
while (size > 12) {
|
||||
a += ((uint32_t)k[0])<<24;
|
||||
a += ((uint32_t)k[1])<<16;
|
||||
a += ((uint32_t)k[2])<<8;
|
||||
a += ((uint32_t)k[3]);
|
||||
b += ((uint32_t)k[4])<<24;
|
||||
b += ((uint32_t)k[5])<<16;
|
||||
b += ((uint32_t)k[6])<<8;
|
||||
b += ((uint32_t)k[7]);
|
||||
c += ((uint32_t)k[8])<<24;
|
||||
c += ((uint32_t)k[9])<<16;
|
||||
c += ((uint32_t)k[10])<<8;
|
||||
c += ((uint32_t)k[11]);
|
||||
_JLU3_MIX(a,b,c);
|
||||
size -= 12;
|
||||
k += 12;
|
||||
}
|
||||
|
||||
/*---------------------------- last block: affect all 32 bits of (c) */
|
||||
switch (size) { /* all the case statements fall through */
|
||||
case 12: c += k[11]; /* fallthrough */
|
||||
case 11: c += ((uint32_t)k[10])<<8; /* fallthrough */
|
||||
case 10: c += ((uint32_t)k[9])<<16; /* fallthrough */
|
||||
case 9: c += ((uint32_t)k[8])<<24; /* fallthrough */
|
||||
case 8: b += k[7]; /* fallthrough */
|
||||
case 7: b += ((uint32_t)k[6])<<8; /* fallthrough */
|
||||
case 6: b += ((uint32_t)k[5])<<16; /* fallthrough */
|
||||
case 5: b += ((uint32_t)k[4])<<24; /* fallthrough */
|
||||
case 4: a += k[3]; /* fallthrough */
|
||||
case 3: a += ((uint32_t)k[2])<<8; /* fallthrough */
|
||||
case 2: a += ((uint32_t)k[1])<<16; /* fallthrough */
|
||||
case 1: a += ((uint32_t)k[0])<<24; /* fallthrough */
|
||||
break;
|
||||
case 0:
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
_JLU3_FINAL(a,b,c);
|
||||
|
||||
exit:
|
||||
return c;
|
||||
}
|
||||
#endif /* defined(_JLU3_jlu32b) */
|
||||
|
||||
#if defined(_JLU3_SELFTEST)
|
||||
|
||||
/* used for timings */
|
||||
static void driver1(void)
|
||||
{
|
||||
uint8_t buf[256];
|
||||
uint32_t i;
|
||||
uint32_t h=0;
|
||||
time_t a,z;
|
||||
|
||||
time(&a);
|
||||
for (i=0; i<256; ++i) buf[i] = 'x';
|
||||
for (i=0; i<1; ++i) {
|
||||
h = jlu32l(h, &buf[0], sizeof(buf[0]));
|
||||
}
|
||||
time(&z);
|
||||
if (z-a > 0) printf("time %d %.8x\n", (int)(z-a), h);
|
||||
}
|
||||
|
||||
/* check that every input bit changes every output bit half the time */
|
||||
#define HASHSTATE 1
|
||||
#define HASHLEN 1
|
||||
#define MAXPAIR 60
|
||||
#define MAXLEN 70
|
||||
static void driver2(void)
|
||||
{
|
||||
uint8_t qa[MAXLEN+1], qb[MAXLEN+2], *a = &qa[0], *b = &qb[1];
|
||||
uint32_t c[HASHSTATE], d[HASHSTATE], i=0, j=0, k, l, m=0, z;
|
||||
uint32_t e[HASHSTATE],f[HASHSTATE],g[HASHSTATE],h[HASHSTATE];
|
||||
uint32_t x[HASHSTATE],y[HASHSTATE];
|
||||
uint32_t hlen;
|
||||
|
||||
printf("No more than %d trials should ever be needed \n",MAXPAIR/2);
|
||||
for (hlen=0; hlen < MAXLEN; ++hlen) {
|
||||
z=0;
|
||||
for (i=0; i<hlen; ++i) { /*-------------- for each input byte, */
|
||||
for (j=0; j<8; ++j) { /*--------------- for each input bit, */
|
||||
for (m=1; m<8; ++m) { /*---- for several possible initvals, */
|
||||
for (l=0; l<HASHSTATE; ++l)
|
||||
e[l]=f[l]=g[l]=h[l]=x[l]=y[l]=~((uint32_t)0);
|
||||
|
||||
/* check that every output bit is affected by that input bit */
|
||||
for (k=0; k<MAXPAIR; k+=2) {
|
||||
uint32_t finished=1;
|
||||
/* keys have one bit different */
|
||||
for (l=0; l<hlen+1; ++l) {a[l] = b[l] = (uint8_t)0;}
|
||||
/* have a and b be two keys differing in only one bit */
|
||||
a[i] ^= (k<<j);
|
||||
a[i] ^= (k>>(8-j));
|
||||
c[0] = jlu32l(m, a, hlen);
|
||||
b[i] ^= ((k+1)<<j);
|
||||
b[i] ^= ((k+1)>>(8-j));
|
||||
d[0] = jlu32l(m, b, hlen);
|
||||
/* check every bit is 1, 0, set, and not set at least once */
|
||||
for (l=0; l<HASHSTATE; ++l) {
|
||||
e[l] &= (c[l]^d[l]);
|
||||
f[l] &= ~(c[l]^d[l]);
|
||||
g[l] &= c[l];
|
||||
h[l] &= ~c[l];
|
||||
x[l] &= d[l];
|
||||
y[l] &= ~d[l];
|
||||
if (e[l]|f[l]|g[l]|h[l]|x[l]|y[l]) finished=0;
|
||||
}
|
||||
if (finished) break;
|
||||
}
|
||||
if (k>z) z=k;
|
||||
if (k == MAXPAIR) {
|
||||
printf("Some bit didn't change: ");
|
||||
printf("%.8x %.8x %.8x %.8x %.8x %.8x ",
|
||||
e[0],f[0],g[0],h[0],x[0],y[0]);
|
||||
printf("i %u j %u m %u len %u\n", i, j, m, hlen);
|
||||
}
|
||||
if (z == MAXPAIR) goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
done:
|
||||
if (z < MAXPAIR) {
|
||||
printf("Mix success %2u bytes %2u initvals ",i,m);
|
||||
printf("required %u trials\n", z/2);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
/* Check for reading beyond the end of the buffer and alignment problems */
|
||||
static void driver3(void)
|
||||
{
|
||||
uint8_t buf[MAXLEN+20], *b;
|
||||
uint32_t len;
|
||||
uint8_t q[] = "This is the time for all good men to come to the aid of their country...";
|
||||
uint32_t h;
|
||||
uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country...";
|
||||
uint32_t i;
|
||||
uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country...";
|
||||
uint32_t j;
|
||||
uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country...";
|
||||
uint32_t ref,x,y;
|
||||
uint8_t *p;
|
||||
uint32_t m = 13;
|
||||
|
||||
printf("Endianness. These lines should all be the same (for values filled in):\n");
|
||||
printf("%.8x %.8x %.8x\n",
|
||||
jlu32w(m, (const uint32_t *)q, (sizeof(q)-1)/4),
|
||||
jlu32w(m, (const uint32_t *)q, (sizeof(q)-5)/4),
|
||||
jlu32w(m, (const uint32_t *)q, (sizeof(q)-9)/4));
|
||||
p = q;
|
||||
printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
|
||||
jlu32l(m, p, sizeof(q)-1), jlu32l(m, p, sizeof(q)-2),
|
||||
jlu32l(m, p, sizeof(q)-3), jlu32l(m, p, sizeof(q)-4),
|
||||
jlu32l(m, p, sizeof(q)-5), jlu32l(m, p, sizeof(q)-6),
|
||||
jlu32l(m, p, sizeof(q)-7), jlu32l(m, p, sizeof(q)-8),
|
||||
jlu32l(m, p, sizeof(q)-9), jlu32l(m, p, sizeof(q)-10),
|
||||
jlu32l(m, p, sizeof(q)-11), jlu32l(m, p, sizeof(q)-12));
|
||||
p = &qq[1];
|
||||
printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
|
||||
jlu32l(m, p, sizeof(q)-1), jlu32l(m, p, sizeof(q)-2),
|
||||
jlu32l(m, p, sizeof(q)-3), jlu32l(m, p, sizeof(q)-4),
|
||||
jlu32l(m, p, sizeof(q)-5), jlu32l(m, p, sizeof(q)-6),
|
||||
jlu32l(m, p, sizeof(q)-7), jlu32l(m, p, sizeof(q)-8),
|
||||
jlu32l(m, p, sizeof(q)-9), jlu32l(m, p, sizeof(q)-10),
|
||||
jlu32l(m, p, sizeof(q)-11), jlu32l(m, p, sizeof(q)-12));
|
||||
p = &qqq[2];
|
||||
printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
|
||||
jlu32l(m, p, sizeof(q)-1), jlu32l(m, p, sizeof(q)-2),
|
||||
jlu32l(m, p, sizeof(q)-3), jlu32l(m, p, sizeof(q)-4),
|
||||
jlu32l(m, p, sizeof(q)-5), jlu32l(m, p, sizeof(q)-6),
|
||||
jlu32l(m, p, sizeof(q)-7), jlu32l(m, p, sizeof(q)-8),
|
||||
jlu32l(m, p, sizeof(q)-9), jlu32l(m, p, sizeof(q)-10),
|
||||
jlu32l(m, p, sizeof(q)-11), jlu32l(m, p, sizeof(q)-12));
|
||||
p = &qqqq[3];
|
||||
printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
|
||||
jlu32l(m, p, sizeof(q)-1), jlu32l(m, p, sizeof(q)-2),
|
||||
jlu32l(m, p, sizeof(q)-3), jlu32l(m, p, sizeof(q)-4),
|
||||
jlu32l(m, p, sizeof(q)-5), jlu32l(m, p, sizeof(q)-6),
|
||||
jlu32l(m, p, sizeof(q)-7), jlu32l(m, p, sizeof(q)-8),
|
||||
jlu32l(m, p, sizeof(q)-9), jlu32l(m, p, sizeof(q)-10),
|
||||
jlu32l(m, p, sizeof(q)-11), jlu32l(m, p, sizeof(q)-12));
|
||||
printf("\n");
|
||||
for (h=0, b=buf+1; h<8; ++h, ++b) {
|
||||
for (i=0; i<MAXLEN; ++i) {
|
||||
len = i;
|
||||
for (j=0; j<i; ++j)
|
||||
*(b+j)=0;
|
||||
|
||||
/* these should all be equal */
|
||||
m = 1;
|
||||
ref = jlu32l(m, b, len);
|
||||
*(b+i)=(uint8_t)~0;
|
||||
*(b-1)=(uint8_t)~0;
|
||||
x = jlu32l(m, b, len);
|
||||
y = jlu32l(m, b, len);
|
||||
if ((ref != x) || (ref != y))
|
||||
printf("alignment error: %.8x %.8x %.8x %u %u\n",ref,x,y, h, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check for problems with nulls */
|
||||
static void driver4(void)
|
||||
{
|
||||
uint8_t buf[1];
|
||||
uint32_t h;
|
||||
uint32_t i;
|
||||
uint32_t state[HASHSTATE];
|
||||
|
||||
buf[0] = ~0;
|
||||
for (i=0; i<HASHSTATE; ++i)
|
||||
state[i] = 1;
|
||||
printf("These should all be different\n");
|
||||
h = 0;
|
||||
for (i=0; i<8; ++i) {
|
||||
h = jlu32l(h, buf, 0);
|
||||
printf("%2ld 0-byte strings, hash is %.8x\n", (long)i, h);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
driver1(); /* test that the key is hashed: used for timings */
|
||||
driver2(); /* test that whole key is hashed thoroughly */
|
||||
driver3(); /* test that nothing but the key is hashed */
|
||||
driver4(); /* test hashing multiple buffers (all buffers are null) */
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* _JLU3_SELFTEST */
|
||||
1483
popt/popt.c
1483
popt/popt.c
File diff suppressed because it is too large
Load Diff
473
popt/popt.h
473
popt/popt.h
@@ -1,5 +1,4 @@
|
||||
/** \file popt/popt.h
|
||||
* \ingroup popt
|
||||
/** @file
|
||||
*/
|
||||
|
||||
/* (C) 1998-2000 Red Hat, Inc. -- Licensing details are in the COPYING
|
||||
@@ -13,45 +12,49 @@
|
||||
|
||||
#define POPT_OPTION_DEPTH 10
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* \name Arg type identifiers
|
||||
*/
|
||||
/*@{*/
|
||||
#define POPT_ARG_NONE 0 /*!< no arg */
|
||||
#define POPT_ARG_STRING 1 /*!< arg will be saved as string */
|
||||
#define POPT_ARG_INT 2 /*!< arg will be converted to int */
|
||||
#define POPT_ARG_LONG 3 /*!< arg will be converted to long */
|
||||
#define POPT_ARG_INCLUDE_TABLE 4 /*!< arg points to table */
|
||||
#define POPT_ARG_CALLBACK 5 /*!< table-wide callback... must be
|
||||
#define POPT_ARG_NONE 0U /*!< no arg */
|
||||
#define POPT_ARG_STRING 1U /*!< arg will be saved as string */
|
||||
#define POPT_ARG_INT 2U /*!< arg ==> int */
|
||||
#define POPT_ARG_LONG 3U /*!< arg ==> long */
|
||||
#define POPT_ARG_INCLUDE_TABLE 4U /*!< arg points to table */
|
||||
#define POPT_ARG_CALLBACK 5U /*!< table-wide callback... must be
|
||||
set first in table; arg points
|
||||
to callback, descrip points to
|
||||
callback data to pass */
|
||||
#define POPT_ARG_INTL_DOMAIN 6 /*!< set the translation domain
|
||||
#define POPT_ARG_INTL_DOMAIN 6U /*!< set the translation domain
|
||||
for this table and any
|
||||
included tables; arg points
|
||||
to the domain string */
|
||||
#define POPT_ARG_VAL 7 /*!< arg should take value val */
|
||||
#define POPT_ARG_FLOAT 8 /*!< arg will be converted to float */
|
||||
#define POPT_ARG_DOUBLE 9 /*!< arg will be converted to double */
|
||||
#define POPT_ARG_VAL 7U /*!< arg should take value val */
|
||||
#define POPT_ARG_FLOAT 8U /*!< arg ==> float */
|
||||
#define POPT_ARG_DOUBLE 9U /*!< arg ==> double */
|
||||
#define POPT_ARG_LONGLONG 10U /*!< arg ==> long long */
|
||||
|
||||
#define POPT_ARG_MASK 0x0000FFFF
|
||||
/*@}*/
|
||||
#define POPT_ARG_MAINCALL (16U+11U) /*!< EXPERIMENTAL: return (*arg) (argc, argv) */
|
||||
#define POPT_ARG_ARGV 12U /*!< dupe'd arg appended to realloc'd argv array. */
|
||||
#define POPT_ARG_SHORT 13U /*!< arg ==> short */
|
||||
#define POPT_ARG_BITSET (16U+14U) /*!< arg ==> bit set */
|
||||
|
||||
/** \ingroup popt
|
||||
#define POPT_ARG_MASK 0x000000FFU
|
||||
#define POPT_GROUP_MASK 0x0000FF00U
|
||||
|
||||
/**
|
||||
* \name Arg modifiers
|
||||
*/
|
||||
/*@{*/
|
||||
#define POPT_ARGFLAG_ONEDASH 0x80000000 /*!< allow -longoption */
|
||||
#define POPT_ARGFLAG_DOC_HIDDEN 0x40000000 /*!< don't show in help/usage */
|
||||
#define POPT_ARGFLAG_STRIP 0x20000000 /*!< strip this arg from argv(only applies to long args) */
|
||||
#define POPT_ARGFLAG_OPTIONAL 0x10000000 /*!< arg may be missing */
|
||||
#define POPT_ARGFLAG_ONEDASH 0x80000000U /*!< allow -longoption */
|
||||
#define POPT_ARGFLAG_DOC_HIDDEN 0x40000000U /*!< don't show in help/usage */
|
||||
#define POPT_ARGFLAG_STRIP 0x20000000U /*!< strip this arg from argv(only applies to long args) */
|
||||
#define POPT_ARGFLAG_OPTIONAL 0x10000000U /*!< arg may be missing */
|
||||
|
||||
#define POPT_ARGFLAG_OR 0x08000000 /*!< arg will be or'ed */
|
||||
#define POPT_ARGFLAG_NOR 0x09000000 /*!< arg will be nor'ed */
|
||||
#define POPT_ARGFLAG_AND 0x04000000 /*!< arg will be and'ed */
|
||||
#define POPT_ARGFLAG_NAND 0x05000000 /*!< arg will be nand'ed */
|
||||
#define POPT_ARGFLAG_XOR 0x02000000 /*!< arg will be xor'ed */
|
||||
#define POPT_ARGFLAG_NOT 0x01000000 /*!< arg will be negated */
|
||||
#define POPT_ARGFLAG_OR 0x08000000U /*!< arg will be or'ed */
|
||||
#define POPT_ARGFLAG_NOR 0x09000000U /*!< arg will be nor'ed */
|
||||
#define POPT_ARGFLAG_AND 0x04000000U /*!< arg will be and'ed */
|
||||
#define POPT_ARGFLAG_NAND 0x05000000U /*!< arg will be nand'ed */
|
||||
#define POPT_ARGFLAG_XOR 0x02000000U /*!< arg will be xor'ed */
|
||||
#define POPT_ARGFLAG_NOT 0x01000000U /*!< arg will be negated */
|
||||
#define POPT_ARGFLAG_LOGICALOPS \
|
||||
(POPT_ARGFLAG_OR|POPT_ARGFLAG_AND|POPT_ARGFLAG_XOR)
|
||||
|
||||
@@ -60,158 +63,126 @@
|
||||
#define POPT_BIT_CLR (POPT_ARG_VAL|POPT_ARGFLAG_NAND)
|
||||
/*!< clear arg bit(s) */
|
||||
|
||||
#define POPT_ARGFLAG_SHOW_DEFAULT 0x00800000 /*!< show default value in --help */
|
||||
#define POPT_ARGFLAG_SHOW_DEFAULT 0x00800000U /*!< show default value in --help */
|
||||
#define POPT_ARGFLAG_RANDOM 0x00400000U /*!< random value in [1,arg] */
|
||||
#define POPT_ARGFLAG_TOGGLE 0x00200000U /*!< permit --[no]opt prefix toggle */
|
||||
|
||||
/*@}*/
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* \name Callback modifiers
|
||||
*/
|
||||
/*@{*/
|
||||
#define POPT_CBFLAG_PRE 0x80000000 /*!< call the callback before parse */
|
||||
#define POPT_CBFLAG_POST 0x40000000 /*!< call the callback after parse */
|
||||
#define POPT_CBFLAG_INC_DATA 0x20000000 /*!< use data from the include line,
|
||||
#define POPT_CBFLAG_PRE 0x80000000U /*!< call the callback before parse */
|
||||
#define POPT_CBFLAG_POST 0x40000000U /*!< call the callback after parse */
|
||||
#define POPT_CBFLAG_INC_DATA 0x20000000U /*!< use data from the include line,
|
||||
not the subtable */
|
||||
#define POPT_CBFLAG_SKIPOPTION 0x10000000 /*!< don't callback with option */
|
||||
#define POPT_CBFLAG_CONTINUE 0x08000000 /*!< continue callbacks with option */
|
||||
/*@}*/
|
||||
#define POPT_CBFLAG_SKIPOPTION 0x10000000U /*!< don't callback with option */
|
||||
#define POPT_CBFLAG_CONTINUE 0x08000000U /*!< continue callbacks with option */
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* \name Error return values
|
||||
*/
|
||||
/*@{*/
|
||||
#define POPT_ERROR_NOARG -10 /*!< missing argument */
|
||||
#define POPT_ERROR_BADOPT -11 /*!< unknown option */
|
||||
#define POPT_ERROR_UNWANTEDARG -12 /*!< option does not take an argument */
|
||||
#define POPT_ERROR_OPTSTOODEEP -13 /*!< aliases nested too deeply */
|
||||
#define POPT_ERROR_BADQUOTE -15 /*!< error in paramter quoting */
|
||||
#define POPT_ERROR_BADQUOTE -15 /*!< error in parameter quoting */
|
||||
#define POPT_ERROR_ERRNO -16 /*!< errno set, use strerror(errno) */
|
||||
#define POPT_ERROR_BADNUMBER -17 /*!< invalid numeric value */
|
||||
#define POPT_ERROR_OVERFLOW -18 /*!< number too large or too small */
|
||||
#define POPT_ERROR_BADOPERATION -19 /*!< mutually exclusive logical operations requested */
|
||||
#define POPT_ERROR_NULLARG -20 /*!< opt->arg should not be NULL */
|
||||
#define POPT_ERROR_MALLOC -21 /*!< memory allocation failed */
|
||||
/*@}*/
|
||||
#define POPT_ERROR_BADCONFIG -22 /*!< config file failed sanity test */
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* \name poptBadOption() flags
|
||||
*/
|
||||
/*@{*/
|
||||
#define POPT_BADOPTION_NOALIAS (1 << 0) /*!< don't go into an alias */
|
||||
/*@}*/
|
||||
#define POPT_BADOPTION_NOALIAS (1U << 0) /*!< don't go into an alias */
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* \name poptGetContext() flags
|
||||
*/
|
||||
/*@{*/
|
||||
#define POPT_CONTEXT_NO_EXEC (1 << 0) /*!< ignore exec expansions */
|
||||
#define POPT_CONTEXT_KEEP_FIRST (1 << 1) /*!< pay attention to argv[0] */
|
||||
#define POPT_CONTEXT_POSIXMEHARDER (1 << 2) /*!< options can't follow args */
|
||||
#define POPT_CONTEXT_ARG_OPTS (1 << 4) /*!< return args as options with value 0 */
|
||||
/*@}*/
|
||||
#define POPT_CONTEXT_NO_EXEC (1U << 0) /*!< ignore exec expansions */
|
||||
#define POPT_CONTEXT_KEEP_FIRST (1U << 1) /*!< pay attention to argv[0] */
|
||||
#define POPT_CONTEXT_POSIXMEHARDER (1U << 2) /*!< options can't follow args */
|
||||
#define POPT_CONTEXT_ARG_OPTS (1U << 4) /*!< return args as options with value 0 */
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
*/
|
||||
struct poptOption {
|
||||
/*@observer@*/ /*@null@*/
|
||||
const char * longName; /*!< may be NULL */
|
||||
char shortName; /*!< may be NUL */
|
||||
int argInfo;
|
||||
/*@shared@*/ /*@null@*/
|
||||
char shortName; /*!< may be '\0' */
|
||||
unsigned int argInfo; /*!< type of argument expected after the option */
|
||||
void * arg; /*!< depends on argInfo */
|
||||
int val; /*!< 0 means don't return, just update flag */
|
||||
/*@observer@*/ /*@null@*/
|
||||
int val; /*!< 0 means don't return, just update arg */
|
||||
const char * descrip; /*!< description for autohelp -- may be NULL */
|
||||
/*@observer@*/ /*@null@*/
|
||||
const char * argDescrip; /*!< argument description for autohelp */
|
||||
const char * argDescrip; /*!< argument description for autohelp -- may be NULL */
|
||||
};
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* A popt alias argument for poptAddAlias().
|
||||
*/
|
||||
struct poptAlias {
|
||||
/*@owned@*/ /*@null@*/
|
||||
const char * longName; /*!< may be NULL */
|
||||
char shortName; /*!< may be NUL */
|
||||
int argc;
|
||||
/*@owned@*/
|
||||
const char ** argv; /*!< must be free()able */
|
||||
};
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* A popt alias or exec argument for poptAddItem().
|
||||
*/
|
||||
/*@-exporttype@*/
|
||||
typedef struct poptItem_s {
|
||||
struct poptOption option; /*!< alias/exec name(s) and description. */
|
||||
int argc; /*!< (alias) no. of args. */
|
||||
/*@owned@*/
|
||||
const char ** argv; /*!< (alias) args, must be free()able. */
|
||||
} * poptItem;
|
||||
/*@=exporttype@*/
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* \name Auto-generated help/usage
|
||||
*/
|
||||
/*@{*/
|
||||
|
||||
/**
|
||||
* Empty table marker to enable displaying popt alias/exec options.
|
||||
*/
|
||||
/*@-exportvar@*/
|
||||
/*@unchecked@*/ /*@observer@*/
|
||||
extern struct poptOption poptAliasOptions[];
|
||||
/*@=exportvar@*/
|
||||
#define POPT_AUTOALIAS { NULL, '\0', POPT_ARG_INCLUDE_TABLE, poptAliasOptions, \
|
||||
0, "Options implemented via popt alias/exec:", NULL },
|
||||
|
||||
/**
|
||||
* Auto help table options.
|
||||
*/
|
||||
/*@-exportvar@*/
|
||||
/*@unchecked@*/ /*@observer@*/
|
||||
extern struct poptOption poptHelpOptions[];
|
||||
/*@=exportvar@*/
|
||||
|
||||
/*@-exportvar@*/
|
||||
/*@unchecked@*/ /*@observer@*/
|
||||
extern struct poptOption * poptHelpOptionsI18N;
|
||||
/*@=exportvar@*/
|
||||
|
||||
#define POPT_AUTOHELP { NULL, '\0', POPT_ARG_INCLUDE_TABLE, poptHelpOptions, \
|
||||
0, "Help options:", NULL },
|
||||
|
||||
#define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL }
|
||||
/*@}*/
|
||||
#define POPT_TABLEEND { NULL, '\0', 0, NULL, 0, NULL, NULL }
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
*/
|
||||
/*@-exporttype@*/
|
||||
typedef /*@abstract@*/ struct poptContext_s * poptContext;
|
||||
/*@=exporttype@*/
|
||||
typedef struct poptContext_s * poptContext;
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
*/
|
||||
#ifndef __cplusplus
|
||||
/*@-exporttype -typeuse@*/
|
||||
typedef struct poptOption * poptOption;
|
||||
/*@=exporttype =typeuse@*/
|
||||
#endif
|
||||
|
||||
/*@-exportconst@*/
|
||||
/**
|
||||
*/
|
||||
enum poptCallbackReason {
|
||||
POPT_CALLBACK_REASON_PRE = 0,
|
||||
POPT_CALLBACK_REASON_POST = 1,
|
||||
POPT_CALLBACK_REASON_OPTION = 2
|
||||
};
|
||||
/*@=exportconst@*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/*@-type@*/
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Table callback prototype.
|
||||
* @param con context
|
||||
* @param reason reason for callback
|
||||
@@ -221,13 +192,18 @@ extern "C" {
|
||||
*/
|
||||
typedef void (*poptCallbackType) (poptContext con,
|
||||
enum poptCallbackReason reason,
|
||||
/*@null@*/ const struct poptOption * opt,
|
||||
/*@null@*/ const char * arg,
|
||||
/*@null@*/ const void * data)
|
||||
/*@globals internalState @*/
|
||||
/*@modifies internalState @*/;
|
||||
const struct poptOption * opt,
|
||||
const char * arg,
|
||||
const void * data);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Destroy context.
|
||||
* @param con context
|
||||
* @return NULL always
|
||||
*/
|
||||
poptContext poptFreeContext( poptContext con);
|
||||
|
||||
/**
|
||||
* Initialize popt context.
|
||||
* @param name context name (usually argv[0] program name)
|
||||
* @param argc no. of arguments
|
||||
@@ -236,97 +212,90 @@ typedef void (*poptCallbackType) (poptContext con,
|
||||
* @param flags or'd POPT_CONTEXT_* bits
|
||||
* @return initialized popt context
|
||||
*/
|
||||
/*@only@*/ /*@null@*/
|
||||
poptContext poptGetContext(
|
||||
/*@dependent@*/ /*@keep@*/ const char * name,
|
||||
int argc, /*@dependent@*/ /*@keep@*/ const char ** argv,
|
||||
/*@dependent@*/ /*@keep@*/ const struct poptOption * options,
|
||||
int flags)
|
||||
/*@*/;
|
||||
const char * name,
|
||||
int argc, const char ** argv,
|
||||
const struct poptOption * options,
|
||||
unsigned int flags);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Destroy context (alternative implementation).
|
||||
* @param con context
|
||||
* @return NULL always
|
||||
*/
|
||||
poptContext poptFini( poptContext con);
|
||||
|
||||
/**
|
||||
* Initialize popt context (alternative implementation).
|
||||
* This routine does poptGetContext() and then poptReadConfigFiles().
|
||||
* @param argc no. of arguments
|
||||
* @param argv argument array
|
||||
* @param options address of popt option table
|
||||
* @param configPaths colon separated file path(s) to read.
|
||||
* @return initialized popt context (NULL on error).
|
||||
*/
|
||||
poptContext poptInit(int argc, const char ** argv,
|
||||
const struct poptOption * options,
|
||||
const char * configPaths);
|
||||
|
||||
/**
|
||||
* Reinitialize popt context.
|
||||
* @param con context
|
||||
*/
|
||||
/*@unused@*/
|
||||
void poptResetContext(/*@null@*/poptContext con)
|
||||
/*@modifies con @*/;
|
||||
void poptResetContext(poptContext con);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Return value of next option found.
|
||||
* @param con context
|
||||
* @return next option val, -1 on last item, POPT_ERROR_* on error
|
||||
*/
|
||||
int poptGetNextOpt(/*@null@*/poptContext con)
|
||||
/*@globals fileSystem, internalState @*/
|
||||
/*@modifies con, fileSystem, internalState @*/;
|
||||
int poptGetNextOpt(poptContext con);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Return next option argument (if any).
|
||||
* @param con context
|
||||
* @return option argument, NULL if no argument is available
|
||||
*/
|
||||
/*@observer@*/ /*@null@*/ /*@unused@*/
|
||||
const char * poptGetOptArg(/*@null@*/poptContext con)
|
||||
/*@modifies con @*/;
|
||||
char * poptGetOptArg(poptContext con);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Return next argument.
|
||||
* @param con context
|
||||
* @return next argument, NULL if no argument is available
|
||||
*/
|
||||
/*@observer@*/ /*@null@*/ /*@unused@*/
|
||||
const char * poptGetArg(/*@null@*/poptContext con)
|
||||
/*@modifies con @*/;
|
||||
const char * poptGetArg(poptContext con);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Peek at current argument.
|
||||
* @param con context
|
||||
* @return current argument, NULL if no argument is available
|
||||
*/
|
||||
/*@observer@*/ /*@null@*/ /*@unused@*/
|
||||
const char * poptPeekArg(/*@null@*/poptContext con)
|
||||
/*@*/;
|
||||
const char * poptPeekArg(poptContext con);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Return remaining arguments.
|
||||
* @param con context
|
||||
* @return argument array, NULL terminated
|
||||
*/
|
||||
/*@observer@*/ /*@null@*/
|
||||
const char ** poptGetArgs(/*@null@*/poptContext con)
|
||||
/*@modifies con @*/;
|
||||
const char ** poptGetArgs(poptContext con);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Return the option which caused the most recent error.
|
||||
* @param con context
|
||||
* @param flags
|
||||
* @return offending option
|
||||
*/
|
||||
/*@observer@*/
|
||||
const char * poptBadOption(/*@null@*/poptContext con, int flags)
|
||||
/*@*/;
|
||||
const char * poptBadOption(poptContext con, unsigned int flags);
|
||||
|
||||
/** \ingroup popt
|
||||
* Destroy context.
|
||||
* @param con context
|
||||
* @return NULL always
|
||||
*/
|
||||
/*@null@*/
|
||||
poptContext poptFreeContext( /*@only@*/ /*@null@*/ poptContext con)
|
||||
/*@modifies con @*/;
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Add arguments to context.
|
||||
* @param con context
|
||||
* @param argv argument array, NULL terminated
|
||||
* @return 0 on success, POPT_ERROR_OPTSTOODEEP on failure
|
||||
*/
|
||||
/*@unused@*/
|
||||
int poptStuffArgs(poptContext con, /*@keep@*/ const char ** argv)
|
||||
/*@modifies con @*/;
|
||||
int poptStuffArgs(poptContext con, const char ** argv);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Add alias to context.
|
||||
* @todo Pass alias by reference, not value.
|
||||
* @deprecated Use poptAddItem instead.
|
||||
@@ -335,44 +304,64 @@ int poptStuffArgs(poptContext con, /*@keep@*/ const char ** argv)
|
||||
* @param flags (unused)
|
||||
* @return 0 on success
|
||||
*/
|
||||
/*@unused@*/
|
||||
int poptAddAlias(poptContext con, struct poptAlias alias, int flags)
|
||||
/*@modifies con @*/;
|
||||
int poptAddAlias(poptContext con, struct poptAlias alias, int flags);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Add alias/exec item to context.
|
||||
* @param con context
|
||||
* @param newItem alias/exec item to add
|
||||
* @param flags 0 for alias, 1 for exec
|
||||
* @return 0 on success
|
||||
*/
|
||||
int poptAddItem(poptContext con, poptItem newItem, int flags)
|
||||
/*@modifies con @*/;
|
||||
int poptAddItem(poptContext con, poptItem newItem, int flags);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Test path/file for config file sanity (regular file, permissions etc)
|
||||
* @param fn file name
|
||||
* @return 1 on OK, 0 on NOTOK.
|
||||
*/
|
||||
int poptSaneFile(const char * fn);
|
||||
|
||||
/**
|
||||
* Read a file into a buffer.
|
||||
* @param fn file name
|
||||
* @retval *bp buffer (malloc'd) (or NULL)
|
||||
* @retval *nbp no. of bytes in buffer (including final NUL) (or NULL)
|
||||
* @param flags 1 to trim escaped newlines
|
||||
* return 0 on success
|
||||
*/
|
||||
int poptReadFile(const char * fn, char ** bp,
|
||||
size_t * nbp, int flags);
|
||||
#define POPT_READFILE_TRIMNEWLINES 1
|
||||
|
||||
/**
|
||||
* Read configuration file.
|
||||
* @param con context
|
||||
* @param fn file name to read
|
||||
* @return 0 on success, POPT_ERROR_ERRNO on failure
|
||||
*/
|
||||
int poptReadConfigFile(poptContext con, const char * fn)
|
||||
/*@globals errno, fileSystem, internalState @*/
|
||||
/*@modifies con->execs, con->numExecs,
|
||||
errno, fileSystem, internalState @*/;
|
||||
int poptReadConfigFile(poptContext con, const char * fn);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Read configuration file(s).
|
||||
* Colon separated files to read, looping over poptReadConfigFile().
|
||||
* Note that an '@' character preceding a path in the list will
|
||||
* also perform additional sanity checks on the file before reading.
|
||||
* @param con context
|
||||
* @param paths colon separated file name(s) to read
|
||||
* @return 0 on success, POPT_ERROR_BADCONFIG on failure
|
||||
*/
|
||||
int poptReadConfigFiles(poptContext con, const char * paths);
|
||||
|
||||
/**
|
||||
* Read default configuration from /etc/popt and $HOME/.popt.
|
||||
* @param con context
|
||||
* @param useEnv (unused)
|
||||
* @return 0 on success, POPT_ERROR_ERRNO on failure
|
||||
*/
|
||||
/*@unused@*/
|
||||
int poptReadDefaultConfig(poptContext con, /*@unused@*/ int useEnv)
|
||||
/*@globals fileSystem, internalState @*/
|
||||
/*@modifies con->execs, con->numExecs,
|
||||
fileSystem, internalState @*/;
|
||||
int poptReadDefaultConfig(poptContext con, int useEnv);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Duplicate an argument array.
|
||||
* @note: The argument array is malloc'd as a single area, so only argv must
|
||||
* be free'd.
|
||||
@@ -383,12 +372,11 @@ int poptReadDefaultConfig(poptContext con, /*@unused@*/ int useEnv)
|
||||
* @retval argvPtr address of returned argument array
|
||||
* @return 0 on success, POPT_ERROR_NOARG on failure
|
||||
*/
|
||||
int poptDupArgv(int argc, /*@null@*/ const char **argv,
|
||||
/*@null@*/ /*@out@*/ int * argcPtr,
|
||||
/*@null@*/ /*@out@*/ const char *** argvPtr)
|
||||
/*@modifies *argcPtr, *argvPtr @*/;
|
||||
int poptDupArgv(int argc, const char **argv,
|
||||
int * argcPtr,
|
||||
const char *** argvPtr);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Parse a string into an argument array.
|
||||
* The parse allows ', ", and \ quoting, but ' is treated the same as " and
|
||||
* both may include \ quotes.
|
||||
@@ -400,10 +388,9 @@ int poptDupArgv(int argc, /*@null@*/ const char **argv,
|
||||
* @retval argvPtr address of returned argument array
|
||||
*/
|
||||
int poptParseArgvString(const char * s,
|
||||
/*@out@*/ int * argcPtr, /*@out@*/ const char *** argvPtr)
|
||||
/*@modifies *argcPtr, *argvPtr @*/;
|
||||
int * argcPtr, const char *** argvPtr);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Parses an input configuration file and returns an string that is a
|
||||
* command line. For use with popt. You must free the return value when done.
|
||||
*
|
||||
@@ -418,8 +405,8 @@ bla=bla
|
||||
|
||||
this_is = fdsafdas
|
||||
bad_line=
|
||||
reall bad line
|
||||
reall bad line = again
|
||||
really bad line
|
||||
really bad line = again
|
||||
5555= 55555
|
||||
test = with lots of spaces
|
||||
\endverbatim
|
||||
@@ -449,83 +436,82 @@ this_is = fdsafdas
|
||||
* @return 0 on success
|
||||
* @see poptParseArgvString
|
||||
*/
|
||||
/*@-fcnuse@*/
|
||||
int poptConfigFileToString(FILE *fp, /*@out@*/ char ** argstrp, int flags)
|
||||
/*@globals fileSystem @*/
|
||||
/*@modifies *fp, *argstrp, fileSystem @*/;
|
||||
/*@=fcnuse@*/
|
||||
int poptConfigFileToString(FILE *fp, char ** argstrp, int flags);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Return formatted error string for popt failure.
|
||||
* @param error popt error
|
||||
* @return error string
|
||||
*/
|
||||
/*@observer@*/
|
||||
const char * poptStrerror(const int error)
|
||||
/*@*/;
|
||||
const char * poptStrerror(const int error);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Limit search for executables.
|
||||
* @param con context
|
||||
* @param path single path to search for executables
|
||||
* @param allowAbsolute absolute paths only?
|
||||
*/
|
||||
/*@unused@*/
|
||||
void poptSetExecPath(poptContext con, const char * path, int allowAbsolute)
|
||||
/*@modifies con @*/;
|
||||
void poptSetExecPath(poptContext con, const char * path, int allowAbsolute);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Print detailed description of options.
|
||||
* @param con context
|
||||
* @param fp ouput file handle
|
||||
* @param fp output file handle
|
||||
* @param flags (unused)
|
||||
*/
|
||||
void poptPrintHelp(poptContext con, FILE * fp, /*@unused@*/ int flags)
|
||||
/*@globals fileSystem @*/
|
||||
/*@modifies *fp, fileSystem @*/;
|
||||
void poptPrintHelp(poptContext con, FILE * fp, int flags);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Print terse description of options.
|
||||
* @param con context
|
||||
* @param fp ouput file handle
|
||||
* @param fp output file handle
|
||||
* @param flags (unused)
|
||||
*/
|
||||
void poptPrintUsage(poptContext con, FILE * fp, /*@unused@*/ int flags)
|
||||
/*@globals fileSystem @*/
|
||||
/*@modifies *fp, fileSystem @*/;
|
||||
void poptPrintUsage(poptContext con, FILE * fp, int flags);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Provide text to replace default "[OPTION...]" in help/usage output.
|
||||
* @param con context
|
||||
* @param text replacement text
|
||||
*/
|
||||
/*@-fcnuse@*/
|
||||
void poptSetOtherOptionHelp(poptContext con, const char * text)
|
||||
/*@modifies con @*/;
|
||||
/*@=fcnuse@*/
|
||||
void poptSetOtherOptionHelp(poptContext con, const char * text);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Return argv[0] from context.
|
||||
* @param con context
|
||||
* @return argv[0]
|
||||
*/
|
||||
/*@-fcnuse@*/
|
||||
/*@observer@*/
|
||||
const char * poptGetInvocationName(poptContext con)
|
||||
/*@*/;
|
||||
/*@=fcnuse@*/
|
||||
const char * poptGetInvocationName(poptContext con);
|
||||
|
||||
/** \ingroup popt
|
||||
/**
|
||||
* Shuffle argv pointers to remove stripped args, returns new argc.
|
||||
* @param con context
|
||||
* @param argc no. of args
|
||||
* @param argv arg vector
|
||||
* @return new argc
|
||||
*/
|
||||
/*@-fcnuse@*/
|
||||
int poptStrippedArgv(poptContext con, int argc, char ** argv)
|
||||
/*@modifies *argv @*/;
|
||||
/*@=fcnuse@*/
|
||||
int poptStrippedArgv(poptContext con, int argc, char ** argv);
|
||||
|
||||
/**
|
||||
* Add a string to an argv array.
|
||||
* @retval *argvp argv array
|
||||
* @param argInfo (unused)
|
||||
* @param val string arg to add (using strdup)
|
||||
* @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION
|
||||
*/
|
||||
int poptSaveString(const char *** argvp, unsigned int argInfo,
|
||||
const char * val);
|
||||
|
||||
/**
|
||||
* Save a long long, performing logical operation with value.
|
||||
* @warning Alignment check may be too strict on certain platorms.
|
||||
* @param arg integer pointer, aligned on int boundary.
|
||||
* @param argInfo logical operation (see POPT_ARGFLAG_*)
|
||||
* @param aLongLong value to use
|
||||
* @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION
|
||||
*/
|
||||
int poptSaveLongLong(long long * arg, unsigned int argInfo,
|
||||
long long aLongLong);
|
||||
|
||||
/**
|
||||
* Save a long, performing logical operation with value.
|
||||
@@ -535,12 +521,17 @@ int poptStrippedArgv(poptContext con, int argc, char ** argv)
|
||||
* @param aLong value to use
|
||||
* @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION
|
||||
*/
|
||||
/*@-incondefs@*/
|
||||
/*@unused@*/
|
||||
int poptSaveLong(/*@null@*/ long * arg, int argInfo, long aLong)
|
||||
/*@modifies *arg @*/
|
||||
/*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/;
|
||||
/*@=incondefs@*/
|
||||
int poptSaveLong(long * arg, unsigned int argInfo, long aLong);
|
||||
|
||||
/**
|
||||
* Save a short integer, performing logical operation with value.
|
||||
* @warning Alignment check may be too strict on certain platorms.
|
||||
* @param arg short pointer, aligned on short boundary.
|
||||
* @param argInfo logical operation (see POPT_ARGFLAG_*)
|
||||
* @param aLong value to use
|
||||
* @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION
|
||||
*/
|
||||
int poptSaveShort(short * arg, unsigned int argInfo, long aLong);
|
||||
|
||||
/**
|
||||
* Save an integer, performing logical operation with value.
|
||||
@@ -550,14 +541,40 @@ int poptSaveLong(/*@null@*/ long * arg, int argInfo, long aLong)
|
||||
* @param aLong value to use
|
||||
* @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION
|
||||
*/
|
||||
/*@-incondefs@*/
|
||||
/*@unused@*/
|
||||
int poptSaveInt(/*@null@*/ int * arg, int argInfo, long aLong)
|
||||
/*@modifies *arg @*/
|
||||
/*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/;
|
||||
/*@=incondefs@*/
|
||||
int poptSaveInt(int * arg, unsigned int argInfo, long aLong);
|
||||
|
||||
/* The bit set typedef. */
|
||||
typedef struct poptBits_s {
|
||||
unsigned int bits[1];
|
||||
} * poptBits;
|
||||
|
||||
#define _POPT_BITS_N 1024U /*!< estimated population */
|
||||
#define _POPT_BITS_M ((3U * _POPT_BITS_N) / 2U)
|
||||
#define _POPT_BITS_K 16U /*!< no. of linear hash combinations */
|
||||
|
||||
extern unsigned int _poptBitsN;
|
||||
extern unsigned int _poptBitsM;
|
||||
extern unsigned int _poptBitsK;
|
||||
|
||||
int poptBitsAdd(poptBits bits, const char * s);
|
||||
int poptBitsChk(poptBits bits, const char * s);
|
||||
int poptBitsClr(poptBits bits);
|
||||
int poptBitsDel(poptBits bits, const char * s);
|
||||
int poptBitsIntersect(poptBits * ap, const poptBits b);
|
||||
int poptBitsUnion(poptBits * ap, const poptBits b);
|
||||
int poptBitsArgs(poptContext con, poptBits * ap);
|
||||
|
||||
/**
|
||||
* Save a string into a bit set (experimental).
|
||||
* @retval *bits bit set (lazily malloc'd if NULL)
|
||||
* @param argInfo logical operation (see POPT_ARGFLAG_*)
|
||||
* @param s string to add to bit set
|
||||
* @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION
|
||||
*/
|
||||
int poptSaveBits(poptBits * bitsp, unsigned int argInfo,
|
||||
const char * s);
|
||||
|
||||
|
||||
/*@=type@*/
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/** \ingroup popt
|
||||
* \file popt/poptconfig.c
|
||||
* @file
|
||||
*/
|
||||
|
||||
/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING
|
||||
@@ -8,54 +8,300 @@
|
||||
|
||||
#include "system.h"
|
||||
#include "poptint.h"
|
||||
/*@access poptContext @*/
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
/*@-compmempass@*/ /* FIX: item->option.longName kept, not dependent. */
|
||||
static void configLine(poptContext con, char * line)
|
||||
/*@modifies con @*/
|
||||
#if defined(HAVE_FNMATCH_H)
|
||||
#include <fnmatch.h>
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_GLOB_H)
|
||||
#include <glob.h>
|
||||
|
||||
#if !defined(HAVE_GLOB_PATTERN_P)
|
||||
/* Return nonzero if PATTERN contains any metacharacters.
|
||||
Metacharacters can be quoted with backslashes if QUOTE is nonzero. */
|
||||
static int
|
||||
glob_pattern_p (const char * pattern, int quote)
|
||||
{
|
||||
size_t nameLength;
|
||||
const char * p;
|
||||
int open = 0;
|
||||
|
||||
for (p = pattern; *p != '\0'; ++p)
|
||||
switch (*p) {
|
||||
case '?':
|
||||
case '*':
|
||||
return 1;
|
||||
break;
|
||||
case '\\':
|
||||
if (quote && p[1] != '\0')
|
||||
++p;
|
||||
break;
|
||||
case '[':
|
||||
open = 1;
|
||||
break;
|
||||
case ']':
|
||||
if (open)
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif /* !defined(__GLIBC__) */
|
||||
|
||||
static int poptGlobFlags = 0;
|
||||
|
||||
static int poptGlob_error(UNUSED(const char * epath),
|
||||
UNUSED(int eerrno))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
#endif /* HAVE_GLOB_H */
|
||||
|
||||
/**
|
||||
* Return path(s) from a glob pattern.
|
||||
* @param con context
|
||||
* @param pattern glob pattern
|
||||
* @retval *acp no. of paths
|
||||
* @retval *avp array of paths
|
||||
* @return 0 on success
|
||||
*/
|
||||
static int poptGlob(UNUSED(poptContext con), const char * pattern,
|
||||
int * acp, const char *** avp)
|
||||
{
|
||||
const char * pat = pattern;
|
||||
int rc = 0; /* assume success */
|
||||
|
||||
#if defined(HAVE_GLOB_H)
|
||||
if (glob_pattern_p(pat, 0)) {
|
||||
glob_t _g, *pglob = &_g;
|
||||
|
||||
if (!(rc = glob(pat, poptGlobFlags, poptGlob_error, pglob))) {
|
||||
if (acp) {
|
||||
*acp = (int) pglob->gl_pathc;
|
||||
pglob->gl_pathc = 0;
|
||||
}
|
||||
if (avp) {
|
||||
*avp = (const char **) pglob->gl_pathv;
|
||||
pglob->gl_pathv = NULL;
|
||||
}
|
||||
globfree(pglob);
|
||||
} else if (rc == GLOB_NOMATCH) {
|
||||
*avp = NULL;
|
||||
*acp = 0;
|
||||
rc = 0;
|
||||
} else
|
||||
rc = POPT_ERROR_ERRNO;
|
||||
} else
|
||||
#endif /* HAVE_GLOB_H */
|
||||
{
|
||||
if (acp)
|
||||
*acp = 1;
|
||||
if (avp && (*avp = calloc((size_t)(1 + 1), sizeof (**avp))) != NULL)
|
||||
(*avp)[0] = xstrdup(pat);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int poptSaneFile(const char * fn)
|
||||
{
|
||||
struct stat sb;
|
||||
|
||||
if (fn == NULL || strstr(fn, ".rpmnew") || strstr(fn, ".rpmsave"))
|
||||
return 0;
|
||||
if (stat(fn, &sb) == -1)
|
||||
return 0;
|
||||
if (!S_ISREG(sb.st_mode))
|
||||
return 0;
|
||||
if (sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int poptReadFile(const char * fn, char ** bp, size_t * nbp, int flags)
|
||||
{
|
||||
int fdno;
|
||||
char * b = NULL;
|
||||
off_t nb = 0;
|
||||
char * s, * t, * se;
|
||||
int rc = POPT_ERROR_ERRNO; /* assume failure */
|
||||
|
||||
fdno = open(fn, O_RDONLY);
|
||||
if (fdno < 0)
|
||||
goto exit;
|
||||
|
||||
if ((nb = lseek(fdno, 0, SEEK_END)) == (off_t)-1
|
||||
|| (uintmax_t)nb >= SIZE_MAX
|
||||
|| lseek(fdno, 0, SEEK_SET) == (off_t)-1
|
||||
|| (b = calloc(sizeof(*b), (size_t)nb + 1)) == NULL
|
||||
|| read(fdno, (char *)b, (size_t)nb) != (ssize_t)nb)
|
||||
{
|
||||
int oerrno = errno;
|
||||
(void) close(fdno);
|
||||
if (nb != (off_t)-1 && (uintmax_t)nb >= SIZE_MAX)
|
||||
errno = -EOVERFLOW;
|
||||
else
|
||||
errno = oerrno;
|
||||
goto exit;
|
||||
}
|
||||
if (close(fdno) == -1)
|
||||
goto exit;
|
||||
if (b == NULL) {
|
||||
rc = POPT_ERROR_MALLOC;
|
||||
goto exit;
|
||||
}
|
||||
rc = 0;
|
||||
|
||||
/* Trim out escaped newlines. */
|
||||
if (flags & POPT_READFILE_TRIMNEWLINES)
|
||||
{
|
||||
for (t = b, s = b, se = b + nb; *s && s < se; s++) {
|
||||
switch (*s) {
|
||||
case '\\':
|
||||
if (s[1] == '\n') {
|
||||
s++;
|
||||
continue;
|
||||
}
|
||||
/* fallthrough */
|
||||
default:
|
||||
*t++ = *s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*t++ = '\0';
|
||||
nb = (off_t)(t - b);
|
||||
}
|
||||
|
||||
exit:
|
||||
if (rc != 0) {
|
||||
if (b)
|
||||
free(b);
|
||||
b = NULL;
|
||||
nb = 0;
|
||||
}
|
||||
if (bp)
|
||||
*bp = b;
|
||||
else if (b)
|
||||
free(b);
|
||||
if (nbp)
|
||||
*nbp = (size_t)nb;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for application match.
|
||||
* @param con context
|
||||
* @param s config application name
|
||||
* return 0 if config application matches
|
||||
*/
|
||||
static int configAppMatch(poptContext con, const char * s)
|
||||
{
|
||||
int rc = 1;
|
||||
|
||||
if (con->appName == NULL) /* XXX can't happen. */
|
||||
return rc;
|
||||
|
||||
#if defined(HAVE_GLOB_H) && defined(HAVE_FNMATCH_H)
|
||||
if (glob_pattern_p(s, 1)) {
|
||||
static int flags = FNM_PATHNAME | FNM_PERIOD;
|
||||
#ifdef FNM_EXTMATCH
|
||||
flags |= FNM_EXTMATCH;
|
||||
#endif
|
||||
rc = fnmatch(s, con->appName, flags);
|
||||
} else
|
||||
#endif
|
||||
rc = strcmp(s, con->appName);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int poptConfigLine(poptContext con, char * line)
|
||||
{
|
||||
char *b = NULL;
|
||||
size_t nb = 0;
|
||||
char * se = line;
|
||||
const char * appName;
|
||||
const char * entryType;
|
||||
const char * opt;
|
||||
poptItem item = (poptItem) alloca(sizeof(*item));
|
||||
struct poptItem_s item_buf;
|
||||
poptItem item = &item_buf;
|
||||
int i, j;
|
||||
int rc = POPT_ERROR_BADCONFIG;
|
||||
|
||||
if (con->appName == NULL)
|
||||
return;
|
||||
nameLength = strlen(con->appName);
|
||||
goto exit;
|
||||
|
||||
/*@-boundswrite@*/
|
||||
memset(item, 0, sizeof(*item));
|
||||
|
||||
if (strncmp(line, con->appName, nameLength)) return;
|
||||
appName = se;
|
||||
while (*se != '\0' && !_isspaceptr(se)) se++;
|
||||
if (*se == '\0')
|
||||
goto exit;
|
||||
else
|
||||
*se++ = '\0';
|
||||
|
||||
line += nameLength;
|
||||
if (*line == '\0' || !isSpace(line)) return;
|
||||
if (configAppMatch(con, appName)) goto exit;
|
||||
|
||||
while (*line != '\0' && isSpace(line)) line++;
|
||||
entryType = line;
|
||||
while (*line == '\0' || !isSpace(line)) line++;
|
||||
*line++ = '\0';
|
||||
while (*se != '\0' && _isspaceptr(se)) se++;
|
||||
entryType = se;
|
||||
while (*se != '\0' && !_isspaceptr(se)) se++;
|
||||
if (*se != '\0') *se++ = '\0';
|
||||
|
||||
while (*line != '\0' && isSpace(line)) line++;
|
||||
if (*line == '\0') return;
|
||||
opt = line;
|
||||
while (*line == '\0' || !isSpace(line)) line++;
|
||||
*line++ = '\0';
|
||||
while (*se != '\0' && _isspaceptr(se)) se++;
|
||||
if (*se == '\0') goto exit;
|
||||
opt = se;
|
||||
while (*se != '\0' && !_isspaceptr(se)) se++;
|
||||
if (opt[0] == '-' && *se == '\0') goto exit;
|
||||
if (*se != '\0') *se++ = '\0';
|
||||
|
||||
while (*line != '\0' && isSpace(line)) line++;
|
||||
if (*line == '\0') return;
|
||||
while (*se != '\0' && _isspaceptr(se)) se++;
|
||||
if (opt[0] == '-' && *se == '\0') goto exit;
|
||||
|
||||
/*@-temptrans@*/ /* FIX: line alias is saved */
|
||||
if (opt[0] == '-' && opt[1] == '-')
|
||||
item->option.longName = opt + 2;
|
||||
else if (opt[0] == '-' && opt[2] == '\0')
|
||||
item->option.shortName = opt[1];
|
||||
/*@=temptrans@*/
|
||||
else {
|
||||
const char * fn = opt;
|
||||
|
||||
if (poptParseArgvString(line, &item->argc, &item->argv)) return;
|
||||
/* XXX handle globs and directories in fn? */
|
||||
if ((rc = poptReadFile(fn, &b, &nb, POPT_READFILE_TRIMNEWLINES)) != 0)
|
||||
goto exit;
|
||||
if (b == NULL || nb == 0)
|
||||
goto exit;
|
||||
|
||||
/* Append remaining text to the interpolated file option text. */
|
||||
if (*se != '\0') {
|
||||
size_t nse = strlen(se) + 1;
|
||||
if ((b = realloc(b, (nb + nse))) == NULL) /* XXX can't happen */
|
||||
goto exit;
|
||||
(void) stpcpy( stpcpy(&b[nb-1], " "), se);
|
||||
nb += nse;
|
||||
}
|
||||
se = b;
|
||||
|
||||
/* Use the basename of the path as the long option name. */
|
||||
{ const char * longName = strrchr(fn, '/');
|
||||
if (longName != NULL)
|
||||
longName++;
|
||||
else
|
||||
longName = fn;
|
||||
if (longName == NULL) /* XXX can't happen. */
|
||||
goto exit;
|
||||
/* Single character basenames are treated as short options. */
|
||||
if (longName[1] != '\0')
|
||||
item->option.longName = longName;
|
||||
else
|
||||
item->option.shortName = longName[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (poptParseArgvString(se, &item->argc, &item->argv)) goto exit;
|
||||
|
||||
/*@-modobserver@*/
|
||||
item->option.argInfo = POPT_ARGFLAG_DOC_HIDDEN;
|
||||
for (i = 0, j = 0; i < item->argc; i++, j++) {
|
||||
const char * f;
|
||||
@@ -81,103 +327,183 @@ static void configLine(poptContext con, char * line)
|
||||
item->argv[j] = NULL;
|
||||
item->argc = j;
|
||||
}
|
||||
/*@=modobserver@*/
|
||||
/*@=boundswrite@*/
|
||||
|
||||
/*@-nullstate@*/ /* FIX: item->argv[] may be NULL */
|
||||
if (!strcmp(entryType, "alias"))
|
||||
(void) poptAddItem(con, item, 0);
|
||||
rc = poptAddItem(con, item, 0);
|
||||
else if (!strcmp(entryType, "exec"))
|
||||
(void) poptAddItem(con, item, 1);
|
||||
/*@=nullstate@*/
|
||||
rc = poptAddItem(con, item, 1);
|
||||
exit:
|
||||
rc = 0; /* XXX for now, always return success */
|
||||
if (b)
|
||||
free(b);
|
||||
return rc;
|
||||
}
|
||||
/*@=compmempass@*/
|
||||
|
||||
int poptReadConfigFile(poptContext con, const char * fn)
|
||||
{
|
||||
const char * file, * chptr, * end;
|
||||
char * buf;
|
||||
/*@dependent@*/ char * dst;
|
||||
int fd, rc;
|
||||
off_t fileLength;
|
||||
|
||||
fd = open(fn, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return (errno == ENOENT ? 0 : POPT_ERROR_ERRNO);
|
||||
|
||||
fileLength = lseek(fd, 0, SEEK_END);
|
||||
if (fileLength == -1 || lseek(fd, 0, 0) == -1) {
|
||||
rc = errno;
|
||||
(void) close(fd);
|
||||
errno = rc;
|
||||
return POPT_ERROR_ERRNO;
|
||||
}
|
||||
|
||||
file = alloca(fileLength + 1);
|
||||
if (read(fd, (char *)file, fileLength) != fileLength) {
|
||||
rc = errno;
|
||||
(void) close(fd);
|
||||
errno = rc;
|
||||
return POPT_ERROR_ERRNO;
|
||||
}
|
||||
if (close(fd) == -1)
|
||||
return POPT_ERROR_ERRNO;
|
||||
|
||||
/*@-boundswrite@*/
|
||||
dst = buf = alloca(fileLength + 1);
|
||||
|
||||
chptr = file;
|
||||
end = (file + fileLength);
|
||||
/*@-infloops@*/ /* LCL: can't detect chptr++ */
|
||||
while (chptr < end) {
|
||||
switch (*chptr) {
|
||||
case '\n':
|
||||
*dst = '\0';
|
||||
dst = buf;
|
||||
while (*dst && isSpace(dst)) dst++;
|
||||
if (*dst && *dst != '#')
|
||||
configLine(con, dst);
|
||||
chptr++;
|
||||
/*@switchbreak@*/ break;
|
||||
case '\\':
|
||||
*dst++ = *chptr++;
|
||||
if (chptr < end) {
|
||||
if (*chptr == '\n')
|
||||
dst--, chptr++;
|
||||
/* \ at the end of a line does not insert a \n */
|
||||
else
|
||||
*dst++ = *chptr++;
|
||||
}
|
||||
/*@switchbreak@*/ break;
|
||||
default:
|
||||
*dst++ = *chptr++;
|
||||
/*@switchbreak@*/ break;
|
||||
}
|
||||
}
|
||||
/*@=infloops@*/
|
||||
/*@=boundswrite@*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int poptReadDefaultConfig(poptContext con, /*@unused@*/ UNUSED(int useEnv))
|
||||
{
|
||||
char * fn, * home;
|
||||
char * b = NULL, *be;
|
||||
size_t nb = 0;
|
||||
const char *se;
|
||||
char *t = NULL, *te;
|
||||
int rc;
|
||||
|
||||
if (con->appName == NULL) return 0;
|
||||
|
||||
rc = poptReadConfigFile(con, "/etc/popt");
|
||||
if (rc) return rc;
|
||||
|
||||
if ((home = getenv("HOME"))) {
|
||||
size_t bufsize = strlen(home) + 20;
|
||||
fn = alloca(bufsize);
|
||||
if (fn == NULL) return 0;
|
||||
snprintf(fn, bufsize, "%s/.popt", home);
|
||||
rc = poptReadConfigFile(con, fn);
|
||||
if (rc) return rc;
|
||||
if ((rc = poptReadFile(fn, &b, &nb, POPT_READFILE_TRIMNEWLINES)) != 0)
|
||||
return (errno == ENOENT ? 0 : rc);
|
||||
if (b == NULL || nb == 0) {
|
||||
rc = POPT_ERROR_BADCONFIG;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
return 0;
|
||||
if ((t = malloc(nb + 1)) == NULL)
|
||||
goto exit;
|
||||
te = t;
|
||||
|
||||
be = (b + nb);
|
||||
for (se = b; se < be; se++) {
|
||||
switch (*se) {
|
||||
case '\n':
|
||||
*te = '\0';
|
||||
te = t;
|
||||
while (*te && _isspaceptr(te)) te++;
|
||||
if (*te && *te != '#')
|
||||
if ((rc = poptConfigLine(con, te)) != 0)
|
||||
goto exit;
|
||||
break;
|
||||
case '\\':
|
||||
*te = *se++;
|
||||
/* \ at the end of a line does not insert a \n */
|
||||
if (se < be && *se != '\n') {
|
||||
te++;
|
||||
*te++ = *se;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
*te++ = *se;
|
||||
break;
|
||||
}
|
||||
}
|
||||
rc = 0;
|
||||
|
||||
exit:
|
||||
free(t);
|
||||
if (b)
|
||||
free(b);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int poptReadConfigFiles(poptContext con, const char * paths)
|
||||
{
|
||||
char * buf = (paths ? xstrdup(paths) : NULL);
|
||||
const char * p;
|
||||
char * pe;
|
||||
int rc = 0; /* assume success */
|
||||
|
||||
for (p = buf; p != NULL && *p != '\0'; p = pe) {
|
||||
const char ** av = NULL;
|
||||
int ac = 0;
|
||||
int i;
|
||||
int xx;
|
||||
|
||||
/* locate start of next path element */
|
||||
pe = strchr(p, ':');
|
||||
if (pe != NULL && *pe == ':')
|
||||
*pe++ = '\0';
|
||||
else
|
||||
pe = (char *) (p + strlen(p));
|
||||
|
||||
xx = poptGlob(con, p, &ac, &av);
|
||||
|
||||
/* work-off each resulting file from the path element */
|
||||
for (i = 0; i < ac; i++) {
|
||||
const char * fn = av[i];
|
||||
if (!poptSaneFile(fn))
|
||||
continue;
|
||||
xx = poptReadConfigFile(con, fn);
|
||||
if (xx && rc == 0)
|
||||
rc = xx;
|
||||
free((void *)av[i]);
|
||||
av[i] = NULL;
|
||||
}
|
||||
free(av);
|
||||
av = NULL;
|
||||
}
|
||||
|
||||
if (buf)
|
||||
free(buf);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int poptReadDefaultConfig(poptContext con, UNUSED(int useEnv))
|
||||
{
|
||||
char * home;
|
||||
struct stat sb;
|
||||
int rc = 0; /* assume success */
|
||||
|
||||
if (con->appName == NULL) goto exit;
|
||||
|
||||
rc = poptReadConfigFile(con, POPT_SYSCONFDIR "/popt");
|
||||
if (rc) goto exit;
|
||||
|
||||
#if defined(HAVE_GLOB_H)
|
||||
if (!stat(POPT_SYSCONFDIR "/popt.d", &sb) && S_ISDIR(sb.st_mode)) {
|
||||
const char ** av = NULL;
|
||||
int ac = 0;
|
||||
int i;
|
||||
|
||||
if ((rc = poptGlob(con, POPT_SYSCONFDIR "/popt.d/*", &ac, &av)) == 0) {
|
||||
for (i = 0; rc == 0 && i < ac; i++) {
|
||||
const char * fn = av[i];
|
||||
if (!poptSaneFile(fn))
|
||||
continue;
|
||||
rc = poptReadConfigFile(con, fn);
|
||||
free((void *)av[i]);
|
||||
av[i] = NULL;
|
||||
}
|
||||
free(av);
|
||||
av = NULL;
|
||||
}
|
||||
}
|
||||
if (rc) goto exit;
|
||||
#endif
|
||||
|
||||
if ((home = getenv("HOME"))) {
|
||||
char * fn = malloc(strlen(home) + 20);
|
||||
if (fn != NULL) {
|
||||
(void) stpcpy(stpcpy(fn, home), "/.popt");
|
||||
rc = poptReadConfigFile(con, fn);
|
||||
free(fn);
|
||||
} else
|
||||
rc = POPT_ERROR_ERRNO;
|
||||
if (rc) goto exit;
|
||||
}
|
||||
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
poptContext
|
||||
poptFini(poptContext con)
|
||||
{
|
||||
return poptFreeContext(con);
|
||||
}
|
||||
|
||||
poptContext
|
||||
poptInit(int argc, const char ** argv,
|
||||
const struct poptOption * options, const char * configPaths)
|
||||
{
|
||||
poptContext con = NULL;
|
||||
const char * argv0;
|
||||
|
||||
if (argv == NULL || argv[0] == NULL || options == NULL)
|
||||
return con;
|
||||
|
||||
if ((argv0 = strrchr(argv[0], '/')) != NULL) argv0++;
|
||||
else argv0 = argv[0];
|
||||
|
||||
con = poptGetContext(argv0, argc, (const char **)argv, options, 0);
|
||||
if (con != NULL&& poptReadConfigFiles(con, configPaths))
|
||||
con = poptFini(con);
|
||||
|
||||
return con;
|
||||
}
|
||||
|
||||
774
popt/popthelp.c
774
popt/popthelp.c
File diff suppressed because it is too large
Load Diff
194
popt/poptint.c
Normal file
194
popt/poptint.c
Normal file
@@ -0,0 +1,194 @@
|
||||
#include "system.h"
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#ifdef HAVE_LANGINFO_H
|
||||
#include <langinfo.h>
|
||||
#endif
|
||||
#include "poptint.h"
|
||||
|
||||
/* Any pair of 32 bit hashes can be used. lookup3.c generates pairs, will do. */
|
||||
#define _JLU3_jlu32lpair 1
|
||||
#define jlu32lpair poptJlu32lpair
|
||||
#include "lookup3.c"
|
||||
|
||||
const char *
|
||||
POPT_prev_char (const char *str)
|
||||
{
|
||||
const char *p = str;
|
||||
|
||||
while (1) {
|
||||
p--;
|
||||
if (((unsigned)*p & 0xc0) != (unsigned)0x80)
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
const char *
|
||||
POPT_next_char (const char *str)
|
||||
{
|
||||
const char *p = str;
|
||||
|
||||
while (*p != '\0') {
|
||||
p++;
|
||||
if (((unsigned)*p & 0xc0) != (unsigned)0x80)
|
||||
break;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
#if !defined(POPT_fprintf) /* XXX lose all the goop ... */
|
||||
|
||||
#if defined(ENABLE_NLS) && defined(HAVE_LIBINTL_H) && defined(HAVE_DCGETTEXT)
|
||||
/*
|
||||
* Rebind a "UTF-8" codeset for popt's internal use.
|
||||
*/
|
||||
char *
|
||||
POPT_dgettext(const char * dom, const char * str)
|
||||
{
|
||||
char * codeset = NULL;
|
||||
char * retval = NULL;
|
||||
|
||||
if (!dom)
|
||||
dom = textdomain(NULL);
|
||||
codeset = bind_textdomain_codeset(dom, NULL);
|
||||
bind_textdomain_codeset(dom, "UTF-8");
|
||||
retval = dgettext(dom, str);
|
||||
bind_textdomain_codeset(dom, codeset);
|
||||
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ICONV
|
||||
/**
|
||||
* Return malloc'd string converted from UTF-8 to current locale.
|
||||
* @param istr input string (UTF-8 encoding assumed)
|
||||
* @return localized string
|
||||
*/
|
||||
static char *
|
||||
strdup_locale_from_utf8 (char * istr)
|
||||
{
|
||||
char * codeset = NULL;
|
||||
char * ostr = NULL;
|
||||
iconv_t cd;
|
||||
|
||||
if (istr == NULL)
|
||||
return NULL;
|
||||
|
||||
#ifdef HAVE_LANGINFO_H
|
||||
codeset = nl_langinfo ((nl_item)CODESET);
|
||||
#endif
|
||||
|
||||
if (codeset != NULL && strcmp(codeset, "UTF-8") != 0
|
||||
&& (cd = iconv_open(codeset, "UTF-8")) != (iconv_t)-1)
|
||||
{
|
||||
char * shift_pin = NULL;
|
||||
size_t db = strlen(istr);
|
||||
char * dstr = malloc((db + 1) * sizeof(*dstr));
|
||||
char * dstr_tmp;
|
||||
char * pin = istr;
|
||||
char * pout = dstr;
|
||||
size_t ib = db;
|
||||
size_t ob = db;
|
||||
size_t err;
|
||||
|
||||
if (dstr == NULL) {
|
||||
(void) iconv_close(cd);
|
||||
return NULL;
|
||||
}
|
||||
err = iconv(cd, NULL, NULL, NULL, NULL);
|
||||
while (1) {
|
||||
*pout = '\0';
|
||||
err = iconv(cd, &pin, &ib, &pout, &ob);
|
||||
if (err != (size_t)-1) {
|
||||
if (shift_pin == NULL) {
|
||||
shift_pin = pin;
|
||||
pin = NULL;
|
||||
ib = 0;
|
||||
continue;
|
||||
}
|
||||
} else
|
||||
switch (errno) {
|
||||
case E2BIG:
|
||||
{ size_t used = (size_t)(pout - dstr);
|
||||
db *= 2;
|
||||
dstr_tmp = realloc(dstr, (db + 1) * sizeof(*dstr));
|
||||
if (dstr_tmp == NULL) {
|
||||
free(dstr);
|
||||
(void) iconv_close(cd);
|
||||
return NULL;
|
||||
}
|
||||
dstr = dstr_tmp;
|
||||
pout = dstr + used;
|
||||
ob = db - used;
|
||||
continue;
|
||||
} break;
|
||||
case EINVAL:
|
||||
case EILSEQ:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
(void) iconv_close(cd);
|
||||
*pout = '\0';
|
||||
ostr = xstrdup(dstr);
|
||||
free(dstr);
|
||||
} else
|
||||
ostr = xstrdup(istr);
|
||||
|
||||
return ostr;
|
||||
}
|
||||
#endif
|
||||
|
||||
int
|
||||
POPT_fprintf (FILE * stream, const char * format, ...)
|
||||
{
|
||||
char * b = NULL, * ob = NULL;
|
||||
int rc;
|
||||
va_list ap;
|
||||
|
||||
#if defined(HAVE_VASPRINTF)
|
||||
va_start(ap, format);
|
||||
if ((rc = vasprintf(&b, format, ap)) < 0)
|
||||
b = NULL;
|
||||
va_end(ap);
|
||||
#else
|
||||
size_t nb = (size_t)1;
|
||||
|
||||
/* HACK: add +1 to the realloc no. of bytes "just in case". */
|
||||
/* XXX Likely unneeded, the issues wrto vsnprintf(3) return b0rkage have
|
||||
* to do with whether the final '\0' is counted (or not). The code
|
||||
* below already adds +1 for the (possibly already counted) trailing NUL.
|
||||
*/
|
||||
while ((b = realloc(b, nb+1)) != NULL) {
|
||||
va_start(ap, format);
|
||||
rc = vsnprintf(b, nb, format, ap);
|
||||
va_end(ap);
|
||||
if (rc > -1) { /* glibc 2.1 */
|
||||
if ((size_t)rc < nb)
|
||||
break;
|
||||
nb = (size_t)(rc + 1); /* precise buffer length known */
|
||||
} else /* glibc 2.0 */
|
||||
nb += (nb < (size_t)100 ? (size_t)100 : nb);
|
||||
ob = b;
|
||||
}
|
||||
#endif
|
||||
|
||||
rc = 0;
|
||||
if (b != NULL) {
|
||||
#ifdef HAVE_ICONV
|
||||
ob = strdup_locale_from_utf8(b);
|
||||
if (ob != NULL) {
|
||||
rc = fprintf(stream, "%s", ob);
|
||||
free(ob);
|
||||
} else
|
||||
#endif
|
||||
rc = fprintf(stream, "%s", b);
|
||||
free (b);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* !defined(POPT_fprintf) */
|
||||
118
popt/poptint.h
118
popt/poptint.h
@@ -1,5 +1,5 @@
|
||||
/** \ingroup popt
|
||||
* \file popt/poptint.h
|
||||
* @file
|
||||
*/
|
||||
|
||||
/* (C) 1998-2000 Red Hat, Inc. -- Licensing details are in the COPYING
|
||||
@@ -9,108 +9,145 @@
|
||||
#ifndef H_POPTINT
|
||||
#define H_POPTINT
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Wrapper to free(3), hides const compilation noise, permit NULL, return NULL.
|
||||
* @param p memory to free
|
||||
* @retval NULL always
|
||||
*/
|
||||
/*@unused@*/ static inline /*@null@*/ void *
|
||||
_free(/*@only@*/ /*@null@*/ const void * p)
|
||||
/*@modifies p @*/
|
||||
static inline void *
|
||||
_free(const void * p)
|
||||
{
|
||||
if (p != NULL) free((void *)p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline int
|
||||
isSpace(const char *ptr)
|
||||
{
|
||||
return isspace(*(unsigned char *)ptr);
|
||||
}
|
||||
|
||||
/* Bit mask macros. */
|
||||
/*@-exporttype -redef @*/
|
||||
typedef unsigned int __pbm_bits;
|
||||
/*@=exporttype =redef @*/
|
||||
#define __PBM_NBITS (8 * sizeof (__pbm_bits))
|
||||
#define __PBM_IX(d) ((d) / __PBM_NBITS)
|
||||
#define __PBM_MASK(d) ((__pbm_bits) 1 << (((unsigned)(d)) % __PBM_NBITS))
|
||||
/*@-exporttype -redef @*/
|
||||
typedef struct {
|
||||
__pbm_bits bits[1];
|
||||
} pbm_set;
|
||||
/*@=exporttype =redef @*/
|
||||
#define __PBM_BITS(set) ((set)->bits)
|
||||
|
||||
#define PBM_ALLOC(d) calloc(__PBM_IX (d) + 1, sizeof(__pbm_bits))
|
||||
#define PBM_ALLOC(d) calloc(__PBM_IX (d) + 1, sizeof(pbm_set))
|
||||
#define PBM_FREE(s) _free(s);
|
||||
#define PBM_SET(d, s) (__PBM_BITS (s)[__PBM_IX (d)] |= __PBM_MASK (d))
|
||||
#define PBM_CLR(d, s) (__PBM_BITS (s)[__PBM_IX (d)] &= ~__PBM_MASK (d))
|
||||
#define PBM_ISSET(d, s) ((__PBM_BITS (s)[__PBM_IX (d)] & __PBM_MASK (d)) != 0)
|
||||
|
||||
extern void poptJlu32lpair(const void *key, size_t size,
|
||||
uint32_t *pc, uint32_t *pb);
|
||||
|
||||
/** \ingroup popt
|
||||
* Typedef's for string and array of strings.
|
||||
*/
|
||||
typedef const char * poptString;
|
||||
typedef poptString * poptArgv;
|
||||
|
||||
/** \ingroup popt
|
||||
* A union to simplify opt->arg access without casting.
|
||||
*/
|
||||
typedef union poptArg_u {
|
||||
void * ptr;
|
||||
int * intp;
|
||||
short * shortp;
|
||||
long * longp;
|
||||
long long * longlongp;
|
||||
float * floatp;
|
||||
double * doublep;
|
||||
const char ** argv;
|
||||
poptCallbackType cb;
|
||||
poptOption opt;
|
||||
} poptArg;
|
||||
|
||||
extern unsigned int _poptArgMask;
|
||||
extern unsigned int _poptGroupMask;
|
||||
|
||||
#define poptArgType(_opt) ((_opt)->argInfo & _poptArgMask)
|
||||
#define poptGroup(_opt) ((_opt)->argInfo & _poptGroupMask)
|
||||
|
||||
#define F_ISSET(_opt, _FLAG) ((_opt)->argInfo & POPT_ARGFLAG_##_FLAG)
|
||||
#define LF_ISSET(_FLAG) (argInfo & POPT_ARGFLAG_##_FLAG)
|
||||
#define CBF_ISSET(_opt, _FLAG) ((_opt)->argInfo & POPT_CBFLAG_##_FLAG)
|
||||
|
||||
/* XXX sick hack to preserve pretense of a popt-1.x ABI. */
|
||||
#define poptSubstituteHelpI18N(opt) \
|
||||
{ if ((opt) == poptHelpOptions) (opt) = poptHelpOptionsI18N; }
|
||||
|
||||
struct optionStackEntry {
|
||||
int argc;
|
||||
/*@only@*/ /*@null@*/
|
||||
const char ** argv;
|
||||
/*@only@*/ /*@null@*/
|
||||
poptArgv argv;
|
||||
pbm_set * argb;
|
||||
int next;
|
||||
/*@only@*/ /*@null@*/
|
||||
const char * nextArg;
|
||||
/*@observer@*/ /*@null@*/
|
||||
char * nextArg;
|
||||
const char * nextCharArg;
|
||||
/*@dependent@*/ /*@null@*/
|
||||
poptItem currAlias;
|
||||
int stuffed;
|
||||
};
|
||||
|
||||
struct poptContext_s {
|
||||
struct optionStackEntry optionStack[POPT_OPTION_DEPTH];
|
||||
/*@dependent@*/
|
||||
struct optionStackEntry * os;
|
||||
/*@owned@*/ /*@null@*/
|
||||
const char ** leftovers;
|
||||
poptArgv leftovers;
|
||||
int numLeftovers;
|
||||
int allocLeftovers;
|
||||
int nextLeftover;
|
||||
/*@keep@*/
|
||||
const struct poptOption * options;
|
||||
int restLeftover;
|
||||
/*@only@*/ /*@null@*/
|
||||
const char * appName;
|
||||
/*@only@*/ /*@null@*/
|
||||
poptItem aliases;
|
||||
int numAliases;
|
||||
int flags;
|
||||
/*@owned@*/ /*@null@*/
|
||||
unsigned int flags;
|
||||
poptItem execs;
|
||||
int numExecs;
|
||||
/*@only@*/ /*@null@*/
|
||||
const char ** finalArgv;
|
||||
char * execFail;
|
||||
poptArgv finalArgv;
|
||||
int finalArgvCount;
|
||||
int finalArgvAlloced;
|
||||
/*@dependent@*/ /*@null@*/
|
||||
int (*maincall) (int argc, const char **argv);
|
||||
poptItem doExec;
|
||||
/*@only@*/
|
||||
const char * execPath;
|
||||
int execAbsolute;
|
||||
/*@only@*/ /*@relnull@*/
|
||||
const char * otherHelp;
|
||||
/*@null@*/
|
||||
pbm_set * arg_strip;
|
||||
};
|
||||
|
||||
#ifdef HAVE_LIBINTL_H
|
||||
#if defined(POPT_fprintf)
|
||||
#define POPT_dgettext dgettext
|
||||
#else
|
||||
#ifdef HAVE_ICONV
|
||||
#include <iconv.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_DCGETTEXT)
|
||||
char *POPT_dgettext(const char * dom, const char * str);
|
||||
#endif
|
||||
|
||||
FORMAT(printf, 2, 3)
|
||||
int POPT_fprintf (FILE* stream, const char *format, ...);
|
||||
#endif /* !defined(POPT_fprintf) */
|
||||
|
||||
const char *POPT_prev_char (const char *str);
|
||||
const char *POPT_next_char (const char *str);
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_NLS) && defined(HAVE_LIBINTL_H)
|
||||
#include <libintl.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_GETTEXT) && !defined(__LCLINT__)
|
||||
#if defined(ENABLE_NLS) && defined(HAVE_GETTEXT)
|
||||
#define _(foo) gettext(foo)
|
||||
#else
|
||||
#define _(foo) foo
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_DCGETTEXT) && !defined(__LCLINT__)
|
||||
#define D_(dom, str) dgettext(dom, str)
|
||||
#if defined(ENABLE_NLS) && defined(HAVE_LIBINTL_H) && defined(HAVE_DCGETTEXT)
|
||||
#define D_(dom, str) POPT_dgettext(dom, str)
|
||||
#define POPT_(foo) D_("popt", foo)
|
||||
#else
|
||||
#define D_(dom, str) str
|
||||
@@ -119,4 +156,3 @@ struct poptContext_s {
|
||||
|
||||
#define N_(foo) foo
|
||||
|
||||
#endif
|
||||
|
||||
100
popt/poptparse.c
100
popt/poptparse.c
@@ -1,5 +1,5 @@
|
||||
/** \ingroup popt
|
||||
* \file popt/poptparse.c
|
||||
* @file
|
||||
*/
|
||||
|
||||
/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING
|
||||
@@ -8,11 +8,8 @@
|
||||
|
||||
#include "system.h"
|
||||
|
||||
#include "poptint.h"
|
||||
|
||||
#define POPT_ARGV_ARRAY_GROW_DELTA 5
|
||||
|
||||
/*@-boundswrite@*/
|
||||
int poptDupArgv(int argc, const char **argv,
|
||||
int * argcPtr, const char *** argvPtr)
|
||||
{
|
||||
@@ -34,13 +31,13 @@ int poptDupArgv(int argc, const char **argv,
|
||||
return POPT_ERROR_MALLOC;
|
||||
argv2 = (void *) dst;
|
||||
dst += (argc + 1) * sizeof(*argv);
|
||||
*dst = '\0';
|
||||
|
||||
/*@-branchstate@*/
|
||||
for (i = 0; i < argc; i++) {
|
||||
argv2[i] = dst;
|
||||
dst += strlcpy(dst, argv[i], nb) + 1;
|
||||
dst = stpcpy(dst, argv[i]);
|
||||
dst++; /* trailing NUL */
|
||||
}
|
||||
/*@=branchstate@*/
|
||||
argv2[argc] = NULL;
|
||||
|
||||
if (argvPtr) {
|
||||
@@ -53,21 +50,25 @@ int poptDupArgv(int argc, const char **argv,
|
||||
*argcPtr = argc;
|
||||
return 0;
|
||||
}
|
||||
/*@=boundswrite@*/
|
||||
|
||||
/*@-bounds@*/
|
||||
int poptParseArgvString(const char * s, int * argcPtr, const char *** argvPtr)
|
||||
{
|
||||
const char * src;
|
||||
char quote = '\0';
|
||||
int argvAlloced = POPT_ARGV_ARRAY_GROW_DELTA;
|
||||
const char ** argv = malloc(sizeof(*argv) * argvAlloced);
|
||||
const char ** argv_tmp;
|
||||
int argc = 0;
|
||||
int buflen = strlen(s) + 1;
|
||||
char * buf = memset(alloca(buflen), 0, buflen);
|
||||
size_t buflen = strlen(s) + 1;
|
||||
char * buf, * bufOrig = NULL;
|
||||
int rc = POPT_ERROR_MALLOC;
|
||||
|
||||
if (argv == NULL) return rc;
|
||||
buf = bufOrig = calloc((size_t)1, buflen);
|
||||
if (buf == NULL) {
|
||||
free(argv);
|
||||
return rc;
|
||||
}
|
||||
argv[argc] = buf;
|
||||
|
||||
for (src = s; *src != '\0'; src++) {
|
||||
@@ -83,13 +84,14 @@ int poptParseArgvString(const char * s, int * argcPtr, const char *** argvPtr)
|
||||
if (*src != quote) *buf++ = '\\';
|
||||
}
|
||||
*buf++ = *src;
|
||||
} else if (isSpace(src)) {
|
||||
} else if (_isspaceptr(src)) {
|
||||
if (*argv[argc] != '\0') {
|
||||
buf++, argc++;
|
||||
if (argc == argvAlloced) {
|
||||
argvAlloced += POPT_ARGV_ARRAY_GROW_DELTA;
|
||||
argv = realloc(argv, sizeof(*argv) * argvAlloced);
|
||||
if (argv == NULL) goto exit;
|
||||
argv_tmp = realloc(argv, sizeof(*argv) * argvAlloced);
|
||||
if (argv_tmp == NULL) goto exit;
|
||||
argv = argv_tmp;
|
||||
}
|
||||
argv[argc] = buf;
|
||||
}
|
||||
@@ -97,17 +99,17 @@ int poptParseArgvString(const char * s, int * argcPtr, const char *** argvPtr)
|
||||
case '"':
|
||||
case '\'':
|
||||
quote = *src;
|
||||
/*@switchbreak@*/ break;
|
||||
break;
|
||||
case '\\':
|
||||
src++;
|
||||
if (!*src) {
|
||||
rc = POPT_ERROR_BADQUOTE;
|
||||
goto exit;
|
||||
}
|
||||
/*@fallthrough@*/
|
||||
/* fallthrough */
|
||||
default:
|
||||
*buf++ = *src;
|
||||
/*@switchbreak@*/ break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,29 +120,30 @@ int poptParseArgvString(const char * s, int * argcPtr, const char *** argvPtr)
|
||||
rc = poptDupArgv(argc, argv, argcPtr, argvPtr);
|
||||
|
||||
exit:
|
||||
if (bufOrig) free(bufOrig);
|
||||
if (argv) free(argv);
|
||||
return rc;
|
||||
}
|
||||
/*@=bounds@*/
|
||||
|
||||
/* still in the dev stage.
|
||||
* return values, perhaps 1== file erro
|
||||
* return values, perhaps 1== file error
|
||||
* 2== line to long
|
||||
* 3== umm.... more?
|
||||
*/
|
||||
int poptConfigFileToString(FILE *fp, char ** argstrp, /*@unused@*/ UNUSED(int flags))
|
||||
int poptConfigFileToString(FILE *fp, char ** argstrp,
|
||||
UNUSED(int flags))
|
||||
{
|
||||
char line[999];
|
||||
char * argstr;
|
||||
char * argstr_tmp;
|
||||
char * p;
|
||||
char * q;
|
||||
char * x;
|
||||
int t;
|
||||
int argvlen = 0;
|
||||
size_t t;
|
||||
size_t argvlen = 0;
|
||||
size_t maxlinelen = sizeof(line);
|
||||
size_t linelen;
|
||||
int maxargvlen = 480;
|
||||
int linenum = 0;
|
||||
size_t maxargvlen = (size_t)480;
|
||||
|
||||
*argstrp = NULL;
|
||||
|
||||
@@ -155,11 +158,10 @@ int poptConfigFileToString(FILE *fp, char ** argstrp, /*@unused@*/ UNUSED(int fl
|
||||
if (argstr == NULL) return POPT_ERROR_MALLOC;
|
||||
|
||||
while (fgets(line, (int)maxlinelen, fp) != NULL) {
|
||||
linenum++;
|
||||
p = line;
|
||||
|
||||
/* loop until first non-space char or EOL */
|
||||
while( *p != '\0' && isSpace(p) )
|
||||
while( *p != '\0' && _isspaceptr(p) )
|
||||
p++;
|
||||
|
||||
linelen = strlen(p);
|
||||
@@ -173,25 +175,29 @@ int poptConfigFileToString(FILE *fp, char ** argstrp, /*@unused@*/ UNUSED(int fl
|
||||
|
||||
q = p;
|
||||
|
||||
while (*q != '\0' && (!isSpace(q)) && *q != '=')
|
||||
while (*q != '\0' && (!_isspaceptr(q)) && *q != '=')
|
||||
q++;
|
||||
|
||||
if (isSpace(q)) {
|
||||
if (_isspaceptr(q)) {
|
||||
/* a space after the name, find next non space */
|
||||
*q++='\0';
|
||||
while( *q != '\0' && isSpace(q) ) q++;
|
||||
while( *q != '\0' && _isspaceptr(q) ) q++;
|
||||
}
|
||||
if (*q == '\0') {
|
||||
/* single command line option (ie, no name=val, just name) */
|
||||
q[-1] = '\0'; /* kill off newline from fgets() call */
|
||||
argvlen += (t = q - p) + (sizeof(" --")-1);
|
||||
argvlen += (t = (size_t)(q - p)) + (sizeof(" --")-1);
|
||||
if (argvlen >= maxargvlen) {
|
||||
maxargvlen = (t > maxargvlen) ? t*2 : maxargvlen*2;
|
||||
argstr = realloc(argstr, maxargvlen);
|
||||
if (argstr == NULL) return POPT_ERROR_MALLOC;
|
||||
argstr_tmp = realloc(argstr, maxargvlen);
|
||||
if (argstr_tmp == NULL) {
|
||||
free(argstr);
|
||||
return POPT_ERROR_MALLOC;
|
||||
}
|
||||
argstr = argstr_tmp;
|
||||
}
|
||||
strlcat(argstr, " --", maxargvlen);
|
||||
strlcat(argstr, p, maxargvlen);
|
||||
strcat(argstr, " --");
|
||||
strcat(argstr, p);
|
||||
continue;
|
||||
}
|
||||
if (*q != '=')
|
||||
@@ -201,29 +207,33 @@ int poptConfigFileToString(FILE *fp, char ** argstrp, /*@unused@*/ UNUSED(int fl
|
||||
*q++ = '\0';
|
||||
|
||||
/* find next non-space letter of value */
|
||||
while (*q != '\0' && isSpace(q))
|
||||
while (*q != '\0' && _isspaceptr(q))
|
||||
q++;
|
||||
if (*q == '\0')
|
||||
continue; /* XXX silently ignore missing value */
|
||||
|
||||
/* now, loop and strip all ending whitespace */
|
||||
x = p + linelen;
|
||||
while (isSpace(--x))
|
||||
*x = 0; /* null out last char if space (including fgets() NL) */
|
||||
while (_isspaceptr(--x))
|
||||
*x = '\0'; /* null out last char if space (including fgets() NL) */
|
||||
|
||||
/* rest of line accept */
|
||||
t = x - p;
|
||||
t = (size_t)(x - p);
|
||||
argvlen += t + (sizeof("' --='")-1);
|
||||
if (argvlen >= maxargvlen) {
|
||||
maxargvlen = (t > maxargvlen) ? t*2 : maxargvlen*2;
|
||||
argstr = realloc(argstr, maxargvlen);
|
||||
if (argstr == NULL) return POPT_ERROR_MALLOC;
|
||||
argstr_tmp = realloc(argstr, maxargvlen);
|
||||
if (argstr_tmp == NULL) {
|
||||
free(argstr);
|
||||
return POPT_ERROR_MALLOC;
|
||||
}
|
||||
argstr = argstr_tmp;
|
||||
}
|
||||
strlcat(argstr, " --", maxargvlen);
|
||||
strlcat(argstr, p, maxargvlen);
|
||||
strlcat(argstr, "=\"", maxargvlen);
|
||||
strlcat(argstr, q, maxargvlen);
|
||||
strlcat(argstr, "\"", maxargvlen);
|
||||
strcat(argstr, " --");
|
||||
strcat(argstr, p);
|
||||
strcat(argstr, "=\"");
|
||||
strcat(argstr, q);
|
||||
strcat(argstr, "\"");
|
||||
}
|
||||
|
||||
*argstrp = argstr;
|
||||
|
||||
154
popt/system.h
154
popt/system.h
@@ -1,134 +1,70 @@
|
||||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#if defined (__GLIBC__) && defined(__LCLINT__)
|
||||
/*@-declundef@*/
|
||||
/*@unchecked@*/
|
||||
extern __const __int32_t *__ctype_tolower;
|
||||
/*@unchecked@*/
|
||||
extern __const __int32_t *__ctype_toupper;
|
||||
/*@=declundef@*/
|
||||
#endif
|
||||
|
||||
#ifdef __TANDEM
|
||||
# include <floss.h(floss_execvp,floss_read)>
|
||||
#endif
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
/* XXX isspace(3) has i18n encoding signedness issues on Solaris. */
|
||||
#define _isspaceptr(_chp) isspace((int)(*(unsigned const char *)(_chp)))
|
||||
|
||||
#if HAVE_MCHECK_H
|
||||
#ifdef HAVE_MCHECK_H
|
||||
#include <mcheck.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#ifdef HAVE_SYS_TYPES_H
|
||||
# include <sys/types.h>
|
||||
#endif
|
||||
#ifdef STDC_HEADERS
|
||||
# include <stdlib.h>
|
||||
# include <stddef.h>
|
||||
#else
|
||||
# ifdef HAVE_STDLIB_H
|
||||
# include <stdlib.h>
|
||||
# endif
|
||||
#endif
|
||||
#ifdef HAVE_STRING_H
|
||||
# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
|
||||
# include <memory.h>
|
||||
# endif
|
||||
# include <string.h>
|
||||
#endif
|
||||
#ifdef HAVE_STRINGS_H
|
||||
# include <strings.h>
|
||||
#endif
|
||||
#ifdef HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef __GNUC__
|
||||
#define __attribute__(x)
|
||||
#endif
|
||||
void * xmalloc (size_t size);
|
||||
|
||||
#ifdef __NeXT
|
||||
/* access macros are not declared in non posix mode in unistd.h -
|
||||
don't try to use posix on NeXTstep 3.3 ! */
|
||||
#include <libc.h>
|
||||
#endif
|
||||
void * xcalloc (size_t nmemb, size_t size);
|
||||
|
||||
#if defined(__LCLINT__)
|
||||
/*@-declundef -incondefs @*/ /* LCL: missing annotation */
|
||||
/*@only@*/ /*@out@*/
|
||||
void * alloca (size_t __size)
|
||||
/*@ensures MaxSet(result) == (__size - 1) @*/
|
||||
/*@*/;
|
||||
/*@=declundef =incondefs @*/
|
||||
#endif
|
||||
void * xrealloc (void * ptr, size_t size);
|
||||
|
||||
/* AIX requires this to be the first thing in the file. */
|
||||
#ifndef __GNUC__
|
||||
# if HAVE_ALLOCA_H
|
||||
# include <alloca.h>
|
||||
# else
|
||||
# ifdef _AIX
|
||||
#pragma alloca
|
||||
# else
|
||||
# ifdef HAVE_ALLOCA
|
||||
# ifndef alloca /* predefined by HP cc +Olibcalls */
|
||||
char *alloca(size_t size);
|
||||
# endif
|
||||
# else
|
||||
# ifdef alloca
|
||||
# undef alloca
|
||||
# endif
|
||||
# define alloca(sz) malloc(sz) /* Kludge this for now */
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
#elif !defined(alloca)
|
||||
#define alloca __builtin_alloca
|
||||
#endif
|
||||
char * xstrdup (const char *str);
|
||||
|
||||
#ifndef HAVE_STRLCPY
|
||||
size_t strlcpy(char *d, const char *s, size_t bufsize);
|
||||
#endif
|
||||
#if !defined(HAVE_STPCPY)
|
||||
/* Copy SRC to DEST, returning the address of the terminating '\0' in DEST. */
|
||||
static inline char * stpcpy (char *dest, const char * src) {
|
||||
register char *d = dest;
|
||||
register const char *s = src;
|
||||
|
||||
#ifndef HAVE_STRLCAT
|
||||
size_t strlcat(char *d, const char *s, size_t bufsize);
|
||||
#endif
|
||||
|
||||
#if HAVE_MCHECK_H && defined(__GNUC__)
|
||||
static inline char *
|
||||
xstrdup(const char *s)
|
||||
{
|
||||
size_t memsize = strlen(s) + 1;
|
||||
char *ptr = malloc(memsize);
|
||||
if (!ptr) {
|
||||
fprintf(stderr, "virtual memory exhausted.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
strlcpy(ptr, s, memsize);
|
||||
return ptr;
|
||||
do
|
||||
*d++ = *s;
|
||||
while (*s++ != '\0');
|
||||
return d - 1;
|
||||
}
|
||||
#else
|
||||
#define xstrdup(_str) strdup(_str)
|
||||
#endif /* HAVE_MCHECK_H && defined(__GNUC__) */
|
||||
#endif
|
||||
|
||||
#if HAVE___SECURE_GETENV && !defined(__LCLINT__)
|
||||
/* Memory allocation via macro defs to get meaningful locations from mtrace() */
|
||||
#if defined(HAVE_MCHECK_H) && defined(__GNUC__)
|
||||
#define vmefail() (fprintf(stderr, "virtual memory exhausted.\n"), exit(EXIT_FAILURE), NULL)
|
||||
#define xmalloc(_size) (malloc(_size) ? : vmefail())
|
||||
#define xcalloc(_nmemb, _size) (calloc((_nmemb), (_size)) ? : vmefail())
|
||||
#define xrealloc(_ptr, _size) (realloc((_ptr), (_size)) ? : vmefail())
|
||||
#define xstrdup(_str) (strcpy((malloc(strlen(_str)+1) ? : vmefail()), (_str)))
|
||||
#else
|
||||
#define xmalloc(_size) malloc(_size)
|
||||
#define xcalloc(_nmemb, _size) calloc((_nmemb), (_size))
|
||||
#define xrealloc(_ptr, _size) realloc((_ptr), (_size))
|
||||
#define xstrdup(_str) strdup(_str)
|
||||
#endif /* defined(HAVE_MCHECK_H) && defined(__GNUC__) */
|
||||
|
||||
#if defined(HAVE_SECURE_GETENV)
|
||||
#define getenv(_s) secure_getenv(_s)
|
||||
#elif defined(HAVE___SECURE_GETENV)
|
||||
#define getenv(_s) __secure_getenv(_s)
|
||||
#endif
|
||||
|
||||
#if !defined HAVE_SNPRINTF || !defined HAVE_C99_VSNPRINTF
|
||||
#define snprintf rsync_snprintf
|
||||
int snprintf(char *str,size_t count,const char *fmt,...);
|
||||
#if !defined(__GNUC__) && !defined(__attribute__)
|
||||
#define __attribute__(x)
|
||||
#endif
|
||||
|
||||
#define UNUSED(x) x __attribute__((__unused__))
|
||||
|
||||
#define PACKAGE "rsync"
|
||||
#define FORMAT(a, b, c) __attribute__((__format__ (a, b, c)))
|
||||
#define NORETURN __attribute__((__noreturn__))
|
||||
|
||||
#include "popt.h"
|
||||
|
||||
102
receiver.c
102
receiver.c
@@ -66,9 +66,11 @@ extern char sender_file_sum[MAX_DIGEST_LEN];
|
||||
extern struct file_list *cur_flist, *first_flist, *dir_flist;
|
||||
extern filter_rule_list daemon_filter_list;
|
||||
extern OFF_T preallocated_len;
|
||||
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;
|
||||
@@ -213,7 +215,12 @@ 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.) */
|
||||
fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
|
||||
/* 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);
|
||||
|
||||
#if 0
|
||||
/* In most cases parent directories will already exist because their
|
||||
@@ -311,7 +318,12 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
|
||||
}
|
||||
}
|
||||
|
||||
while ((i = recv_token(f_in, &data)) != 0) {
|
||||
while (1) {
|
||||
data = NULL;
|
||||
i = recv_token(f_in, &data);
|
||||
if (i == 0)
|
||||
break;
|
||||
|
||||
if (INFO_GTE(PROGRESS, 1))
|
||||
show_progress(offset, total_size);
|
||||
|
||||
@@ -319,6 +331,10 @@ 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));
|
||||
@@ -336,6 +352,11 @@ 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)
|
||||
@@ -435,7 +456,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(partialptr, fname) < 0) {
|
||||
if (do_rename_at(partialptr, fname) < 0) {
|
||||
rsyserr(FERROR_XFER, errno,
|
||||
"rename failed for %s (from %s)",
|
||||
full_fname(fname), partialptr);
|
||||
@@ -451,7 +472,10 @@ 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 = flist->files[ndx - flist->ndx_start];
|
||||
struct file_struct *file;
|
||||
if (ndx < flist->ndx_start)
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
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));
|
||||
@@ -551,6 +575,8 @@ int recv_files(int f_in, int f_out, char *local_name)
|
||||
progress_init();
|
||||
|
||||
while (1) {
|
||||
const char *basedir = NULL;
|
||||
|
||||
cleanup_disable();
|
||||
|
||||
/* This call also sets cur_flist. */
|
||||
@@ -586,6 +612,8 @@ 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);
|
||||
@@ -716,28 +744,34 @@ int recv_files(int f_in, int f_out, char *local_name)
|
||||
fnamecmp = get_backup_name(fname);
|
||||
break;
|
||||
case FNAMECMP_FUZZY:
|
||||
if (fuzzy_basis == 0) {
|
||||
rprintf(FERROR_XFER, "rsync: refusing malicious fuzzy operation for %s\n", xname);
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
if (file->dirname) {
|
||||
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, file->dirname, xname);
|
||||
fnamecmp = fnamecmpbuf;
|
||||
} else
|
||||
fnamecmp = xname;
|
||||
basedir = file->dirname;
|
||||
}
|
||||
fnamecmp = xname;
|
||||
break;
|
||||
default:
|
||||
if (fnamecmp_type > FNAMECMP_FUZZY && fnamecmp_type-FNAMECMP_FUZZY <= basis_dir_cnt) {
|
||||
fnamecmp_type -= FNAMECMP_FUZZY + 1;
|
||||
if (file->dirname) {
|
||||
stringjoin(fnamecmpbuf, sizeof fnamecmpbuf,
|
||||
basis_dir[fnamecmp_type], "/", file->dirname, "/", xname, NULL);
|
||||
} else
|
||||
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], xname);
|
||||
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], file->dirname);
|
||||
basedir = fnamecmpbuf;
|
||||
} else {
|
||||
basedir = basis_dir[fnamecmp_type];
|
||||
}
|
||||
fnamecmp = xname;
|
||||
} else if (fnamecmp_type >= basis_dir_cnt) {
|
||||
rprintf(FERROR,
|
||||
"invalid basis_dir index: %d.\n",
|
||||
fnamecmp_type);
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
} else
|
||||
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basis_dir[fnamecmp_type], fname);
|
||||
fnamecmp = fnamecmpbuf;
|
||||
} else {
|
||||
basedir = basis_dir[fnamecmp_type];
|
||||
fnamecmp = fname;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!fnamecmp || (daemon_filter_list.head
|
||||
@@ -760,25 +794,31 @@ int recv_files(int f_in, int f_out, char *local_name)
|
||||
}
|
||||
|
||||
/* open the file */
|
||||
fd1 = do_open(fnamecmp, O_RDONLY, 0);
|
||||
fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);
|
||||
|
||||
if (fd1 == -1 && protocol_version < 29) {
|
||||
if (fnamecmp != fname) {
|
||||
fnamecmp = fname;
|
||||
fnamecmp_type = FNAMECMP_FNAME;
|
||||
fd1 = do_open(fnamecmp, O_RDONLY, 0);
|
||||
fd1 = do_open_nofollow(fnamecmp, O_RDONLY);
|
||||
}
|
||||
|
||||
if (fd1 == -1 && basis_dir[0]) {
|
||||
/* pre-29 allowed only one alternate basis */
|
||||
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf,
|
||||
basis_dir[0], fname);
|
||||
fnamecmp = fnamecmpbuf;
|
||||
basedir = basis_dir[0];
|
||||
fnamecmp = fname;
|
||||
fnamecmp_type = FNAMECMP_BASIS_DIR_LOW;
|
||||
fd1 = do_open(fnamecmp, O_RDONLY, 0);
|
||||
fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (basedir) {
|
||||
// for the following code we need the full
|
||||
// path name as a single string
|
||||
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, basedir, fnamecmp);
|
||||
fnamecmp = fnamecmpbuf;
|
||||
}
|
||||
|
||||
one_inplace = inplace_partial && fnamecmp_type == FNAMECMP_PARTIAL_DIR;
|
||||
updating_basis_or_equiv = one_inplace
|
||||
|| (inplace && (fnamecmp == fname || fnamecmp_type == FNAMECMP_BACKUP));
|
||||
@@ -839,11 +879,21 @@ 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;
|
||||
fd2 = do_open(fnametmp, O_WRONLY|O_CREAT, 0600);
|
||||
/* 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_relative_open(NULL, fnametmp, O_WRONLY|O_CREAT, 0600);
|
||||
else
|
||||
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? */
|
||||
fd2 = do_open(fname, O_WRONLY, 0600);
|
||||
if (use_secure_symlinks)
|
||||
fd2 = secure_relative_open(NULL, fname, O_WRONLY, 0600);
|
||||
else
|
||||
fd2 = do_open(fname, O_WRONLY, 0600);
|
||||
}
|
||||
#endif
|
||||
if (fd2 == -1) {
|
||||
@@ -895,7 +945,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(partialptr);
|
||||
do_unlink_at(partialptr);
|
||||
handle_partial_dir(partialptr, PDIR_DELETE);
|
||||
}
|
||||
} else if (keep_partial && partialptr && (!one_inplace || delay_updates)) {
|
||||
@@ -904,7 +954,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(fnametmp);
|
||||
do_unlink_at(fnametmp);
|
||||
recv_ok = -1;
|
||||
} else if (!finish_transfer(partialptr, fnametmp, fnamecmp, NULL,
|
||||
file, recv_ok, !partial_dir))
|
||||
@@ -915,7 +965,7 @@ int recv_files(int f_in, int f_out, char *local_name)
|
||||
} else
|
||||
partialptr = NULL;
|
||||
} else if (!one_inplace)
|
||||
do_unlink(fnametmp);
|
||||
do_unlink_at(fnametmp);
|
||||
|
||||
cleanup_disable();
|
||||
|
||||
|
||||
24
rsync.1.md
24
rsync.1.md
@@ -513,6 +513,7 @@ 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
|
||||
@@ -2817,6 +2818,22 @@ 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
|
||||
@@ -3995,7 +4012,7 @@ option (though the 2 commands behave differently if deletions are enabled):
|
||||
> rsync -aiR x/y/file.txt host:/tmp/
|
||||
|
||||
The following command does not need an include of the "x" directory because it
|
||||
is not a part of the transfer (note the traililng slash). Running this command
|
||||
is not a part of the transfer (note the trailing slash). Running this command
|
||||
would copy just "`/tmp/x/file.txt`" because the "y" and "z" dirs get excluded:
|
||||
|
||||
> rsync -ai -f'+ file.txt' -f'- *' x/ host:/tmp/x/
|
||||
@@ -4818,7 +4835,7 @@ An rsync web site is available at <https://rsync.samba.org/>. The site
|
||||
includes an FAQ-O-Matic which may cover questions unanswered by this manual
|
||||
page.
|
||||
|
||||
The rsync github project is <https://github.com/WayneD/rsync>.
|
||||
The rsync github project is <https://github.com/RsyncProject/rsync>.
|
||||
|
||||
We would be delighted to hear from you if you like this program. Please
|
||||
contact the mailing-list at <rsync@lists.samba.org>.
|
||||
@@ -4838,8 +4855,7 @@ David Bell. I've probably missed some people, my apologies if I have.
|
||||
## AUTHOR
|
||||
|
||||
Rsync was originally written by Andrew Tridgell and Paul Mackerras. Many
|
||||
people have later contributed to it. It is currently maintained by Wayne
|
||||
Davison.
|
||||
people from around the world have helped to maintain and improve it.
|
||||
|
||||
Mailing lists for support and development are available at
|
||||
<https://lists.samba.org/>.
|
||||
|
||||
13
rsync.c
13
rsync.c
@@ -437,7 +437,10 @@ int read_ndx_and_attrs(int f_in, int f_out, int *iflag_ptr, uchar *type_ptr, cha
|
||||
*/
|
||||
void free_sums(struct sum_struct *s)
|
||||
{
|
||||
if (s->sums) free(s->sums);
|
||||
if (s->sums) {
|
||||
free(s->sums);
|
||||
free(s->sum2_array);
|
||||
}
|
||||
free(s);
|
||||
}
|
||||
|
||||
@@ -544,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(fname, uid, gid) != 0) {
|
||||
if (do_lchown_at(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",
|
||||
@@ -654,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(fname, new_mode);
|
||||
int ret = am_root < 0 ? 0 : do_chmod_at(fname, new_mode);
|
||||
if (ret < 0) {
|
||||
rsyserr(FERROR_XFER, errno,
|
||||
"failed to set permissions on %s",
|
||||
@@ -755,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(fnametmp);
|
||||
do_unlink_at(fnametmp);
|
||||
return 0;
|
||||
}
|
||||
if (ret == 0) {
|
||||
@@ -771,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(fnametmp, fname) < 0) {
|
||||
if (do_rename_at(fnametmp, fname) < 0) {
|
||||
rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\"",
|
||||
full_fname(fnametmp), fname);
|
||||
return 0;
|
||||
|
||||
30
rsync.h
30
rsync.h
@@ -92,6 +92,7 @@
|
||||
#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. */
|
||||
|
||||
@@ -110,7 +111,7 @@
|
||||
|
||||
/* Update this if you make incompatible changes and ALSO update the
|
||||
* SUBPROTOCOL_VERSION if it is not a final (official) release. */
|
||||
#define PROTOCOL_VERSION 31
|
||||
#define PROTOCOL_VERSION 32
|
||||
|
||||
/* This is used when working on a new protocol version or for any unofficial
|
||||
* protocol tweaks. It should be a non-zero value for each pre-release repo
|
||||
@@ -162,6 +163,29 @@
|
||||
/* 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 */
|
||||
@@ -958,12 +982,12 @@ struct sum_buf {
|
||||
uint32 sum1; /**< simple checksum */
|
||||
int32 chain; /**< next hash-table collision */
|
||||
short flags; /**< flag bits */
|
||||
char sum2[SUM_LENGTH]; /**< checksum */
|
||||
};
|
||||
|
||||
struct sum_struct {
|
||||
OFF_T flength; /**< total file length */
|
||||
struct sum_buf *sums; /**< points to info for each chunk */
|
||||
char *sum2_array; /**< checksums of length xfer_sum_len */
|
||||
int32 count; /**< how many chunks */
|
||||
int32 blength; /**< block_length */
|
||||
int32 remainder; /**< flength % block_length */
|
||||
@@ -982,6 +1006,8 @@ struct map_struct {
|
||||
int status; /* first errno from read errors */
|
||||
};
|
||||
|
||||
#define sum2_at(s, i) ((s)->sum2_array + ((size_t)(i) * xfer_sum_len))
|
||||
|
||||
#define NAME_IS_FILE (0) /* filter name as a file */
|
||||
#define NAME_IS_DIR (1<<0) /* filter name as a dir */
|
||||
#define NAME_IS_XATTR (1<<2) /* filter name as an xattr */
|
||||
|
||||
@@ -1073,6 +1073,16 @@ 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
|
||||
@@ -1188,6 +1198,9 @@ An example nginx proxy setup is as follows:
|
||||
> }
|
||||
> ```
|
||||
|
||||
If rsyncd should be accessible encrypted and unencrypted at the same time make
|
||||
the proxy listen on port 873 as well and let it handle both streams.
|
||||
|
||||
## DAEMON CONFIG EXAMPLES
|
||||
|
||||
A simple rsyncd.conf file that allow anonymous rsync to a ftp area at
|
||||
@@ -1260,7 +1273,7 @@ Rsync is distributed under the GNU General Public License. See the file
|
||||
[COPYING](COPYING) for details.
|
||||
|
||||
An rsync web site is available at <https://rsync.samba.org/> and its github
|
||||
project is <https://github.com/WayneD/rsync>.
|
||||
project is <https://github.com/RsyncProject/rsync>.
|
||||
|
||||
## THANKS
|
||||
|
||||
@@ -1270,8 +1283,7 @@ Thanks to Karsten Thygesen for his many suggestions and documentation!
|
||||
## AUTHOR
|
||||
|
||||
Rsync was originally written by Andrew Tridgell and Paul Mackerras. Many
|
||||
people have later contributed to it. It is currently maintained by Wayne
|
||||
Davison.
|
||||
people from around the world have helped to maintain and improve it.
|
||||
|
||||
Mailing lists for support and development are available at
|
||||
<https://lists.samba.org/>.
|
||||
|
||||
485
runtests.py
Executable file
485
runtests.py
Executable file
@@ -0,0 +1,485 @@
|
||||
#!/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
360
runtests.sh
@@ -1,360 +0,0 @@
|
||||
#! /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
|
||||
30
sender.c
30
sender.c
@@ -31,6 +31,7 @@ extern int log_before_transfer;
|
||||
extern int stdout_format_has_i;
|
||||
extern int logfile_format_has_i;
|
||||
extern int want_xattr_optim;
|
||||
extern int xfer_sum_len;
|
||||
extern int csum_length;
|
||||
extern int append_mode;
|
||||
extern int copy_links;
|
||||
@@ -47,6 +48,8 @@ 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;
|
||||
@@ -94,10 +97,11 @@ static struct sum_struct *receive_sums(int f)
|
||||
return(s);
|
||||
|
||||
s->sums = new_array(struct sum_buf, s->count);
|
||||
s->sum2_array = new_array(char, (size_t)s->count * xfer_sum_len);
|
||||
|
||||
for (i = 0; i < s->count; i++) {
|
||||
s->sums[i].sum1 = read_int(f);
|
||||
read_buf(f, s->sums[i].sum2, s->s2length);
|
||||
read_buf(f, sum2_at(s, i), s->s2length);
|
||||
|
||||
s->sums[i].offset = offset;
|
||||
s->sums[i].flags = 0;
|
||||
@@ -136,6 +140,8 @@ 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;
|
||||
@@ -260,6 +266,8 @@ 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)) {
|
||||
@@ -348,7 +356,25 @@ void send_files(int f_in, int f_out)
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
|
||||
fd = do_open(fname, O_RDONLY, 0);
|
||||
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];
|
||||
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;
|
||||
}
|
||||
fd = secure_relative_open(module_dir, secure_path, O_RDONLY, 0);
|
||||
} else {
|
||||
fd = do_open_checklinks(fname);
|
||||
}
|
||||
if (fd == -1) {
|
||||
if (errno == ENOENT) {
|
||||
enum logcode c = am_daemon && protocol_version < 28 ? FERROR : FWARNING;
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
#endif
|
||||
|
||||
// Missing from the headers on gcc 6 and older, clang 8 and older
|
||||
typedef long long __m128i_u __attribute__((__vector_size__(16), __may_alias__, __aligned__(1)));
|
||||
typedef long long __m256i_u __attribute__((__vector_size__(32), __may_alias__, __aligned__(1)));
|
||||
typedef long long __m128i_u __attribute__((__vector_size__(16), __may_alias__, __aligned__(16)));
|
||||
typedef long long __m256i_u __attribute__((__vector_size__(32), __may_alias__, __aligned__(16)));
|
||||
|
||||
/* Compatibility macros to let our SSSE3 algorithm run with only SSE2.
|
||||
These used to be neat individual functions with target attributes switching between SSE2 and SSSE3 implementations
|
||||
@@ -347,8 +347,7 @@ __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;
|
||||
mul_one = _mm256_abs_epi8(_mm256_cmpeq_epi16(mul_one,mul_one)); // set all vector elements to 1
|
||||
__m256i mul_one = _mm256_set1_epi8(1);
|
||||
|
||||
for (; i < (len-64); i+=64) {
|
||||
// Load ... 4*[int8*16]
|
||||
@@ -548,6 +547,118 @@ 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__ */
|
||||
|
||||
30
socket.c
30
socket.c
@@ -47,21 +47,23 @@ 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[1024];
|
||||
char *authhdr, authbuf[1024];
|
||||
char *cp, buffer[PROXY_BUF_SIZE + 1];
|
||||
char *authhdr, authbuf[PROXY_BUF_SIZE + 1];
|
||||
int len;
|
||||
|
||||
if (proxy_user && proxy_pass) {
|
||||
stringjoin(buffer, sizeof buffer,
|
||||
stringjoin(buffer, PROXY_BUF_SIZE,
|
||||
proxy_user, ":", proxy_pass, NULL);
|
||||
len = strlen(buffer);
|
||||
|
||||
if ((len*8 + 5) / 6 >= (int)sizeof authbuf - 3) {
|
||||
if ((len*8 + 5) / 6 >= PROXY_BUF_SIZE - 3) {
|
||||
rprintf(FERROR,
|
||||
"authentication information is too long\n");
|
||||
return -1;
|
||||
@@ -74,14 +76,14 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
|
||||
authhdr = "";
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
if (write(fd, buffer, len) != len) {
|
||||
rsyserr(FERROR, errno, "failed to write to proxy");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
|
||||
for (cp = buffer; cp < &buffer[PROXY_BUF_SIZE - 1]; cp++) {
|
||||
if (read(fd, cp, 1) != 1) {
|
||||
rsyserr(FERROR, errno, "failed to read from proxy");
|
||||
return -1;
|
||||
@@ -90,11 +92,13 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
|
||||
break;
|
||||
}
|
||||
|
||||
if (*cp != '\n')
|
||||
cp++;
|
||||
*cp-- = '\0';
|
||||
if (*cp == '\r')
|
||||
*cp = '\0';
|
||||
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 (strncmp(buffer, "HTTP/", 5) != 0) {
|
||||
rprintf(FERROR, "bad response from proxy -- %s\n",
|
||||
buffer);
|
||||
@@ -110,7 +114,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[sizeof buffer - 1]; cp++) {
|
||||
for (cp = buffer; cp < &buffer[PROXY_BUF_SIZE]; cp++) {
|
||||
if (read(fd, cp, 1) != 1) {
|
||||
rsyserr(FERROR, errno,
|
||||
"failed to read from proxy");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os, re, argparse, subprocess
|
||||
from datetime import datetime
|
||||
from datetime import datetime, UTC
|
||||
|
||||
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.utcfromtimestamp(commit_time).strftime("%Y-%m-%d %H:%M:%S")
|
||||
ts = datetime.fromtimestamp(commit_time, UTC).strftime("%Y-%m-%d %H:%M:%S")
|
||||
chg = '.' if mtime == commit_time else '*'
|
||||
print(chg, ts, fn)
|
||||
|
||||
|
||||
11
support/install_deps_ubuntu.sh
Executable file
11
support/install_deps_ubuntu.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# install script for build dependencies for ubuntu/debian systems
|
||||
|
||||
sudo apt install -y gcc g++ gawk autoconf automake python3-cmarkgfm
|
||||
sudo apt install -y acl libacl1-dev
|
||||
sudo apt install -y attr libattr1-dev
|
||||
sudo apt install -y libxxhash-dev
|
||||
sudo apt install -y libzstd-dev
|
||||
sudo apt install -y liblz4-dev
|
||||
sudo apt install -y libssl-dev
|
||||
@@ -46,6 +46,7 @@ long_opts = {
|
||||
'compare-dest': 2,
|
||||
'compress-choice': 1,
|
||||
'compress-level': 1,
|
||||
'compress-threads': 1,
|
||||
'copy-dest': 2,
|
||||
'copy-devices': -1,
|
||||
'copy-unsafe-links': 0,
|
||||
@@ -59,6 +60,7 @@ long_opts = {
|
||||
'delete-during': 0,
|
||||
'delete-excluded': 0,
|
||||
'delete-missing-args': 0,
|
||||
'dirs': 0,
|
||||
'existing': 0,
|
||||
'fake-super': 0,
|
||||
'files-from': 3,
|
||||
@@ -156,6 +158,10 @@ def main():
|
||||
command = os.environ.get('SSH_ORIGINAL_COMMAND', None)
|
||||
if not command:
|
||||
die("Not invoked via sshd")
|
||||
if command == 'true':
|
||||
# Allow checking connectivity with "ssh <host> true". (For example,
|
||||
# rsbackup uses this.)
|
||||
sys.exit(0)
|
||||
command = command.split(' ', 2)
|
||||
if command[0:1] != ['rsync']:
|
||||
die("SSH_ORIGINAL_COMMAND does not run rsync")
|
||||
@@ -296,6 +302,7 @@ 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)")
|
||||
|
||||
@@ -5,7 +5,7 @@ rrsync - a script to setup restricted rsync users via ssh logins
|
||||
## SYNOPSIS
|
||||
|
||||
```
|
||||
rrsync [-ro|-rw] [-munge] [-no-del] [-no-lock] [-no-overwrite] DIR
|
||||
rrsync [-ro|-wo] [-munge] [-no-del] [-no-lock] [-no-overwrite] DIR
|
||||
```
|
||||
|
||||
The single non-option argument specifies the restricted _DIR_ to use. It can be
|
||||
@@ -163,7 +163,7 @@ rsync is distributed under the GNU General Public License. See the file
|
||||
[COPYING](COPYING) for details.
|
||||
|
||||
An rsync web site is available at <https://rsync.samba.org/> and its github
|
||||
project is <https://github.com/WayneD/rsync>.
|
||||
project is <https://github.com/RsyncProject/rsync>.
|
||||
|
||||
## AUTHOR
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
REAL_RSYNC=/usr/bin/rsync
|
||||
IGNOREEXIT=24
|
||||
IGNOREOUT='^(file has vanished: |rsync warning: some files vanished before they could be transferred)'
|
||||
IGNOREOUT='^((file|directory) 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
|
||||
|
||||
117
t_chmod_secure.c
Normal file
117
t_chmod_secure.c
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
151
t_secure_relpath.c
Normal file
151
t_secure_relpath.c
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
4
t_stub.c
4
t_stub.c
@@ -23,12 +23,14 @@
|
||||
|
||||
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;
|
||||
int module_id = -1;
|
||||
int relative_paths = 0;
|
||||
int module_dirlen = 0;
|
||||
unsigned int module_dirlen = 0;
|
||||
int preserve_xattrs = 0;
|
||||
int preserve_perms = 0;
|
||||
int preserve_executability = 0;
|
||||
|
||||
@@ -28,6 +28,9 @@ int am_root = 0;
|
||||
int am_sender = 1;
|
||||
int read_only = 0;
|
||||
int list_only = 0;
|
||||
int copy_links = 0;
|
||||
int copy_unsafe_links = 0;
|
||||
|
||||
short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
|
||||
|
||||
int
|
||||
|
||||
113
testsuite/alt-dest-symlink-race.test
Executable file
113
testsuite/alt-dest-symlink-race.test
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/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
|
||||
206
testsuite/bare-do-open-symlink-race.test
Executable file
206
testsuite/bare-do-open-symlink-race.test
Executable file
@@ -0,0 +1,206 @@
|
||||
#!/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
|
||||
135
testsuite/chdir-symlink-race.test
Executable file
135
testsuite/chdir-symlink-race.test
Executable file
@@ -0,0 +1,135 @@
|
||||
#!/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
|
||||
68
testsuite/chmod-symlink-race.test
Executable file
68
testsuite/chmod-symlink-race.test
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/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
|
||||
27
testsuite/clean-fname-underflow.test
Normal file
27
testsuite/clean-fname-underflow.test
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/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
|
||||
98
testsuite/copy-dest-source-symlink.test
Executable file
98
testsuite/copy-dest-source-symlink.test
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/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
|
||||
111
testsuite/daemon-chroot-acl.test
Normal file
111
testsuite/daemon-chroot-acl.test
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/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
|
||||
51
testsuite/daemon-refuse-compress.test
Normal file
51
testsuite/daemon-refuse-compress.test
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/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
|
||||
@@ -77,5 +77,12 @@ 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
|
||||
|
||||
@@ -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"
|
||||
ls -la /etc >"$longdir/2" || [ $? -eq 1 ]
|
||||
else
|
||||
ls -la / >"$longdir/2"
|
||||
ls -la / >"$longdir/2" || [ $? -eq 1 ]
|
||||
fi
|
||||
checkit "$RSYNC --delete -avH '$fromdir/' '$todir'" "$fromdir/" "$todir"
|
||||
|
||||
|
||||
32
testsuite/open-noatime.test
Normal file
32
testsuite/open-noatime.test
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/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
|
||||
@@ -10,22 +10,28 @@
|
||||
. "$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 (probably need root)"
|
||||
pr_lvl=`cat /proc/sys/fs/protected_regular 2>/dev/null` || test_skipped "Can't check if fs.protected_regular is enabled"
|
||||
test "$pr_lvl" != 0 || test_skipped "fs.protected_regular is not enabled"
|
||||
|
||||
workdir="$tmpdir/files"
|
||||
mkdir "$workdir"
|
||||
mkdir -p "$workdir"
|
||||
chmod 1777 "$workdir"
|
||||
|
||||
echo "Source" > "$workdir/src"
|
||||
echo "" > "$workdir/dst"
|
||||
chown 5001 "$workdir/dst" || test_skipped "Can't chown (probably need root)"
|
||||
|
||||
# Output is only shown in case of an error
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
128
testsuite/proxy-response-line-too-long.test
Executable file
128
testsuite/proxy-response-line-too-long.test
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user