diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 5112ef885..000000000 --- a/.cirrus.yml +++ /dev/null @@ -1,23 +0,0 @@ -freebsd_task: - name: FreeBSD - freebsd_instance: - image_family: freebsd-12-2 - 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 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 9e34b4c97..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,120 +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@v2 - - 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 - - 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@v2 - with: - name: ubuntu-bin - path: | - rsync - rsync-ssl - rsync.1 - rsync-ssl.1 - rsyncd.conf.5 - - macos-build: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - 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 - - 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@v2 - with: - name: macos-bin - path: | - rsync - rsync-ssl - rsync.1 - rsync-ssl.1 - rsyncd.conf.5 - - cygwin-build: - runs-on: windows-latest - if: (github.event_name == 'schedule' || contains(github.event.head_commit.message, '[buildall]')) - steps: - - uses: actions/checkout@v2 - - uses: crazy-max/ghaction-chocolatey@v1.2.2 - with: - args: install -y --no-progress cygwin cyg-get - - name: prep - run: | - cyg-get make autoconf automake gcc-core attr libattr-devel python38 python38-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' - - 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@v2 - with: - name: cygwin-bin - path: | - rsync.exe - rsync-ssl - rsync.1 - rsync-ssl.1 - rsyncd.conf.5 diff --git a/.github/workflows/cygwin-build.yml b/.github/workflows/cygwin-build.yml new file mode 100644 index 000000000..dc14cb9f5 --- /dev/null +++ b/.github/workflows/cygwin-build.yml @@ -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,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@v4 + with: + name: cygwin-bin + path: | + rsync.exe + rsync-ssl + rsync.1 + rsync-ssl.1 + rsyncd.conf.5 + rrsync.1 + rrsync diff --git a/.github/workflows/freebsd-build.yml b/.github/workflows/freebsd-build.yml new file mode 100644 index 000000000..749a6d76f --- /dev/null +++ b/.github/workflows/freebsd-build.yml @@ -0,0 +1,49 @@ +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 + ./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 diff --git a/.github/workflows/macos-build.yml b/.github/workflows/macos-build.yml new file mode 100644 index 000000000..5471bf534 --- /dev/null +++ b/.github/workflows/macos-build.yml @@ -0,0 +1,53 @@ +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 + sudo pip3 install commonmark + 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 diff --git a/.github/workflows/solaris-build.yml b/.github/workflows/solaris-build.yml new file mode 100644 index 000000000..50ba7501d --- /dev/null +++ b/.github/workflows/solaris-build.yml @@ -0,0 +1,49 @@ +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 + ./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 diff --git a/.github/workflows/ubuntu-build.yml b/.github/workflows/ubuntu-build.yml new file mode 100644 index 000000000..5efadce5b --- /dev/null +++ b/.github/workflows/ubuntu-build.yml @@ -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 diff --git a/.gitignore b/.gitignore index 13c6db8dc..9e59c9c40 100644 --- a/.gitignore +++ b/.gitignore @@ -15,9 +15,11 @@ config.status aclocal.m4 /proto.h /proto.h-tstamp -/rsync*.1 -/rsync*.5 +/rsync*.[15] +/rrsync +/rrsync*.1 /rsync*.html +/rrsync*.html /help-rsync*.h /default-cvsignore.h /default-dont-compress.h diff --git a/INSTALL.md b/INSTALL.md index 1f373c691..19d390545 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -13,11 +13,11 @@ You need to have a C compiler installed and optionally a C++ compiler in order to try to build some hardware-accelerated checksum routines. Rsync also needs a modern awk, which might be provided via gawk or nawk on some OSes. -## Autoconf & man pages +## Autoconf & manpages If you're installing from the git repo (instead of a release tar file) you'll also need the GNU autotools (autoconf & automake) and your choice of 2 python3 -markdown libraries: cmarkgfm or commonmark (needed to generate the man pages). +markdown libraries: cmarkgfm or commonmark (needed to generate the manpages). If your OS doesn't provide a python3-cmarkgfm or python3-commonmark package, you can run the following to install the commonmark python library for your build user (after installing python3's pip package): @@ -26,9 +26,9 @@ build user (after installing python3's pip package): You can test if you've got it fixed by running (from the rsync checkout): -> ./md2man --test rsync-ssl.1.md +> ./md-convert --test rsync-ssl.1.md -Alternately, you can avoid generating the man pages by fetching the very latest +Alternately, you can avoid generating the manpages by fetching the very latest versions (that match the latest git source) from the [generated-files][6] dir. One way to do that is to run: @@ -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 @@ -178,9 +180,9 @@ config.h, or just override them in your /etc/rsyncd.conf file. As of 2.4.7, rsync uses Eric Troan's popt option-parsing library. A cut-down copy of a recent release is included in the rsync distribution, and will be used if there is no popt library on your build host, or if -the --with-included-popt option is passed to ./configure. +the `--with-included-popt` option is passed to ./configure. -If you configure using --enable-maintainer-mode, then rsync will try +If you configure using `--enable-maintainer-mode`, then rsync will try to pop up an xterm on DISPLAY=:0 if it crashes. You might find this useful, but it should be turned off for production builds. @@ -194,7 +196,7 @@ This is helpful when using the branch-from-patch and patch-update scripts to maintain the official rsync patches. If you ever need to build from a "detached head" git position then you'll need to manually chdir into the build dir to run make. I also like to create 2 more symlinks in the -source dir: ln -s build/rsync . ; ln -s build/testtmp . +source dir: `ln -s build/rsync . ; ln -s build/testtmp .` ## Make compatibility @@ -228,7 +230,10 @@ Some versions of Mac OS X (Darwin) seem to have an IPv6 stack, but do 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. +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 diff --git a/Makefile.in b/Makefile.in index c4c00e964..6340403be 100644 --- a/Makefile.in +++ b/Makefile.in @@ -6,6 +6,7 @@ exec_prefix=@exec_prefix@ bindir=@bindir@ libdir=@libdir@/rsync mandir=@mandir@ +with_rrsync=@with_rrsync@ LIBS=@LIBS@ CC=@CC@ @@ -29,11 +30,13 @@ SHELL=/bin/sh .SUFFIXES: .SUFFIXES: .c .o -SIMD_x86_64=simd-checksum-x86_64.o simd-checksum-avx2.o -ASM_x86_64=lib/md5-asm-x86_64.o +ROLL_SIMD_x86_64=simd-checksum-x86_64.o +ROLL_ASM_x86_64=simd-checksum-avx2.o +MD5_ASM_x86_64=lib/md5-asm-x86_64.o GENFILES=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 + rsync-ssl.1 rsync-ssl.1.html rsyncd.conf.5 rsyncd.conf.5.html \ + @GEN_RRSYNC@ HEADERS=byteorder.h config.h errcode.h proto.h rsync.h ifuncs.h itypes.h inums.h \ lib/pool_alloc.h lib/mdigest.h lib/md-defines.h LIBOBJ=lib/wildmatch.o lib/compat.o lib/snprintf.o lib/mdfour.o lib/md5.o \ @@ -44,11 +47,11 @@ OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \ util1.o util2.o main.o checksum.o match.o syscall.o log.o backup.o delete.o OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o hashtable.o \ usage.o fileio.o batch.o clientname.o chmod.o acls.o xattrs.o -OBJS3=progress.o pipe.o @ASM@ +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 -OBJS=$(OBJS1) $(OBJS2) $(OBJS3) @SIMD@ $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@ +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@ @@ -67,7 +70,9 @@ CHECK_OBJS=tls.o testrun.o getgroups.o getfsdev.o t_stub.o t_unsafe.o trimslash. $(CC) -I. -I$(srcdir) $(CFLAGS) $(CPPFLAGS) -c $< @CC_SHOBJ_FLAG@ @OBJ_RESTORE@ -all: Makefile rsync$(EXEEXT) stunnel-rsyncd.conf @MAKE_MAN@ +# NOTE: consider running "packaging/smart-make" instead of "make" to auto-handle +# any changes to configure.sh and the main Makefile prior to a "make all". +all: Makefile rsync$(EXEEXT) stunnel-rsyncd.conf @MAKE_RRSYNC@ @MAKE_MAN@ .PHONY: all .PHONY: install @@ -80,6 +85,10 @@ install: all if test -f rsync.1; then $(INSTALLMAN) -m 644 rsync.1 $(DESTDIR)$(mandir)/man1; fi if test -f rsync-ssl.1; then $(INSTALLMAN) -m 644 rsync-ssl.1 $(DESTDIR)$(mandir)/man1; fi if test -f rsyncd.conf.5; then $(INSTALLMAN) -m 644 rsyncd.conf.5 $(DESTDIR)$(mandir)/man5; fi + if test "$(with_rrsync)" = yes; then \ + $(INSTALLCMD) -m 755 rrsync $(DESTDIR)$(bindir); \ + if test -f rrsync.1; then $(INSTALLMAN) -m 644 rrsync.1 $(DESTDIR)$(mandir)/man1; fi; \ + fi install-ssl-daemon: stunnel-rsyncd.conf -$(MKDIR_P) $(DESTDIR)/etc/stunnel @@ -96,6 +105,9 @@ install-strip: rsync$(EXEEXT): $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) +rrsync: support/rrsync + cp -p $(srcdir)/support/rrsync rrsync + $(OBJS): $(HEADERS) $(CHECK_OBJS): $(HEADERS) tls.o xattrs.o: lib/sysxattrs.h @@ -138,13 +150,13 @@ git-version.h: ALWAYS_RUN ALWAYS_RUN: simd-checksum-x86_64.o: simd-checksum-x86_64.cpp - @$(srcdir)/cmd-or-msg disable-simd $(CXX) -I. $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $(srcdir)/simd-checksum-x86_64.cpp + @$(srcdir)/cmd-or-msg disable-roll-simd $(CXX) -I. $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $(srcdir)/simd-checksum-x86_64.cpp simd-checksum-avx2.o: simd-checksum-avx2.S - @$(srcdir)/cmd-or-msg disable-asm $(CC) $(CFLAGS) --include=$(srcdir)/rsync.h -DAVX2_ASM -I. @NOEXECSTACK@ -c -o $@ $(srcdir)/simd-checksum-avx2.S + @$(srcdir)/cmd-or-msg disable-roll-asm $(CC) $(CFLAGS) -I. @NOEXECSTACK@ -c -o $@ $(srcdir)/simd-checksum-avx2.S -lib/md5-asm-x86_64.o: lib/md5-asm-x86_64.S config.h lib/md-defines.h - @$(srcdir)/cmd-or-msg disable-asm $(CC) -I. @NOEXECSTACK@ -c -o $@ $(srcdir)/lib/md5-asm-x86_64.S +lib/md5-asm-x86_64.o: lib/md5-asm-x86_64.S lib/md-defines.h + @$(srcdir)/cmd-or-msg disable-md5-asm $(CC) -I. @NOEXECSTACK@ -c -o $@ $(srcdir)/lib/md5-asm-x86_64.S tls$(EXEEXT): $(TLS_OBJ) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TLS_OBJ) $(LIBS) @@ -172,14 +184,6 @@ 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 @@ -200,7 +204,7 @@ configure.sh config.h.in: configure.ac aclocal.m4 else \ echo "config.h.in has CHANGED."; \ fi - @if test -f configure.sh.old -o -f config.h.in.old; then \ + @if test -f configure.sh.old || test -f config.h.in.old; then \ if test "$(MAKECMDGOALS)" = reconfigure; then \ echo 'Continuing with "make reconfigure".'; \ else \ @@ -247,22 +251,25 @@ proto.h-tstamp: $(srcdir)/*.c $(srcdir)/lib/compat.c daemon-parm.h $(AWK) -f $(srcdir)/mkproto.awk $(srcdir)/*.c $(srcdir)/lib/compat.c daemon-parm.h .PHONY: man -man: rsync.1 rsync-ssl.1 rsyncd.conf.5 +man: rsync.1 rsync-ssl.1 rsyncd.conf.5 @MAKE_RRSYNC_1@ + +rsync.1: rsync.1.md md-convert version.h Makefile + @$(srcdir)/maybe-make-man rsync.1.md -rsync.1: rsync.1.md md2man version.h Makefile - @$(srcdir)/maybe-make-man $(srcdir) rsync.1.md +rsync-ssl.1: rsync-ssl.1.md md-convert version.h Makefile + @$(srcdir)/maybe-make-man rsync-ssl.1.md -rsync-ssl.1: rsync-ssl.1.md md2man version.h Makefile - @$(srcdir)/maybe-make-man $(srcdir) rsync-ssl.1.md +rsyncd.conf.5: rsyncd.conf.5.md md-convert version.h Makefile + @$(srcdir)/maybe-make-man rsyncd.conf.5.md -rsyncd.conf.5: rsyncd.conf.5.md md2man version.h Makefile - @$(srcdir)/maybe-make-man $(srcdir) rsyncd.conf.5.md +rrsync.1: support/rrsync.1.md md-convert Makefile + @$(srcdir)/maybe-make-man support/rrsync.1.md .PHONY: clean clean: cleantests - rm -f *~ $(OBJS) $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_SYMLINKS) \ - git-version.h rounding rounding.h *.old rsync*.1 rsync*.5 rsync*.html \ - daemon-parm.h help-*.h default-*.h proto.h proto.h-tstamp + rm -f *~ $(OBJS) $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_SYMLINKS) @MAKE_RRSYNC@ \ + git-version.h rounding rounding.h *.old rsync*.1 rsync*.5 @MAKE_RRSYNC_1@ \ + *.html daemon-parm.h help-*.h default-*.h proto.h proto.h-tstamp .PHONY: cleantests cleantests: @@ -351,4 +358,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/ diff --git a/NEWS.md b/NEWS.md index cfcad9f41..96608cf94 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,80 +1,580 @@ - +# NEWS for rsync 3.4.2 (UNRELEASED) -# NEWS for rsync 3.2.4 (UNRELEASED) +## Changes in this version: + +### BUG FIXES: + +- ... + +------------------------------------------------------------------------------ + +# 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: + +### BUG FIXES: + +- Fixed a bug with `--sparse --inplace` where a trailing gap in the source + file would not clear out the trailing data in the destination file. + +- Fixed an buffer overflow in the checksum2 code if SHA1 is being used for + the checksum2 algorithm. + +- Fixed an issue when rsync is compiled using `_FORTIFY_SOURCE` so that the + extra tests don't complain about a strlcpy() limit value (which was too + large, even though it wasn't possible for the larger value to cause an + overflow). + +- Add a backtick to the list of characters that the filename quoting needs to + escape using backslashes. + +- Fixed a string-comparison issue in the internal handling of `--progress` (a + locale such as tr_TR.utf-8 needed the internal triggering of `--info` options + to use upper-case flag names to ensure that they match). + +- Make sure that a local transfer marks the sender side as trusted. + +- Change the argv handling to work with a newer popt library -- one that likes + to free more data than it used to. + +- Rsync now calls `OpenSSL_add_all_algorithms()` when compiled against an older + openssl library. + +- Fixed a problem in the daemon auth for older protocols (29 and before) if the + openssl library is being used to compute MD4 checksums. + +- Fixed `rsync -VV` on Cygwin -- it needed a flush of stdout. + +- Fixed an old stats bug that counted devices as symlinks. + +### ENHANCEMENTS: + +- Enhanced rrsync with the `-no-overwrite` option that allows you to ensure + that existing files on your restricted but writable directory can't be + modified. + +- Enhanced the manpages to mark links with .UR & .UE. If your nroff doesn't + support these idioms, touch the file `.md2man-force` in the source directory + so that `md-convert` gets called with the `--force-link-text` option, and + that should ensure that your manpages are still readable even with the + ignored markup. + +- Some manpage improvements on the handling of [global] modules. + +- Changed the mapfrom & mapto perl scripts (in the support dir) into a single + python script named idmap. Converted a couple more perl scripts into python. + +- Changed the mnt-excl perl script (in the support dir) into a python script. + +### DEVELOPER RELATED: + + - Updated config.guess (timestamp 2023-01-01) and config.sub (timestamp + 2023-01-21). + +------------------------------------------------------------------------------ + +# NEWS for rsync 3.2.7 (20 Oct 2022) + +## Changes in this version: + +### BUG FIXES: + +- Fixed the client-side validating of the remote sender's filtering behavior. + +- More fixes for the "unrequested file-list name" name, including a copy of + "/" with `--relative` enabled and a copy with a lot of related paths with + `--relative` enabled (often derived from a `--files-from` list). + +- When rsync gets an unpack error on an ACL, mention the filename. + +- Avoid over-setting sanitize_paths when a daemon is serving "/" (even if + "use chroot" is false). + +### ENHANCEMENTS: + +- Added negotiated daemon-auth support that allows a stronger checksum digest + to be used to validate a user's login to the daemon. Added SHA512, SHA256, + and SHA1 digests to MD5 & MD4. These new digests are at the highest priority + in the new daemon-auth negotiation list. + +- Added support for the SHA1 digest in file checksums. While this tends to be + overkill, it is available if someone really needs it. This overly-long + checksum is at the lowest priority in the normal checksum negotiation list. + See [`--checksum-choice`](rsync.1#opt) (`--cc`) and the `RSYNC_CHECKSUM_LIST` + environment var for how to customize this. + +- Improved the xattr hash table to use a 64-bit key without slowing down the + key's computation. This should make extra sure that a hash collision doesn't + happen. + +- If the `--version` option is repeated (e.g. `-VV`) then the information is + output in a (still readable) JSON format. Client side only. + +- The script `support/json-rsync-version` is available to get the JSON style + version output from any rsync. The script accepts either text on stdin + **or** an arg that specifies an rsync executable to run with a doubled + `--version` option. If the text we get isn't already in JSON format, it is + converted. Newer rsync versions will provide more complete json info than + older rsync versions. Various tweaks are made to keep the flag names + consistent across versions. + +- The [`use chroot`](rsyncd.conf.5#) daemon parameter now defaults to "unset" + so that rsync can use chroot when it works and a sanitized copy when chroot + is not supported (e.g., for a non-root daemon). Explicitly setting the + parameter to true or false (on or off) behaves the same way as before. + +- The `--fuzzy` option was optimized a bit to try to cut down on the amount of + computations when considering a big pool of files. The simple heuristic from + Kenneth Finnegan resulted in about a 2x speedup. + +- If rsync is forced to use protocol 29 or before (perhaps due to talking to an + rsync before 3.0.0), the modify time of a file is limited to 4-bytes. Rsync + now interprets this value as an unsigned integer so that a current year past + 2038 can continue to be represented. This does mean that years prior to 1970 + cannot be represented in an older protocol, but this trade-off seems like the + right choice given that (1) 2038 is very rapidly approaching, and (2) newer + protocols support a much wider range of old and new dates. + +- The rsync client now treats an empty destination arg as an error, just like + it does for an empty source arg. This doesn't affect a `host:` arg (which is + treated the same as `host:.`) since the arg is not completely empty. The use + of [`--old-args`](rsync.1#opt) (including via `RSYNC_OLD_ARGS`) allows the + prior behavior of treating an empty destination arg as a ".". + +### PACKAGING RELATED: + +- The checksum code now uses openssl's EVP methods, which gets rid of various + deprecation warnings and makes it easy to support more digest methods. On + newer systems, the MD4 digest is marked as legacy in the openssl code, which + makes openssl refuse to support it via EVP. You can choose to ignore this + and allow rsync's MD4 code to be used for older rsync connections (when + talking to an rsync prior to 3.0.0) or you can choose to configure rsync to + tell openssl to enable legacy algorithms (see below). + +- A simple openssl config file is supplied that can be installed for rsync to + use. If you install packaging/openssl-rsync.cnf to a public spot (such as + `/etc/ssl/openssl-rsync.cnf`) and then run configure with the option + `--with-openssl-conf=/path/name.cnf`, this will cause rsync to export the + configured path in the OPENSSL_CONF environment variable (when the variable + is not already set). This will enable openssl's MD4 code for rsync to use. + +- The packager may wish to include an explicit "use chroot = true" in the top + section of their supplied /etc/rsyncd.conf file if the daemon is being + installed to run as the root user (though rsync should behave the same even + with the value unset, a little extra paranoia doesn't hurt). + +- I've noticed that some packagers haven't installed support/nameconvert for + users to use in their chrooted rsync configs. Even if it is not installed + as an executable script (to avoid a python3 dependency) it would be good to + install it with the other rsync-related support scripts. + +- It would be good to add support/json-rsync-version to the list of installed + support scripts. + +------------------------------------------------------------------------------ + +# NEWS for rsync 3.2.6 (9 Sep 2022) + +## Changes in this version: + +### BUG FIXES: + +- More path-cleaning improvements in the file-list validation code to avoid + rejecting of valid args. + +- A file-list validation fix for a [`--files-from`](rsync.1#opt) file that ends + without a line-terminating character. + +- Added a safety check that prevents the sender from removing destination files + when a local copy using [`--remove-source-files`](rsync.1#opt) has some files + that are shared between the sending & receiving hierarchies, including the + case where the source dir & destination dir are identical. + +- Fixed a bug in the internal MD4 checksum code that could cause the digest + to be sporadically incorrect (the openssl version was/is fine). + +- A minor tweak to rrsync added "copy-devices" to the list of known args, but + left it disabled by default. + +### ENHANCEMENTS: + +- Rename `--protect-args` to [`--secluded-args`](rsync.1#opt) to make it + clearer how it differs from the default backslash-escaped arg-protecting + behavior of rsync. The old option names are still accepted. The + environment-variable override did not change its name. + +### PACKAGING RELATED: + +- The configure option `--with-protected-args` was renamed to + `--with-secluded-args`. This option makes `--secluded-args` the default + rsync behavior instead of using backslash escaping for protecting args. + +- The mkgitver script now makes sure that a `.git` dir/file is in the top-level + source dir before calling `git describe`. It also runs a basic check on the + version value. This should avoid using an unrelated git description for + rsync's version. + +### DEVELOPER RELATED: + +- The configure script no longer sets the -pedantic-errors CFLAG (which it + used to try to do only for gcc). + +- The name_num_obj struct was modified to allow its dynamic name_num_item list + to be initialized in a better way. + +------------------------------------------------------------------------------ + +# NEWS for rsync 3.2.5 (14 Aug 2022) + +## Changes in this version: + +### SECURITY FIXES: + +- Added some file-list safety checking that helps to ensure that a rogue + sending rsync can't add unrequested top-level names and/or include recursive + names that should have been excluded by the sender. These extra safety + checks only require the receiver rsync to be updated. When dealing with an + untrusted sending host, it is safest to copy into a dedicated destination + directory for the remote content (i.e. don't copy into a destination + directory that contains files that aren't from the remote host unless you + trust the remote host). Fixes CVE-2022-29154. + + - A fix for CVE-2022-37434 in the bundled zlib (buffer overflow issue). + +### BUG FIXES: + +- Fixed the handling of filenames specified with backslash-quoted wildcards + when the default remote-arg-escaping is enabled. + +- Fixed the configure check for signed char that was causing a host that + defaults to unsigned characters to generate bogus rolling checksums. This + made rsync send mostly literal data for a copy instead of finding matching + data in the receiver's basis file (for a file that contains high-bit + characters). + +- Lots of manpage improvements, including an attempt to better describe how + include/exclude filters work. + +- If rsync is compiled with an xxhash 0.8 library and then moved to a system + with a dynamically linked xxhash 0.7 library, we now detect this and disable + the XX3 hashes (since these routines didn't stabilize until 0.8). + +### ENHANCEMENTS: + +- The [`--trust-sender`](rsync.1#opt) option was added as a way to bypass the + extra file-list safety checking (should that be required). + +### PACKAGING RELATED: + +- A note to those wanting to patch older rsync versions: the changes in this + release requires the quoted argument change from 3.2.4. Then, you'll want + every single code change from 3.2.5 since there is no fluff in this release. + +- The build date that goes into the manpages is now based on the developer's + release date, not on the build's local-timezone interpretation of the date. + +### DEVELOPER RELATED: + +- Configure now defaults GETGROUPS_T to gid_t when cross compiling. + +- Configure now looks for the bsd/string.h include file in order to fix the + build on a host that has strlcpy() in the main libc but not defined in the + main string.h file. + +------------------------------------------------------------------------------ + +# NEWS for rsync 3.2.4 (15 Apr 2022) ## Changes in this version: +### BEHAVIOR CHANGES: + + - A new form of arg protection was added that works similarly to the older + `--protect-args` ([`-s`](rsync.1#opt)) option but in a way that avoids + breaking things like rrsync (the restricted rsync script): rsync now uses + backslash escaping for sending "shell-active" characters to the remote shell + (such as `$(){}<>#&` and others). This includes spaces, so fetching a remote + file via a quoted filename value now works by default without any extra + quoting: + + ```shell + rsync -aiv host:'a simple file.pdf' . + ``` + + Wildcards are not escaped in filename args, but they are escaped in options + like the [`--suffix`](rsync.1#opt) and [`--usermap`](rsync.1#opt) values. + + If a script depends on the old arg behavior (perhaps because it quotes or + protects the args already, or perhaps because it expects arg splitting), + there are two easy ways to get things going with a modern rsync: either + `export RSYNC_OLD_ARGS=1` in the script's environment (perhaps in the script + itself) or add the option [`--old-args`](rsync.1#opt) to the rsync commands + that are run. See also the [ADVANCED USAGE](rsync.1#) section of rsync's + manpage for how to use a more modern arg style. + + - A long-standing bug was preventing rsync from figuring out the current + locale's decimal point character, which made rsync always output numbers + using the "C" locale. Since this is now fixed in 3.2.4, a script that + parses rsync's decimal numbers (e.g. from the verbose footer) may want to + setup the environment in a way that the output continues to be in the C + locale. For instance, one of the following should work fine: + + ```shell + export LC_ALL=C.UTF-8 + ``` + + or if iconv translations are needed: + + ```shell + if [ "${LC_ALL:-}" ]; then + export LANG="$LC_ALL" + export LC_CTYPE="$LC_ALL" + unset LC_ALL + fi + export LC_NUMERIC=C.UTF-8 + ``` + +### SECURITY FIXES: + + - A fix for CVE-2018-25032 in the bundled zlib (memory corruption issue). + ### BUG FIXES: - - Fixed a bug with `--inplace` + `--sparse` where the destination file could - get reconstructed with bogus data. This bug can be worked-around in older - rsync versions by also specifying `--no-W -M--no-W`. When running 3.2.4 or - newer for your copy, rsync now sends `--no-W` to the remote rsync in such a - scenario (just in case the remote rsync is a version with this bug). + - Fixed a bug with [`--inplace`](rsync.1#opt) + [`--sparse`](rsync.1#opt) (and + a lack of [`--whole-file`](rsync.1#opt)) where the destination file could + get reconstructed with bogus data. Since the bug can also be avoided by + using (the seemingly redundant) [`--no-W`](rsync.1#opt) on the receiving + side, the latest rsync will now send `--no-W` to a remote receiver when this + option combination occurs. If your client rsync is not new enough to do + this for you (or if you're just paranoid), you can manually specify `--no-W + -M--no-W` (when not using [`--whole-file`](rsync.1#opt)) to make sure the + bug is avoided. - - Fixed a bug with `--mkpath` if a single-file copy specifies an existing - destination dir with a non-existing destination filename. + - Fixed a bug with [`--mkpath`](rsync.1#opt) if a single-file copy specifies + an existing destination dir with a non-existing destination filename. - Fixed `--update -vv` to output "is uptodate" instead of "is newer" messages - for files that are being skipped due to an identical modify time. (This - was a new output quirk in 3.2.3.) + for files that are being skipped due to an identical modify time. (This was + a new output quirk in 3.2.3.) - When doing an append transfer, the sending side's file must not get shorter or it is skipped. Fixes a crash that could occur when the size changes to 0 in the middle of the send negotiations. - - When dealing with a special file in an alt-dest hierarchy, rsync now checks - the non-permissions mode bits to ensure that the 2 special files are really - the same. + - When dealing with special files (see [`--specials`](rsync.1#opt)) in an + alt-dest hierarchy, rsync now checks the non-permission mode bits to ensure + that the 2 special files are really the same before hard-linking them + together. - - Fixed a bug where `--delay-updates` with stale partial data could cause a - file to fail to update. + - Fixed a bug where [`--delay-updates`](rsync.1#opt) with stale partial data + could cause a file to fail to update. - - Fixed a few places that would output an INFO message with `--info=NAME` that - should only have been output given `--verbose` or `--itemize-changes`. + - Fixed a few places that would output an INFO message with + [`--info=NAME`](rsync.1#opt) that should only have been output given + [`--verbose`](rsync.1#opt) or [`--itemize-changes`](rsync.1#opt). - - Avoid a weird failure if you run a local copy with a (useless) `--rsh` - option that contains a `V`. + - Avoid a weird failure if you run a local copy with a (useless) + [`--rsh`](rsync.1#opt) option that contains a `V` in the command. + + - Fixed a long-standing compression bug where the compression level of the + first file transferred affected the level for all future files. Also, the + per-file compression skipping has apparently never worked, so it is now + documented as being ineffective. + + - Fixed a truncate error when a `--write-devices` copy wrote a file onto a + device that was shorter than the device. + + - Made `--write-devices` support both `--checksum` and `--no-whole-file` when + copying to a device. + + - Improved how the [`--stop-at`](rsync.1#opt), [`--stop-after`](rsync.1#opt), + and (the deprecated) [`--time-limit`](rsync.1#opt) options check to see if + the allowed time is over, which should make rsync exit more consistently. + + - Tweak --progress to display "`??:??:??`" when the time-remaining value is so + large as to be meaningless. + + - Silence some chmod warnings about symlinks when it looks like we have a + function to set their permissions but they can't really be set. + + - Fixed a potential issue in git-set-file-times when handling commits with + high-bit characters in the description & when handling a description that + might mimic the git raw-commit deliniators. (See the support dir.) + + - The bundled systemd/rsync.service file now includes `Restart=on-failure`. ### ENHANCEMENTS: - Use openssl's `-verify_hostname` option in the rsync-ssl script. - - Added extra info to the "FILENAME exists" output of `--ignore-existing` when - `--info=skip2` is used. The skip message becomes "FILENAME exists (INFO)" - where the INFO is one of "type change", "sum change" (requires `-c`), "file - change" (based on the quick check), "attr change", or "uptodate". Prior - versions only supported `--info=skip1`. + - Added extra info to the "FILENAME exists" output of + [`--ignore-existing`](rsync.1#opt) when [`--info=skip2`](rsync.1#opt) is + used. The skip message becomes "FILENAME exists (INFO)" where the INFO is + one of "type change", "sum change" (requires [`--checksum`](rsync.1#opt)), + "file change" (based on the quick check), "attr change", or "uptodate". + Prior versions only supported `--info=skip1`. + + - Added the [`--fsync`](rsync.1#opt) option (promoted from the patches repo). - - Added the `--fsync` option (promoted from the patches repo). + - Added the [`--copy-devices`](rsync.1#opt) option. Compared to the + historical version from the rsync-patches repo, this version: properly + handles `--checksum`; fixes a truncation bug when doing an `--inplace` copy + onto a longer file; fixes several bugs in the `--itemize` output; and only + the sending side needs the enhanced rsync for the copy to work. - Reduced memory usage for an incremental transfer that has a bunch of small - diretories. + directories. - The rsync daemon can now handle a client address with an implied "%scope" suffix. - - Added support for `--atimes` on macOS and fixed using using it without -t. + - Added support for [`--atimes`](rsync.1#opt) on macOS and fixed a bug where + it wouldn't work without [`--times`](rsync.1#opt). - Rsync can now update the xattrs on a read-only file when your user can temporarily add user-write permission to the file. (It always worked for a root transfer.) - - Rsync can now work around an `--inplace` update of a file that is being - refused due to the Linux fs.protected_regular sysctl setting. + - Rsync can now work around an [`--inplace`](rsync.1#opt) update of a file + that is being refused due to the Linux fs.protected_regular sysctl setting. + + - When [`--chown`](rsync.1#opt), [`--usermap`](rsync.1#opt), or + [`--groupmap`](rsync.1#opt) is specified, rsync now makes sure that the + appropriate [`--owner`](rsync.1#opt) and/or [`--group`](rsync.1#opt) options + are enabled. + + - Added the [`--info=NONREG`](rsync.1#opt) setting to control if rsync should + warn about non-regular files in the transfer. This is enabled by default + (keeping the behavior the same as before), so specifying `--info=nonreg0` + can be used to turn the warnings off. + + - An optional asm optimization for the rolling checksum from Shark64. Enable + it with `./configure --enable-roll-asm`. + + - Using `--debug=FILTER` now outputs a caution message if a filter rule + has trailing whitespace. + + - Transformed rrsync into a python script with improvements: + - Security has been beefed up. + - The known rsync options were updated to include recent additions. + - Make rrsync reject [`--copy-links`](rsync.1#opt) (`-L`), + [`--copy-dirlinks`](rsync.1#opt) (`-k`), & + [`--keep-dirlinks`](rsync.1#opt) (`-K`) by default to make it harder to + exploit any out-of-subdir symlinks. + - A new rrsync option of [`-munge`](rrsync.1#opt) tells rrsync to always + enable rsync's [`--munge-links`](rsync.1#opt) option on the server side. + - A new rrsync option of [`-no-lock`](rrsync.1#opt) disables a new + single-use locking idiom that is the default when [`-ro`](rrsync.1#opt) is + not used (useful with [`-munge`](rrsync.1#opt)). + - A new rrsync option of [`-no-del`](rrsync.1#opt) disables all `--remove*` + and `--delete*` rsync options on the server side. + - The log format has been tweaked slightly to add seconds to the timestamp + and to output the command executed as a tuple (making the args clearer). + - An rrsync.1 manpage was added (in the support dir with rrsync). + + - Added options to the lsh script to facilitate rrsync testing. (See the + support dir.) + + - Transformed the atomic-rsync script into a python script and added the + ability to ignore one or more non-zero exit codes. By default, it now + ignores code 24, the file-vanished exit code. (See the support dir.) + + - Transformed the munge-symlinks script into python. (See the support dir.) + + - Improved the rsync-no-vanished script to not join stdout & stderr together. + (See the support dir.) - - When `--chown`, `--usermap`, or `--groupmap` is used, rsync now implies - the appropriate `--owner` and/or `--group` option. + - Work around a glibc bug where lchmod() breaks in a chroot w/o /proc mounted. - - More ASM optimizations from Shark64. + - Try to support a client that sent a remote rsync a wacko stderr file handle + (such as an older File::RsyncP perl library used by BackupPC). - - Make rrsync handle the latest options. + - Lots of manpage improvements, including better HTML versions. - - Work around a glibc bug where lchmod() breaks in a chroot w/o /proc mounted. +### PACKAGING RELATED: - - Some manpage improvements. + - Give configure the `--with-rrsync` option if you want `make install` to + install the (now python3) rrsync script and its new manpage. -### PACKAGING RELATED: + - If the rrsync script is installed, its package should be changed to depend + on python3 and the (suggested but not mandatory) python3 braceexpand lib. - When creating a package from a non-release version (w/o a git checkout), the packager can elect to create git-version.h and define RSYNC_GITVER to the @@ -82,23 +582,39 @@ using the output of `git describe` when building inside a non-shallow git checkout, though.) - - Improved the IPv6 determination in configure. + - Renamed configure's `--enable-simd` option to `--enable-roll-simd` and added + the option `--enable-roll-asm` to use the new asm version of the code. Both + are x86_64/amd64 only. + + - Renamed configure's `--enable-asm` option to `--enable-md5-asm` to avoid + confusion with the asm option for the rolling checksum. It is also honored + even when openssl crypto is in use. This allows: normal MD4 & MD5, normal + MD4 + asm MD5, openssl MD4 & MD5, or openssl MD4 + asm MD5 depending on the + configure options selected. - - Made SIMD & ASM configure default to "no" on non-linux hosts due to various - reports of problems on NetBSD & macOS hosts. These tests were also tweaked - to support a host_cpu of amd64 in addition to x86_64. + - Made SIMD & asm configure checks default to "no" on non-Linux hosts due to + various reports of problems on NetBSD & macOS hosts. These were also + tweaked to allow enabling the feature on a host_cpu of amd64 (was only + allowed on x86_64 before). - Fixed configure to not fail at the SIMD check when cross-compiling. - - Compile the C files with `-pedantic-errors` when possible so that we get - warned about an overflowed static initialization (among other things). + - Improved the IPv6 determination in configure. + + - Compile the C files with `-pedantic-errors` (when possible) so that we will + get warned if a static initialization overflows in the future (among other + things). + + - When linking with an external zlib, rsync renames its `read_buf()` function + to `read_buf_()` to avoid a symbol clash on an unpatched zlib. - Added a SECURITY.md file. ### DEVELOPER RELATED: - Made it easier to write rsync tests that diff the output while also checking - the status code, and used the idiom to improve the existing tests. + the status code, and used the idiom to improve the existing tests. (See the + `checkdiff` and `checkdiff2` idioms in the `testsuite/*.test` files. - The packaging scripts & related python lib got some minor enhancements. @@ -109,10 +625,13 @@ - Improve the logic in compat.c so that we don't need to try to remember to sprinkle `!local_server` exceptions throughout the protocol logic. - - One more C99 Flexible Array improvement (started in the last release). + - One more C99 Flexible Array improvement (started in the last release) and + make use of the C99 `%zd` format string when printing size_t values (when + possible). + + - Use mallinfo2() instead of mallinfo(), when available. ------------------------------------------------------------------------------ - # NEWS for rsync 3.2.3 (6 Aug 2020) @@ -126,67 +645,72 @@ - Fixed a bug in the xattr code that was not leaving room for the "rsync." prefix in some instances where it needed to be added. - - Restored the ability to use `--bwlimit=0` to specify no bandwidth limit. (It - was accidentally broken in 3.2.2.) + - Restored the ability to use [`--bwlimit=0`](rsync.1#opt) to specify no + bandwidth limit. (It was accidentally broken in 3.2.2.) - - Fixed a bug when combining `--delete-missing-args` with `--no-implied-dirs` & - `-R` where rsync might create the destination path of a missing arg. The - code also avoids some superfluous warnings for nested paths of removed args. + - Fixed a bug when combining [`--delete-missing-args`](rsync.1#opt) with + [`--no-implied-dirs`](rsync.1#opt) & [`-R`](rsync.1#opt) where rsync might + create the destination path of a missing arg. The code also avoids some + superfluous warnings for nested paths of removed args. - Fixed an issue where hard-linked devices could cause the rdev_major value to get out of sync between the sender and the receiver, which could cause a device to get created with the wrong major value in its major,minor pair. - - Rsync now complains about a missing `--temp-dir` before starting any file - transfers. + - Rsync now complains about a missing [`--temp-dir`](rsync.1#opt) before + starting any file transfers. - A completely empty source arg is now a fatal error. This doesn't change the handling of implied dot-dir args such as "localhost:" and such. ### ENHANCEMENTS: - - Allow `--max-alloc=0` to specify no limit to the alloc sanity check. + - Allow [`--max-alloc=0`](rsync.1#opt) to specify no limit to the alloc sanity + check. - - Allow `--block-size=SIZE` to specify the size using units (e.g. "100K"). + - Allow [`--block-size=SIZE`](rsync.1#opt) to specify the size using units + (e.g. "100K"). - The name of the id-0 user & group are now sent to the receiver along with the other user/group names in the transfer (instead of assuming that both sides have the same id-0 names). - - Added the `--stop-after=MINS` and `--stop-at=DATE_TIME` options (with the - `--time-limit=MINS` option accepted as an alias for `--stop-after`). This - is an enhanced version of the time-limit patch from the patches repo. - - - Added the `name converter` daemon parameter to make it easier to convert - user & group names inside a chrooted daemon module. This is based on the - nameconverter patch with some improvements, including a tweak to the request - protocol (so if you used this patch in the past, be sure to update your - converter script to use newlines instead of null chars). - - - Added `--crtimes` (`-N`) option for preserving the file's create time (I - believe that this is macOS only at the moment). - - - Added `--mkpath` option to tell rsync that it should create a non-existing - path component of the destination arg. - - - Added `--stderr=errors|all|client` to replace the `--msgs2stderr` and - `--no-msgs2stderr` options (which are still accepted). The default use of - stderr was changed to be `--stderr=errors` where all the processes that have - stderr available output directly to stderr, which should help error messages - get to the user more quickly, especially when doing a push (which includes - local copying). This also allows rsync to exit quickly when a receiver - failure occurs, since rsync doesn't need to try to keep the connection alive - long enough for the fatal error to go from the receiver to the generator to - the sender. The old default can be requested via `--stderr=client`. Also - changed is that a non-default stderr mode is conveyed to the remote rsync - (using the older option names) instead of requiring the user to use - `--remote-option` (`-M`) to tell the remote rsync what to do. - - - Added the ability to specify "@netgroup" names to the `hosts allow` and - `hosts deny` daemon parameters. This is a finalized version of the - netgroup-auth patch from the patches repo. - - - Rsync can now hard-link symlinks on FreeBSD due to it making ues of the + - Added the [`--stop-after`](rsync.1#opt) and [`--stop-at`](rsync.1#opt) + options (with a [`--time-limit`](rsync.1#opt) alias for `--stop-after`). + This is an enhanced version of the time-limit patch from the patches repo. + + - Added the [`name converter`](rsyncd.conf.5#opt) daemon parameter to make it + easier to convert user & group names inside a chrooted daemon module. This + is based on the nameconverter patch with some improvements, including a + tweak to the request protocol (so if you used this patch in the past, be + sure to update your converter script to use newlines instead of null chars). + + - Added [`--crtimes`](rsync.1#opt) (`-N`) option for preserving the file's + create time (I believe that this is macOS only at the moment). + + - Added [`--mkpath`](rsync.1#opt) option to tell rsync that it should create a + non-existing path component of the destination arg. + + - Added [`--stderr=errors|all|client`](rsync.1#opt) to replace the + `--msgs2stderr` and `--no-msgs2stderr` options (which are still accepted). + The default use of stderr was changed to be `--stderr=errors` where all the + processes that have stderr available output directly to stderr, which should + help error messages get to the user more quickly, especially when doing a + push (which includes local copying). This also allows rsync to exit quickly + when a receiver failure occurs, since rsync doesn't need to try to keep the + connection alive long enough for the fatal error to go from the receiver to + the generator to the sender. The old default can be requested via + `--stderr=client`. Also changed is that a non-default stderr mode is + conveyed to the remote rsync (using the older option names) instead of + requiring the user to use [`--remote-option`](rsync.1#opt) (`-M`) to tell + the remote rsync what to do. + + - Added the ability to specify "@netgroup" names to the [`hosts + allow`](rsyncd.conf.5#opt) and [`hosts deny`](rsyncd.conf.5#opt) daemon + parameters. This is a finalized version of the netgroup-auth patch from the + patches repo. + + - Rsync can now hard-link symlinks on FreeBSD due to it making use of the linkat() function when it is available. - Output file+line info on out-of-memory & overflow errors while also avoiding @@ -213,7 +737,6 @@ (with a fallback to the old 1-char string kluge for older compilers). ------------------------------------------------------------------------------ - # NEWS for rsync 3.2.2 (4 Jul 2020) @@ -267,7 +790,7 @@ - Put optimizations into their own list in the `--version` output. - - Improved the man page a bit more. + - Improved the manpage a bit more. ### PACKAGING RELATED: @@ -289,7 +812,6 @@ can create the interrelated structs and accessors that loadparm.c needs. ------------------------------------------------------------------------------ - # NEWS for rsync 3.2.1 (22 Jun 2020) @@ -352,7 +874,6 @@ - Merged the OLDNEWS.md file into NEWS.md. ------------------------------------------------------------------------------ - # NEWS for rsync 3.2.0 (19 Jun 2020) @@ -496,7 +1017,7 @@ - The daemon now locks its pid file (when configured to use one) so that it will not fail to start when the file exists but no daemon is running. - - Various man page improvements, including some html representations (that + - Various manpage improvements, including some html representations (that aren't installed by default). - Made `-V` the short option for `--version` and improved its information. @@ -513,7 +1034,7 @@ - Add installed bash script: /usr/bin/rsync-ssl - - Add installed man page: /usr/man/man1/rsync-ssl.1 + - Add installed manpage: /usr/man/man1/rsync-ssl.1 - Tweak auxiliary doc file names, such as: README.md, INSTALL.md, & NEWS.md. @@ -535,8 +1056,8 @@ SIMD checksum optimizations. - Add _build_ dependency for _either_ python3-cmarkcfm or python3-commonmark - to allow for patching of man pages or building a git release. This is not - required for a release-tar build, since it comes with pre-built man pages. + to allow for patching of manpages or building a git release. This is not + required for a release-tar build, since it comes with pre-built manpages. Note that cmarkcfm is faster than commonmark, but they generate the same data. The commonmark dependency is easiest to install since it's native python, and can even be installed via `pip3 install --user commonmark` if @@ -549,7 +1070,7 @@ - Silenced some annoying warnings about major() & minor() by improving an autoconf include-file check. - - Converted the man pages from yodl to markdown. They are now processed via a + - Converted the manpages from yodl to markdown. They are now processed via a simple python3 script using the cmarkgfm **or** commonmark library. This should make it easier to package rsync, since yodl is rather obscure. @@ -564,7 +1085,6 @@ - Some code typos were fixed (as pointed out by a Fossies run). ------------------------------------------------------------------------------ - # NEWS for rsync 3.1.3 (28 Jan 2018) @@ -582,7 +1102,8 @@ - Don't output about a new backup dir without appropriate info verbosity. - - Fixed some issues with the sort functions in support/rsyncstats script. + - Fixed some issues with the sort functions in the rsyncstats script (in the + support dir). - Added a way to specify daemon config lists (e.g. users, groups, etc) that contain spaces (see `auth users` in the latest rsyncd.conf manpage). @@ -627,14 +1148,13 @@ ### DEVELOPER RELATED: - - Tweak the `make` output when yodl isn't around to create the man pages. + - Tweak the `make` output when yodl isn't around to create the manpages. - Changed an obsolete autoconf compile macro. - - Support newer yodl versions when converting man pages. + - Support newer yodl versions when converting manpages. ------------------------------------------------------------------------------ - # NEWS for rsync 3.1.2 (21 Dec 2015) @@ -700,7 +1220,6 @@ - Improved the m4 generation rules and some autoconf idioms. ------------------------------------------------------------------------------ - # NEWS for rsync 3.1.1 (22 Jun 2014) @@ -793,7 +1312,7 @@ non-bundled zlib. See the `--new-compress` and `--old-compress` options in the manpage. - - Added the support/rsync-no-vanished wrapper script. + - Added the rsync-no-vanished shell script. (See the support dir.) - Made configure more prominently mention when we failed to find yodl (in case the user wants to be able to generate manpages from `*.yo` files). @@ -820,7 +1339,6 @@ and/or zlib code is put early in the CFLAGS. ------------------------------------------------------------------------------ - # NEWS for rsync 3.1.0 (28 Sep 2013) @@ -866,7 +1384,7 @@ - Fixed a bug in the iconv code when EINVAL or EILSEQ is returned with a full output buffer. - - Fixed some rare bugs in `--iconv` processing that might cause a multibyte + - Fixed some rare bugs in `--iconv` processing that might cause a multi-byte character to get translated incorrectly. - Fixed a bogus `vanished file` error if some files were specified with `./` @@ -1071,7 +1589,6 @@ - Fixed some build issues for Android and Minix. ------------------------------------------------------------------------------ - # NEWS for rsync 3.0.9 (23 Sep 2011) @@ -1124,14 +1641,13 @@ - Avoid trying to reference `SO_BROADCAST` if the OS doesn't support it. - - Fix some issues with the post-processing of the man pages. + - Fix some issues with the post-processing of the manpages. - - Fixed the user home-dir handling in the support/lsh script. + - Fixed the user home-dir handling in the lsh script. (See the support dir.) - Some minor manpage improvements. ------------------------------------------------------------------------------ - # NEWS for rsync 3.0.8 (26 Mar 2011) @@ -1244,10 +1760,10 @@ reject an attempt to supply one (can configure `--with-included-popt` if your system's popt library doesn't yet have this fix). - - A couple minor option tweaks to the support/rrsync script, and also some - regex changes that make vim highlighting happier. + - A couple minor option tweaks to the rrsync script, and also some regex + changes that make vim highlighting happier. (See the support dir.) - - Fixed some issues in the support/mnt-excl script. + - Fixed some issues in the mnt-excl script. (See the support dir.) - Various manpage improvements. @@ -1270,7 +1786,6 @@ - Fixed the testsuite/xattrs.test script on OS X. ------------------------------------------------------------------------------ - # NEWS for rsync 3.0.7 (31 Dec 2009) @@ -1284,7 +1799,7 @@ that hasn't really been created. - Fixed a problem with `--compress` (`-z`) where the receiving side could - return the error "inflate (token) returned -5". + return the error "`inflate (token) returned -5`". - Fixed a bug where `--delete-during` could delete in a directory before it noticed that the sending side sent an I/O error for that directory (both @@ -1303,7 +1818,7 @@ - An absolute-path filter rule (i.e. with a '/' modifier) no longer loses its modifier when sending the filter rules to the remote rsync. - - Improved the "--delete does not work without -r or -d" message. + - Improved the "`--delete does not work without -r or -d`" message. - Improved rsync's handling of `--timeout` to avoid a weird timeout case where the sender could timeout even though it has recently written data to the @@ -1338,7 +1853,6 @@ - The testsuite no longer uses `id -u`, so it works better on Solaris. ------------------------------------------------------------------------------ - # NEWS for rsync 3.0.6 (8 May 2009) @@ -1397,7 +1911,6 @@ - Fixed an failure transferring special files from Solaris to Linux. ------------------------------------------------------------------------------ - # NEWS for rsync 3.0.5 (28 Dec 2008) @@ -1457,12 +1970,11 @@ ### ENHANCEMENTS: - - Made the support/atomic-rsync script able to perform a fully atomic update - of the copied hierarchy when the destination is setup using a particular - symlink idiom. + - Made the atomic-rsync script able to perform a fully atomic update of the + copied hierarchy when the destination is setup using a particular symlink + idiom. (See the support dir.) ------------------------------------------------------------------------------ - # NEWS for rsync 3.0.4 (6 Sep 2008) @@ -1530,7 +2042,6 @@ even more consistency checks on the files. ------------------------------------------------------------------------------ - # NEWS for rsync 3.0.3 (29 Jun 2008) @@ -1610,18 +2121,17 @@ of files, and the ensuring that daemon excludes can't affect a dot-dir arg. - Improved some build rules for those that build in a separate directory from - the source, including better install rules for the man pages, and the fixing + the source, including better install rules for the manpages, and the fixing of a proto.h-tstamp rule that could make the binaries get rebuild without cause. - - Improved the testsuite to work around a problem with some utilities (e.g. cp - -p & touch -r) rounding sub-second timestamps. + - Improved the testsuite to work around a problem with some utilities (e.g. + `cp -p` & `touch -r`) rounding sub-second timestamps. - Ensure that the early patches don't cause any generated-file hunks to bleed-over into patches that follow. ------------------------------------------------------------------------------ - # NEWS for rsync 3.0.2 (8 Apr 2008) @@ -1643,7 +2153,6 @@ packaging dir. ------------------------------------------------------------------------------ - # NEWS for rsync 3.0.1 (3 Apr 2008) @@ -1727,8 +2236,9 @@ - Fixed the inclusion of per-dir merge files from implied dirs. - - Fixed the support/rrsync script to work with the latest options that rsync - sends (including its flag-specifying use of `-e` to the server). + - Fixed the rrsync script to work with the latest options that rsync sends, + including its flag-specifying use of `-e` to the server. (See the support + dir.) ### ENHANCEMENTS: @@ -1781,7 +2291,6 @@ - Updated the build scripts to work with a revised FTP directory structure. ------------------------------------------------------------------------------ - # NEWS for rsync 3.0.0 (1 Mar 2008) @@ -2132,7 +2641,6 @@ the 3.0.0 release. ------------------------------------------------------------------------------ - # NEWS for rsync 2.6.9 (6 Nov 2006) @@ -2221,7 +2729,7 @@ - Added the `--log-file=FILE` and `--log-file-format=FORMAT` options. These can be used to tell any rsync to output what it is doing to a log file. - They work with a client rsync, a non-daemon server rsync (see the man page + They work with a client rsync, a non-daemon server rsync (see the manpage for instructions), and also allows the overriding of rsyncd.conf settings when starting a daemon. @@ -2292,7 +2800,6 @@ consistent opening comments. ------------------------------------------------------------------------------ - # NEWS for rsync 2.6.8 (22 Apr 2006) @@ -2362,7 +2869,6 @@ actions to a file (something that only a daemon supports at present). ------------------------------------------------------------------------------ - # NEWS for rsync 2.6.7 (11 Mar 2006) @@ -2377,7 +2883,7 @@ option, below. - The way rsync escapes unreadable characters has changed. First, rsync now - has support for recognizing valid multibyte character sequences in your + has support for recognizing valid multi-byte character sequences in your current locale, allowing it to escape fewer characters than before for a locale such as UTF-8. Second, it now uses an escape idiom of `\#123`, which is the literal string `\#` followed by exactly 3 octal digits. Rsync no @@ -2517,7 +3023,7 @@ - Added two config items to the rsyncd.conf parsing: `pre-xfer exec` and `post-xfer exec`. These allow a command to be specified on a per-module basis that will be run before and/or after a daemon-mode transfer. (See the - man page for a list of the environment variables that are set with + manpage for a list of the environment variables that are set with information about the transfer.) - When using the `--relative` option, you can now insert a dot dir in the @@ -2686,7 +3192,6 @@ ~/.popt. ------------------------------------------------------------------------------ - # NEWS for rsync 2.6.6 (28 Jul 2005) @@ -2732,10 +3237,10 @@ - Made the `max verbosity` setting in the rsyncd.conf file settable on a per-module basis (which now matches the documentation). - - The support/rrsync script has been upgraded to verify the args of options - that take args (instead of rejecting any such options). The script was also - changed to try to be more secure and to fix a problem in the parsing of a - pull operation that has multiple sources. + - The rrsync script has been upgraded to verify the args of options that take + args (instead of rejecting any such options). It was also changed to try to + be more secure and to fix a problem in the parsing of a pull operation that + has multiple source args. (See the support dir.) - Improved the documentation that explains the difference between a normal daemon transfer and a daemon-over remote-shell transfer. @@ -2752,7 +3257,6 @@ (log-format w/%i) and some double-verbose messages. ------------------------------------------------------------------------------ - # NEWS for rsync 2.6.5 (1 Jun 2005) @@ -2887,7 +3391,7 @@ usually run with the `--no-detach` option that was necessary to see the error on stderr). - - The man pages now consistently refer to an rsync daemon as a `daemon` + - The manpages now consistently refer to an rsync daemon as a `daemon` instead of a `server` (to distinguish it from the server process in a non-daemon transfer). @@ -2933,7 +3437,6 @@ enables the optional copying of extended attributes. ------------------------------------------------------------------------------ - # NEWS for rsync 2.6.4 (30 March 2005) @@ -3315,7 +3818,6 @@ - Improved configure to better handle cross-compiling. ------------------------------------------------------------------------------ - # NEWS for rsync 2.6.3 (30 Sep 2004) @@ -3456,12 +3958,12 @@ without using a temporary file. The matching of existing data in the destination file can be severely limited by this, but there are also cases where this is more efficient (such as appending data). Use only when needed - (see the man page for more details). + (see the manpage for more details). - Added the `write only` option for the daemon's config file. - Added long-option names for `-4` and `-6` (namely `--ipv4` and `--ipv6`) and - documented all these options in the man page. + documented all these options in the manpage. - Improved the handling of the `--bwlimit` option so that it's less bursty, more accurate, and works properly over a larger range of values. @@ -3536,7 +4038,7 @@ ### BUILD CHANGES: - Added a `gen` target to rebuild most of the generated files, including - configure, config.h.in, the man pages, and proto.h. + configure, config.h.in, the manpages, and proto.h. - If `make proto` doesn't find some changes in the prototypes, the proto.h file is left untouched (its time-stamp used to always be updated). @@ -3559,7 +4061,6 @@ removed. ------------------------------------------------------------------------------ - # NEWS for rsync 2.6.2 (30 Apr 2004) @@ -3601,7 +4102,6 @@ - Two new diffs were added to the patches dir. ------------------------------------------------------------------------------ - # NEWS for rsync 2.6.1 (26 Apr 2004) @@ -3793,7 +4293,6 @@ applied, and rebuilt the rest. ------------------------------------------------------------------------------ - # NEWS for rsync 2.6.0 (1 Jan 2004) @@ -3933,7 +4432,6 @@ other side (primarily for testing purposes). (Wayne Davison) ------------------------------------------------------------------------------ - # NEWS for rsync 2.5.7 (4 Dec 2003) @@ -3945,7 +4443,6 @@ Andrea Barisani) ------------------------------------------------------------------------------ - # NEWS for rsync 2.5.6, aka "the dwd-between-jobs release" (26 Jan 2003) @@ -4040,7 +4537,6 @@ should build on more platforms. (Paul Green) ------------------------------------------------------------------------------ - # NEWS for rsync 2.5.5, aka Snowy River (2 Apr 2002) @@ -4079,7 +4575,6 @@ - Improved network error handling. (Greg A. Woods) ------------------------------------------------------------------------------ - # NEWS for rsync 2.5.4, aka "Imitation lizard skin" (13 Mar 2002) @@ -4099,7 +4594,6 @@ - Additional test cases for `--compress`. (Martin Pool) ------------------------------------------------------------------------------ - # NEWS for rsync 2.5.3, aka "Happy 26" (11 Mar 2002) @@ -4142,13 +4636,12 @@ - Added `--no-whole-file` and `--no-blocking-io` options (Dave Dykstra) - Made the `--write-batch` and `--read-batch` options actually work and added - documentation in the man page (Jos Backus) + documentation in the manpage (Jos Backus) - If the daemon is unable to fork a child to accept a connection, print an error message. (Colin Walters) ------------------------------------------------------------------------------ - # NEWS for rsync 2.5.2 (26 Jan 2002) @@ -4197,7 +4690,6 @@ Razor. (Debian #124286) ------------------------------------------------------------------------------ - # NEWS for rsync 2.5.1 (3 Jan 2002) @@ -4232,7 +4724,6 @@ - Clearer error messages for some conditions. ------------------------------------------------------------------------------ - # NEWS for rsync 2.5.0 (30 Nov 2001) @@ -4324,7 +4815,7 @@ - HP PA-RISC HP-UX 11.11 cc - IRIX 6.5 MIPS cc - IRIX 6.5 MIPS gcc - - Mac OS X PPC (--disable-ipv6) cc + - Mac OS X PPC (`--disable-ipv6`) cc - NetBSD 1.5 i386 gcc - NetBSD Current i386 cc - OpenBSD 2.5 Sparc gcc @@ -4358,7 +4849,14 @@ | RELEASE DATE | VER. | DATE OF COMMIT\* | PROTOCOL | |--------------|--------|------------------|-------------| -| ?? Sep 2020 | 3.2.4 | | 31 | +| ?? ??? 2025 | 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 | +| 14 Aug 2022 | 3.2.5 | | 31 | +| 15 Apr 2022 | 3.2.4 | | 31 | | 06 Aug 2020 | 3.2.3 | | 31 | | 04 Jul 2020 | 3.2.2 | | 31 | | 22 Jun 2020 | 3.2.1 | | 31 | @@ -4429,3 +4927,5 @@ \* DATE OF COMMIT is the date the protocol change was committed to version control. + +@USE_GFM_PARSER@ diff --git a/README.md b/README.md index 3671e8c6c..eb400592a 100644 --- a/README.md +++ b/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 ----- @@ -65,8 +65,8 @@ RSYNC DAEMONS ------------- Rsync can also talk to "rsync daemons" which can provide anonymous or -authenticated rsync. See the rsyncd.conf(5) man page for details on how -to setup an rsync daemon. See the rsync(1) man page for info on how to +authenticated rsync. See the rsyncd.conf(5) manpage for details on how +to setup an rsync daemon. See the rsync(1) manpage for info on how to connect to an rsync daemon. @@ -112,6 +112,7 @@ page of the web site. Alternately, email your bug report to . +For security issues please email details of the issue to . 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 diff --git a/SECURITY.md b/SECURITY.md index c24357413..f390ff9f0 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -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 + Rsync Project + diff --git a/access.c b/access.c index f6d6e272f..b6afce37c 100644 --- a/access.c +++ b/access.c @@ -2,7 +2,7 @@ * Routines to authenticate access to a daemon (hosts allow/deny). * * Copyright (C) 1998 Andrew Tridgell - * Copyright (C) 2004-2020 Wayne Davison + * Copyright (C) 2004-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 as published by diff --git a/acls.c b/acls.c index c98f7b40b..bd119e8ee 100644 --- a/acls.c +++ b/acls.c @@ -3,7 +3,7 @@ * * Copyright (C) 1996 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras - * Copyright (C) 2006-2020 Wayne Davison + * Copyright (C) 2006-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 as published by @@ -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; @@ -519,6 +519,7 @@ static int get_rsync_acl(const char *fname, rsync_acl *racl, sys_acl_free_acl(sacl); if (!ok) { + rsyserr(FERROR_XFER, errno, "get_acl: unpack_smb_acl(%s)", fname); return -1; } } else if (no_acl_syscall_error(errno)) { @@ -764,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. */ @@ -980,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); diff --git a/authenticate.c b/authenticate.c index 4306d1672..b7f6ead93 100644 --- a/authenticate.c +++ b/authenticate.c @@ -2,7 +2,7 @@ * Support rsync daemon authentication. * * Copyright (C) 1998-2000 Andrew Tridgell - * Copyright (C) 2002-2020 Wayne Davison + * Copyright (C) 2002-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 as published by @@ -24,6 +24,7 @@ extern int read_only; extern char *password_file; +extern struct name_num_obj valid_auth_checksums; /*************************************************************************** encode a buffer using base64 - simple and slow algorithm. null terminates @@ -72,9 +73,9 @@ static void gen_challenge(const char *addr, char *challenge) SIVAL(input, 20, tv.tv_usec); SIVAL(input, 24, getpid()); - sum_init(-1, 0); + len = sum_init(valid_auth_checksums.negotiated_nni, 0); sum_update(input, sizeof input); - len = sum_end(digest); + sum_end(digest); base64_encode(digest, len, challenge, 0); } @@ -86,10 +87,10 @@ static void generate_hash(const char *in, const char *challenge, char *out) char buf[MAX_DIGEST_LEN]; int len; - sum_init(-1, 0); + len = sum_init(valid_auth_checksums.negotiated_nni, 0); sum_update(in, strlen(in)); sum_update(challenge, strlen(challenge)); - len = sum_end(buf); + sum_end(buf); base64_encode(buf, len, out, 0); } @@ -238,6 +239,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host, if (!users || !*users) return ""; + negotiate_daemon_auth(f_out, 0); gen_challenge(addr, challenge); io_printf(f_out, "%s%s\n", leader, challenge); @@ -350,6 +352,7 @@ void auth_client(int fd, const char *user, const char *challenge) if (!user || !*user) user = "nobody"; + negotiate_daemon_auth(-1, 1); if (!(pass = getpassf(password_file)) && !(pass = getenv("RSYNC_PASSWORD"))) { diff --git a/backup.c b/backup.c index be406bef8..686cb2972 100644 --- a/backup.c +++ b/backup.c @@ -2,7 +2,7 @@ * Backup handling code. * * Copyright (C) 1999 Andrew Tridgell - * Copyright (C) 2003-2020 Wayne Davison + * 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 as published by @@ -304,7 +304,8 @@ int make_backup(const char *fname, BOOL prefer_rename) #endif if (!ret && !S_ISREG(file->mode)) { - rprintf(FINFO, "make_bak: skipping non-regular file %s\n", fname); + if (INFO_GTE(NONREG, 1)) + rprintf(FINFO, "make_bak: skipping non-regular file %s\n", fname); unmake_file(file); #ifdef SUPPORT_ACLS uncache_tmp_acls(); diff --git a/batch.c b/batch.c index a9711c569..accc4c6e6 100644 --- a/batch.c +++ b/batch.c @@ -3,7 +3,7 @@ * * Copyright (C) 1999 Weiss * Copyright (C) 2004 Chris Shoemaker - * Copyright (C) 2004-2020 Wayne Davison + * Copyright (C) 2004-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 as published by @@ -194,7 +194,7 @@ static int write_opt(const char *opt, const char *arg) { int len = strlen(opt); int err = write(batch_sh_fd, " ", 1) != 1; - err = write(batch_sh_fd, opt, len) != len ? 1 : 0; + err = write(batch_sh_fd, opt, len) != len ? 1 : 0; if (arg) { err |= write(batch_sh_fd, "=", 1) != 1; err |= write_arg(arg); diff --git a/byteorder.h b/byteorder.h index 525eaba04..059cc7086 100644 --- a/byteorder.h +++ b/byteorder.h @@ -2,7 +2,7 @@ * Simple byteorder handling. * * Copyright (C) 1992-1995 Andrew Tridgell - * Copyright (C) 2007-2020 Wayne Davison + * Copyright (C) 2007-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 as published by @@ -129,4 +129,3 @@ SIVAL(char *buf, int pos, uint32 val) { SIVALu((uchar*)buf, pos, val); } - diff --git a/checksum.c b/checksum.c index 1ed768281..66e808967 100644 --- a/checksum.c +++ b/checksum.c @@ -3,7 +3,7 @@ * * Copyright (C) 1996 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras - * Copyright (C) 2004-2020 Wayne Davison + * Copyright (C) 2004-2023 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,41 +42,94 @@ extern int protocol_version; extern int proper_seed_order; extern const char *checksum_choice; -struct name_num_obj valid_checksums = { - "checksum", NULL, NULL, 0, 0, { +#define NNI_BUILTIN (1<<0) +#define NNI_EVP (1<<1) +#define NNI_EVP_OK (1<<2) + +struct name_num_item valid_checksums_items[] = { #ifdef SUPPORT_XXH3 - { CSUM_XXH3_128, "xxh128", NULL }, - { CSUM_XXH3_64, "xxh3", NULL }, + { CSUM_XXH3_128, 0, "xxh128", NULL }, + { CSUM_XXH3_64, 0, "xxh3", NULL }, #endif #ifdef SUPPORT_XXHASH - { CSUM_XXH64, "xxh64", NULL }, - { CSUM_XXH64, "xxhash", NULL }, + { CSUM_XXH64, 0, "xxh64", NULL }, + { CSUM_XXH64, 0, "xxhash", NULL }, #endif - { CSUM_MD5, "md5", NULL }, - { CSUM_MD4, "md4", NULL }, - { CSUM_NONE, "none", NULL }, - { 0, NULL, NULL } - } + { CSUM_MD5, NNI_BUILTIN|NNI_EVP, "md5", NULL }, + { CSUM_MD4, NNI_BUILTIN|NNI_EVP, "md4", NULL }, +#ifdef SHA_DIGEST_LENGTH + { CSUM_SHA1, NNI_EVP, "sha1", NULL }, +#endif + { CSUM_NONE, 0, "none", NULL }, + { 0, 0, NULL, NULL } +}; + +struct name_num_obj valid_checksums = { + "checksum", NULL, 0, 0, valid_checksums_items +}; + +struct name_num_item valid_auth_checksums_items[] = { +#ifdef SHA512_DIGEST_LENGTH + { CSUM_SHA512, NNI_EVP, "sha512", NULL }, +#endif +#ifdef SHA256_DIGEST_LENGTH + { CSUM_SHA256, NNI_EVP, "sha256", NULL }, +#endif +#ifdef SHA_DIGEST_LENGTH + { CSUM_SHA1, NNI_EVP, "sha1", NULL }, +#endif + { CSUM_MD5, NNI_BUILTIN|NNI_EVP, "md5", NULL }, + { CSUM_MD4, NNI_BUILTIN|NNI_EVP, "md4", NULL }, + { 0, 0, NULL, NULL } }; -int xfersum_type = 0; /* used for the file transfer checksums */ -int checksum_type = 0; /* used for the pre-transfer (--checksum) checksums */ +struct name_num_obj valid_auth_checksums = { + "daemon auth checksum", NULL, 0, 0, valid_auth_checksums_items +}; + +/* These cannot make use of openssl, so they're marked just as built-in */ +struct name_num_item implied_checksum_md4 = + { CSUM_MD4, NNI_BUILTIN, "md4", NULL }; +struct name_num_item implied_checksum_md5 = + { CSUM_MD5, NNI_BUILTIN, "md5", NULL }; + +struct name_num_item *xfer_sum_nni; /* used for the transfer checksum2 computations */ +int xfer_sum_len; +struct name_num_item *file_sum_nni; /* used for the pre-transfer --checksum computations */ +int file_sum_len, file_sum_extra_cnt; + +#ifdef USE_OPENSSL +const EVP_MD *xfer_sum_evp_md; +const EVP_MD *file_sum_evp_md; +EVP_MD_CTX *ctx_evp = NULL; +#endif -int parse_csum_name(const char *name, int len) +static int initialized_choices = 0; + +struct name_num_item *parse_csum_name(const char *name, int len) { struct name_num_item *nni; if (len < 0 && name) len = strlen(name); + init_checksum_choices(); + if (!name || (len == 4 && strncasecmp(name, "auto", 4) == 0)) { - if (protocol_version >= 30) - return CSUM_MD5; - if (protocol_version >= 27) - return CSUM_MD4_OLD; - if (protocol_version >= 21) - return CSUM_MD4_BUSTED; - return CSUM_MD4_ARCHAIC; + if (protocol_version >= 30) { + if (!proper_seed_order) + return &implied_checksum_md5; + name = "md5"; + len = 3; + } else { + if (protocol_version >= 27) + implied_checksum_md4.num = CSUM_MD4_OLD; + else if (protocol_version >= 21) + implied_checksum_md4.num = CSUM_MD4_BUSTED; + else + implied_checksum_md4.num = CSUM_MD4_ARCHAIC; + return &implied_checksum_md4; + } } nni = get_nni_by_name(&valid_checksums, name, len); @@ -86,44 +139,74 @@ int parse_csum_name(const char *name, int len) exit_cleanup(RERR_UNSUPPORTED); } - return nni->num; + return nni; } -static const char *checksum_name(int num) +#ifdef USE_OPENSSL +static const EVP_MD *csum_evp_md(struct name_num_item *nni) { - struct name_num_item *nni = get_nni_by_num(&valid_checksums, num); - - return nni ? nni->name : num < CSUM_MD4 ? "md4" : "UNKNOWN"; + const EVP_MD *emd; + if (!(nni->flags & NNI_EVP)) + return NULL; + +#ifdef USE_MD5_ASM + if (nni->num == CSUM_MD5) + emd = NULL; + else +#endif + emd = EVP_get_digestbyname(nni->name); + if (emd && !(nni->flags & NNI_EVP_OK)) { /* Make sure it works before we advertise it */ + if (!ctx_evp && !(ctx_evp = EVP_MD_CTX_create())) + out_of_memory("csum_evp_md"); + /* Some routines are marked as legacy and are not enabled in the openssl.cnf file. + * If we can't init the emd, we'll fall back to our built-in code. */ + if (EVP_DigestInit_ex(ctx_evp, emd, NULL) == 0) + emd = NULL; + else + nni->flags = (nni->flags & ~NNI_BUILTIN) | NNI_EVP_OK; + } + if (!emd) + nni->flags &= ~NNI_EVP; + return emd; } +#endif void parse_checksum_choice(int final_call) { - if (valid_checksums.negotiated_name) - xfersum_type = checksum_type = valid_checksums.negotiated_num; + if (valid_checksums.negotiated_nni) + xfer_sum_nni = file_sum_nni = valid_checksums.negotiated_nni; else { char *cp = checksum_choice ? strchr(checksum_choice, ',') : NULL; if (cp) { - xfersum_type = parse_csum_name(checksum_choice, cp - checksum_choice); - checksum_type = parse_csum_name(cp+1, -1); + xfer_sum_nni = parse_csum_name(checksum_choice, cp - checksum_choice); + file_sum_nni = parse_csum_name(cp+1, -1); } else - xfersum_type = checksum_type = parse_csum_name(checksum_choice, -1); + xfer_sum_nni = file_sum_nni = parse_csum_name(checksum_choice, -1); if (am_server && checksum_choice) - validate_choice_vs_env(NSTR_CHECKSUM, xfersum_type, checksum_type); + validate_choice_vs_env(NSTR_CHECKSUM, xfer_sum_nni->num, file_sum_nni->num); } + xfer_sum_len = csum_len_for_type(xfer_sum_nni->num, 0); + file_sum_len = csum_len_for_type(file_sum_nni->num, 0); +#ifdef USE_OPENSSL + xfer_sum_evp_md = csum_evp_md(xfer_sum_nni); + file_sum_evp_md = csum_evp_md(file_sum_nni); +#endif + + file_sum_extra_cnt = (file_sum_len + EXTRA_LEN - 1) / EXTRA_LEN; - if (xfersum_type == CSUM_NONE) + if (xfer_sum_nni->num == CSUM_NONE) whole_file = 1; /* Snag the checksum name for both write_batch's option output & the following debug output. */ - if (valid_checksums.negotiated_name) - checksum_choice = valid_checksums.negotiated_name; + if (valid_checksums.negotiated_nni) + checksum_choice = valid_checksums.negotiated_nni->name; else if (checksum_choice == NULL) - checksum_choice = checksum_name(xfersum_type); + checksum_choice = xfer_sum_nni->name; if (final_call && DEBUG_GTE(NSTR, am_server ? 3 : 1)) { rprintf(FINFO, "%s%s checksum: %s\n", am_server ? "Server" : "Client", - valid_checksums.negotiated_name ? " negotiated" : "", + valid_checksums.negotiated_nni ? " negotiated" : "", checksum_choice); } } @@ -143,6 +226,18 @@ int csum_len_for_type(int cst, BOOL flist_csum) return MD4_DIGEST_LEN; case CSUM_MD5: return MD5_DIGEST_LEN; +#ifdef SHA_DIGEST_LENGTH + case CSUM_SHA1: + return SHA_DIGEST_LENGTH; +#endif +#ifdef SHA256_DIGEST_LENGTH + case CSUM_SHA256: + return SHA256_DIGEST_LENGTH; +#endif +#ifdef SHA512_DIGEST_LENGTH + case CSUM_SHA512: + return SHA512_DIGEST_LENGTH; +#endif case CSUM_XXH64: case CSUM_XXH3_64: return 64/8; @@ -168,6 +263,9 @@ int canonical_checksum(int csum_type) break; case CSUM_MD4: case CSUM_MD5: + case CSUM_SHA1: + case CSUM_SHA256: + case CSUM_SHA512: return -1; case CSUM_XXH64: case CSUM_XXH3_64: @@ -179,7 +277,7 @@ int canonical_checksum(int csum_type) return 0; } -#ifndef HAVE_SIMD /* See simd-checksum-*.cpp. */ +#ifndef USE_ROLL_SIMD /* See simd-checksum-*.cpp. */ /* a simple 32 bit checksum that can be updated from either end (inspired by Mark Adler's Adler-32 checksum) @@ -202,9 +300,25 @@ uint32 get_checksum1(char *buf1, int32 len) } #endif +/* The "sum" buffer must be at least MAX_DIGEST_LEN bytes! */ void get_checksum2(char *buf, int32 len, char *sum) { - switch (xfersum_type) { +#ifdef USE_OPENSSL + if (xfer_sum_evp_md) { + static EVP_MD_CTX *evp = NULL; + uchar seedbuf[4]; + if (!evp && !(evp = EVP_MD_CTX_create())) + out_of_memory("get_checksum2"); + EVP_DigestInit_ex(evp, xfer_sum_evp_md, NULL); + if (checksum_seed) { + SIVALu(seedbuf, 0, checksum_seed); + EVP_DigestUpdate(evp, seedbuf, 4); + } + EVP_DigestUpdate(evp, (uchar *)buf, len); + EVP_DigestFinal_ex(evp, (uchar *)sum, NULL); + } else +#endif + switch (xfer_sum_nni->num) { #ifdef SUPPORT_XXHASH case CSUM_XXH64: SIVAL64(sum, 0, XXH64(buf, len, checksum_seed)); @@ -222,40 +336,26 @@ void get_checksum2(char *buf, int32 len, char *sum) } #endif case CSUM_MD5: { - MD5_CTX m5; + md_context m5; uchar seedbuf[4]; - MD5_Init(&m5); + md5_begin(&m5); if (proper_seed_order) { if (checksum_seed) { SIVALu(seedbuf, 0, checksum_seed); - MD5_Update(&m5, seedbuf, 4); + md5_update(&m5, seedbuf, 4); } - MD5_Update(&m5, (uchar *)buf, len); + md5_update(&m5, (uchar *)buf, len); } else { - MD5_Update(&m5, (uchar *)buf, len); + md5_update(&m5, (uchar *)buf, len); if (checksum_seed) { SIVALu(seedbuf, 0, checksum_seed); - MD5_Update(&m5, seedbuf, 4); + md5_update(&m5, seedbuf, 4); } } - MD5_Final((uchar *)sum, &m5); + md5_result(&m5, (uchar *)sum); break; } case CSUM_MD4: -#ifdef USE_OPENSSL - { - MD4_CTX m4; - MD4_Init(&m4); - MD4_Update(&m4, (uchar *)buf, len); - if (checksum_seed) { - uchar seedbuf[4]; - SIVALu(seedbuf, 0, checksum_seed); - MD4_Update(&m4, seedbuf, 4); - } - MD4_Final((uchar *)sum, &m4); - break; - } -#endif case CSUM_MD4_OLD: case CSUM_MD4_BUSTED: case CSUM_MD4_ARCHAIC: { @@ -288,7 +388,7 @@ void get_checksum2(char *buf, int32 len, char *sum) * are multiples of 64. This is fixed by calling mdfour_update() * even when there are no more bytes. */ - if (len - i > 0 || xfersum_type > CSUM_MD4_BUSTED) + if (len - i > 0 || xfer_sum_nni->num > CSUM_MD4_BUSTED) mdfour_update(&m, (uchar *)(buf1+i), len-i); mdfour_result(&m, (uchar *)sum); @@ -306,15 +406,33 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum) int32 remainder; int fd; - memset(sum, 0, MAX_DIGEST_LEN); - - fd = do_open(fname, O_RDONLY, 0); - if (fd == -1) + fd = do_open_checklinks(fname); + if (fd == -1) { + memset(sum, 0, file_sum_len); return; + } buf = map_file(fd, len, MAX_MAP_SIZE, CHUNK_SIZE); - switch (checksum_type) { +#ifdef USE_OPENSSL + if (file_sum_evp_md) { + static EVP_MD_CTX *evp = NULL; + if (!evp && !(evp = EVP_MD_CTX_create())) + out_of_memory("file_checksum"); + + EVP_DigestInit_ex(evp, file_sum_evp_md, NULL); + + for (i = 0; i + CHUNK_SIZE <= len; i += CHUNK_SIZE) + EVP_DigestUpdate(evp, (uchar *)map_ptr(buf, i, CHUNK_SIZE), CHUNK_SIZE); + + remainder = (int32)(len - i); + if (remainder > 0) + EVP_DigestUpdate(evp, (uchar *)map_ptr(buf, i, remainder), remainder); + + EVP_DigestFinal_ex(evp, (uchar *)sum, NULL); + } else +#endif + switch (file_sum_nni->num) { #ifdef SUPPORT_XXHASH case CSUM_XXH64: { static XXH64_state_t* state = NULL; @@ -374,38 +492,21 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum) } #endif case CSUM_MD5: { - MD5_CTX m5; + md_context m5; - MD5_Init(&m5); + md5_begin(&m5); for (i = 0; i + CHUNK_SIZE <= len; i += CHUNK_SIZE) - MD5_Update(&m5, (uchar *)map_ptr(buf, i, CHUNK_SIZE), CHUNK_SIZE); + md5_update(&m5, (uchar *)map_ptr(buf, i, CHUNK_SIZE), CHUNK_SIZE); remainder = (int32)(len - i); if (remainder > 0) - MD5_Update(&m5, (uchar *)map_ptr(buf, i, remainder), remainder); + md5_update(&m5, (uchar *)map_ptr(buf, i, remainder), remainder); - MD5_Final((uchar *)sum, &m5); + md5_result(&m5, (uchar *)sum); break; } case CSUM_MD4: -#ifdef USE_OPENSSL - { - MD4_CTX m4; - - MD4_Init(&m4); - - for (i = 0; i + CHUNK_SIZE <= len; i += CHUNK_SIZE) - MD4_Update(&m4, (uchar *)map_ptr(buf, i, CHUNK_SIZE), CHUNK_SIZE); - - remainder = (int32)(len - i); - if (remainder > 0) - MD4_Update(&m4, (uchar *)map_ptr(buf, i, remainder), remainder); - - MD4_Final((uchar *)sum, &m4); - break; - } -#endif case CSUM_MD4_OLD: case CSUM_MD4_BUSTED: case CSUM_MD4_ARCHAIC: { @@ -413,15 +514,15 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum) mdfour_begin(&m); - for (i = 0; i + CHUNK_SIZE <= len; i += CHUNK_SIZE) - mdfour_update(&m, (uchar *)map_ptr(buf, i, CHUNK_SIZE), CHUNK_SIZE); + for (i = 0; i + CSUM_CHUNK <= len; i += CSUM_CHUNK) + mdfour_update(&m, (uchar *)map_ptr(buf, i, CSUM_CHUNK), CSUM_CHUNK); /* Prior to version 27 an incorrect MD4 checksum was computed * by failing to call mdfour_tail() for block sizes that * are multiples of 64. This is fixed by calling mdfour_update() * even when there are no more bytes. */ remainder = (int32)(len - i); - if (remainder > 0 || checksum_type > CSUM_MD4_BUSTED) + if (remainder > 0 || file_sum_nni->num > CSUM_MD4_BUSTED) mdfour_update(&m, (uchar *)map_ptr(buf, i, remainder), remainder); mdfour_result(&m, (uchar *)sum); @@ -429,7 +530,7 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum) } default: rprintf(FERROR, "Invalid checksum-choice for --checksum: %s (%d)\n", - checksum_name(checksum_type), checksum_type); + file_sum_nni->name, file_sum_nni->num); exit_cleanup(RERR_UNSUPPORTED); } @@ -438,30 +539,43 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum) } static int32 sumresidue; -static union { - md_context md; -#ifdef USE_OPENSSL - MD4_CTX m4; -#endif - MD5_CTX m5; -} ctx; +static md_context ctx_md; #ifdef SUPPORT_XXHASH static XXH64_state_t* xxh64_state; #endif #ifdef SUPPORT_XXH3 static XXH3_state_t* xxh3_state; #endif -static int cursum_type; +static struct name_num_item *cur_sum_nni; +int cur_sum_len; + +#ifdef USE_OPENSSL +static const EVP_MD *cur_sum_evp_md; +#endif -void sum_init(int csum_type, int seed) +/* Initialize a hash digest accumulator. Data is supplied via + * sum_update() and the resulting binary digest is retrieved via + * sum_end(). This only supports one active sum at a time. */ +int sum_init(struct name_num_item *nni, int seed) { char s[4]; - if (csum_type < 0) - csum_type = parse_csum_name(NULL, 0); - cursum_type = csum_type; + if (!nni) + nni = parse_csum_name(NULL, 0); + cur_sum_nni = nni; + cur_sum_len = csum_len_for_type(nni->num, 0); +#ifdef USE_OPENSSL + cur_sum_evp_md = csum_evp_md(nni); +#endif - switch (csum_type) { +#ifdef USE_OPENSSL + if (cur_sum_evp_md) { + if (!ctx_evp && !(ctx_evp = EVP_MD_CTX_create())) + out_of_memory("file_checksum"); + EVP_DigestInit_ex(ctx_evp, cur_sum_evp_md, NULL); + } else +#endif + switch (cur_sum_nni->num) { #ifdef SUPPORT_XXHASH case CSUM_XXH64: if (!xxh64_state && !(xxh64_state = XXH64_createState())) @@ -482,20 +596,16 @@ void sum_init(int csum_type, int seed) break; #endif case CSUM_MD5: - MD5_Init(&ctx.m5); + md5_begin(&ctx_md); break; case CSUM_MD4: -#ifdef USE_OPENSSL - MD4_Init(&ctx.m4); -#else - mdfour_begin(&ctx.md); + mdfour_begin(&ctx_md); sumresidue = 0; -#endif break; case CSUM_MD4_OLD: case CSUM_MD4_BUSTED: case CSUM_MD4_ARCHAIC: - mdfour_begin(&ctx.md); + mdfour_begin(&ctx_md); sumresidue = 0; SIVAL(s, 0, seed); sum_update(s, 4); @@ -505,19 +615,19 @@ void sum_init(int csum_type, int seed) default: /* paranoia to prevent missing case values */ exit_cleanup(RERR_UNSUPPORTED); } + + return cur_sum_len; } -/** - * Feed data into an MD4 accumulator, md. The results may be - * retrieved using sum_end(). md is used for different purposes at - * different points during execution. - * - * @todo Perhaps get rid of md and just pass in the address each time. - * Very slightly clearer and slower. - **/ +/* Feed data into a hash digest accumulator. */ void sum_update(const char *p, int32 len) { - switch (cursum_type) { +#ifdef USE_OPENSSL + if (cur_sum_evp_md) { + EVP_DigestUpdate(ctx_evp, (uchar *)p, len); + } else +#endif + switch (cur_sum_nni->num) { #ifdef SUPPORT_XXHASH case CSUM_XXH64: XXH64_update(xxh64_state, p, len); @@ -532,39 +642,35 @@ void sum_update(const char *p, int32 len) break; #endif case CSUM_MD5: - MD5_Update(&ctx.m5, (uchar *)p, len); + md5_update(&ctx_md, (uchar *)p, len); break; case CSUM_MD4: -#ifdef USE_OPENSSL - MD4_Update(&ctx.m4, (uchar *)p, len); - break; -#endif case CSUM_MD4_OLD: case CSUM_MD4_BUSTED: case CSUM_MD4_ARCHAIC: if (len + sumresidue < CSUM_CHUNK) { - memcpy(ctx.md.buffer + sumresidue, p, len); + memcpy(ctx_md.buffer + sumresidue, p, len); sumresidue += len; break; } if (sumresidue) { int32 i = CSUM_CHUNK - sumresidue; - memcpy(ctx.md.buffer + sumresidue, p, i); - mdfour_update(&ctx.md, (uchar *)ctx.md.buffer, CSUM_CHUNK); + memcpy(ctx_md.buffer + sumresidue, p, i); + mdfour_update(&ctx_md, (uchar *)ctx_md.buffer, CSUM_CHUNK); len -= i; p += i; } while (len >= CSUM_CHUNK) { - mdfour_update(&ctx.md, (uchar *)p, CSUM_CHUNK); + mdfour_update(&ctx_md, (uchar *)p, CSUM_CHUNK); len -= CSUM_CHUNK; p += CSUM_CHUNK; } sumresidue = len; if (sumresidue) - memcpy(ctx.md.buffer, p, sumresidue); + memcpy(ctx_md.buffer, p, sumresidue); break; case CSUM_NONE: break; @@ -573,13 +679,18 @@ void sum_update(const char *p, int32 len) } } -/* NOTE: all the callers of sum_end() pass in a pointer to a buffer that is - * MAX_DIGEST_LEN in size, so even if the csum-len is shorter that that (i.e. - * CSUM_MD4_ARCHAIC), we don't have to worry about limiting the data we write - * into the "sum" buffer. */ -int sum_end(char *sum) +/* The sum buffer only needs to be as long as the current checksum's digest + * len, not MAX_DIGEST_LEN. Note that for CSUM_MD4_ARCHAIC that is the full + * MD4_DIGEST_LEN even if the file-list code is going to ignore all but the + * first 2 bytes of it. */ +void sum_end(char *sum) { - switch (cursum_type) { +#ifdef USE_OPENSSL + if (cur_sum_evp_md) { + EVP_DigestFinal_ex(ctx_evp, (uchar *)sum, NULL); + } else +#endif + switch (cur_sum_nni->num) { #ifdef SUPPORT_XXHASH case CSUM_XXH64: SIVAL64(sum, 0, XXH64_digest(xxh64_state)); @@ -597,22 +708,18 @@ int sum_end(char *sum) } #endif case CSUM_MD5: - MD5_Final((uchar *)sum, &ctx.m5); + md5_result(&ctx_md, (uchar *)sum); break; case CSUM_MD4: -#ifdef USE_OPENSSL - MD4_Final((uchar *)sum, &ctx.m4); - break; -#endif case CSUM_MD4_OLD: - mdfour_update(&ctx.md, (uchar *)ctx.md.buffer, sumresidue); - mdfour_result(&ctx.md, (uchar *)sum); + mdfour_update(&ctx_md, (uchar *)ctx_md.buffer, sumresidue); + mdfour_result(&ctx_md, (uchar *)sum); break; case CSUM_MD4_BUSTED: case CSUM_MD4_ARCHAIC: if (sumresidue) - mdfour_update(&ctx.md, (uchar *)ctx.md.buffer, sumresidue); - mdfour_result(&ctx.md, (uchar *)sum); + mdfour_update(&ctx_md, (uchar *)ctx_md.buffer, sumresidue); + mdfour_result(&ctx_md, (uchar *)sum); break; case CSUM_NONE: *sum = '\0'; @@ -620,6 +727,78 @@ int sum_end(char *sum) default: /* paranoia to prevent missing case values */ exit_cleanup(RERR_UNSUPPORTED); } +} + +#if defined SUPPORT_XXH3 || defined USE_OPENSSL +static void verify_digest(struct name_num_item *nni, BOOL check_auth_list) +{ +#ifdef SUPPORT_XXH3 + static int xxh3_result = 0; +#endif +#ifdef USE_OPENSSL + static int prior_num = 0, prior_flags = 0, prior_result = 0; +#endif + +#ifdef SUPPORT_XXH3 + if (nni->num == CSUM_XXH3_64 || nni->num == CSUM_XXH3_128) { + if (!xxh3_result) { + char buf[32816]; + int j; + for (j = 0; j < (int)sizeof buf; j++) + buf[j] = ' ' + (j % 96); + sum_init(nni, 0); + sum_update(buf, 32816); + sum_update(buf, 31152); + sum_update(buf, 32474); + sum_update(buf, 9322); + xxh3_result = XXH3_64bits_digest(xxh3_state) != 0xadbcf16d4678d1de ? -1 : 1; + } + if (xxh3_result < 0) + nni->num = CSUM_gone; + return; + } +#endif + +#ifdef USE_OPENSSL + if (BITS_SETnUNSET(nni->flags, NNI_EVP, NNI_BUILTIN|NNI_EVP_OK)) { + if (nni->num == prior_num && nni->flags == prior_flags) { + nni->flags = prior_result; + if (!(nni->flags & NNI_EVP)) + nni->num = CSUM_gone; + } else { + prior_num = nni->num; + prior_flags = nni->flags; + if (!csum_evp_md(nni)) + nni->num = CSUM_gone; + prior_result = nni->flags; + if (check_auth_list && (nni = get_nni_by_num(&valid_auth_checksums, prior_num)) != NULL) + verify_digest(nni, False); + } + } +#endif +} +#endif + +void init_checksum_choices() +{ +#if defined SUPPORT_XXH3 || defined USE_OPENSSL + struct name_num_item *nni; +#endif + + if (initialized_choices) + return; + +#if defined USE_OPENSSL && OPENSSL_VERSION_NUMBER < 0x10100000L + OpenSSL_add_all_algorithms(); +#endif + +#if defined SUPPORT_XXH3 || defined USE_OPENSSL + for (nni = valid_checksums.list; nni->name; nni++) + verify_digest(nni, True); + + for (nni = valid_auth_checksums.list; nni->name; nni++) + verify_digest(nni, False); +#endif - return csum_len_for_type(cursum_type, 0); + initialized_choices = 1; } diff --git a/clientname.c b/clientname.c index 1572f3ef0..ea94894bb 100644 --- a/clientname.c +++ b/clientname.c @@ -3,7 +3,7 @@ * * Copyright (C) 1992-2001 Andrew Tridgell * Copyright (C) 2001, 2002 Martin Pool - * Copyright (C) 2002-2020 Wayne Davison + * Copyright (C) 2002-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 as published by diff --git a/clientserver.c b/clientserver.c index 14e3c544a..7c897abc4 100644 --- a/clientserver.c +++ b/clientserver.c @@ -3,7 +3,7 @@ * * Copyright (C) 1998-2001 Andrew Tridgell * Copyright (C) 2001-2002 Martin Pool - * Copyright (C) 2002-2020 Wayne Davison + * Copyright (C) 2002-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 as published by @@ -47,6 +47,7 @@ extern int protocol_version; extern int io_timeout; extern int no_detach; extern int write_batch; +extern int old_style_args; extern int default_af_hint; extern int logfile_format_has_i; extern int logfile_format_has_o_or_i; @@ -66,6 +67,7 @@ extern uid_t our_uid; extern gid_t our_gid; char *auth_user; +char *daemon_auth_choices; int read_only = 0; int module_id = -1; int pid_file_fd = -1; @@ -148,13 +150,9 @@ int start_socket_client(char *host, int remote_argc, char *remote_argv[], static int exchange_protocols(int f_in, int f_out, char *buf, size_t bufsiz, int am_client) { int remote_sub = -1; -#if SUBPROTOCOL_VERSION != 0 - int our_sub = protocol_version < PROTOCOL_VERSION ? 0 : SUBPROTOCOL_VERSION; -#else - int our_sub = 0; -#endif + int our_sub = get_subprotocol_version(); - io_printf(f_out, "@RSYNCD: %d.%d\n", protocol_version, our_sub); + output_daemon_greeting(f_out, am_client); if (!am_client) { char *motd = lp_motd_file(); if (motd && *motd) { @@ -186,16 +184,30 @@ static int exchange_protocols(int f_in, int f_out, char *buf, size_t bufsiz, int } if (remote_sub < 0) { - if (remote_protocol == 30) { + if (remote_protocol >= 30) { if (am_client) - rprintf(FERROR, "rsync: server is speaking an incompatible beta of protocol 30\n"); + rprintf(FERROR, "rsync: the server omitted the subprotocol value: %s\n", buf); else - io_printf(f_out, "@ERROR: your client is speaking an incompatible beta of protocol 30\n"); + io_printf(f_out, "@ERROR: your client omitted the subprotocol value: %s\n", buf); return -1; } remote_sub = 0; } + daemon_auth_choices = strchr(buf + 9, ' '); + if (daemon_auth_choices) { + char *cp; + daemon_auth_choices = strdup(daemon_auth_choices + 1); + if ((cp = strchr(daemon_auth_choices, '\n')) != NULL) + *cp = '\0'; + } else if (remote_protocol > 31) { + if (am_client) + rprintf(FERROR, "rsync: the server omitted the digest name list: %s\n", buf); + else + io_printf(f_out, "@ERROR: your client omitted the digest name list: %s\n", buf); + return -1; + } + if (protocol_version > remote_protocol) { protocol_version = remote_protocol; if (remote_sub) @@ -288,20 +300,45 @@ int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char sargs[sargc++] = "."; + if (!old_style_args) + snprintf(line, sizeof line, " %.*s/", modlen, modname); + while (argc > 0) { if (sargc >= MAX_ARGS - 1) { arg_overflow: rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n"); exit_cleanup(RERR_SYNTAX); } - if (strncmp(*argv, modname, modlen) == 0 - && argv[0][modlen] == '\0') + if (strncmp(*argv, modname, modlen) == 0 && argv[0][modlen] == '\0') sargs[sargc++] = modname; /* we send "modname/" */ - else if (**argv == '-') { - if (asprintf(sargs + sargc++, "./%s", *argv) < 0) - out_of_memory("start_inband_exchange"); - } else - sargs[sargc++] = *argv; + else { + char *arg = *argv; + int extra_chars = *arg == '-' ? 2 : 0; /* a leading dash needs a "./" prefix. */ + /* If --old-args was not specified, make sure that the arg won't split at a mod name! */ + if (!old_style_args && (p = strstr(arg, line)) != NULL) { + do { + extra_chars += 2; + } while ((p = strstr(p+1, line)) != NULL); + } + if (extra_chars) { + char *f = arg; + char *t = arg = new_array(char, strlen(arg) + extra_chars + 1); + if (*f == '-') { + *t++ = '.'; + *t++ = '/'; + } + while (*f) { + if (*f == ' ' && strncmp(f, line, modlen+2) == 0) { + *t++ = '['; + *t++ = *f++; + *t++ = ']'; + } else + *t++ = *f++; + } + *t = '\0'; + } + sargs[sargc++] = arg; + } argv++; argc--; } @@ -355,7 +392,7 @@ int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char if (rl_nulls) { for (i = 0; i < sargc; i++) { - if (!sargs[i]) /* stop at --protect-args NULL */ + if (!sargs[i]) /* stop at --secluded-args NULL */ break; write_sbuf(f_out, sargs[i]); write_byte(f_out, 0); @@ -403,7 +440,7 @@ static int read_arg_from_pipe(int fd, char *buf, int limit) } #endif -static void set_env_str(const char *var, const char *str) +void set_env_str(const char *var, const char *str) { #ifdef HAVE_SETENV if (setenv(var, str, 1) < 0) @@ -664,7 +701,7 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char int set_uid; char *p, *err_msg = NULL; char *name = lp_name(i); - int use_chroot = lp_use_chroot(i); + int use_chroot = lp_use_chroot(i); /* might be 1 (yes), 0 (no), or -1 (unset) */ int ret, pre_exec_arg_fd = -1, pre_exec_error_fd = -1; int save_munge_symlinks; pid_t pre_exec_pid = 0; @@ -789,6 +826,20 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char io_printf(f_out, "@ERROR: no path setting.\n"); return -1; } + if (use_chroot < 0) { + if (strstr(module_dir, "/./") != NULL) + use_chroot = 1; /* The module is expecting a chroot inner & outer path. */ + else if (chroot("/") < 0) { + rprintf(FLOG, "chroot test failed: %s. " + "Switching 'use chroot' from unset to false.\n", + strerror(errno)); + use_chroot = 0; + } else { + if (chdir("/") < 0) + rsyserr(FLOG, errno, "chdir(\"/\") failed"); + use_chroot = 1; + } + } if (use_chroot) { if ((p = strstr(module_dir, "/./")) != NULL) { *p = '\0'; /* Temporary... */ @@ -925,20 +976,8 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char } if (use_chroot) { - /* - * XXX: The 'use chroot' flag is a fairly reliable - * source of confusion, because it fails under two - * important circumstances: running as non-root, - * running on Win32 (or possibly others). On the - * other hand, if you are running as root, then it - * might be better to always use chroot. - * - * So, perhaps if we can't chroot we should just issue - * a warning, unless a "require chroot" flag is set, - * in which case we fail. - */ if (chroot(module_chdir)) { - rsyserr(FLOG, errno, "chroot %s failed", module_chdir); + rsyserr(FLOG, errno, "chroot(\"%s\") failed", module_chdir); io_printf(f_out, "@ERROR: chroot failed\n"); return -1; } @@ -947,7 +986,7 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char if (!change_dir(module_chdir, CD_NORMAL)) return path_failure(f_out, module_chdir, True); - if (module_dirlen || (!use_chroot && !*lp_daemon_chroot())) + if (module_dirlen) sanitize_paths = 1; if ((munge_symlinks = lp_munge_symlinks(module_id)) < 0) @@ -1262,8 +1301,12 @@ int start_daemon(int f_in, int f_out) p = lp_daemon_chroot(); if (*p) { log_init(0); /* Make use we've initialized syslog before chrooting. */ - if (chroot(p) < 0 || chdir("/") < 0) { - rsyserr(FLOG, errno, "daemon chroot %s failed", p); + if (chroot(p) < 0) { + rsyserr(FLOG, errno, "daemon chroot(\"%s\") failed", p); + return -1; + } + if (chdir("/") < 0) { + rsyserr(FLOG, errno, "daemon chdir(\"/\") failed"); return -1; } } diff --git a/compat.c b/compat.c index 0a882cda0..4ce8c6d01 100644 --- a/compat.c +++ b/compat.c @@ -3,7 +3,7 @@ * * Copyright (C) Andrew Tridgell 1996 * Copyright (C) Paul Mackerras 1996 - * Copyright (C) 2004-2020 Wayne Davison + * Copyright (C) 2004-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 as published by @@ -52,19 +52,24 @@ 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 saw_stderr_opt; +extern int msgs2stderr; extern char *shell_cmd; extern char *partial_dir; extern char *files_from; extern char *filesfrom_host; extern const char *checksum_choice; extern const char *compress_choice; +extern char *daemon_auth_choices; extern filter_rule_list filter_list; extern int need_unsorted_flist; #ifdef ICONV_OPTION extern iconv_t ic_send, ic_recv; extern char *iconv_opt; #endif -extern struct name_num_obj valid_checksums; +extern struct name_num_obj valid_checksums, valid_auth_checksums; + +extern struct name_num_item *xfer_sum_nni; int remote_protocol = 0; int file_extra_cnt = 0; /* count of file-list extras that everyone gets */ @@ -77,6 +82,9 @@ int inplace_partial = 0; int do_negotiated_strings = 0; int xmit_id0_names = 0; +struct name_num_item *xattr_sum_nni; +int xattr_sum_len = 0; + /* These index values are for the file-list's extra-attribute array. */ int pathname_ndx, depth_ndx, atimes_ndx, crtimes_ndx, uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx; @@ -89,19 +97,21 @@ int filesfrom_convert = 0; #define MAX_NSTR_STRLEN 256 -struct name_num_obj valid_compressions = { - "compress", NULL, NULL, 0, 0, { +struct name_num_item valid_compressions_items[] = { #ifdef SUPPORT_ZSTD - { CPRES_ZSTD, "zstd", NULL }, + { CPRES_ZSTD, 0, "zstd", NULL }, #endif #ifdef SUPPORT_LZ4 - { CPRES_LZ4, "lz4", NULL }, + { CPRES_LZ4, 0, "lz4", NULL }, #endif - { CPRES_ZLIBX, "zlibx", NULL }, - { CPRES_ZLIB, "zlib", NULL }, - { CPRES_NONE, "none", NULL }, - { 0, NULL, NULL } - } + { CPRES_ZLIBX, 0, "zlibx", NULL }, + { CPRES_ZLIB, 0, "zlib", NULL }, + { CPRES_NONE, 0, "none", NULL }, + { 0, 0, NULL, NULL } +}; + +struct name_num_obj valid_compressions = { + "compress", NULL, 0, 0, valid_compressions_items }; #define CF_INC_RECURSE (1<<0) @@ -123,11 +133,7 @@ static void check_sub_protocol(void) { char *dot; int their_protocol, their_sub; -#if SUBPROTOCOL_VERSION != 0 - int our_sub = protocol_version < PROTOCOL_VERSION ? 0 : SUBPROTOCOL_VERSION; -#else - int our_sub = 0; -#endif + int our_sub = get_subprotocol_version(); /* client_info starts with VER.SUB string if client is a pre-release. */ if (!(their_protocol = atoi(client_info)) @@ -174,8 +180,8 @@ void set_allow_inc_recurse(void) void parse_compress_choice(int final_call) { - if (valid_compressions.negotiated_name) - do_compression = valid_compressions.negotiated_num; + if (valid_compressions.negotiated_nni) + do_compression = valid_compressions.negotiated_nni->num; else if (compress_choice) { struct name_num_item *nni = get_nni_by_name(&valid_compressions, compress_choice, -1); if (!nni) { @@ -197,8 +203,8 @@ void parse_compress_choice(int final_call) compress_choice = NULL; /* Snag the compression name for both write_batch's option output & the following debug output. */ - if (valid_compressions.negotiated_name) - compress_choice = valid_compressions.negotiated_name; + if (valid_compressions.negotiated_nni) + compress_choice = valid_compressions.negotiated_nni->name; else if (compress_choice == NULL) { struct name_num_item *nni = get_nni_by_num(&valid_compressions, do_compression); compress_choice = nni ? nni->name : "UNKNOWN"; @@ -208,7 +214,7 @@ void parse_compress_choice(int final_call) && (do_compression != CPRES_NONE || do_compression_level != CLVL_NOT_SPECIFIED)) { rprintf(FINFO, "%s%s compress: %s (level %d)\n", am_server ? "Server" : "Client", - valid_compressions.negotiated_name ? " negotiated" : "", + valid_compressions.negotiated_nni ? " negotiated" : "", compress_choice, do_compression_level); } } @@ -221,6 +227,8 @@ struct name_num_item *get_nni_by_name(struct name_num_obj *nno, const char *name len = strlen(name); for (nni = nno->list; nni->name; nni++) { + if (nni->num == CSUM_gone) + continue; if (strncasecmp(name, nni->name, len) == 0 && nni->name[len] == '\0') return nni; } @@ -255,10 +263,12 @@ static void init_nno_saw(struct name_num_obj *nno, int val) if (!nno->saw) { nno->saw = new_array0(uchar, nno->saw_len); - /* We'll take this opportunity to make sure that the main_name values are set right. */ + /* We'll take this opportunity to set the main_nni values for duplicates. */ for (cnt = 1, nni = nno->list; nni->name; nni++, cnt++) { + if (nni->num == CSUM_gone) + continue; if (nno->saw[nni->num]) - nni->main_name = nno->list[nno->saw[nni->num]-1].name; + nni->main_nni = &nno->list[nno->saw[nni->num]-1]; else nno->saw[nni->num] = cnt; } @@ -284,8 +294,8 @@ static int parse_nni_str(struct name_num_obj *nno, const char *from, char *tobuf struct name_num_item *nni = get_nni_by_name(nno, tok, to - tok); if (nni && !nno->saw[nni->num]) { nno->saw[nni->num] = ++cnt; - if (nni->main_name) { - to = tok + strlcpy(tok, nni->main_name, tobuf_len - (tok - tobuf)); + if (nni->main_nni) { + to = tok + strlcpy(tok, nni->main_nni->name, tobuf_len - (tok - tobuf)); if (to - tobuf >= tobuf_len) { to = tok - 1; break; @@ -319,13 +329,44 @@ static int parse_nni_str(struct name_num_obj *nno, const char *from, char *tobuf return to - tobuf; } +static int parse_negotiate_str(struct name_num_obj *nno, char *tmpbuf) +{ + struct name_num_item *nni, *ret = NULL; + int best = nno->saw_len; /* We want best == 1 from the client list, so start with a big number. */ + char *space, *tok = tmpbuf; + while (tok) { + while (*tok == ' ') tok++; /* Should be unneeded... */ + if (!*tok) + break; + if ((space = strchr(tok, ' ')) != NULL) + *space = '\0'; + nni = get_nni_by_name(nno, tok, -1); + if (space) { + *space = ' '; + tok = space + 1; + } else + tok = NULL; + if (!nni || !nno->saw[nni->num] || best <= nno->saw[nni->num]) + continue; + ret = nni; + best = nno->saw[nni->num]; + if (best == 1 || am_server) /* The server side stops at the first acceptable client choice */ + break; + } + if (ret) { + free(nno->saw); + nno->saw = NULL; + nno->negotiated_nni = ret->main_nni ? ret->main_nni : ret; + return 1; + } + return 0; +} + /* This routine is always called with a tmpbuf of MAX_NSTR_STRLEN length, but the * buffer may be pre-populated with a "len" length string to use OR a len of -1 * to tell us to read a string from the fd. */ static void recv_negotiate_str(int f_in, struct name_num_obj *nno, char *tmpbuf, int len) { - struct name_num_item *ret = NULL; - if (len < 0) len = read_vstring(f_in, tmpbuf, MAX_NSTR_STRLEN); @@ -336,37 +377,8 @@ static void recv_negotiate_str(int f_in, struct name_num_obj *nno, char *tmpbuf, rprintf(FINFO, "Server %s list (on client): %s\n", nno->type, tmpbuf); } - if (len > 0) { - struct name_num_item *nni; - int best = nno->saw_len; /* We want best == 1 from the client list, so start with a big number. */ - char *space, *tok = tmpbuf; - while (tok) { - while (*tok == ' ') tok++; /* Should be unneeded... */ - if (!*tok) - break; - if ((space = strchr(tok, ' ')) != NULL) - *space = '\0'; - nni = get_nni_by_name(nno, tok, -1); - if (space) { - *space = ' '; - tok = space + 1; - } else - tok = NULL; - if (!nni || !nno->saw[nni->num] || best <= nno->saw[nni->num]) - continue; - ret = nni; - best = nno->saw[nni->num]; - if (best == 1 || am_server) /* The server side stops at the first acceptable client choice */ - break; - } - if (ret) { - free(nno->saw); - nno->saw = NULL; - nno->negotiated_name = ret->main_name ? ret->main_name : ret->name; - nno->negotiated_num = ret->num; - return; - } - } + if (len > 0 && parse_negotiate_str(nno, tmpbuf)) + return; if (!am_server || !do_negotiated_strings) { char *cp = tmpbuf; @@ -398,7 +410,7 @@ static const char *getenv_nstr(int ntype) const char *env_str = getenv(ntype == NSTR_COMPRESS ? "RSYNC_COMPRESS_LIST" : "RSYNC_CHECKSUM_LIST"); /* When writing a batch file, we always negotiate an old-style choice. */ - if (write_batch) + if (write_batch) env_str = ntype == NSTR_COMPRESS ? "zlib" : protocol_version >= 30 ? "md5" : "md4"; if (am_server && env_str) { @@ -431,7 +443,7 @@ void validate_choice_vs_env(int ntype, int num1, int num2) nno->saw[CSUM_MD4_ARCHAIC] = nno->saw[CSUM_MD4_BUSTED] = nno->saw[CSUM_MD4_OLD] = nno->saw[CSUM_MD4]; if (!nno->saw[num1] || (num2 >= 0 && !nno->saw[num2])) { - rprintf(FERROR, "Your --%s-choice value (%s) was refused by the server.\n", + rprintf(FERROR, "Your --%s-choice value (%s) was refused by the server.\n", ntype == NSTR_COMPRESS ? "compress" : "checksum", ntype == NSTR_COMPRESS ? compress_choice : checksum_choice); exit_cleanup(RERR_UNSUPPORTED); @@ -462,8 +474,10 @@ int get_default_nno_list(struct name_num_obj *nno, char *to_buf, int to_buf_len, init_nno_saw(nno, 0); for (nni = nno->list, len = 0; nni->name; nni++) { - if (nni->main_name) { - if (!dup_markup) + if (nni->num == CSUM_gone) + continue; + if (nni->main_nni) { + if (!dup_markup || nni->main_nni->num == CSUM_gone) continue; delim = dup_markup; } @@ -521,6 +535,8 @@ static void negotiate_the_strings(int f_in, int f_out) { /* We send all the negotiation strings before we start to read them to help avoid a slow startup. */ + init_checksum_choices(); + if (!checksum_choice) send_negotiate_str(f_out, &valid_checksums, NSTR_CHECKSUM); @@ -550,7 +566,7 @@ static void negotiate_the_strings(int f_in, int f_out) /* If the other side is too old to negotiate, the above steps just made sure that * the env didn't disallow the old algorithm. Mark things as non-negotiated. */ if (!do_negotiated_strings) - valid_checksums.negotiated_name = valid_compressions.negotiated_name = NULL; + valid_checksums.negotiated_nni = valid_compressions.negotiated_nni = NULL; } void setup_protocol(int f_out,int f_in) @@ -602,7 +618,7 @@ void setup_protocol(int f_out,int f_in) if (remote_protocol < MIN_PROTOCOL_VERSION || remote_protocol > MAX_PROTOCOL_VERSION) { rprintf(FERROR,"protocol version mismatch -- is your shell clean?\n"); - rprintf(FERROR,"(see the rsync man page for an explanation)\n"); + rprintf(FERROR,"(see the rsync manpage for an explanation)\n"); exit_cleanup(RERR_PROTOCOL); } if (remote_protocol < OLD_PROTOCOL_VERSION) { @@ -622,6 +638,9 @@ void setup_protocol(int f_out,int f_in) if (read_batch) check_batch_flags(); + if (!saw_stderr_opt && protocol_version <= 28 && am_server) + msgs2stderr = 0; /* The client side may not have stderr setup for us. */ + #ifndef SUPPORT_PREALLOCATION if (preallocate_files && !am_sender) { rprintf(FERROR, "preallocation is not supported on this %s\n", @@ -796,11 +815,77 @@ void setup_protocol(int f_out,int f_in) checksum_seed = read_int(f_in); } - parse_checksum_choice(1); /* Sets checksum_type & xfersum_type */ + parse_checksum_choice(1); /* Sets file_sum_nni & xfer_sum_nni */ parse_compress_choice(1); /* Sets do_compression */ + /* TODO in the future allow this algorithm to be chosen somehow, but it can't get too + * long or the size starts to cause a problem in the xattr abbrev/non-abbrev code. */ + xattr_sum_nni = parse_csum_name(NULL, 0); + xattr_sum_len = csum_len_for_type(xattr_sum_nni->num, 0); + if (write_batch && !am_server) write_batch_shell_file(); init_flist(); } + +void output_daemon_greeting(int f_out, int am_client) +{ + char tmpbuf[MAX_NSTR_STRLEN]; + int our_sub = get_subprotocol_version(); + + init_checksum_choices(); + + get_default_nno_list(&valid_auth_checksums, tmpbuf, MAX_NSTR_STRLEN, '\0'); + + io_printf(f_out, "@RSYNCD: %d.%d %s\n", protocol_version, our_sub, tmpbuf); + + if (am_client && DEBUG_GTE(NSTR, 2)) + rprintf(FINFO, "Client %s list (on client): %s\n", valid_auth_checksums.type, tmpbuf); +} + +void negotiate_daemon_auth(int f_out, int am_client) +{ + char tmpbuf[MAX_NSTR_STRLEN]; + int save_am_server = am_server; + int md4_is_old = 0; + + if (!am_client) + am_server = 1; + + if (daemon_auth_choices) + strlcpy(tmpbuf, daemon_auth_choices, MAX_NSTR_STRLEN); + else { + strlcpy(tmpbuf, protocol_version >= 30 ? "md5" : "md4", MAX_NSTR_STRLEN); + md4_is_old = 1; + } + + if (am_client) { + recv_negotiate_str(-1, &valid_auth_checksums, tmpbuf, strlen(tmpbuf)); + if (DEBUG_GTE(NSTR, 1)) { + rprintf(FINFO, "Client negotiated %s: %s\n", valid_auth_checksums.type, + valid_auth_checksums.negotiated_nni->name); + } + } else { + if (!parse_negotiate_str(&valid_auth_checksums, tmpbuf)) { + get_default_nno_list(&valid_auth_checksums, tmpbuf, MAX_NSTR_STRLEN, '\0'); + io_printf(f_out, "@ERROR: your client does not support one of our daemon-auth checksums: %s\n", + tmpbuf); + exit_cleanup(RERR_UNSUPPORTED); + } + } + am_server = save_am_server; + if (md4_is_old && valid_auth_checksums.negotiated_nni->num == CSUM_MD4) { + valid_auth_checksums.negotiated_nni->num = CSUM_MD4_OLD; + valid_auth_checksums.negotiated_nni->flags = 0; + } +} + +int get_subprotocol_version() +{ +#if SUBPROTOCOL_VERSION != 0 + return protocol_version < PROTOCOL_VERSION ? 0 : SUBPROTOCOL_VERSION; +#else + return 0; +#endif +} diff --git a/config.guess b/config.guess index 92bfc33e2..69188da73 100644 --- a/config.guess +++ b/config.guess @@ -1,12 +1,14 @@ #! /bin/sh # Attempt to guess a canonical system name. -# Copyright 1992-2020 Free Software Foundation, Inc. +# Copyright 1992-2023 Free Software Foundation, Inc. -timestamp='2020-04-26' +# shellcheck disable=SC2006,SC2268 # see below for rationale + +timestamp='2023-01-01' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or +# the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but @@ -27,11 +29,19 @@ timestamp='2020-04-26' # Originally written by Per Bothner; maintained since 2000 by Ben Elliston. # # You can get the latest version of this script from: -# https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess +# https://git.savannah.gnu.org/cgit/config.git/plain/config.guess # # Please send patches to . +# The "shellcheck disable" line above the timestamp inhibits complaints +# about features and limitations of the classic Bourne shell that were +# superseded or lifted in POSIX. However, this script identifies a wide +# variety of pre-POSIX systems that do not have POSIX shells at all, and +# even some reasonably current systems (Solaris 10 as case-in-point) still +# have a pre-POSIX /bin/sh. + + me=`echo "$0" | sed -e 's,.*/,,'` usage="\ @@ -50,7 +60,7 @@ version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. -Copyright 1992-2020 Free Software Foundation, Inc. +Copyright 1992-2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." @@ -84,6 +94,9 @@ if test $# != 0; then exit 1 fi +# Just in case it came from the environment. +GUESS= + # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a @@ -102,7 +115,7 @@ set_cc_for_build() { # prevent multiple calls if $tmp is already set test "$tmp" && return 0 : "${TMPDIR=/tmp}" - # shellcheck disable=SC2039 + # shellcheck disable=SC2039,SC3028 { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } || @@ -112,7 +125,7 @@ set_cc_for_build() { ,,) echo "int x;" > "$dummy.c" for driver in cc gcc c89 c99 ; do if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then - CC_FOR_BUILD="$driver" + CC_FOR_BUILD=$driver break fi done @@ -133,14 +146,12 @@ fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown -UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown -case "$UNAME_SYSTEM" in +case $UNAME_SYSTEM in Linux|GNU|GNU/*) - # If the system lacks a compiler, then just pick glibc. - # We could probably try harder. - LIBC=gnu + LIBC=unknown set_cc_for_build cat <<-EOF > "$dummy.c" @@ -149,24 +160,37 @@ Linux|GNU|GNU/*) LIBC=uclibc #elif defined(__dietlibc__) LIBC=dietlibc - #else + #elif defined(__GLIBC__) LIBC=gnu + #else + #include + /* First heuristic to detect musl libc. */ + #ifdef __DEFINED_va_list + LIBC=musl + #endif #endif EOF - eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`" + cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` + eval "$cc_set_libc" - # If ldd exists, use it to detect musl libc. - if command -v ldd >/dev/null && \ - ldd --version 2>&1 | grep -q ^musl - then - LIBC=musl + # Second heuristic to detect musl libc. + if [ "$LIBC" = unknown ] && + command -v ldd >/dev/null && + ldd --version 2>&1 | grep -q ^musl; then + LIBC=musl + fi + + # If the system lacks a compiler, then just pick glibc. + # We could probably try harder. + if [ "$LIBC" = unknown ]; then + LIBC=gnu fi ;; esac # Note: order is significant - the case branches are not exclusive. -case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in +case $UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, @@ -178,12 +202,12 @@ case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". - sysctl="sysctl -n hw.machine_arch" UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ - "/sbin/$sysctl" 2>/dev/null || \ - "/usr/sbin/$sysctl" 2>/dev/null || \ + /sbin/sysctl -n hw.machine_arch 2>/dev/null || \ + /usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \ echo unknown)` - case "$UNAME_MACHINE_ARCH" in + case $UNAME_MACHINE_ARCH in + aarch64eb) machine=aarch64_be-unknown ;; armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; @@ -192,13 +216,13 @@ case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in earmv*) arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'` endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'` - machine="${arch}${endian}"-unknown + machine=${arch}${endian}-unknown ;; - *) machine="$UNAME_MACHINE_ARCH"-unknown ;; + *) machine=$UNAME_MACHINE_ARCH-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently (or will in the future) and ABI. - case "$UNAME_MACHINE_ARCH" in + case $UNAME_MACHINE_ARCH in earm*) os=netbsdelf ;; @@ -219,7 +243,7 @@ case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in ;; esac # Determine ABI tags. - case "$UNAME_MACHINE_ARCH" in + case $UNAME_MACHINE_ARCH in earm*) expr='s/^earmv[0-9]/-eabi/;s/eb$//' abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"` @@ -230,7 +254,7 @@ case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. - case "$UNAME_VERSION" in + case $UNAME_VERSION in Debian*) release='-gnu' ;; @@ -241,51 +265,57 @@ case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. - echo "$machine-${os}${release}${abi-}" - exit ;; + GUESS=$machine-${os}${release}${abi-} + ;; *:Bitrig:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` - echo "$UNAME_MACHINE_ARCH"-unknown-bitrig"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE_ARCH-unknown-bitrig$UNAME_RELEASE + ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` - echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE_ARCH-unknown-openbsd$UNAME_RELEASE + ;; + *:SecBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/SecBSD.//'` + GUESS=$UNAME_MACHINE_ARCH-unknown-secbsd$UNAME_RELEASE + ;; *:LibertyBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` - echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE_ARCH-unknown-libertybsd$UNAME_RELEASE + ;; *:MidnightBSD:*:*) - echo "$UNAME_MACHINE"-unknown-midnightbsd"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE-unknown-midnightbsd$UNAME_RELEASE + ;; *:ekkoBSD:*:*) - echo "$UNAME_MACHINE"-unknown-ekkobsd"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE-unknown-ekkobsd$UNAME_RELEASE + ;; *:SolidBSD:*:*) - echo "$UNAME_MACHINE"-unknown-solidbsd"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE-unknown-solidbsd$UNAME_RELEASE + ;; *:OS108:*:*) - echo "$UNAME_MACHINE"-unknown-os108_"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE-unknown-os108_$UNAME_RELEASE + ;; macppc:MirBSD:*:*) - echo powerpc-unknown-mirbsd"$UNAME_RELEASE" - exit ;; + GUESS=powerpc-unknown-mirbsd$UNAME_RELEASE + ;; *:MirBSD:*:*) - echo "$UNAME_MACHINE"-unknown-mirbsd"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE-unknown-mirbsd$UNAME_RELEASE + ;; *:Sortix:*:*) - echo "$UNAME_MACHINE"-unknown-sortix - exit ;; + GUESS=$UNAME_MACHINE-unknown-sortix + ;; *:Twizzler:*:*) - echo "$UNAME_MACHINE"-unknown-twizzler - exit ;; + GUESS=$UNAME_MACHINE-unknown-twizzler + ;; *:Redox:*:*) - echo "$UNAME_MACHINE"-unknown-redox - exit ;; + GUESS=$UNAME_MACHINE-unknown-redox + ;; mips:OSF1:*.*) - echo mips-dec-osf1 - exit ;; + GUESS=mips-dec-osf1 + ;; alpha:OSF1:*:*) + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + trap '' 0 case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` @@ -299,7 +329,7 @@ case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` - case "$ALPHA_CPU_TYPE" in + case $ALPHA_CPU_TYPE in "EV4 (21064)") UNAME_MACHINE=alpha ;; "EV4.5 (21064)") @@ -336,117 +366,121 @@ case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. - echo "$UNAME_MACHINE"-dec-osf"`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`" - # Reset EXIT trap before exiting to avoid spurious non-zero exit code. - exitcode=$? - trap '' 0 - exit $exitcode ;; + OSF_REL=`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` + GUESS=$UNAME_MACHINE-dec-osf$OSF_REL + ;; Amiga*:UNIX_System_V:4.0:*) - echo m68k-unknown-sysv4 - exit ;; + GUESS=m68k-unknown-sysv4 + ;; *:[Aa]miga[Oo][Ss]:*:*) - echo "$UNAME_MACHINE"-unknown-amigaos - exit ;; + GUESS=$UNAME_MACHINE-unknown-amigaos + ;; *:[Mm]orph[Oo][Ss]:*:*) - echo "$UNAME_MACHINE"-unknown-morphos - exit ;; + GUESS=$UNAME_MACHINE-unknown-morphos + ;; *:OS/390:*:*) - echo i370-ibm-openedition - exit ;; + GUESS=i370-ibm-openedition + ;; *:z/VM:*:*) - echo s390-ibm-zvmoe - exit ;; + GUESS=s390-ibm-zvmoe + ;; *:OS400:*:*) - echo powerpc-ibm-os400 - exit ;; + GUESS=powerpc-ibm-os400 + ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) - echo arm-acorn-riscix"$UNAME_RELEASE" - exit ;; + GUESS=arm-acorn-riscix$UNAME_RELEASE + ;; arm*:riscos:*:*|arm*:RISCOS:*:*) - echo arm-unknown-riscos - exit ;; + GUESS=arm-unknown-riscos + ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) - echo hppa1.1-hitachi-hiuxmpp - exit ;; + GUESS=hppa1.1-hitachi-hiuxmpp + ;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. - if test "`(/bin/universe) 2>/dev/null`" = att ; then - echo pyramid-pyramid-sysv3 - else - echo pyramid-pyramid-bsd - fi - exit ;; + case `(/bin/universe) 2>/dev/null` in + att) GUESS=pyramid-pyramid-sysv3 ;; + *) GUESS=pyramid-pyramid-bsd ;; + esac + ;; NILE*:*:*:dcosx) - echo pyramid-pyramid-svr4 - exit ;; + GUESS=pyramid-pyramid-svr4 + ;; DRS?6000:unix:4.0:6*) - echo sparc-icl-nx6 - exit ;; + GUESS=sparc-icl-nx6 + ;; DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) case `/usr/bin/uname -p` in - sparc) echo sparc-icl-nx7; exit ;; - esac ;; + sparc) GUESS=sparc-icl-nx7 ;; + esac + ;; s390x:SunOS:*:*) - echo "$UNAME_MACHINE"-ibm-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" - exit ;; + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=$UNAME_MACHINE-ibm-solaris2$SUN_REL + ;; sun4H:SunOS:5.*:*) - echo sparc-hal-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" - exit ;; + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=sparc-hal-solaris2$SUN_REL + ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) - echo sparc-sun-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" - exit ;; + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=sparc-sun-solaris2$SUN_REL + ;; i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) - echo i386-pc-auroraux"$UNAME_RELEASE" - exit ;; + GUESS=i386-pc-auroraux$UNAME_RELEASE + ;; i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) set_cc_for_build SUN_ARCH=i386 # If there is a compiler, see if it is configured for 64-bit objects. # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. # This test works for both compilers. - if [ "$CC_FOR_BUILD" != no_compiler_found ]; then + if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ - (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + (CCOPTS="" $CC_FOR_BUILD -m64 -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then SUN_ARCH=x86_64 fi fi - echo "$SUN_ARCH"-pc-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" - exit ;; + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=$SUN_ARCH-pc-solaris2$SUN_REL + ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. - echo sparc-sun-solaris3"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" - exit ;; + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=sparc-sun-solaris3$SUN_REL + ;; sun4*:SunOS:*:*) - case "`/usr/bin/arch -k`" in + case `/usr/bin/arch -k` in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. - echo sparc-sun-sunos"`echo "$UNAME_RELEASE"|sed -e 's/-/_/'`" - exit ;; + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'` + GUESS=sparc-sun-sunos$SUN_REL + ;; sun3*:SunOS:*:*) - echo m68k-sun-sunos"$UNAME_RELEASE" - exit ;; + GUESS=m68k-sun-sunos$UNAME_RELEASE + ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 - case "`/bin/arch`" in + case `/bin/arch` in sun3) - echo m68k-sun-sunos"$UNAME_RELEASE" + GUESS=m68k-sun-sunos$UNAME_RELEASE ;; sun4) - echo sparc-sun-sunos"$UNAME_RELEASE" + GUESS=sparc-sun-sunos$UNAME_RELEASE ;; esac - exit ;; + ;; aushp:SunOS:*:*) - echo sparc-auspex-sunos"$UNAME_RELEASE" - exit ;; + GUESS=sparc-auspex-sunos$UNAME_RELEASE + ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor @@ -456,41 +490,41 @@ case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) - echo m68k-atari-mint"$UNAME_RELEASE" - exit ;; + GUESS=m68k-atari-mint$UNAME_RELEASE + ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) - echo m68k-atari-mint"$UNAME_RELEASE" - exit ;; + GUESS=m68k-atari-mint$UNAME_RELEASE + ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) - echo m68k-atari-mint"$UNAME_RELEASE" - exit ;; + GUESS=m68k-atari-mint$UNAME_RELEASE + ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) - echo m68k-milan-mint"$UNAME_RELEASE" - exit ;; + GUESS=m68k-milan-mint$UNAME_RELEASE + ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) - echo m68k-hades-mint"$UNAME_RELEASE" - exit ;; + GUESS=m68k-hades-mint$UNAME_RELEASE + ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) - echo m68k-unknown-mint"$UNAME_RELEASE" - exit ;; + GUESS=m68k-unknown-mint$UNAME_RELEASE + ;; m68k:machten:*:*) - echo m68k-apple-machten"$UNAME_RELEASE" - exit ;; + GUESS=m68k-apple-machten$UNAME_RELEASE + ;; powerpc:machten:*:*) - echo powerpc-apple-machten"$UNAME_RELEASE" - exit ;; + GUESS=powerpc-apple-machten$UNAME_RELEASE + ;; RISC*:Mach:*:*) - echo mips-dec-mach_bsd4.3 - exit ;; + GUESS=mips-dec-mach_bsd4.3 + ;; RISC*:ULTRIX:*:*) - echo mips-dec-ultrix"$UNAME_RELEASE" - exit ;; + GUESS=mips-dec-ultrix$UNAME_RELEASE + ;; VAX*:ULTRIX*:*:*) - echo vax-dec-ultrix"$UNAME_RELEASE" - exit ;; + GUESS=vax-dec-ultrix$UNAME_RELEASE + ;; 2020:CLIX:*:* | 2430:CLIX:*:*) - echo clipper-intergraph-clix"$UNAME_RELEASE" - exit ;; + GUESS=clipper-intergraph-clix$UNAME_RELEASE + ;; mips:*:*:UMIPS | mips:*:*:RISCos) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" @@ -518,75 +552,76 @@ EOF dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` && SYSTEM_NAME=`"$dummy" "$dummyarg"` && { echo "$SYSTEM_NAME"; exit; } - echo mips-mips-riscos"$UNAME_RELEASE" - exit ;; + GUESS=mips-mips-riscos$UNAME_RELEASE + ;; Motorola:PowerMAX_OS:*:*) - echo powerpc-motorola-powermax - exit ;; + GUESS=powerpc-motorola-powermax + ;; Motorola:*:4.3:PL8-*) - echo powerpc-harris-powermax - exit ;; + GUESS=powerpc-harris-powermax + ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) - echo powerpc-harris-powermax - exit ;; + GUESS=powerpc-harris-powermax + ;; Night_Hawk:Power_UNIX:*:*) - echo powerpc-harris-powerunix - exit ;; + GUESS=powerpc-harris-powerunix + ;; m88k:CX/UX:7*:*) - echo m88k-harris-cxux7 - exit ;; + GUESS=m88k-harris-cxux7 + ;; m88k:*:4*:R4*) - echo m88k-motorola-sysv4 - exit ;; + GUESS=m88k-motorola-sysv4 + ;; m88k:*:3*:R3*) - echo m88k-motorola-sysv3 - exit ;; + GUESS=m88k-motorola-sysv3 + ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` - if [ "$UNAME_PROCESSOR" = mc88100 ] || [ "$UNAME_PROCESSOR" = mc88110 ] + if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110 then - if [ "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx ] || \ - [ "$TARGET_BINARY_INTERFACE"x = x ] + if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \ + test "$TARGET_BINARY_INTERFACE"x = x then - echo m88k-dg-dgux"$UNAME_RELEASE" + GUESS=m88k-dg-dgux$UNAME_RELEASE else - echo m88k-dg-dguxbcs"$UNAME_RELEASE" + GUESS=m88k-dg-dguxbcs$UNAME_RELEASE fi else - echo i586-dg-dgux"$UNAME_RELEASE" + GUESS=i586-dg-dgux$UNAME_RELEASE fi - exit ;; + ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) - echo m88k-dolphin-sysv3 - exit ;; + GUESS=m88k-dolphin-sysv3 + ;; M88*:*:R3*:*) # Delta 88k system running SVR3 - echo m88k-motorola-sysv3 - exit ;; + GUESS=m88k-motorola-sysv3 + ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) - echo m88k-tektronix-sysv3 - exit ;; + GUESS=m88k-tektronix-sysv3 + ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) - echo m68k-tektronix-bsd - exit ;; + GUESS=m68k-tektronix-bsd + ;; *:IRIX*:*:*) - echo mips-sgi-irix"`echo "$UNAME_RELEASE"|sed -e 's/-/_/g'`" - exit ;; + IRIX_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/g'` + GUESS=mips-sgi-irix$IRIX_REL + ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. - echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id - exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + GUESS=romp-ibm-aix # uname -m gives an 8 hex-code CPU id + ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) - echo i386-ibm-aix - exit ;; + GUESS=i386-ibm-aix + ;; ia64:AIX:*:*) - if [ -x /usr/bin/oslevel ] ; then + if test -x /usr/bin/oslevel ; then IBM_REV=`/usr/bin/oslevel` else - IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" + IBM_REV=$UNAME_VERSION.$UNAME_RELEASE fi - echo "$UNAME_MACHINE"-ibm-aix"$IBM_REV" - exit ;; + GUESS=$UNAME_MACHINE-ibm-aix$IBM_REV + ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then set_cc_for_build @@ -603,16 +638,16 @@ EOF EOF if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` then - echo "$SYSTEM_NAME" + GUESS=$SYSTEM_NAME else - echo rs6000-ibm-aix3.2.5 + GUESS=rs6000-ibm-aix3.2.5 fi elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then - echo rs6000-ibm-aix3.2.4 + GUESS=rs6000-ibm-aix3.2.4 else - echo rs6000-ibm-aix3.2 + GUESS=rs6000-ibm-aix3.2 fi - exit ;; + ;; *:AIX:*:[4567]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then @@ -620,56 +655,56 @@ EOF else IBM_ARCH=powerpc fi - if [ -x /usr/bin/lslpp ] ; then - IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | + if test -x /usr/bin/lslpp ; then + IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | \ awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` else - IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" + IBM_REV=$UNAME_VERSION.$UNAME_RELEASE fi - echo "$IBM_ARCH"-ibm-aix"$IBM_REV" - exit ;; + GUESS=$IBM_ARCH-ibm-aix$IBM_REV + ;; *:AIX:*:*) - echo rs6000-ibm-aix - exit ;; + GUESS=rs6000-ibm-aix + ;; ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) - echo romp-ibm-bsd4.4 - exit ;; + GUESS=romp-ibm-bsd4.4 + ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and - echo romp-ibm-bsd"$UNAME_RELEASE" # 4.3 with uname added to - exit ;; # report: romp-ibm BSD 4.3 + GUESS=romp-ibm-bsd$UNAME_RELEASE # 4.3 with uname added to + ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) - echo rs6000-bull-bosx - exit ;; + GUESS=rs6000-bull-bosx + ;; DPX/2?00:B.O.S.:*:*) - echo m68k-bull-sysv3 - exit ;; + GUESS=m68k-bull-sysv3 + ;; 9000/[34]??:4.3bsd:1.*:*) - echo m68k-hp-bsd - exit ;; + GUESS=m68k-hp-bsd + ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) - echo m68k-hp-bsd4.4 - exit ;; + GUESS=m68k-hp-bsd4.4 + ;; 9000/[34678]??:HP-UX:*:*) - HPUX_REV=`echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//'` - case "$UNAME_MACHINE" in + HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` + case $UNAME_MACHINE in 9000/31?) HP_ARCH=m68000 ;; 9000/[34]??) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) - if [ -x /usr/bin/getconf ]; then + if test -x /usr/bin/getconf; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` - case "$sc_cpu_version" in + case $sc_cpu_version in 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 - case "$sc_kernel_bits" in + case $sc_kernel_bits in 32) HP_ARCH=hppa2.0n ;; 64) HP_ARCH=hppa2.0w ;; '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 esac ;; esac fi - if [ "$HP_ARCH" = "" ]; then + if test "$HP_ARCH" = ""; then set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" @@ -708,7 +743,7 @@ EOF test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac - if [ "$HP_ARCH" = hppa2.0w ] + if test "$HP_ARCH" = hppa2.0w then set_cc_for_build @@ -729,12 +764,12 @@ EOF HP_ARCH=hppa64 fi fi - echo "$HP_ARCH"-hp-hpux"$HPUX_REV" - exit ;; + GUESS=$HP_ARCH-hp-hpux$HPUX_REV + ;; ia64:HP-UX:*:*) - HPUX_REV=`echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//'` - echo ia64-hp-hpux"$HPUX_REV" - exit ;; + HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` + GUESS=ia64-hp-hpux$HPUX_REV + ;; 3050*:HI-UX:*:*) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" @@ -764,36 +799,36 @@ EOF EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` && { echo "$SYSTEM_NAME"; exit; } - echo unknown-hitachi-hiuxwe2 - exit ;; + GUESS=unknown-hitachi-hiuxwe2 + ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) - echo hppa1.1-hp-bsd - exit ;; + GUESS=hppa1.1-hp-bsd + ;; 9000/8??:4.3bsd:*:*) - echo hppa1.0-hp-bsd - exit ;; + GUESS=hppa1.0-hp-bsd + ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) - echo hppa1.0-hp-mpeix - exit ;; + GUESS=hppa1.0-hp-mpeix + ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) - echo hppa1.1-hp-osf - exit ;; + GUESS=hppa1.1-hp-osf + ;; hp8??:OSF1:*:*) - echo hppa1.0-hp-osf - exit ;; + GUESS=hppa1.0-hp-osf + ;; i*86:OSF1:*:*) - if [ -x /usr/sbin/sysversion ] ; then - echo "$UNAME_MACHINE"-unknown-osf1mk + if test -x /usr/sbin/sysversion ; then + GUESS=$UNAME_MACHINE-unknown-osf1mk else - echo "$UNAME_MACHINE"-unknown-osf1 + GUESS=$UNAME_MACHINE-unknown-osf1 fi - exit ;; + ;; parisc*:Lites*:*:*) - echo hppa1.1-hp-lites - exit ;; + GUESS=hppa1.1-hp-lites + ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) - echo c1-convex-bsd - exit ;; + GUESS=c1-convex-bsd + ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd @@ -801,17 +836,18 @@ EOF fi exit ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) - echo c34-convex-bsd - exit ;; + GUESS=c34-convex-bsd + ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) - echo c38-convex-bsd - exit ;; + GUESS=c38-convex-bsd + ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) - echo c4-convex-bsd - exit ;; + GUESS=c4-convex-bsd + ;; CRAY*Y-MP:*:*:*) - echo ymp-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' - exit ;; + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=ymp-cray-unicos$CRAY_REL + ;; CRAY*[A-Z]90:*:*:*) echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ @@ -819,112 +855,133 @@ EOF -e 's/\.[^.]*$/.X/' exit ;; CRAY*TS:*:*:*) - echo t90-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' - exit ;; + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=t90-cray-unicos$CRAY_REL + ;; CRAY*T3E:*:*:*) - echo alphaev5-cray-unicosmk"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' - exit ;; + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=alphaev5-cray-unicosmk$CRAY_REL + ;; CRAY*SV1:*:*:*) - echo sv1-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' - exit ;; + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=sv1-cray-unicos$CRAY_REL + ;; *:UNICOS/mp:*:*) - echo craynv-cray-unicosmp"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' - exit ;; + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=craynv-cray-unicosmp$CRAY_REL + ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'` - echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" - exit ;; + GUESS=${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} + ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` - echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" - exit ;; + GUESS=sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} + ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) - echo "$UNAME_MACHINE"-pc-bsdi"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE-pc-bsdi$UNAME_RELEASE + ;; sparc*:BSD/OS:*:*) - echo sparc-unknown-bsdi"$UNAME_RELEASE" - exit ;; + GUESS=sparc-unknown-bsdi$UNAME_RELEASE + ;; *:BSD/OS:*:*) - echo "$UNAME_MACHINE"-unknown-bsdi"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE-unknown-bsdi$UNAME_RELEASE + ;; arm:FreeBSD:*:*) UNAME_PROCESSOR=`uname -p` set_cc_for_build if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then - echo "${UNAME_PROCESSOR}"-unknown-freebsd"`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`"-gnueabi + FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabi else - echo "${UNAME_PROCESSOR}"-unknown-freebsd"`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`"-gnueabihf + FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabihf fi - exit ;; + ;; *:FreeBSD:*:*) UNAME_PROCESSOR=`/usr/bin/uname -p` - case "$UNAME_PROCESSOR" in + case $UNAME_PROCESSOR in amd64) UNAME_PROCESSOR=x86_64 ;; i386) UNAME_PROCESSOR=i586 ;; esac - echo "$UNAME_PROCESSOR"-unknown-freebsd"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" - exit ;; + FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL + ;; i*:CYGWIN*:*) - echo "$UNAME_MACHINE"-pc-cygwin - exit ;; + GUESS=$UNAME_MACHINE-pc-cygwin + ;; *:MINGW64*:*) - echo "$UNAME_MACHINE"-pc-mingw64 - exit ;; + GUESS=$UNAME_MACHINE-pc-mingw64 + ;; *:MINGW*:*) - echo "$UNAME_MACHINE"-pc-mingw32 - exit ;; + GUESS=$UNAME_MACHINE-pc-mingw32 + ;; *:MSYS*:*) - echo "$UNAME_MACHINE"-pc-msys - exit ;; + GUESS=$UNAME_MACHINE-pc-msys + ;; i*:PW*:*) - echo "$UNAME_MACHINE"-pc-pw32 - exit ;; + GUESS=$UNAME_MACHINE-pc-pw32 + ;; + *:SerenityOS:*:*) + GUESS=$UNAME_MACHINE-pc-serenity + ;; *:Interix*:*) - case "$UNAME_MACHINE" in + case $UNAME_MACHINE in x86) - echo i586-pc-interix"$UNAME_RELEASE" - exit ;; + GUESS=i586-pc-interix$UNAME_RELEASE + ;; authenticamd | genuineintel | EM64T) - echo x86_64-unknown-interix"$UNAME_RELEASE" - exit ;; + GUESS=x86_64-unknown-interix$UNAME_RELEASE + ;; IA64) - echo ia64-unknown-interix"$UNAME_RELEASE" - exit ;; + GUESS=ia64-unknown-interix$UNAME_RELEASE + ;; esac ;; i*:UWIN*:*) - echo "$UNAME_MACHINE"-pc-uwin - exit ;; + GUESS=$UNAME_MACHINE-pc-uwin + ;; amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) - echo x86_64-pc-cygwin - exit ;; + GUESS=x86_64-pc-cygwin + ;; prep*:SunOS:5.*:*) - echo powerpcle-unknown-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" - exit ;; + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=powerpcle-unknown-solaris2$SUN_REL + ;; *:GNU:*:*) # the GNU system - echo "`echo "$UNAME_MACHINE"|sed -e 's,[-/].*$,,'`-unknown-$LIBC`echo "$UNAME_RELEASE"|sed -e 's,/.*$,,'`" - exit ;; + GNU_ARCH=`echo "$UNAME_MACHINE" | sed -e 's,[-/].*$,,'` + GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's,/.*$,,'` + GUESS=$GNU_ARCH-unknown-$LIBC$GNU_REL + ;; *:GNU/*:*:*) # other systems with GNU libc and userland - echo "$UNAME_MACHINE-unknown-`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"``echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`-$LIBC" - exit ;; + GNU_SYS=`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"` + GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC + ;; + x86_64:[Mm]anagarm:*:*|i?86:[Mm]anagarm:*:*) + GUESS="$UNAME_MACHINE-pc-managarm-mlibc" + ;; + *:[Mm]anagarm:*:*) + GUESS="$UNAME_MACHINE-unknown-managarm-mlibc" + ;; *:Minix:*:*) - echo "$UNAME_MACHINE"-unknown-minix - exit ;; + GUESS=$UNAME_MACHINE-unknown-minix + ;; aarch64:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; aarch64_be:Linux:*:*) UNAME_MACHINE=aarch64_be - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null` in EV5) UNAME_MACHINE=alphaev5 ;; @@ -937,60 +994,63 @@ EOF esac objdump --private-headers /bin/sh | grep -q ld.so.1 if test "$?" = 0 ; then LIBC=gnulibc1 ; fi - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; - arc:Linux:*:* | arceb:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + arc:Linux:*:* | arceb:Linux:*:* | arc32:Linux:*:* | arc64:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; arm*:Linux:*:*) set_cc_for_build if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_EABI__ then - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC else if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabi + GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabi else - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabihf + GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabihf fi fi - exit ;; + ;; avr32*:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; cris:Linux:*:*) - echo "$UNAME_MACHINE"-axis-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-axis-linux-$LIBC + ;; crisv32:Linux:*:*) - echo "$UNAME_MACHINE"-axis-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-axis-linux-$LIBC + ;; e2k:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; frv:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; hexagon:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; i*86:Linux:*:*) - echo "$UNAME_MACHINE"-pc-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-pc-linux-$LIBC + ;; ia64:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; k1om:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + loongarch32:Linux:*:* | loongarch64:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; m32r*:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; m68*:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; mips:Linux:*:* | mips64:Linux:*:*) set_cc_for_build IS_GLIBC=0 @@ -1035,123 +1095,135 @@ EOF #endif #endif EOF - eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'`" + cc_set_vars=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'` + eval "$cc_set_vars" test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; } ;; mips64el:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; openrisc*:Linux:*:*) - echo or1k-unknown-linux-"$LIBC" - exit ;; + GUESS=or1k-unknown-linux-$LIBC + ;; or32:Linux:*:* | or1k*:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; padre:Linux:*:*) - echo sparc-unknown-linux-"$LIBC" - exit ;; + GUESS=sparc-unknown-linux-$LIBC + ;; parisc64:Linux:*:* | hppa64:Linux:*:*) - echo hppa64-unknown-linux-"$LIBC" - exit ;; + GUESS=hppa64-unknown-linux-$LIBC + ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in - PA7*) echo hppa1.1-unknown-linux-"$LIBC" ;; - PA8*) echo hppa2.0-unknown-linux-"$LIBC" ;; - *) echo hppa-unknown-linux-"$LIBC" ;; + PA7*) GUESS=hppa1.1-unknown-linux-$LIBC ;; + PA8*) GUESS=hppa2.0-unknown-linux-$LIBC ;; + *) GUESS=hppa-unknown-linux-$LIBC ;; esac - exit ;; + ;; ppc64:Linux:*:*) - echo powerpc64-unknown-linux-"$LIBC" - exit ;; + GUESS=powerpc64-unknown-linux-$LIBC + ;; ppc:Linux:*:*) - echo powerpc-unknown-linux-"$LIBC" - exit ;; + GUESS=powerpc-unknown-linux-$LIBC + ;; ppc64le:Linux:*:*) - echo powerpc64le-unknown-linux-"$LIBC" - exit ;; + GUESS=powerpc64le-unknown-linux-$LIBC + ;; ppcle:Linux:*:*) - echo powerpcle-unknown-linux-"$LIBC" - exit ;; - riscv32:Linux:*:* | riscv64:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=powerpcle-unknown-linux-$LIBC + ;; + riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; s390:Linux:*:* | s390x:Linux:*:*) - echo "$UNAME_MACHINE"-ibm-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-ibm-linux-$LIBC + ;; sh64*:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; sh*:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; sparc:Linux:*:* | sparc64:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; tile*:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; vax:Linux:*:*) - echo "$UNAME_MACHINE"-dec-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-dec-linux-$LIBC + ;; x86_64:Linux:*:*) set_cc_for_build + CPU=$UNAME_MACHINE LIBCABI=$LIBC - if [ "$CC_FOR_BUILD" != no_compiler_found ]; then - if (echo '#ifdef __ILP32__'; echo IS_X32; echo '#endif') | \ - (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ - grep IS_X32 >/dev/null - then - LIBCABI="$LIBC"x32 - fi + if test "$CC_FOR_BUILD" != no_compiler_found; then + ABI=64 + sed 's/^ //' << EOF > "$dummy.c" + #ifdef __i386__ + ABI=x86 + #else + #ifdef __ILP32__ + ABI=x32 + #endif + #endif +EOF + cc_set_abi=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^ABI' | sed 's, ,,g'` + eval "$cc_set_abi" + case $ABI in + x86) CPU=i686 ;; + x32) LIBCABI=${LIBC}x32 ;; + esac fi - echo "$UNAME_MACHINE"-pc-linux-"$LIBCABI" - exit ;; + GUESS=$CPU-pc-linux-$LIBCABI + ;; xtensa*:Linux:*:*) - echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" - exit ;; + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. - echo i386-sequent-sysv4 - exit ;; + GUESS=i386-sequent-sysv4 + ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. - echo "$UNAME_MACHINE"-pc-sysv4.2uw"$UNAME_VERSION" - exit ;; + GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION + ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. - echo "$UNAME_MACHINE"-pc-os2-emx - exit ;; + GUESS=$UNAME_MACHINE-pc-os2-emx + ;; i*86:XTS-300:*:STOP) - echo "$UNAME_MACHINE"-unknown-stop - exit ;; + GUESS=$UNAME_MACHINE-unknown-stop + ;; i*86:atheos:*:*) - echo "$UNAME_MACHINE"-unknown-atheos - exit ;; + GUESS=$UNAME_MACHINE-unknown-atheos + ;; i*86:syllable:*:*) - echo "$UNAME_MACHINE"-pc-syllable - exit ;; + GUESS=$UNAME_MACHINE-pc-syllable + ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) - echo i386-unknown-lynxos"$UNAME_RELEASE" - exit ;; + GUESS=i386-unknown-lynxos$UNAME_RELEASE + ;; i*86:*DOS:*:*) - echo "$UNAME_MACHINE"-pc-msdosdjgpp - exit ;; + GUESS=$UNAME_MACHINE-pc-msdosdjgpp + ;; i*86:*:4.*:*) UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then - echo "$UNAME_MACHINE"-univel-sysv"$UNAME_REL" + GUESS=$UNAME_MACHINE-univel-sysv$UNAME_REL else - echo "$UNAME_MACHINE"-pc-sysv"$UNAME_REL" + GUESS=$UNAME_MACHINE-pc-sysv$UNAME_REL fi - exit ;; + ;; i*86:*:5:[678]*) # UnixWare 7.x, OpenUNIX and OpenServer 6. case `/bin/uname -X | grep "^Machine"` in @@ -1159,12 +1231,12 @@ EOF *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac - echo "$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}" - exit ;; + GUESS=$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 @@ -1174,11 +1246,11 @@ EOF && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 - echo "$UNAME_MACHINE"-pc-sco"$UNAME_REL" + GUESS=$UNAME_MACHINE-pc-sco$UNAME_REL else - echo "$UNAME_MACHINE"-pc-sysv32 + GUESS=$UNAME_MACHINE-pc-sysv32 fi - exit ;; + ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about @@ -1186,31 +1258,31 @@ EOF # Note: whatever this is, it MUST be the same as what config.sub # prints for the "djgpp" host, or else GDB configure will decide that # this is a cross-build. - echo i586-pc-msdosdjgpp - exit ;; + GUESS=i586-pc-msdosdjgpp + ;; Intel:Mach:3*:*) - echo i386-pc-mach3 - exit ;; + GUESS=i386-pc-mach3 + ;; paragon:*:*:*) - echo i860-intel-osf1 - exit ;; + GUESS=i860-intel-osf1 + ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then - echo i860-stardent-sysv"$UNAME_RELEASE" # Stardent Vistra i860-SVR4 + GUESS=i860-stardent-sysv$UNAME_RELEASE # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. - echo i860-unknown-sysv"$UNAME_RELEASE" # Unknown i860-SVR4 + GUESS=i860-unknown-sysv$UNAME_RELEASE # Unknown i860-SVR4 fi - exit ;; + ;; mini*:CTIX:SYS*5:*) # "miniframe" - echo m68010-convergent-sysv - exit ;; + GUESS=m68010-convergent-sysv + ;; mc68k:UNIX:SYSTEM5:3.51m) - echo m68k-convergent-sysv - exit ;; + GUESS=m68k-convergent-sysv + ;; M680?0:D-NIX:5.3:*) - echo m68k-diab-dnix - exit ;; + GUESS=m68k-diab-dnix + ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) @@ -1235,113 +1307,119 @@ EOF /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) - echo m68k-unknown-lynxos"$UNAME_RELEASE" - exit ;; + GUESS=m68k-unknown-lynxos$UNAME_RELEASE + ;; mc68030:UNIX_System_V:4.*:*) - echo m68k-atari-sysv4 - exit ;; + GUESS=m68k-atari-sysv4 + ;; TSUNAMI:LynxOS:2.*:*) - echo sparc-unknown-lynxos"$UNAME_RELEASE" - exit ;; + GUESS=sparc-unknown-lynxos$UNAME_RELEASE + ;; rs6000:LynxOS:2.*:*) - echo rs6000-unknown-lynxos"$UNAME_RELEASE" - exit ;; + GUESS=rs6000-unknown-lynxos$UNAME_RELEASE + ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) - echo powerpc-unknown-lynxos"$UNAME_RELEASE" - exit ;; + GUESS=powerpc-unknown-lynxos$UNAME_RELEASE + ;; SM[BE]S:UNIX_SV:*:*) - echo mips-dde-sysv"$UNAME_RELEASE" - exit ;; + GUESS=mips-dde-sysv$UNAME_RELEASE + ;; RM*:ReliantUNIX-*:*:*) - echo mips-sni-sysv4 - exit ;; + GUESS=mips-sni-sysv4 + ;; RM*:SINIX-*:*:*) - echo mips-sni-sysv4 - exit ;; + GUESS=mips-sni-sysv4 + ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` - echo "$UNAME_MACHINE"-sni-sysv4 + GUESS=$UNAME_MACHINE-sni-sysv4 else - echo ns32k-sni-sysv + GUESS=ns32k-sni-sysv fi - exit ;; + ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says - echo i586-unisys-sysv4 - exit ;; + GUESS=i586-unisys-sysv4 + ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm - echo hppa1.1-stratus-sysv4 - exit ;; + GUESS=hppa1.1-stratus-sysv4 + ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. - echo i860-stratus-sysv4 - exit ;; + GUESS=i860-stratus-sysv4 + ;; i*86:VOS:*:*) # From Paul.Green@stratus.com. - echo "$UNAME_MACHINE"-stratus-vos - exit ;; + GUESS=$UNAME_MACHINE-stratus-vos + ;; *:VOS:*:*) # From Paul.Green@stratus.com. - echo hppa1.1-stratus-vos - exit ;; + GUESS=hppa1.1-stratus-vos + ;; mc68*:A/UX:*:*) - echo m68k-apple-aux"$UNAME_RELEASE" - exit ;; + GUESS=m68k-apple-aux$UNAME_RELEASE + ;; news*:NEWS-OS:6*:*) - echo mips-sony-newsos6 - exit ;; + GUESS=mips-sony-newsos6 + ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) - if [ -d /usr/nec ]; then - echo mips-nec-sysv"$UNAME_RELEASE" + if test -d /usr/nec; then + GUESS=mips-nec-sysv$UNAME_RELEASE else - echo mips-unknown-sysv"$UNAME_RELEASE" + GUESS=mips-unknown-sysv$UNAME_RELEASE fi - exit ;; + ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. - echo powerpc-be-beos - exit ;; + GUESS=powerpc-be-beos + ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. - echo powerpc-apple-beos - exit ;; + GUESS=powerpc-apple-beos + ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. - echo i586-pc-beos - exit ;; + GUESS=i586-pc-beos + ;; BePC:Haiku:*:*) # Haiku running on Intel PC compatible. - echo i586-pc-haiku - exit ;; - x86_64:Haiku:*:*) - echo x86_64-unknown-haiku - exit ;; + GUESS=i586-pc-haiku + ;; + ppc:Haiku:*:*) # Haiku running on Apple PowerPC + GUESS=powerpc-apple-haiku + ;; + *:Haiku:*:*) # Haiku modern gcc (not bound by BeOS compat) + GUESS=$UNAME_MACHINE-unknown-haiku + ;; SX-4:SUPER-UX:*:*) - echo sx4-nec-superux"$UNAME_RELEASE" - exit ;; + GUESS=sx4-nec-superux$UNAME_RELEASE + ;; SX-5:SUPER-UX:*:*) - echo sx5-nec-superux"$UNAME_RELEASE" - exit ;; + GUESS=sx5-nec-superux$UNAME_RELEASE + ;; SX-6:SUPER-UX:*:*) - echo sx6-nec-superux"$UNAME_RELEASE" - exit ;; + GUESS=sx6-nec-superux$UNAME_RELEASE + ;; SX-7:SUPER-UX:*:*) - echo sx7-nec-superux"$UNAME_RELEASE" - exit ;; + GUESS=sx7-nec-superux$UNAME_RELEASE + ;; SX-8:SUPER-UX:*:*) - echo sx8-nec-superux"$UNAME_RELEASE" - exit ;; + GUESS=sx8-nec-superux$UNAME_RELEASE + ;; SX-8R:SUPER-UX:*:*) - echo sx8r-nec-superux"$UNAME_RELEASE" - exit ;; + GUESS=sx8r-nec-superux$UNAME_RELEASE + ;; SX-ACE:SUPER-UX:*:*) - echo sxace-nec-superux"$UNAME_RELEASE" - exit ;; + GUESS=sxace-nec-superux$UNAME_RELEASE + ;; Power*:Rhapsody:*:*) - echo powerpc-apple-rhapsody"$UNAME_RELEASE" - exit ;; + GUESS=powerpc-apple-rhapsody$UNAME_RELEASE + ;; *:Rhapsody:*:*) - echo "$UNAME_MACHINE"-apple-rhapsody"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE-apple-rhapsody$UNAME_RELEASE + ;; + arm64:Darwin:*:*) + GUESS=aarch64-apple-darwin$UNAME_RELEASE + ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` case $UNAME_PROCESSOR in @@ -1356,7 +1434,7 @@ EOF else set_cc_for_build fi - if [ "$CC_FOR_BUILD" != no_compiler_found ]; then + if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null @@ -1377,109 +1455,119 @@ EOF # uname -m returns i386 or x86_64 UNAME_PROCESSOR=$UNAME_MACHINE fi - echo "$UNAME_PROCESSOR"-apple-darwin"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_PROCESSOR-apple-darwin$UNAME_RELEASE + ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = x86; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi - echo "$UNAME_PROCESSOR"-"$UNAME_MACHINE"-nto-qnx"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_PROCESSOR-$UNAME_MACHINE-nto-qnx$UNAME_RELEASE + ;; *:QNX:*:4*) - echo i386-pc-qnx - exit ;; + GUESS=i386-pc-qnx + ;; NEO-*:NONSTOP_KERNEL:*:*) - echo neo-tandem-nsk"$UNAME_RELEASE" - exit ;; + GUESS=neo-tandem-nsk$UNAME_RELEASE + ;; NSE-*:NONSTOP_KERNEL:*:*) - echo nse-tandem-nsk"$UNAME_RELEASE" - exit ;; + GUESS=nse-tandem-nsk$UNAME_RELEASE + ;; NSR-*:NONSTOP_KERNEL:*:*) - echo nsr-tandem-nsk"$UNAME_RELEASE" - exit ;; + GUESS=nsr-tandem-nsk$UNAME_RELEASE + ;; NSV-*:NONSTOP_KERNEL:*:*) - echo nsv-tandem-nsk"$UNAME_RELEASE" - exit ;; + GUESS=nsv-tandem-nsk$UNAME_RELEASE + ;; NSX-*:NONSTOP_KERNEL:*:*) - echo nsx-tandem-nsk"$UNAME_RELEASE" - exit ;; + GUESS=nsx-tandem-nsk$UNAME_RELEASE + ;; *:NonStop-UX:*:*) - echo mips-compaq-nonstopux - exit ;; + GUESS=mips-compaq-nonstopux + ;; BS2000:POSIX*:*:*) - echo bs2000-siemens-sysv - exit ;; + GUESS=bs2000-siemens-sysv + ;; DS/*:UNIX_System_V:*:*) - echo "$UNAME_MACHINE"-"$UNAME_SYSTEM"-"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE-$UNAME_SYSTEM-$UNAME_RELEASE + ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. - # shellcheck disable=SC2154 - if test "$cputype" = 386; then + if test "${cputype-}" = 386; then UNAME_MACHINE=i386 - else - UNAME_MACHINE="$cputype" + elif test "x${cputype-}" != x; then + UNAME_MACHINE=$cputype fi - echo "$UNAME_MACHINE"-unknown-plan9 - exit ;; + GUESS=$UNAME_MACHINE-unknown-plan9 + ;; *:TOPS-10:*:*) - echo pdp10-unknown-tops10 - exit ;; + GUESS=pdp10-unknown-tops10 + ;; *:TENEX:*:*) - echo pdp10-unknown-tenex - exit ;; + GUESS=pdp10-unknown-tenex + ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) - echo pdp10-dec-tops20 - exit ;; + GUESS=pdp10-dec-tops20 + ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) - echo pdp10-xkl-tops20 - exit ;; + GUESS=pdp10-xkl-tops20 + ;; *:TOPS-20:*:*) - echo pdp10-unknown-tops20 - exit ;; + GUESS=pdp10-unknown-tops20 + ;; *:ITS:*:*) - echo pdp10-unknown-its - exit ;; + GUESS=pdp10-unknown-its + ;; SEI:*:*:SEIUX) - echo mips-sei-seiux"$UNAME_RELEASE" - exit ;; + GUESS=mips-sei-seiux$UNAME_RELEASE + ;; *:DragonFly:*:*) - echo "$UNAME_MACHINE"-unknown-dragonfly"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" - exit ;; + DRAGONFLY_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_MACHINE-unknown-dragonfly$DRAGONFLY_REL + ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` - case "$UNAME_MACHINE" in - A*) echo alpha-dec-vms ; exit ;; - I*) echo ia64-dec-vms ; exit ;; - V*) echo vax-dec-vms ; exit ;; + case $UNAME_MACHINE in + A*) GUESS=alpha-dec-vms ;; + I*) GUESS=ia64-dec-vms ;; + V*) GUESS=vax-dec-vms ;; esac ;; *:XENIX:*:SysV) - echo i386-pc-xenix - exit ;; + GUESS=i386-pc-xenix + ;; i*86:skyos:*:*) - echo "$UNAME_MACHINE"-pc-skyos"`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'`" - exit ;; + SKYOS_REL=`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'` + GUESS=$UNAME_MACHINE-pc-skyos$SKYOS_REL + ;; i*86:rdos:*:*) - echo "$UNAME_MACHINE"-pc-rdos - exit ;; - i*86:AROS:*:*) - echo "$UNAME_MACHINE"-pc-aros - exit ;; + GUESS=$UNAME_MACHINE-pc-rdos + ;; + i*86:Fiwix:*:*) + GUESS=$UNAME_MACHINE-pc-fiwix + ;; + *:AROS:*:*) + GUESS=$UNAME_MACHINE-unknown-aros + ;; x86_64:VMkernel:*:*) - echo "$UNAME_MACHINE"-unknown-esx - exit ;; + GUESS=$UNAME_MACHINE-unknown-esx + ;; amd64:Isilon\ OneFS:*:*) - echo x86_64-unknown-onefs - exit ;; + GUESS=x86_64-unknown-onefs + ;; *:Unleashed:*:*) - echo "$UNAME_MACHINE"-unknown-unleashed"$UNAME_RELEASE" - exit ;; + GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE + ;; esac +# Do we have a guess based on uname results? +if test "x$GUESS" != x; then + echo "$GUESS" + exit +fi + # No uname command or uname output not recognized. set_cc_for_build cat > "$dummy.c" </dev/null && SYSTEM_NAME=`$dummy` && +$CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=`"$dummy"` && { echo "$SYSTEM_NAME"; exit; } # Apollos put the system type in the environment. @@ -1619,7 +1707,7 @@ test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; } echo "$0: unable to guess system type" >&2 -case "$UNAME_MACHINE:$UNAME_SYSTEM" in +case $UNAME_MACHINE:$UNAME_SYSTEM in mips:Linux | mips64:Linux) # If we got here on MIPS GNU/Linux, output extra information. cat >&2 <&2 <&2 - exit 1 + # No normalization, but not necessarily accepted, that comes below. ;; esac + else # Here we handle the default operating systems that come with various machines. @@ -1528,6 +1531,7 @@ else # will signal an error saying that MANUFACTURER isn't an operating # system, and we'll never get to this point. +kernel= case $cpu-$vendor in score-*) os=elf @@ -1539,7 +1543,8 @@ case $cpu-$vendor in os=riscix1.2 ;; arm*-rebel) - os=linux + kernel=linux + os=gnu ;; arm*-semi) os=aout @@ -1705,84 +1710,193 @@ case $cpu-$vendor in os=none ;; esac + fi +# Now, validate our (potentially fixed-up) OS. +case $os in + # Sometimes we do "kernel-libc", so those need to count as OSes. + musl* | newlib* | relibc* | uclibc*) + ;; + # Likewise for "kernel-abi" + eabi* | gnueabi*) + ;; + # VxWorks passes extra cpu info in the 4th filed. + simlinux | simwindows | spe) + ;; + # Now accept the basic system types. + # The portable systems comes first. + # Each alternative MUST end in a * to match a version number. + gnu* | android* | bsd* | mach* | minix* | genix* | ultrix* | irix* \ + | *vms* | esix* | aix* | cnk* | sunos | sunos[34]* \ + | hpux* | unos* | osf* | luna* | dgux* | auroraux* | solaris* \ + | sym* | plan9* | psp* | sim* | xray* | os68k* | v88r* \ + | hiux* | abug | nacl* | netware* | windows* \ + | os9* | macos* | osx* | ios* \ + | mpw* | magic* | mmixware* | mon960* | lnews* \ + | amigaos* | amigados* | msdos* | newsos* | unicos* | aof* \ + | aos* | aros* | cloudabi* | sortix* | twizzler* \ + | nindy* | vxsim* | vxworks* | ebmon* | hms* | mvs* \ + | clix* | riscos* | uniplus* | iris* | isc* | rtu* | xenix* \ + | mirbsd* | netbsd* | dicos* | openedition* | ose* \ + | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \ + | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \ + | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \ + | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \ + | udi* | lites* | ieee* | go32* | aux* | hcos* \ + | chorusrdb* | cegcc* | glidix* | serenity* \ + | cygwin* | msys* | pe* | moss* | proelf* | rtems* \ + | midipix* | mingw32* | mingw64* | mint* \ + | uxpv* | beos* | mpeix* | udk* | moxiebox* \ + | interix* | uwin* | mks* | rhapsody* | darwin* \ + | openstep* | oskit* | conix* | pw32* | nonstopux* \ + | storm-chaos* | tops10* | tenex* | tops20* | its* \ + | os2* | vos* | palmos* | uclinux* | nucleus* | morphos* \ + | scout* | superux* | sysv* | rtmk* | tpf* | windiss* \ + | powermax* | dnix* | nx6 | nx7 | sei* | dragonfly* \ + | skyos* | haiku* | rdos* | toppers* | drops* | es* \ + | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \ + | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \ + | nsk* | powerunix* | genode* | zvmoe* | qnx* | emx* | zephyr* \ + | fiwix* | mlibc* ) + ;; + # This one is extra strict with allowed versions + sco3.2v2 | sco3.2v[4-9]* | sco5v6*) + # Don't forget version if it is 3.2v4 or newer. + ;; + none) + ;; + kernel* ) + # Restricted further below + ;; + *) + echo Invalid configuration \`"$1"\': OS \`"$os"\' not recognized 1>&2 + exit 1 + ;; +esac + +# As a final step for OS-related things, validate the OS-kernel combination +# (given a valid OS), if there is a kernel. +case $kernel-$os in + linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \ + | linux-musl* | linux-relibc* | linux-uclibc* | linux-mlibc* ) + ;; + uclinux-uclibc* ) + ;; + managarm-mlibc* | managarm-kernel* ) + ;; + -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* | -mlibc* ) + # These are just libc implementations, not actual OSes, and thus + # require a kernel. + echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2 + exit 1 + ;; + -kernel* ) + echo "Invalid configuration \`$1': \`$os' needs explicit kernel." 1>&2 + exit 1 + ;; + *-kernel* ) + echo "Invalid configuration \`$1': \`$kernel' does not support \`$os'." 1>&2 + exit 1 + ;; + kfreebsd*-gnu* | kopensolaris*-gnu*) + ;; + vxworks-simlinux | vxworks-simwindows | vxworks-spe) + ;; + nto-qnx*) + ;; + os2-emx) + ;; + *-eabi* | *-gnueabi*) + ;; + -*) + # Blank kernel with real OS is always fine. + ;; + *-*) + echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2 + exit 1 + ;; +esac + # Here we handle the case where we know the os, and the CPU type, but not the # manufacturer. We pick the logical manufacturer. case $vendor in unknown) - case $os in - riscix*) + case $cpu-$os in + *-riscix*) vendor=acorn ;; - sunos*) + *-sunos*) vendor=sun ;; - cnk*|-aix*) + *-cnk* | *-aix*) vendor=ibm ;; - beos*) + *-beos*) vendor=be ;; - hpux*) + *-hpux*) vendor=hp ;; - mpeix*) + *-mpeix*) vendor=hp ;; - hiux*) + *-hiux*) vendor=hitachi ;; - unos*) + *-unos*) vendor=crds ;; - dgux*) + *-dgux*) vendor=dg ;; - luna*) + *-luna*) vendor=omron ;; - genix*) + *-genix*) vendor=ns ;; - clix*) + *-clix*) vendor=intergraph ;; - mvs* | opened*) + *-mvs* | *-opened*) + vendor=ibm + ;; + *-os400*) vendor=ibm ;; - os400*) + s390-* | s390x-*) vendor=ibm ;; - ptx*) + *-ptx*) vendor=sequent ;; - tpf*) + *-tpf*) vendor=ibm ;; - vxsim* | vxworks* | windiss*) + *-vxsim* | *-vxworks* | *-windiss*) vendor=wrs ;; - aux*) + *-aux*) vendor=apple ;; - hms*) + *-hms*) vendor=hitachi ;; - mpw* | macos*) + *-mpw* | *-macos*) vendor=apple ;; - *mint | mint[0-9]* | *MiNT | MiNT[0-9]*) + *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*) vendor=atari ;; - vos*) + *-vos*) vendor=stratus ;; esac ;; esac -echo "$cpu-$vendor-$os" +echo "$cpu-$vendor-${kernel:+$kernel-}$os" exit # Local variables: diff --git a/configure.ac b/configure.ac index d80194ee4..d2bcb471e 100644 --- a/configure.ac +++ b/configure.ac @@ -4,7 +4,6 @@ AC_INIT([rsync],[ ],[https://rsync.samba.org/bug-tracking.html]) AC_C_BIGENDIAN AC_HEADER_DIRENT -AC_HEADER_TIME AC_HEADER_SYS_WAIT AC_CHECK_HEADERS(sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h \ unistd.h utime.h compat.h sys/param.h ctype.h sys/wait.h sys/stat.h \ @@ -13,16 +12,17 @@ AC_CHECK_HEADERS(sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h \ netdb.h malloc.h float.h limits.h iconv.h libcharset.h langinfo.h mcheck.h \ sys/acl.h acl/libacl.h attr/xattr.h sys/xattr.h sys/extattr.h dl.h \ popt.h popt/popt.h linux/falloc.h netinet/in_systm.h netgroup.h \ - zlib.h xxhash.h openssl/md4.h openssl/md5.h zstd.h lz4.h sys/file.h) + zlib.h xxhash.h openssl/md4.h openssl/md5.h zstd.h lz4.h sys/file.h \ + bsd/string.h) AC_CHECK_HEADERS([netinet/ip.h], [], [], [[#include ]]) AC_HEADER_MAJOR_FIXED AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([byteorder.h]) -AC_CONFIG_HEADER(config.h) +AC_CONFIG_HEADERS([config.h]) AC_PREREQ([2.69]) -PACKAGE_VERSION=`sed 's/.*"\(.*\)".*/\1/' <$srcdir/version.h` +PACKAGE_VERSION=`sed -n 's/.*RSYNC_VERSION.*"\(.*\)".*/\1/p' <$srcdir/version.h` AC_MSG_NOTICE([Configuring rsync $PACKAGE_VERSION]) @@ -60,7 +60,6 @@ AC_PROG_AWK AC_PROG_EGREP AC_PROG_INSTALL AC_PROG_MKDIR_P -AC_PROG_CC_STDC AC_SUBST(SHELL) AC_PATH_PROG([PERL], [perl]) AC_PATH_PROG([PYTHON3], [python3]) @@ -83,7 +82,7 @@ if test x"$enable_profile" = x"yes"; then CFLAGS="$CFLAGS -pg" fi -AC_MSG_CHECKING([if md2man can create man pages]) +AC_MSG_CHECKING([if md2man can create manpages]) if test x"$ac_cv_path_PYTHON3" = x; then AC_MSG_RESULT(no - python3 not found) md2man_works=no @@ -101,7 +100,7 @@ fi AC_MSG_CHECKING([if we require man-page building]) AC_ARG_ENABLE([md2man], - AS_HELP_STRING([--disable-md2man],[disable to omit man page creation])) + AS_HELP_STRING([--disable-md2man],[disable to omit manpage creation])) if test x"$enable_md2man" != x"no"; then if test -f "$srcdir/rsync.1"; then AC_MSG_RESULT(optional) @@ -109,7 +108,7 @@ if test x"$enable_md2man" != x"no"; then AC_MSG_RESULT(required) if test x"$md2man_works" = x"no"; then err_msg="$err_msg$nl- You need python3 and either the cmarkgfm OR commonmark python3 lib in order" - err_msg="$err_msg$nl to build man pages based on the git source (man pages are included in the" + err_msg="$err_msg$nl to build manpages based on the git source (manpages are included in the" err_msg="$err_msg$nl official release tar files)." no_lib="$no_lib md2man" fi @@ -117,7 +116,6 @@ if test x"$enable_md2man" != x"no"; then MAKE_MAN=man else AC_MSG_RESULT(no) - MAKE_MAN='' fi # Specifically, this turns on panic_action handling. @@ -136,16 +134,37 @@ if test x"$GCC" = x"yes"; then CFLAGS="$CFLAGS -Wall -W" fi +AC_ARG_WITH(openssl-conf, + AS_HELP_STRING([--with-openssl-conf=PATH],[set default OPENSSL_CONF path for rsync])) +case "$with_openssl_conf" in + *[^-/a-zA-Z0-9.,=@+_]*) AC_MSG_ERROR([Invalid path given to --with-openssl-conf]) ;; + /*) CFLAGS="$CFLAGS -DSET_OPENSSL_CONF=$with_openssl_conf" ;; + no|'') ;; + yes) AC_MSG_ERROR([No path given to --with-openssl-conf]) ;; + *) AC_MSG_ERROR([Non absolute path given to --with-openssl-conf]) ;; +esac + +AC_ARG_WITH(rrsync, + AS_HELP_STRING([--with-rrsync],[also install the rrsync script and its manpage])) +if test x"$with_rrsync" != x"yes"; then + with_rrsync=no +else + MAKE_RRSYNC='rrsync' + MAKE_RRSYNC_1='rrsync.1' + GEN_RRSYNC='rrsync.1 rrsync.1.html' +fi +AC_SUBST(with_rrsync) + AC_ARG_WITH(included-popt, AS_HELP_STRING([--with-included-popt],[use bundled popt library, not from system])) AC_ARG_WITH(included-zlib, AS_HELP_STRING([--with-included-zlib],[use bundled zlib library, not from system])) -AC_ARG_WITH(protected-args, - AS_HELP_STRING([--with-protected-args],[make --protected-args option the default])) -if test x"$with_protected_args" = x"yes"; then - AC_DEFINE_UNQUOTED(RSYNC_USE_PROTECTED_ARGS, 1, [Define to 1 if --protected-args should be the default]) +AC_ARG_WITH(secluded-args, + AS_HELP_STRING([--with-secluded-args],[make --secluded-args option the default])) +if test x"$with_secluded_args" = x"yes"; then + AC_DEFINE_UNQUOTED(RSYNC_USE_SECLUDED_ARGS, 1, [Define to 1 if --secluded-args should be the default]) fi AC_ARG_WITH(rsync-path, @@ -219,12 +238,12 @@ fi AC_DEFINE_UNQUOTED(NOBODY_USER, "$NOBODY_USER", [unprivileged user--e.g. nobody]) AC_DEFINE_UNQUOTED(NOBODY_GROUP, "$NOBODY_GROUP", [unprivileged group for unprivileged user]) -# SIMD optimizations -SIMD= +# rolling-checksum SIMD optimizations +ROLL_SIMD= -AC_MSG_CHECKING([whether to enable SIMD optimizations]) -AC_ARG_ENABLE(simd, - AS_HELP_STRING([--enable-simd],[enable/disable to control SIMD optimizations (requires c++)])) +AC_MSG_CHECKING([whether to enable rolling-checksum SIMD optimizations]) +AC_ARG_ENABLE(roll-simd, + AS_HELP_STRING([--enable-roll-simd],[enable/disable to control rolling-checksum SIMD optimizations (requires c++)])) # Clag is crashing with -g -O2, so we'll get rid of -g for now. CXXFLAGS=`echo "$CXXFLAGS" | sed 's/-g //'` @@ -253,18 +272,18 @@ __attribute__ ((target("ssse3"))) void more_testing(char* buf, int len) } ]]) -if test x"$enable_simd" = x""; then +if test x"$enable_roll_simd" = x""; then case "$host_os" in *linux*) ;; - *) enable_simd=no ;; + *) enable_roll_simd=no ;; esac fi -if test x"$enable_simd" != x"no"; then +if test x"$enable_roll_simd" != x"no"; then # For x86-64 SIMD, g++ >=5 or clang++ >=7 is required - if test x"$host_cpu" = x"x86_64" -o x"$host_cpu" = x"amd64"; then + if test x"$host_cpu" = x"x86_64" || test x"$host_cpu" = x"amd64"; then AC_LANG(C++) - if test x"$host_cpu" = x"$build_cpu"; then + if test x"$host" = x"$build"; then AC_RUN_IFELSE([AC_LANG_PROGRAM([SIMD_X86_64_TEST],[[if (test_ssse3(42) != 42 || test_sse2(42) != 42 || test_avx2(42) != 42) exit(1);]])], [CXX_OK=yes],[CXX_OK=no]) else @@ -273,23 +292,23 @@ if test x"$enable_simd" != x"no"; then AC_LANG(C) if test x"$CXX_OK" = x"yes"; then # AC_MSG_RESULT() is called below. - SIMD="$host_cpu" - elif test x"$enable_simd" = x"yes"; then + ROLL_SIMD="$host_cpu" + elif test x"$enable_roll_simd" = x"yes"; then AC_MSG_RESULT(error) - AC_MSG_ERROR(The SIMD compilation test failed. -Omit --enable-simd to continue without it.) + AC_MSG_ERROR(The rolling-checksum SIMD compilation test failed. +Omit --enable-roll-simd to continue without it.) fi - elif test x"$enable_simd" = x"yes"; then + elif test x"$enable_roll_simd" = x"yes"; then AC_MSG_RESULT(unavailable) - AC_MSG_ERROR(The SIMD optimizations are currently x86_64|amd64 only. -Omit --enable-simd to continue without it.) + AC_MSG_ERROR(The rolling-checksum SIMD optimizations are currently x86_64|amd64 only. +Omit --enable-roll-simd to continue without it.) fi fi -if test x"$SIMD" != x""; then - AC_MSG_RESULT([yes ($SIMD)]) - AC_DEFINE(HAVE_SIMD, 1, [Define to 1 to enable SIMD optimizations]) - SIMD='$(SIMD_'"$SIMD)" +if test x"$ROLL_SIMD" != x""; then + AC_MSG_RESULT([yes ($ROLL_SIMD)]) + AC_DEFINE(USE_ROLL_SIMD, 1, [Define to 1 to enable rolling-checksum SIMD optimizations]) + ROLL_SIMD='$(ROLL_SIMD_'"$ROLL_SIMD)" # We only use c++ for its target attribute dispatching, disable unneeded bulky features CXXFLAGS="$CXXFLAGS -fno-exceptions -fno-rtti" # Apple often has "g++" as a symlink for clang. Try to find out the truth. @@ -301,7 +320,7 @@ else AC_MSG_RESULT(no) fi -AC_SUBST(SIMD) +AC_SUBST(ROLL_SIMD) AC_MSG_CHECKING([if assembler accepts noexecstack]) OLD_CFLAGS="$CFLAGS" @@ -312,52 +331,19 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]], [[return 0;]])], CFLAGS="$OLD_CFLAGS" AC_SUBST(NOEXECSTACK) -ASM= - -AC_MSG_CHECKING([whether to enable ASM optimizations]) -AC_ARG_ENABLE(asm, - AS_HELP_STRING([--enable-asm],[enable/disable to control ASM optimizations])) - -if test x"$enable_asm" = x""; then - case "$host_os" in - *linux*) ;; - *) enable_asm=no ;; - esac -fi - -if test x"$enable_asm" != x"no"; then - if test x"$host_cpu" = x"x86_64" -o x"$host_cpu" = x"amd64"; then - ASM="$host_cpu" - elif test x"$enable_asm" = x"yes"; then - AC_MSG_RESULT(unavailable) - AC_MSG_ERROR(The ASM optimizations are currently x86_64|amd64 only. -Omit --enable-asm to continue without it.) - fi -fi - -if test x"$ASM" != x""; then - AC_MSG_RESULT([yes ($ASM)]) - AC_DEFINE(HAVE_ASM, 1, [Define to 1 to enable ASM optimizations]) - ASM='$(ASM_'"$ASM)" -else - AC_MSG_RESULT(no) -fi - -AC_SUBST(ASM) - # arrgh. libc in some old debian version screwed up the largefile # stuff, getting byte range locking wrong AC_CACHE_CHECK([for broken largefile support],rsync_cv_HAVE_BROKEN_LARGEFILE,[ AC_RUN_IFELSE([AC_LANG_SOURCE([[ #define _FILE_OFFSET_BITS 64 -#include -#include -#ifdef HAVE_SYS_TYPES_H -#include +$ac_includes_default +#ifdef HAVE_FCNTL_H +# include +#elif defined HAVE_SYS_FCNTL_H +# include #endif +#ifdef HAVE_SYS_WAIT_H #include -#if HAVE_UNISTD_H -#include #endif int main(void) @@ -402,22 +388,22 @@ AS_HELP_STRING([--disable-ipv6],[disable to omit ipv6 support]), ;; esac ], - AC_TRY_RUN([ /* AF_INET6 avalable check */ + AC_RUN_IFELSE([AC_LANG_SOURCE([[ /* AF_INET6 availability check */ #include #include #include -main() +int main() { if (socket(AF_INET6, SOCK_STREAM, 0) < 0) exit(1); else exit(0); } -], - AC_MSG_RESULT(yes) - AC_DEFINE(INET6, 1, [true if you have IPv6]), - AC_MSG_RESULT(no), - AC_MSG_RESULT(no) +]])], + [AC_MSG_RESULT(yes) + AC_DEFINE(INET6, 1, true if you have IPv6)], + [AC_MSG_RESULT(no)], + [AC_MSG_RESULT(no)] )) dnl Do you want to disable use of locale functions @@ -438,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])) @@ -446,19 +452,77 @@ AH_TEMPLATE([USE_OPENSSL], if test x"$enable_openssl" != x"no"; then if test x"$ac_cv_header_openssl_md4_h" = x"yes" && test x"$ac_cv_header_openssl_md5_h" = x"yes"; then AC_MSG_RESULT(yes) - AC_SEARCH_LIBS(MD5_Init, crypto, - [AC_DEFINE(USE_OPENSSL)], - [err_msg="$err_msg$nl- Failed to find MD5_Init function in openssl crypto lib."; + AC_SEARCH_LIBS(EVP_MD_CTX_copy, crypto, + [AC_DEFINE(USE_OPENSSL) + enable_openssl=yes], + [err_msg="$err_msg$nl- Failed to find EVP_MD_CTX_copy function in openssl crypto lib."; no_lib="$no_lib openssl"]) else AC_MSG_RESULT(no) err_msg="$err_msg$nl- Failed to find openssl/md4.h and openssl/md5.h for openssl crypto lib support." no_lib="$no_lib openssl" fi + if test x"$enable_md5_asm" != x"yes"; then + enable_md5_asm=no + fi else AC_MSG_RESULT(no) fi +MD5_ASM= + +AC_MSG_CHECKING([whether to enable MD5 ASM optimizations]) +AC_ARG_ENABLE(md5-asm, + AS_HELP_STRING([--enable-md5-asm],[enable/disable to control MD5 ASM optimizations])) + +if test x"$enable_md5_asm" = x""; then + case "$host_os" in + *linux*) ;; + *) enable_md5_asm=no ;; + esac +fi + +if test x"$enable_md5_asm" != x"no"; then + if test x"$host_cpu" = x"x86_64" || test x"$host_cpu" = x"amd64"; then + MD5_ASM="$host_cpu" + elif test x"$enable_md5_asm" = x"yes"; then + AC_MSG_RESULT(unavailable) + AC_MSG_ERROR(The ASM optimizations are currently x86_64|amd64 only. +Omit --enable-md5-asm to continue without it.) + fi +fi + +if test x"$MD5_ASM" != x""; then + AC_MSG_RESULT([yes ($MD5_ASM)]) + AC_DEFINE(USE_MD5_ASM, 1, [Define to 1 to enable MD5 ASM optimizations]) + MD5_ASM='$(MD5_ASM_'"$MD5_ASM)" +else + AC_MSG_RESULT(no) +fi + +AC_SUBST(MD5_ASM) + +ROLL_ASM= + +AC_MSG_CHECKING([whether to enable rolling-checksum ASM optimizations]) +AC_ARG_ENABLE(roll-asm, + AS_HELP_STRING([--enable-roll-asm],[enable/disable to control rolling-checksum ASM optimizations (requires --enable-roll-simd)])) + +if test x"$ROLL_SIMD" = x""; then + enable_roll_asm=no +fi + +if test x"$enable_roll_asm" = x"yes"; then + ROLL_ASM="$host_cpu" + AC_MSG_RESULT([yes ($ROLL_ASM)]) + AC_DEFINE(USE_ROLL_ASM, 1, [Define to 1 to enable rolling-checksum ASM optimizations (requires --enable-roll-simd)]) + ROLL_ASM='$(ROLL_ASM_'"$ROLL_ASM)" +else + AC_MSG_RESULT(no) +fi + +AC_SUBST(ROLL_ASM) + AC_MSG_CHECKING([whether to enable xxhash checksum support]) AC_ARG_ENABLE([xxhash], AS_HELP_STRING([--disable-xxhash],[disable to omit xxhash checksums])) @@ -482,7 +546,7 @@ fi AC_MSG_CHECKING([whether to enable zstd compression]) AC_ARG_ENABLE([zstd], - AC_HELP_STRING([--disable-zstd], [disable to omit zstd compression])) + AS_HELP_STRING([--disable-zstd], [disable to omit zstd compression])) AH_TEMPLATE([SUPPORT_ZSTD], [Undefine if you do not want zstd compression. By default this is defined.]) if test x"$enable_zstd" != x"no"; then @@ -503,7 +567,7 @@ fi AC_MSG_CHECKING([whether to enable LZ4 compression]) AC_ARG_ENABLE([lz4], - AC_HELP_STRING([--disable-lz4], [disable to omit LZ4 compression])) + AS_HELP_STRING([--disable-lz4], [disable to omit LZ4 compression])) AH_TEMPLATE([SUPPORT_LZ4], [Undefine if you do not want LZ4 compression. By default this is defined.]) if test x"$enable_lz4" != x"no"; then @@ -528,8 +592,8 @@ if test x"$no_lib" != x; then echo "$err_msg" echo "" echo "See the INSTALL file for hints on how to install the missing libraries and/or" - echo "how to generate (or fetch) man pages:" - echo " https://github.com/WayneD/rsync/blob/master/INSTALL.md" + echo "how to generate (or fetch) manpages:" + 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 @@ -589,7 +653,11 @@ fi AC_TYPE_UID_T AC_CHECK_TYPES([mode_t,off_t,size_t,pid_t,id_t]) -AC_TYPE_GETGROUPS +if test "$cross_compiling" = no; then + AC_TYPE_GETGROUPS +else + AC_DEFINE([GETGROUPS_T],[gid_t],[Define to the type of elements in the array set by `getgroups'. Usually this is either `int' or `gid_t'.]) +fi AC_CHECK_MEMBERS([struct stat.st_rdev, struct stat.st_mtimensec, struct stat.st_mtimespec.tv_nsec, @@ -712,7 +780,7 @@ yes #endif], rsync_cv_HAVE_GETADDR_DEFINES=yes, rsync_cv_HAVE_GETADDR_DEFINES=no)]) -AS_IF([test x"$rsync_cv_HAVE_GETADDR_DEFINES" = x"yes" -a x"$ac_cv_type_struct_addrinfo" = x"yes"],[ +AS_IF([test x"$rsync_cv_HAVE_GETADDR_DEFINES" = x"yes" && test x"$ac_cv_type_struct_addrinfo" = x"yes"],[ # Tru64 UNIX has getaddrinfo() but has it renamed in libc as # something else so we must include to get the # redefinition. @@ -822,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 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 \ @@ -1031,26 +1099,13 @@ elif test x"$ac_cv_header_popt_h" != x"yes"; then with_included_popt=yes fi -if test x"$GCC" = x"yes"; then - if test x"$with_included_popt" != x"yes"; then - # Turn pedantic warnings into errors to ensure an array-init overflow is an error. - CFLAGS="$CFLAGS -pedantic-errors" - else - # Our internal popt code cannot be compiled with pedantic warnings as errors, so try to - # turn off pedantic warnings (which will not lose the error for array-init overflow). - # Older gcc versions don't understand -Wno-pedantic, so check if --help=warnings lists - # -Wpedantic and use that as a flag. - case `$CC --help=warnings 2>/dev/null | grep Wpedantic` in - *-Wpedantic*) CFLAGS="$CFLAGS -pedantic-errors -Wno-pedantic" ;; - esac - fi -fi - AC_MSG_CHECKING([whether to use included libpopt]) 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; @@ -1061,28 +1116,8 @@ 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 = ""]])],[rsync_cv_SIGNED_CHAR_OK=yes],[rsync_cv_SIGNED_CHAR_OK=no])]) +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 AC_DEFINE(SIGNED_CHAR_OK, 1, [Define to 1 if "signed char" is a valid type]) fi @@ -1246,6 +1281,9 @@ AC_SUBST(OBJ_RESTORE) AC_SUBST(CC_SHOBJ_FLAG) AC_SUBST(BUILD_POPT) AC_SUBST(BUILD_ZLIB) +AC_SUBST(MAKE_RRSYNC) +AC_SUBST(MAKE_RRSYNC_1) +AC_SUBST(GEN_RRSYNC) AC_SUBST(MAKE_MAN) AC_CHECK_FUNCS(_acl __acl _facl __facl) @@ -1384,7 +1422,7 @@ else esac fi -if test x"$enable_acl_support" = x"no" -o x"$enable_xattr_support" = x"no" -o x"$enable_iconv" = x"no"; then +if test x"$enable_acl_support" = x"no" || test x"$enable_xattr_support" = x"no" || test x"$enable_iconv" = x"no"; then AC_MSG_CHECKING([whether $CC supports -Wno-unused-parameter]) OLD_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -Wno-unused-parameter" diff --git a/csprotocol.txt b/csprotocol.txt index c8dadd414..7ba09ab02 100644 --- a/csprotocol.txt +++ b/csprotocol.txt @@ -7,39 +7,54 @@ basically a summary of clientserver.c and authenticate.c. This is the protocol used for rsync --daemon; i.e. connections to port 873 rather than invocations over a remote shell. -When the server accepts a connection, it prints a greeting +When the server accepts a connection, it prints a newline-terminated +greeting line: - @RSYNCD: . + @RSYNCD: . -where is the numeric version (see PROTOCOL_VERSION in rsync.h) -'.' is a literal period, and is the numeric subprotocol -version (see SUBPROTOCOL_VERSION -- it will be 0 for final releases). -Protocols prior to 30 only output alone. The daemon expects -to see a similar greeting back from the client. For protocols prior to -30, an absent "." value is assumed to be 0. For protocol -30, an absent value is a fatal error. The daemon then follows this line -with a free-format text message-of-the-day (if any is defined). +The is the numeric version (see PROTOCOL_VERSION in rsync.h) +The is the numeric subprotocol version (which is 0 for a +final protocol version, as the SUBPROTOCOL_VERSION define discusses). +The names are the authentication digest algorithms that the +daemon supports, listed in order of preference. + +An rsync prior to 3.2.7 omits the digest names. An rsync prior to 3.0.0 +also omits the period and the value. Since a final +protocol has a subprotocol value of 0, a missing subprotocol value is +assumed to be 0 for any protocol prior to 30. It is considered a fatal +error for protocol 30 and above to omit it. It is considered a fatal +error for protocol 32 and above to omit the digest name list (currently +31 is the newest protocol). + +The daemon expects to see a similar greeting line back from the client. +Once received, the daemon follows the opening line with a free-format +text message-of-the-day (if any is defined). The server is now in the connected state. The client can either send -the command +the command: #list -to get a listing of modules, or the name of a module. After this, the +(to get a listing of modules) or the name of a module. After this, the connection is now bound to a particular module. Access per host for this module is now checked, as is per-module connection limits. -If authentication is required to use this module, the server will say +If authentication is required to use this module, the server will say: @RSYNCD: AUTHREQD where is a random string of base64 characters. The client -must respond with +must respond with: -where is the username they claim to be, and is the -base64 form of the MD4 hash of challenge+password. +The is the username they claim to be. The is the +base64 form of the digest hash of the challenge+password string. The +chosen digest method is the most preferred client method that is also in +the server's list. If no digest list was explicitly provided, the side +expecting a list assumes the other side provided either the single name +"md5" (for a negotiated protocol 30 or 31), or the single name "md4" +(for an older protocol). At this point the server applies all remaining constraints before handing control to the client, including switching uid/gid, setting up @@ -76,6 +91,13 @@ stay tuned (or write it yourself!). ------------ Protocol version changes +31 (2013-09-28, 3.1.0) + + Initial release of protocol 31 had no changes. Rsync 3.2.7 + introduced the suffixed list of digest names on the greeting + line. The presence of the list is allowed even if the greeting + indicates an older protocol version number. + 30 (2007-10-04, 3.0.0pre1) The use of a "." number was added to diff --git a/daemon-parm.txt b/daemon-parm.txt index 3b438b022..69034173f 100644 --- a/daemon-parm.txt +++ b/daemon-parm.txt @@ -60,9 +60,9 @@ BOOL read_only True BOOL reverse_lookup True BOOL strict_modes True BOOL transfer_logging False -BOOL use_chroot True BOOL write_only False BOOL3 munge_symlinks Unset BOOL3 numeric_ids Unset BOOL3 open_noatime Unset +BOOL3 use_chroot Unset diff --git a/delete.c b/delete.c index 4a2948530..89c1f8d67 100644 --- a/delete.c +++ b/delete.c @@ -4,7 +4,7 @@ * Copyright (C) 1996-2000 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2002 Martin Pool - * Copyright (C) 2003-2020 Wayne Davison + * Copyright (C) 2003-2024 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -188,7 +188,7 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags) stats.deleted_symlinks++; #endif else if (IS_DEVICE(mode)) - stats.deleted_symlinks++; + stats.deleted_devices++; else stats.deleted_specials++; } diff --git a/exclude.c b/exclude.c index e0957443b..87edbcf71 100644 --- a/exclude.c +++ b/exclude.c @@ -4,7 +4,7 @@ * Copyright (C) 1996-2001 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2002 Martin Pool - * Copyright (C) 2003-2020 Wayne Davison + * Copyright (C) 2003-2024 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,16 +25,21 @@ extern int am_server; extern int am_sender; +extern int am_generator; extern int eol_nulls; extern int io_error; +extern int xfer_dirs; +extern int recurse; extern int local_server; extern int prune_empty_dirs; extern int ignore_perishable; +extern int relative_paths; extern int delete_mode; extern int delete_excluded; extern int cvs_exclude; extern int sanitize_paths; extern int protocol_version; +extern int trust_sender_args; extern int module_id; extern char curr_dir[MAXPATHLEN]; @@ -44,8 +49,11 @@ extern unsigned int module_dirlen; filter_rule_list filter_list = { .debug_type = "" }; filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" }; filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" }; +filter_rule_list implied_filter_list = { .debug_type = " [implied]" }; int saw_xattr_filter = 0; +int trust_sender_args = 0; +int trust_sender_filter = 0; /* Need room enough for ":MODS " prefix plus some room to grow. */ #define MAX_RULE_PREFIX (16) @@ -70,6 +78,10 @@ static filter_rule **mergelist_parents; static int mergelist_cnt = 0; static int mergelist_size = 0; +#define LOCAL_RULE 1 +#define REMOTE_RULE 2 +static uchar cur_elide_value = REMOTE_RULE; + /* Each filter_list_struct describes a singly-linked list by keeping track * of both the head and tail pointers. The list is slightly unusual in that * a parent-dir's content can be appended to the end of the local list in a @@ -152,13 +164,17 @@ static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_ { const char *cp; unsigned int pre_len, suf_len, slash_cnt = 0; + char *mention_rule_suffix; - if (DEBUG_GTE(FILTER, 2)) { - rprintf(FINFO, "[%s] add_rule(%s%.*s%s)%s\n", + if (DEBUG_GTE(FILTER, 1) && pat_len && (pat[pat_len-1] == ' ' || pat[pat_len-1] == '\t')) + mention_rule_suffix = " -- CAUTION: trailing whitespace!"; + else + mention_rule_suffix = DEBUG_GTE(FILTER, 2) ? "" : NULL; + if (mention_rule_suffix) { + rprintf(FINFO, "[%s] add_rule(%s%.*s%s)%s%s\n", who_am_i(), get_rule_prefix(rule, pat, 0, NULL), - (int)pat_len, pat, - (rule->rflags & FILTRULE_DIRECTORY) ? "/" : "", - listp->debug_type); + (int)pat_len, pat, (rule->rflags & FILTRULE_DIRECTORY) ? "/" : "", + listp->debug_type, mention_rule_suffix); } /* These flags also indicate that we're reading a list that @@ -208,6 +224,7 @@ static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_ slash_cnt++; } } + rule->elide = 0; strlcpy(rule->pattern + pre_len, pat, pat_len + 1); pat_len += pre_len; if (suf_len) { @@ -288,6 +305,271 @@ static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_ } } +/* If the wildcards failed, the remote shell might give us a file matching the literal + * wildcards. Since "*" & "?" already match themselves, this just needs to deal with + * failed "[foo]" idioms. + */ +static void maybe_add_literal_brackets_rule(filter_rule const *based_on, int arg_len) +{ + filter_rule *rule; + const char *arg = based_on->pattern, *cp; + char *p; + int cnt = 0; + + if (arg_len < 0) + arg_len = strlen(arg); + + for (cp = arg; *cp; cp++) { + if (*cp == '\\' && cp[1]) { + cp++; + } else if (*cp == '[') + cnt++; + } + if (!cnt) + return; + + rule = new0(filter_rule); + rule->rflags = based_on->rflags; + rule->u.slash_cnt = based_on->u.slash_cnt; + p = rule->pattern = new_array(char, arg_len + cnt + 1); + for (cp = arg; *cp; ) { + if (*cp == '\\' && cp[1]) { + *p++ = *cp++; + } else if (*cp == '[') + *p++ = '\\'; + *p++ = *cp++; + } + *p++ = '\0'; + + rule->next = implied_filter_list.head; + implied_filter_list.head = rule; + if (DEBUG_GTE(FILTER, 3)) { + rprintf(FINFO, "[%s] add_implied_include(%s%s)\n", who_am_i(), rule->pattern, + rule->rflags & FILTRULE_DIRECTORY ? "/" : ""); + } +} + +static char *partial_string_buf = NULL; +static int partial_string_len = 0; +void implied_include_partial_string(const char *s_start, const char *s_end) +{ + partial_string_len = s_end - s_start; + if (partial_string_len <= 0 || partial_string_len >= MAXPATHLEN) { /* too-large should be impossible... */ + partial_string_len = 0; + return; + } + if (!partial_string_buf) + partial_string_buf = new_array(char, MAXPATHLEN); + memcpy(partial_string_buf, s_start, partial_string_len); +} + +void free_implied_include_partial_string() +{ + if (partial_string_buf) { + if (partial_string_len) + add_implied_include("", 0); + free(partial_string_buf); + partial_string_buf = NULL; + } + partial_string_len = 0; /* paranoia */ +} + +/* Each arg the client sends to the remote sender turns into an implied include + * that the receiver uses to validate the file list from the sender. */ +void add_implied_include(const char *arg, int skip_daemon_module) +{ + int arg_len, saw_wild = 0, saw_live_open_brkt = 0, backslash_cnt = 0; + int slash_cnt = 0; + const char *cp; + char *p; + if (trust_sender_args) + return; + if (partial_string_len) { + arg_len = strlen(arg); + if (partial_string_len + arg_len >= MAXPATHLEN) { + partial_string_len = 0; + return; /* Should be impossible... */ + } + memcpy(partial_string_buf + partial_string_len, arg, arg_len + 1); + partial_string_len = 0; + arg = partial_string_buf; + } + if (skip_daemon_module) { + if ((cp = strchr(arg, '/')) != NULL) + arg = cp + 1; + else + arg = ""; + } + if (relative_paths) { + if ((cp = strstr(arg, "/./")) != NULL) + arg = cp + 3; + } else if ((cp = strrchr(arg, '/')) != NULL) { + arg = cp + 1; + } + if (*arg == '.' && arg[1] == '\0') + arg++; + arg_len = strlen(arg); + if (arg_len) { + char *new_pat; + if (strpbrk(arg, "*[?")) { + /* We need to add room to escape backslashes if wildcard chars are present. */ + for (cp = arg; (cp = strchr(cp, '\\')) != NULL; cp++) + arg_len++; + saw_wild = 1; + } + arg_len++; /* Leave room for the prefixed slash */ + p = new_pat = new_array(char, arg_len + 1); + *p++ = '/'; + slash_cnt++; + for (cp = arg; *cp; ) { + switch (*cp) { + case '\\': + if (cp[1] == ']') { + if (!saw_wild) + cp++; /* A \] in a non-wild filter causes a problem, so drop the \ . */ + } else if (!strchr("*[?", cp[1])) { + backslash_cnt++; + if (saw_wild) + *p++ = '\\'; + } + *p++ = *cp++; + break; + case '/': + if (p[-1] == '/') { /* This is safe because of the initial slash. */ + if (*++cp == '\0') { + slash_cnt--; + p--; + } + } else if (cp[1] == '\0') { + cp++; + } else { + slash_cnt++; + *p++ = *cp++; + } + break; + case '.': + if (p[-1] == '/') { + if (cp[1] == '/') { + cp += 2; + if (!*cp) { + slash_cnt--; + p--; + } + } else if (cp[1] == '\0') { + cp++; + slash_cnt--; + p--; + } else + *p++ = *cp++; + } else + *p++ = *cp++; + break; + case '[': + saw_live_open_brkt = 1; + *p++ = *cp++; + break; + default: + *p++ = *cp++; + break; + } + } + *p = '\0'; + arg_len = p - new_pat; + if (!arg_len) + free(new_pat); + else { + filter_rule *rule = new0(filter_rule); + rule->rflags = FILTRULE_INCLUDE + (saw_wild ? FILTRULE_WILD : 0); + rule->u.slash_cnt = slash_cnt; + arg = rule->pattern = new_pat; + if (!implied_filter_list.head) + implied_filter_list.head = implied_filter_list.tail = rule; + else { + rule->next = implied_filter_list.head; + implied_filter_list.head = rule; + } + if (DEBUG_GTE(FILTER, 3)) + rprintf(FINFO, "[%s] add_implied_include(%s)\n", who_am_i(), arg); + if (saw_live_open_brkt) + maybe_add_literal_brackets_rule(rule, arg_len); + if (relative_paths && slash_cnt) { + int sub_slash_cnt = slash_cnt; + while ((p = strrchr(new_pat, '/')) != NULL && p != new_pat) { + filter_rule const *ent; + filter_rule *R_rule; + int found = 0; + *p = '\0'; + for (ent = implied_filter_list.head; ent; ent = ent->next) { + if (ent != rule && strcmp(ent->pattern, new_pat) == 0) { + found = 1; + break; + } + } + if (found) { + *p = '/'; + break; /* We added all parent dirs already */ + } + R_rule = new0(filter_rule); + R_rule->rflags = FILTRULE_INCLUDE | FILTRULE_DIRECTORY; + /* Check if our sub-path has wildcards or escaped backslashes */ + if (saw_wild && strpbrk(new_pat, "*[?\\")) + R_rule->rflags |= FILTRULE_WILD; + R_rule->pattern = strdup(new_pat); + R_rule->u.slash_cnt = --sub_slash_cnt; + R_rule->next = implied_filter_list.head; + implied_filter_list.head = R_rule; + if (DEBUG_GTE(FILTER, 3)) { + rprintf(FINFO, "[%s] add_implied_include(%s/)\n", + who_am_i(), R_rule->pattern); + } + if (saw_live_open_brkt) + maybe_add_literal_brackets_rule(R_rule, -1); + } + for (p = new_pat; sub_slash_cnt < slash_cnt; sub_slash_cnt++) { + p += strlen(p); + *p = '/'; + } + } + } + } + + if (recurse || xfer_dirs) { + /* Now create a rule with an added "/" & "**" or "*" at the end */ + filter_rule *rule = new0(filter_rule); + rule->rflags = FILTRULE_INCLUDE | FILTRULE_WILD; + if (recurse) + rule->rflags |= FILTRULE_WILD2; + /* We must leave enough room for / * * \0. */ + if (!saw_wild && backslash_cnt) { + /* We are appending a wildcard, so now the backslashes need to be escaped. */ + p = rule->pattern = new_array(char, arg_len + backslash_cnt + 3 + 1); + for (cp = arg; *cp; ) { /* Note that arg_len != 0 because backslash_cnt > 0 */ + if (*cp == '\\') + *p++ = '\\'; + *p++ = *cp++; + } + } else { + p = rule->pattern = new_array(char, arg_len + 3 + 1); + if (arg_len) { + memcpy(p, arg, arg_len); + p += arg_len; + } + } + *p++ = '/'; + *p++ = '*'; + if (recurse) + *p++ = '*'; + *p = '\0'; + rule->u.slash_cnt = slash_cnt + 1; + rule->next = implied_filter_list.head; + implied_filter_list.head = rule; + if (DEBUG_GTE(FILTER, 3)) + rprintf(FINFO, "[%s] add_implied_include(%s)\n", who_am_i(), rule->pattern); + if (saw_live_open_brkt) + maybe_add_literal_brackets_rule(rule, p - rule->pattern); + } +} + /* This frees any non-inherited items, leaving just inherited items on the list. */ static void pop_filter_list(filter_rule_list *listp) { @@ -438,7 +720,8 @@ static BOOL setup_merge_file(int mergelist_num, filter_rule *ex, parent_dirscan = True; while (*y) { char save[MAXPATHLEN]; - strlcpy(save, y, MAXPATHLEN); + /* copylen is strlen(y) which is < MAXPATHLEN. +1 for \0 */ + size_t copylen = strlcpy(save, y, MAXPATHLEN) + 1; *y = '\0'; dirbuf_len = y - dirbuf; strlcpy(x, ex->pattern, MAXPATHLEN - (x - buf)); @@ -452,7 +735,7 @@ static BOOL setup_merge_file(int mergelist_num, filter_rule *ex, lp->head = NULL; } lp->tail = NULL; - strlcpy(y, save, MAXPATHLEN); + strlcpy(y, save, copylen); while ((*x++ = *y++) != '/') {} } parent_dirscan = False; @@ -625,7 +908,7 @@ static int rule_matches(const char *fname, filter_rule *ex, int name_flags) const char *strings[16]; /* more than enough */ const char *name = fname + (*fname == '/'); - if (!*name) + if (!*name || ex->elide == cur_elide_value) return 0; if (!(name_flags & NAME_IS_XATTR) ^ !(ex->rflags & FILTRULE_XATTR)) @@ -702,11 +985,12 @@ static void report_filter_result(enum logcode code, char const *name, filter_rule const *ent, int name_flags, const char *type) { + int log_level = am_sender || am_generator ? 1 : 3; + /* If a trailing slash is present to match only directories, * then it is stripped out by add_rule(). So as a special - * case we add it back in here. */ - - if (DEBUG_GTE(FILTER, 1)) { + * case we add it back in the log output. */ + if (DEBUG_GTE(FILTER, log_level)) { static char *actions[2][2] = { {"show", "hid"}, {"risk", "protect"} }; const char *w = who_am_i(); @@ -714,7 +998,7 @@ static void report_filter_result(enum logcode code, char const *name, : name_flags & NAME_IS_DIR ? "directory" : "file"; rprintf(code, "[%s] %sing %s %s because of pattern %s%s%s\n", - w, actions[*w!='s'][!(ent->rflags & FILTRULE_INCLUDE)], + w, actions[*w=='g'][!(ent->rflags & FILTRULE_INCLUDE)], t, name, ent->pattern, ent->rflags & FILTRULE_DIRECTORY ? "/" : "", type); } @@ -740,6 +1024,15 @@ int name_is_excluded(const char *fname, int name_flags, int filter_level) return 0; } +int check_server_filter(filter_rule_list *listp, enum logcode code, const char *name, int name_flags) +{ + int ret; + cur_elide_value = LOCAL_RULE; + ret = check_filter(listp, code, name, name_flags); + cur_elide_value = REMOTE_RULE; + return ret; +} + /* Return -1 if file "name" is defined to be excluded by the specified * exclude list, 1 if it is included, and 0 if it was not matched. */ int check_filter(filter_rule_list *listp, enum logcode code, @@ -886,6 +1179,7 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr, } switch (ch) { case ':': + trust_sender_filter = 1; rule->rflags |= FILTRULE_PERDIR_MERGE | FILTRULE_FINISH_SETUP; /* FALL THROUGH */ @@ -1294,7 +1588,7 @@ char *get_rule_prefix(filter_rule *rule, const char *pat, int for_xfer, static void send_rules(int f_out, filter_rule_list *flp) { - filter_rule *ent, *prev = NULL; + filter_rule *ent; for (ent = flp->head; ent; ent = ent->next) { unsigned int len, plen, dlen; @@ -1309,21 +1603,15 @@ static void send_rules(int f_out, filter_rule_list *flp) * merge files as an optimization (since they can only have * include/exclude rules). */ if (ent->rflags & FILTRULE_SENDER_SIDE) - elide = am_sender ? 1 : -1; + elide = am_sender ? LOCAL_RULE : REMOTE_RULE; if (ent->rflags & FILTRULE_RECEIVER_SIDE) - elide = elide ? 0 : am_sender ? -1 : 1; + elide = elide ? 0 : am_sender ? REMOTE_RULE : LOCAL_RULE; else if (delete_excluded && !elide && (!(ent->rflags & FILTRULE_PERDIR_MERGE) || ent->rflags & FILTRULE_NO_PREFIXES)) - elide = am_sender ? 1 : -1; - if (elide < 0) { - if (prev) - prev->next = ent->next; - else - flp->head = ent->next; - } else - prev = ent; - if (elide > 0) + elide = am_sender ? LOCAL_RULE : REMOTE_RULE; + ent->elide = elide; + if (elide == LOCAL_RULE) continue; if (ent->rflags & FILTRULE_CVS_IGNORE && !(ent->rflags & FILTRULE_MERGE_FILE)) { @@ -1351,7 +1639,6 @@ static void send_rules(int f_out, filter_rule_list *flp) if (dlen) write_byte(f_out, '/'); } - flp->tail = prev; } /* This is only called by the client. */ diff --git a/fileio.c b/fileio.c index f80af19e4..69c9a7b49 100644 --- a/fileio.c +++ b/fileio.c @@ -3,7 +3,7 @@ * * Copyright (C) 1998 Andrew Tridgell * Copyright (C) 2002 Martin Pool - * Copyright (C) 2004-2020 Wayne Davison + * Copyright (C) 2004-2023 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,30 +40,34 @@ OFF_T preallocated_len = 0; static OFF_T sparse_seek = 0; static OFF_T sparse_past_write = 0; -int sparse_end(int f, OFF_T size) +int sparse_end(int f, OFF_T size, int updating_basis_or_equiv) { - int ret; - - sparse_past_write = 0; - - if (!sparse_seek) - return 0; + int ret = 0; + if (updating_basis_or_equiv) { + if (sparse_seek && do_punch_hole(f, sparse_past_write, sparse_seek) < 0) + ret = -1; +#ifdef HAVE_FTRUNCATE /* A compilation formality -- in-place requires ftruncate() */ + else /* Just in case the original file was longer */ + ret = do_ftruncate(f, size); +#endif + } else if (sparse_seek) { #ifdef HAVE_FTRUNCATE - ret = do_ftruncate(f, size); + ret = do_ftruncate(f, size); #else - if (do_lseek(f, sparse_seek-1, SEEK_CUR) != size-1) - ret = -1; - else { - do { - ret = write(f, "", 1); - } while (ret < 0 && errno == EINTR); - - ret = ret <= 0 ? -1 : 0; - } + if (do_lseek(f, sparse_seek-1, SEEK_CUR) != size-1) + ret = -1; + else { + do { + ret = write(f, "", 1); + } while (ret < 0 && errno == EINTR); + + ret = ret <= 0 ? -1 : 0; + } #endif + } - sparse_seek = 0; + sparse_past_write = sparse_seek = 0; return ret; } diff --git a/flist.c b/flist.c index 803490bda..17832533e 100644 --- a/flist.c +++ b/flist.c @@ -4,7 +4,7 @@ * Copyright (C) 1996 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2001, 2002 Martin Pool - * Copyright (C) 2002-2020 Wayne Davison + * Copyright (C) 2002-2023 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,7 +33,6 @@ extern int am_sender; extern int am_generator; extern int inc_recurse; extern int always_checksum; -extern int checksum_type; extern int module_id; extern int ignore_errors; extern int numeric_ids; @@ -43,6 +42,7 @@ extern int use_qsort; extern int xfer_dirs; extern int filesfrom_fd; extern int one_file_system; +extern int copy_devices; extern int copy_dirlinks; extern int preserve_uid; extern int preserve_gid; @@ -72,18 +72,20 @@ extern int need_unsorted_flist; extern int sender_symlink_iconv; extern int output_needs_newline; extern int sender_keeps_checksum; +extern int trust_sender_filter; extern int unsort_ndx; extern uid_t our_uid; extern struct stats stats; extern char *filesfrom_host; extern char *usermap, *groupmap; +extern struct name_num_item *file_sum_nni; + extern char curr_dir[MAXPATHLEN]; extern struct chmod_mode_struct *chmod_modes; -extern filter_rule_list filter_list; -extern filter_rule_list daemon_filter_list; +extern filter_rule_list filter_list, implied_filter_list, daemon_filter_list; #ifdef ICONV_OPTION extern int filesfrom_convert; @@ -144,7 +146,8 @@ void init_flist(void) rprintf(FINFO, "FILE_STRUCT_LEN=%d, EXTRA_LEN=%d\n", (int)FILE_STRUCT_LEN, (int)EXTRA_LEN); } - flist_csum_len = csum_len_for_type(checksum_type, 1); + /* Note that this isn't identical to file_sum_len in the case of CSUM_MD4_ARCHAIC: */ + flist_csum_len = csum_len_for_type(file_sum_nni->num, 1); show_filelist_progress = INFO_GTE(FLIST, 1) && xfer_dirs && !am_server && !inc_recurse; } @@ -700,6 +703,7 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x int alloc_len, basename_len, linkname_len; int extra_len = file_extra_cnt * EXTRA_LEN; int first_hlink_ndx = -1; + char real_ISREG_entry; int64 file_length; #ifdef CAN_SET_NSEC uint32 modtime_nsec; @@ -752,7 +756,7 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x if (*thisname && (clean_fname(thisname, CFN_REFUSE_DOT_DOT_DIRS) < 0 || (!relative_paths && *thisname == '/'))) { rprintf(FERROR, "ABORTING due to unsafe pathname from sender: %s\n", thisname); - exit_cleanup(RERR_PROTOCOL); + exit_cleanup(RERR_UNSUPPORTED); } if (sanitize_paths) @@ -814,6 +818,7 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x linkname_len = strlen(F_SYMLINK(first)) + 1; else linkname_len = 0; + real_ISREG_entry = S_ISREG(mode) ? 1 : 0; goto create_object; } } @@ -831,7 +836,7 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x } #endif } else - modtime = read_int(f); + modtime = read_uint(f); } if (xflags & XMIT_MOD_NSEC) #ifndef CAN_SET_NSEC @@ -941,10 +946,20 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x #endif linkname_len = 0; + if (copy_devices && IS_DEVICE(mode)) { + /* This is impossible in the official release, but some pre-release patches + * didn't convert the device into a file before sending, so we'll do it here + * (even though the length is typically 0 and any checksum data is zeros). */ + mode = S_IFREG | (mode & ACCESSPERMS); + modtime = time(NULL); /* The mtime on the device is not up-to-date, so set it to "now". */ + real_ISREG_entry = 0; + } else + real_ISREG_entry = S_ISREG(mode) ? 1 : 0; + #ifdef SUPPORT_HARD_LINKS create_object: if (preserve_hard_links) { - if (protocol_version < 28 && S_ISREG(mode)) + if (protocol_version < 28 && real_ISREG_entry) xflags |= XMIT_HLINKED; if (xflags & XMIT_HLINKED) extra_len += (inc_recurse+1) * EXTRA_LEN; @@ -973,6 +988,19 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x exit_cleanup(RERR_UNSUPPORTED); } + if (*thisname == '/' ? thisname[1] != '.' || thisname[2] != '\0' : *thisname != '.' || thisname[1] != '\0') { + int filt_flags = S_ISDIR(mode) ? NAME_IS_DIR : NAME_IS_FILE; + if (!trust_sender_filter /* a per-dir filter rule means we must trust the sender's filtering */ + && filter_list.head && check_server_filter(&filter_list, FINFO, thisname, filt_flags) < 0) { + rprintf(FERROR, "ERROR: rejecting excluded file-list name: %s\n", thisname); + exit_cleanup(RERR_UNSUPPORTED); + } + if (implied_filter_list.head && check_filter(&implied_filter_list, FINFO, thisname, filt_flags) <= 0) { + rprintf(FERROR, "ERROR: rejecting unrequested file-list name: %s\n", thisname); + exit_cleanup(RERR_UNSUPPORTED); + } + } + if (inc_recurse && S_ISDIR(mode)) { if (one_file_system) { /* Room to save the dir's device for -x */ @@ -1160,8 +1188,8 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x } #endif - if (always_checksum && (S_ISREG(mode) || protocol_version < 28)) { - if (S_ISREG(mode)) + if (always_checksum && (real_ISREG_entry || protocol_version < 28)) { + if (real_ISREG_entry) bp = F_SUM(file); else { /* Prior to 28, we get a useless set of nulls. */ @@ -1360,6 +1388,18 @@ struct file_struct *make_file(const char *fname, struct file_list *flist, linkname_len = 0; #endif + if (copy_devices && am_sender && IS_DEVICE(st.st_mode)) { + if (st.st_size == 0) { + int fd = do_open_checklinks(fname); + if (fd >= 0) { + st.st_size = get_device_size(fd, fname); + close(fd); + } + } + st.st_mode = S_IFREG | (st.st_mode & ACCESSPERMS); + st.st_mtime = time(NULL); /* The mtime on the device is not up-to-date, so set it to "now". */ + } + #ifdef ST_MTIME_NSEC if (st.ST_MTIME_NSEC && protocol_version >= 31) extra_len += EXTRA_LEN; @@ -2327,7 +2367,7 @@ struct file_list *send_file_list(int f, int argc, char *argv[]) } dirlen = dir ? strlen(dir) : 0; - if (dirlen != lastdir_len || memcmp(lastdir, dir, dirlen) != 0) { + if (dirlen != lastdir_len || (dirlen && memcmp(lastdir, dir, dirlen) != 0)) { if (!change_pathname(NULL, dir, -dirlen)) goto bad_path; lastdir = pathname; @@ -2544,6 +2584,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); @@ -2602,7 +2655,7 @@ struct file_list *recv_file_list(int f, int dir_ndx) rprintf(FERROR, "ABORTING due to invalid path from sender: %s/%s\n", cur_dir, file->basename); - exit_cleanup(RERR_PROTOCOL); + exit_cleanup(RERR_UNSUPPORTED); } good_dirname = cur_dir; } @@ -2619,7 +2672,7 @@ struct file_list *recv_file_list(int f, int dir_ndx) } else if (S_ISLNK(file->mode)) stats.num_symlinks++; else if (IS_DEVICE(file->mode)) - stats.num_symlinks++; + stats.num_devices++; else stats.num_specials++; diff --git a/generator.c b/generator.c index 4e75ae2e9..b56fa569a 100644 --- a/generator.c +++ b/generator.c @@ -4,7 +4,7 @@ * Copyright (C) 1996-2000 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2002 Martin Pool - * Copyright (C) 2003-2020 Wayne Davison + * Copyright (C) 2003-2023 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,11 +35,11 @@ extern int inc_recurse; extern int relative_paths; extern int implied_dirs; extern int keep_dirlinks; +extern int write_devices; extern int preserve_acls; extern int preserve_xattrs; extern int preserve_links; extern int preserve_devices; -extern int write_devices; extern int preserve_specials; extern int preserve_hard_links; extern int preserve_executability; @@ -532,7 +532,7 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre iflags |= ITEM_REPORT_CRTIME; } #endif -#if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST +#ifndef CAN_CHMOD_SYMLINK if (S_ISLNK(file->mode)) { ; } else @@ -783,7 +783,7 @@ static int generate_and_send_sums(int fd, OFF_T len, int f_out, int f_copy) for (i = 0; i < sum.count; i++) { int32 n1 = (int32)MIN(len, (OFF_T)sum.blength); char *map = map_ptr(mapbuf, offset, n1); - char sum2[SUM_LENGTH]; + char sum2[MAX_DIGEST_LEN]; uint32 sum1; len -= n1; @@ -875,9 +875,12 @@ static struct file_struct *find_fuzzy(struct file_struct *file, struct file_list len = strlen(name); suf = find_filename_suffix(name, len, &suf_len); - dist = fuzzy_distance(name, len, fname, fname_len); - /* Add some extra weight to how well the suffixes match. */ - dist += fuzzy_distance(suf, suf_len, fname_suf, fname_suf_len) * 10; + dist = fuzzy_distance(name, len, fname, fname_len, lowest_dist); + /* Add some extra weight to how well the suffixes match unless we've already disqualified + * this file based on a heuristic. */ + if (dist < 0xFFFF0000U) { + dist += fuzzy_distance(suf, suf_len, fname_suf, fname_suf_len, 0xFFFF0000U) * 10; + } if (DEBUG_GTE(FUZZY, 2)) { rprintf(FINFO, "fuzzy distance for %s = %d.%05d\n", f_name(fp, NULL), (int)(dist>>16), (int)(dist&0xFFFF)); @@ -1678,9 +1681,11 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, } if (ftype != FT_REG) { - if (solo_file) - fname = f_name(file, NULL); - rprintf(FINFO, "skipping non-regular file \"%s\"\n", fname); + if (INFO_GTE(NONREG, 1)) { + if (solo_file) + fname = f_name(file, NULL); + rprintf(FINFO, "skipping non-regular file \"%s\"\n", fname); + } goto cleanup; } @@ -1791,6 +1796,12 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, goto cleanup; } + 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_nofollow(fnamecmp, O_RDONLY)) >= 0) + real_sx.st.st_size = sx.st.st_size = get_device_size(fd, fnamecmp); + } + if (fnamecmp_type <= FNAMECMP_BASIS_DIR_HIGH) ; else if (fnamecmp_type >= FNAMECMP_FUZZY) @@ -1811,7 +1822,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, goto cleanup; return_with_success: if (!dry_run) - send_msg_int(MSG_SUCCESS, ndx); + send_msg_success(fname, ndx); goto cleanup; } @@ -1856,7 +1867,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, } /* open the file */ - if ((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: @@ -1873,11 +1884,9 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, if (inplace && make_backups > 0 && fnamecmp_type == FNAMECMP_FNAME) { if (!(backupptr = get_backup_name(fname))) { - close(fd); goto cleanup; } if (!(back_file = make_file(fname, NULL, NULL, 0, NO_FILTERS))) { - close(fd); goto pretend_missing; } if (robust_unlink(backupptr) && errno != ENOENT) { @@ -1885,14 +1894,12 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, full_fname(backupptr)); unmake_file(back_file); back_file = NULL; - close(fd); goto cleanup; } if ((f_copy = do_open(backupptr, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) { rsyserr(FERROR_XFER, errno, "open %s", full_fname(backupptr)); unmake_file(back_file); back_file = NULL; - close(fd); goto cleanup; } fnamecmp_type = FNAMECMP_BACKUP; @@ -1943,7 +1950,6 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, write_sum_head(f_out, NULL); else if (sx.st.st_size <= 0) { write_sum_head(f_out, NULL); - close(fd); } else { if (generate_and_send_sums(fd, sx.st.st_size, f_out, f_copy) < 0) { rprintf(FWARNING, @@ -1951,10 +1957,11 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, fnamecmp); write_sum_head(f_out, NULL); } - close(fd); } cleanup: + if (fd >= 0) + close(fd); if (back_file) { int save_preserve_xattrs = preserve_xattrs; if (f_copy >= 0) @@ -2034,8 +2041,12 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const if (!skip_atomic) { if (do_rename(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)); + full_tmpname, full_fname(fname)); + free(full_tmpname); do_unlink(tmpname); return 0; } diff --git a/hashtable.c b/hashtable.c index e272f439d..2cc4e5504 100644 --- a/hashtable.c +++ b/hashtable.c @@ -1,7 +1,7 @@ /* * Routines to provide a memory-efficient hashtable. * - * Copyright (C) 2007-2020 Wayne Davison + * Copyright (C) 2007-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 as published by @@ -350,6 +350,9 @@ void *hashtable_find(struct hashtable *tbl, int64 key, void *data_when_new) ------------------------------------------------------------------------------- */ +#define NON_ZERO_32(x) ((x) ? (x) : (uint32_t)1) +#define NON_ZERO_64(x, y) ((x) || (y) ? (y) | (int64)(x) << 32 | (y) : (int64)1) + uint32_t hashlittle(const void *key, size_t length) { uint32_t a,b,c; /* internal state */ @@ -390,7 +393,7 @@ uint32_t hashlittle(const void *key, size_t length) case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ case 1 : a+=k8[0]; break; - case 0 : return c; + case 0 : return NON_ZERO_32(c); } } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ @@ -436,7 +439,7 @@ uint32_t hashlittle(const void *key, size_t length) break; case 1 : a+=k8[0]; break; - case 0 : return c; /* zero length requires no mixing */ + case 0 : return NON_ZERO_32(c); /* zero length requires no mixing */ } } else { /* need to read the key one byte at a time */ @@ -489,10 +492,171 @@ uint32_t hashlittle(const void *key, size_t length) /* FALLTHROUGH */ case 1 : a+=k[0]; break; - case 0 : return c; + case 0 : return NON_ZERO_32(c); } } final(a,b,c); - return c; + return NON_ZERO_32(c); } + +#if SIZEOF_INT64 >= 8 +/* + * hashlittle2: return 2 32-bit hash values joined into an int64. + * + * This is identical to hashlittle(), 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)". + */ +int64 hashlittle2(const void *key, size_t length) +{ + uint32_t a,b,c; /* internal state */ + union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length); + + u.ptr = key; + if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { + const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ + const uint8_t *k8; + + /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : return NON_ZERO_64(b, c); + } + } 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 (length > 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); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + 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; /* fall through */ + 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]; /* fall through */ + 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; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : return NON_ZERO_64(b, c); /* zero length strings require no mixing */ + } + + } 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 (length > 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; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + 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 : return NON_ZERO_64(b, c); + } + } + + final(a,b,c); + return NON_ZERO_64(b, c); +} +#else +#define hashlittle2(key, len) hashlittle(key, len) +#endif diff --git a/hlink.c b/hlink.c index 66810a3eb..2c14407ad 100644 --- a/hlink.c +++ b/hlink.c @@ -4,7 +4,7 @@ * Copyright (C) 1996 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2002 Martin Pool - * Copyright (C) 2004-2020 Wayne Davison + * Copyright (C) 2004-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 as published by @@ -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]]; @@ -446,7 +446,7 @@ int hard_link_check(struct file_struct *file, int ndx, char *fname, return -1; if (remove_source_files == 1 && do_xfers) - send_msg_int(MSG_SUCCESS, ndx); + send_msg_success(fname, ndx); return 1; } @@ -519,7 +519,7 @@ void finish_hard_link(struct file_struct *file, const char *fname, int fin_ndx, if (val < 0) continue; if (remove_source_files == 1 && do_xfers) - send_msg_int(MSG_SUCCESS, ndx); + send_msg_success(fname, ndx); } if (inc_recurse) { diff --git a/ifuncs.h b/ifuncs.h index 491f0807e..956fc22eb 100644 --- a/ifuncs.h +++ b/ifuncs.h @@ -1,6 +1,6 @@ /* Inline functions for rsync. * - * Copyright (C) 2007-2020 Wayne Davison + * Copyright (C) 2007-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 as published by diff --git a/install-sh b/install-sh index 956817d4b..8c409fbb9 100755 --- a/install-sh +++ b/install-sh @@ -115,7 +115,7 @@ else # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. - if [ -f $src -o -d $src ] + if [ -f $src ] || [ -d $src ] then true else diff --git a/io.c b/io.c index cc70ce887..bb60eecab 100644 --- a/io.c +++ b/io.c @@ -4,7 +4,7 @@ * Copyright (C) 1996-2001 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2001, 2002 Martin Pool - * Copyright (C) 2003-2020 Wayne Davison + * 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 as published by @@ -41,6 +41,7 @@ extern int am_server; extern int am_sender; extern int am_receiver; extern int am_generator; +extern int local_server; extern int msgs2stderr; extern int inc_recurse; extern int io_error; @@ -54,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; @@ -84,6 +86,8 @@ int sock_f_out = -1; int64 total_data_read = 0; int64 total_data_written = 0; +char num_dev_ino_buf[4 + 8 + 8]; + static struct { xbuf in, out, msg; int in_fd; @@ -264,15 +268,18 @@ static size_t safe_read(int fd, char *buf, size_t len) rprintf(FINFO, "select exception on fd %d\n", fd); */ if (FD_ISSET(fd, &r_fds)) { - int n = read(fd, buf + got, len - got); - if (DEBUG_GTE(IO, 2)) - rprintf(FINFO, "[%s] safe_read(%d)=%ld\n", who_am_i(), fd, (long)n); + ssize_t n = read(fd, buf + got, len - got); + if (DEBUG_GTE(IO, 2)) { + rprintf(FINFO, "[%s] safe_read(%d)=%" SIZE_T_FMT_MOD "d\n", + who_am_i(), fd, (SIZE_T_FMT_CAST)n); + } if (n == 0) break; if (n < 0) { if (errno == EINTR) continue; - rsyserr(FERROR, errno, "safe_read failed to read %ld bytes", (long)len); + rsyserr(FERROR, errno, "safe_read failed to read %" SIZE_T_FMT_MOD "d bytes", + (SIZE_T_FMT_CAST)len); exit_cleanup(RERR_STREAMIO); } if ((got += (size_t)n) == len) @@ -304,7 +311,7 @@ static const char *what_fd_is(int fd) * is not used on the socket except very early in the transfer. */ static void safe_write(int fd, const char *buf, size_t len) { - int n; + ssize_t n; assert(fd != iobuf.out_fd); @@ -315,8 +322,8 @@ static void safe_write(int fd, const char *buf, size_t len) if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN) { write_failed: rsyserr(FERROR, errno, - "safe_write failed to write %ld bytes to %s", - (long)len, what_fd_is(fd)); + "safe_write failed to write %" SIZE_T_FMT_MOD "d bytes to %s", + (SIZE_T_FMT_CAST)len, what_fd_is(fd)); exit_cleanup(RERR_STREAMIO); } } else { @@ -362,7 +369,7 @@ static void safe_write(int fd, const char *buf, size_t len) * a chunk of data and put it into the output buffer. */ static void forward_filesfrom_data(void) { - int len; + ssize_t len; len = read(ff_forward_fd, ff_xb.buf + ff_xb.len, ff_xb.size - ff_xb.len); if (len <= 0) { @@ -373,12 +380,15 @@ static void forward_filesfrom_data(void) free_xbuf(&ff_xb); if (ff_reenable_multiplex >= 0) io_start_multiplex_out(ff_reenable_multiplex); + free_implied_include_partial_string(); } return; } - if (DEBUG_GTE(IO, 2)) - rprintf(FINFO, "[%s] files-from read=%ld\n", who_am_i(), (long)len); + if (DEBUG_GTE(IO, 2)) { + rprintf(FINFO, "[%s] files-from read=%" SIZE_T_FMT_MOD "d\n", + who_am_i(), (SIZE_T_FMT_CAST)len); + } #ifdef ICONV_OPTION len += ff_xb.len; @@ -414,6 +424,7 @@ static void forward_filesfrom_data(void) while (s != eob) { if (*s++ == '\0') { ff_xb.len = s - sob - 1; + add_implied_include(sob, 0); if (iconvbufs(ic_send, &ff_xb, &iobuf.out, flags) < 0) exit_cleanup(RERR_PROTOCOL); /* impossible? */ write_buf(iobuf.out_fd, s-1, 1); /* Send the '\0'. */ @@ -429,6 +440,7 @@ static void forward_filesfrom_data(void) ff_lastchar = '\0'; else { /* Handle a partial string specially, saving any incomplete chars. */ + implied_include_partial_string(sob, s); flags &= ~ICB_INCLUDE_INCOMPLETE; if (iconvbufs(ic_send, &ff_xb, &iobuf.out, flags) < 0) { if (errno == E2BIG) @@ -445,13 +457,17 @@ static void forward_filesfrom_data(void) char *f = ff_xb.buf + ff_xb.pos; char *t = ff_xb.buf; char *eob = f + len; + char *cur = t; /* Eliminate any multi-'\0' runs. */ while (f != eob) { if (!(*t++ = *f++)) { + add_implied_include(cur, 0); + cur = t; while (f != eob && *f == '\0') f++; } } + implied_include_partial_string(cur, t); ff_lastchar = f[-1]; if ((len = t - ff_xb.buf) != 0) { /* This will not circle back to perform_io() because we only get @@ -562,52 +578,59 @@ static char *perform_io(size_t needed, int flags) case PIO_NEED_INPUT: /* We never resize the circular input buffer. */ if (iobuf.in.size < needed) { - rprintf(FERROR, "need to read %ld bytes, iobuf.in.buf is only %ld bytes.\n", - (long)needed, (long)iobuf.in.size); + rprintf(FERROR, "need to read %" SIZE_T_FMT_MOD "d bytes," + " iobuf.in.buf is only %" SIZE_T_FMT_MOD "d bytes.\n", + (SIZE_T_FMT_CAST)needed, (SIZE_T_FMT_CAST)iobuf.in.size); exit_cleanup(RERR_PROTOCOL); } if (msgs2stderr == 1 && DEBUG_GTE(IO, 3)) { - rprintf(FINFO, "[%s] perform_io(%ld, %sinput)\n", - who_am_i(), (long)needed, flags & PIO_CONSUME_INPUT ? "consume&" : ""); + rprintf(FINFO, "[%s] perform_io(%" SIZE_T_FMT_MOD "d, %sinput)\n", + who_am_i(), (SIZE_T_FMT_CAST)needed, flags & PIO_CONSUME_INPUT ? "consume&" : ""); } break; case PIO_NEED_OUTROOM: /* We never resize the circular output buffer. */ if (iobuf.out.size - iobuf.out_empty_len < needed) { - fprintf(stderr, "need to write %ld bytes, iobuf.out.buf is only %ld bytes.\n", - (long)needed, (long)(iobuf.out.size - iobuf.out_empty_len)); + fprintf(stderr, "need to write %" SIZE_T_FMT_MOD "d bytes," + " iobuf.out.buf is only %" SIZE_T_FMT_MOD "d bytes.\n", + (SIZE_T_FMT_CAST)needed, (SIZE_T_FMT_CAST)(iobuf.out.size - iobuf.out_empty_len)); exit_cleanup(RERR_PROTOCOL); } if (msgs2stderr == 1 && DEBUG_GTE(IO, 3)) { - rprintf(FINFO, "[%s] perform_io(%ld, outroom) needs to flush %ld\n", - who_am_i(), (long)needed, + rprintf(FINFO, "[%s] perform_io(%" SIZE_T_FMT_MOD "d," + " outroom) needs to flush %" SIZE_T_FMT_MOD "d\n", + who_am_i(), (SIZE_T_FMT_CAST)needed, iobuf.out.len + needed > iobuf.out.size - ? (long)(iobuf.out.len + needed - iobuf.out.size) : 0L); + ? (SIZE_T_FMT_CAST)(iobuf.out.len + needed - iobuf.out.size) : (SIZE_T_FMT_CAST)0); } break; case PIO_NEED_MSGROOM: /* We never resize the circular message buffer. */ if (iobuf.msg.size < needed) { - fprintf(stderr, "need to write %ld bytes, iobuf.msg.buf is only %ld bytes.\n", - (long)needed, (long)iobuf.msg.size); + fprintf(stderr, "need to write %" SIZE_T_FMT_MOD "d bytes," + " iobuf.msg.buf is only %" SIZE_T_FMT_MOD "d bytes.\n", + (SIZE_T_FMT_CAST)needed, (SIZE_T_FMT_CAST)iobuf.msg.size); exit_cleanup(RERR_PROTOCOL); } if (msgs2stderr == 1 && DEBUG_GTE(IO, 3)) { - rprintf(FINFO, "[%s] perform_io(%ld, msgroom) needs to flush %ld\n", - who_am_i(), (long)needed, + rprintf(FINFO, "[%s] perform_io(%" SIZE_T_FMT_MOD "d," + " msgroom) needs to flush %" SIZE_T_FMT_MOD "d\n", + who_am_i(), (SIZE_T_FMT_CAST)needed, iobuf.msg.len + needed > iobuf.msg.size - ? (long)(iobuf.msg.len + needed - iobuf.msg.size) : 0L); + ? (SIZE_T_FMT_CAST)(iobuf.msg.len + needed - iobuf.msg.size) : (SIZE_T_FMT_CAST)0); } break; case 0: - if (msgs2stderr == 1 && DEBUG_GTE(IO, 3)) - rprintf(FINFO, "[%s] perform_io(%ld, %d)\n", who_am_i(), (long)needed, flags); + if (msgs2stderr == 1 && DEBUG_GTE(IO, 3)) { + rprintf(FINFO, "[%s] perform_io(%" SIZE_T_FMT_MOD "d, %d)\n", + who_am_i(), (SIZE_T_FMT_CAST)needed, flags); + } break; default: @@ -665,8 +688,8 @@ static char *perform_io(size_t needed, int flags) ((MPLEX_BASE + (int)MSG_DATA)<<24) + iobuf.out.len - 4); if (msgs2stderr == 1 && DEBUG_GTE(IO, 1)) { - rprintf(FINFO, "[%s] send_msg(%d, %ld)\n", - who_am_i(), (int)MSG_DATA, (long)iobuf.out.len - 4); + rprintf(FINFO, "[%s] send_msg(%d, %" SIZE_T_FMT_MOD "d)\n", + who_am_i(), (int)MSG_DATA, (SIZE_T_FMT_CAST)iobuf.out.len - 4); } /* reserve room for the next MSG_DATA header */ @@ -757,7 +780,7 @@ static char *perform_io(size_t needed, int flags) if (iobuf.in_fd >= 0 && FD_ISSET(iobuf.in_fd, &r_fds)) { size_t len, pos = iobuf.in.pos + iobuf.in.len; - int n; + ssize_t n; if (pos >= iobuf.in.size) { pos -= iobuf.in.size; len = iobuf.in.size - iobuf.in.len; @@ -784,15 +807,13 @@ static char *perform_io(size_t needed, int flags) exit_cleanup(RERR_SOCKETIO); } } - if (msgs2stderr == 1 && DEBUG_GTE(IO, 2)) - rprintf(FINFO, "[%s] recv=%ld\n", who_am_i(), (long)n); + if (msgs2stderr == 1 && DEBUG_GTE(IO, 2)) { + rprintf(FINFO, "[%s] recv=%" SIZE_T_FMT_MOD "d\n", + who_am_i(), (SIZE_T_FMT_CAST)n); + } - if (io_timeout || stop_at_utime) { + if (io_timeout) { last_io_in = time(NULL); - if (stop_at_utime && last_io_in >= stop_at_utime) { - rprintf(FERROR, "stopping at requested limit\n"); - exit_cleanup(RERR_TIMEOUT); - } if (io_timeout && flags & PIO_NEED_INPUT) maybe_send_keepalive(last_io_in, 0); } @@ -801,9 +822,14 @@ static char *perform_io(size_t needed, int flags) iobuf.in.len += n; } + if (stop_at_utime && time(NULL) >= stop_at_utime) { + rprintf(FERROR, "stopping at requested limit\n"); + exit_cleanup(RERR_TIMEOUT); + } + if (out && FD_ISSET(iobuf.out_fd, &w_fds)) { size_t len = iobuf.raw_flushing_ends_before ? iobuf.raw_flushing_ends_before - out->pos : out->len; - int n; + ssize_t n; if (bwlimit_writemax && len > bwlimit_writemax) len = bwlimit_writemax; @@ -824,8 +850,8 @@ static char *perform_io(size_t needed, int flags) } } if (msgs2stderr == 1 && DEBUG_GTE(IO, 2)) { - rprintf(FINFO, "[%s] %s sent=%ld\n", - who_am_i(), out == &iobuf.out ? "out" : "msg", (long)n); + rprintf(FINFO, "[%s] %s sent=%" SIZE_T_FMT_MOD "d\n", + who_am_i(), out == &iobuf.out ? "out" : "msg", (SIZE_T_FMT_CAST)n); } if (io_timeout) @@ -945,8 +971,10 @@ int send_msg(enum msgcode code, const char *buf, size_t len, int convert) if (!OUT_MULTIPLEXED) return 0; - if (want_debug) - rprintf(FINFO, "[%s] send_msg(%d, %ld)\n", who_am_i(), (int)code, (long)len); + if (want_debug) { + rprintf(FINFO, "[%s] send_msg(%d, %" SIZE_T_FMT_MOD "d)\n", + who_am_i(), (int)code, (SIZE_T_FMT_CAST)len); + } /* When checking for enough free space for this message, we need to * make sure that there is space for the 4-byte header, plus we'll @@ -1021,8 +1049,10 @@ int send_msg(enum msgcode code, const char *buf, size_t len, int convert) SIVAL(hdr, 0, ((MPLEX_BASE + (int)code)<<24) + len); - if (want_debug && convert > 0) - rprintf(FINFO, "[%s] converted msg len=%ld\n", who_am_i(), (long)len); + if (want_debug && convert > 0) { + rprintf(FINFO, "[%s] converted msg len=%" SIZE_T_FMT_MOD "d\n", + who_am_i(), (SIZE_T_FMT_CAST)len); + } return 1; } @@ -1038,6 +1068,24 @@ void send_msg_int(enum msgcode code, int num) send_msg(code, numbuf, 4, -1); } +void send_msg_success(const char *fname, int num) +{ + if (local_server) { + STRUCT_STAT st; + + if (DEBUG_GTE(IO, 1)) + rprintf(FINFO, "[%s] send_msg_success(%d)\n", who_am_i(), num); + + if (stat(fname, &st) < 0) + memset(&st, 0, sizeof (STRUCT_STAT)); + SIVAL(num_dev_ino_buf, 0, num); + SIVAL64(num_dev_ino_buf, 4, st.st_dev); + SIVAL64(num_dev_ino_buf, 4+8, st.st_ino); + send_msg(MSG_SUCCESS, num_dev_ino_buf, sizeof num_dev_ino_buf, -1); + } else + send_msg_int(MSG_SUCCESS, num); +} + static void got_flist_entry_status(enum festatus status, int ndx) { struct file_list *flist = flist_for_ndx(ndx, "got_flist_entry_status"); @@ -1052,8 +1100,12 @@ static void got_flist_entry_status(enum festatus status, int ndx) switch (status) { case FES_SUCCESS: - if (remove_source_files) - send_msg_int(MSG_SUCCESS, ndx); + if (remove_source_files) { + if (local_server) + send_msg(MSG_SUCCESS, num_dev_ino_buf, sizeof num_dev_ino_buf, -1); + else + send_msg_int(MSG_SUCCESS, ndx); + } /* FALL THROUGH */ case FES_NO_SEND: #ifdef SUPPORT_HARD_LINKS @@ -1436,8 +1488,10 @@ static void read_a_msg(void) msg_bytes = tag & 0xFFFFFF; tag = (tag >> 24) - MPLEX_BASE; - if (msgs2stderr == 1 && DEBUG_GTE(IO, 1)) - rprintf(FINFO, "[%s] got msg=%d, len=%ld\n", who_am_i(), (int)tag, (long)msg_bytes); + if (msgs2stderr == 1 && DEBUG_GTE(IO, 1)) { + rprintf(FINFO, "[%s] got msg=%d, len=%" SIZE_T_FMT_MOD "d\n", + who_am_i(), (int)tag, (SIZE_T_FMT_CAST)msg_bytes); + } switch (tag) { case MSG_DATA: @@ -1546,14 +1600,15 @@ static void read_a_msg(void) } break; case MSG_SUCCESS: - if (msg_bytes != 4) { + if (msg_bytes != (local_server ? 4+8+8 : 4)) { invalid_msg: rprintf(FERROR, "invalid multi-message %d:%lu [%s%s]\n", tag, (unsigned long)msg_bytes, who_am_i(), inc_recurse ? "/inc" : ""); exit_cleanup(RERR_STREAMIO); } - val = raw_read_int(); + raw_read_buf(num_dev_ino_buf, msg_bytes); + val = IVAL(num_dev_ino_buf, 0); iobuf.in_multiplexed = 1; if (am_generator) got_flist_entry_status(FES_SUCCESS, val); @@ -1613,8 +1668,10 @@ static void read_a_msg(void) else goto invalid_msg; iobuf.in_multiplexed = 1; - if (DEBUG_GTE(EXIT, 3)) - rprintf(FINFO, "[%s] got MSG_ERROR_EXIT with %ld bytes\n", who_am_i(), (long)msg_bytes); + if (DEBUG_GTE(EXIT, 3)) { + rprintf(FINFO, "[%s] got MSG_ERROR_EXIT with %" SIZE_T_FMT_MOD "d bytes\n", + who_am_i(), (SIZE_T_FMT_CAST)msg_bytes); + } if (msg_bytes == 0) { if (!am_sender && !am_generator) { if (DEBUG_GTE(EXIT, 3)) { @@ -1728,6 +1785,13 @@ int32 read_int(int f) return num; } +uint32 read_uint(int f) +{ + char b[4]; + read_buf(f, b, 4); + return IVAL(b, 0); +} + int32 read_varint(int f) { union { @@ -1820,6 +1884,7 @@ int64 read_longint(int f) #endif } +/* Debugging note: this will be named read_buf_() when using an external zlib. */ void read_buf(int f, char *buf, size_t len) { if (f != iobuf.in_fd) { @@ -1913,7 +1978,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); diff --git a/itypes.h b/itypes.h index a78300b3b..0a7111f1a 100644 --- a/itypes.h +++ b/itypes.h @@ -1,6 +1,6 @@ /* Inline functions for rsync. * - * Copyright (C) 2007-2020 Wayne Davison + * Copyright (C) 2007-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 as published by diff --git a/latest-year.h b/latest-year.h index ddbf2b5ef..f0f7d5201 100644 --- a/latest-year.h +++ b/latest-year.h @@ -1 +1 @@ -#define LATEST_YEAR "2020" +#define LATEST_YEAR "2025" diff --git a/lib/md-defines.h b/lib/md-defines.h index 1410af5fa..6ef6a6897 100644 --- a/lib/md-defines.h +++ b/lib/md-defines.h @@ -1,11 +1,28 @@ /* Keep this simple so both C and ASM can use it */ +/* These allow something like CFLAGS=-DDISABLE_SHA512_DIGEST */ +#ifdef DISABLE_SHA256_DIGEST +#undef SHA256_DIGEST_LENGTH +#endif +#ifdef DISABLE_SHA512_DIGEST +#undef SHA512_DIGEST_LENGTH +#endif + #define MD4_DIGEST_LEN 16 #define MD5_DIGEST_LEN 16 +#if defined SHA512_DIGEST_LENGTH +#define MAX_DIGEST_LEN SHA512_DIGEST_LENGTH +#elif defined SHA256_DIGEST_LENGTH +#define MAX_DIGEST_LEN SHA256_DIGEST_LENGTH +#elif defined SHA_DIGEST_LENGTH +#define MAX_DIGEST_LEN SHA_DIGEST_LENGTH +#else #define MAX_DIGEST_LEN MD5_DIGEST_LEN +#endif #define CSUM_CHUNK 64 +#define CSUM_gone -1 #define CSUM_NONE 0 #define CSUM_MD4_ARCHAIC 1 #define CSUM_MD4_BUSTED 2 @@ -15,3 +32,6 @@ #define CSUM_XXH64 6 #define CSUM_XXH3_64 7 #define CSUM_XXH3_128 8 +#define CSUM_SHA1 9 +#define CSUM_SHA256 10 +#define CSUM_SHA512 11 diff --git a/lib/md5-asm-x86_64.S b/lib/md5-asm-x86_64.S index 383f193a8..3737058f8 100644 --- a/lib/md5-asm-x86_64.S +++ b/lib/md5-asm-x86_64.S @@ -27,7 +27,7 @@ #include "config.h" #include "md-defines.h" -#if !defined USE_OPENSSL && CSUM_CHUNK == 64 +#ifdef USE_MD5_ASM /* { */ #ifdef __APPLE__ #define md5_process_asm _md5_process_asm @@ -698,4 +698,4 @@ md5_process_asm: pop %rbp ret -#endif /* !USE_OPENSSL ... */ +#endif /* } USE_MD5_ASM */ diff --git a/lib/md5.c b/lib/md5.c index 41f158b82..f36c5ba7e 100644 --- a/lib/md5.c +++ b/lib/md5.c @@ -2,7 +2,7 @@ * RFC 1321 compliant MD5 implementation * * Copyright (C) 2001-2003 Christophe Devine - * Copyright (C) 2007-2020 Wayne Davison + * Copyright (C) 2007-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 as published by @@ -20,7 +20,6 @@ #include "rsync.h" -#ifndef USE_OPENSSL void md5_begin(md_context *ctx) { ctx->A = 0x67452301; @@ -148,7 +147,10 @@ static void md5_process(md_context *ctx, const uchar data[CSUM_CHUNK]) ctx->D += D; } -#if defined HAVE_ASM && CSUM_CHUNK == 64 +#ifdef USE_MD5_ASM +#if CSUM_CHUNK != 64 +#error The MD5 ASM code does not support CSUM_CHUNK != 64 +#endif extern void md5_process_asm(md_context *ctx, const void *data, size_t num); #endif @@ -176,20 +178,20 @@ void md5_update(md_context *ctx, const uchar *input, uint32 length) left = 0; } -#if defined HAVE_ASM && CSUM_CHUNK == 64 +#ifdef USE_MD5_ASM /* { */ if (length >= CSUM_CHUNK) { uint32 chunks = length / CSUM_CHUNK; md5_process_asm(ctx, input, chunks); length -= chunks * CSUM_CHUNK; input += chunks * CSUM_CHUNK; } -#else +#else /* } { */ while (length >= CSUM_CHUNK) { md5_process(ctx, input); length -= CSUM_CHUNK; input += CSUM_CHUNK; } -#endif +#endif /* } */ if (length) memcpy(ctx->buffer + left, input, length); @@ -221,9 +223,8 @@ void md5_result(md_context *ctx, uchar digest[MD5_DIGEST_LEN]) SIVALu(digest, 8, ctx->C); SIVALu(digest, 12, ctx->D); } -#endif -#ifdef TEST_MD5 +#ifdef TEST_MD5 /* { */ void get_md5(uchar *out, const uchar *input, int n) { @@ -317,4 +318,4 @@ int main(int argc, char *argv[]) return 0; } -#endif +#endif /* } */ diff --git a/lib/mdigest.h b/lib/mdigest.h index db174017c..9d52ef5f4 100644 --- a/lib/mdigest.h +++ b/lib/mdigest.h @@ -1,8 +1,8 @@ /* The include file for both the MD4 and MD5 routines. */ #ifdef USE_OPENSSL -#include "openssl/md4.h" -#include "openssl/md5.h" +#include +#include #endif #include "md-defines.h" @@ -17,13 +17,6 @@ void mdfour_begin(md_context *md); void mdfour_update(md_context *md, const uchar *in, uint32 length); void mdfour_result(md_context *md, uchar digest[MD4_DIGEST_LEN]); -#ifndef USE_OPENSSL -#define MD5_CTX md_context -#define MD5_Init md5_begin -#define MD5_Update md5_update -#define MD5_Final(digest, cptr) md5_result(cptr, digest) - void md5_begin(md_context *ctx); void md5_update(md_context *ctx, const uchar *input, uint32 length); void md5_result(md_context *ctx, uchar digest[MD5_DIGEST_LEN]); -#endif diff --git a/lib/pool_alloc.c b/lib/pool_alloc.c index a1a7245f6..b49e3a7bd 100644 --- a/lib/pool_alloc.c +++ b/lib/pool_alloc.c @@ -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); diff --git a/lib/snprintf.c b/lib/snprintf.c index 52fdd11fb..15c0529dc 100644 --- a/lib/snprintf.c +++ b/lib/snprintf.c @@ -20,7 +20,7 @@ * for string length. This covers a nasty loophole. * * The other functions are there to prevent NULL pointers from - * causing nast effects. + * causing nasty effects. * * More Recently: * Brandon Long 9/15/96 for mutt 0.43 diff --git a/lib/sysacls.c b/lib/sysacls.c index a9c3f1bd5..a5abe408d 100644 --- a/lib/sysacls.c +++ b/lib/sysacls.c @@ -2,7 +2,7 @@ * Unix SMB/CIFS implementation. * Based on the Samba ACL support code. * Copyright (C) Jeremy Allison 2000. - * Copyright (C) 2007-2020 Wayne Davison + * Copyright (C) 2007-2022 Wayne Davison * * The permission functions have been changed to get/set all bits via * one call. Some functions that rsync doesn't need were also removed. @@ -175,7 +175,7 @@ int sys_acl_delete_def_file(const char *name) return acl_delete_def_file(name); } -int sys_acl_free_acl(SMB_ACL_T the_acl) +int sys_acl_free_acl(SMB_ACL_T the_acl) { return acl_free(the_acl); } @@ -185,7 +185,7 @@ int sys_acl_free_acl(SMB_ACL_T the_acl) * The interface to DEC/Compaq Tru64 UNIX ACLs * is based on Draft 13 of the POSIX spec which is * slightly different from the Draft 16 interface. - * + * * Also, some of the permset manipulation functions * such as acl_clear_perm() and acl_add_perm() appear * to be broken on Tru64 so we have to manipulate @@ -310,7 +310,7 @@ int sys_acl_delete_def_file(const char *name) return acl_delete_def_file((char *)name); } -int sys_acl_free_acl(SMB_ACL_T the_acl) +int sys_acl_free_acl(SMB_ACL_T the_acl) { return acl_free(the_acl); } @@ -457,7 +457,7 @@ SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type) break; } ndefault = count - naccess; - + /* * if the caller wants the default ACL we have to copy * the entries down to the start of the acl[] buffer @@ -517,7 +517,7 @@ SMB_ACL_T sys_acl_get_fd(int fd) if (acl_d->acl[naccess].a_type & ACL_DEFAULT) break; } - + acl_d->count = naccess; return acl_d; @@ -532,7 +532,7 @@ int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *b if (*tag_type_p == SMB_ACL_USER || *tag_type_p == SMB_ACL_GROUP) *u_g_id_p = entry->a_id; - + return 0; } @@ -633,7 +633,7 @@ static int acl_sort(SMB_ACL_T acl_d) } return 0; } - + int sys_acl_valid(SMB_ACL_T acl_d) { return acl_sort(acl_d); @@ -755,11 +755,11 @@ int sys_acl_delete_def_file(const char *path) ret = acl(path, SETACL, acl_d->count, acl_d->acl); sys_acl_free_acl(acl_d); - + return ret; } -int sys_acl_free_acl(SMB_ACL_T acl_d) +int sys_acl_free_acl(SMB_ACL_T acl_d) { SAFE_FREE(acl_d); return 0; @@ -895,10 +895,10 @@ SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type) int ndefault; /* # of default ACL entries */ if (hpux_acl_call_presence() == False) { - /* Looks like we don't have the acl() system call on HPUX. + /* Looks like we don't have the acl() system call on HPUX. * May be the system doesn't have the latest version of JFS. */ - return NULL; + return NULL; } if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) { @@ -949,7 +949,7 @@ SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type) break; } ndefault = count - naccess; - + /* * if the caller wants the default ACL we have to copy * the entries down to the start of the acl[] buffer @@ -1109,9 +1109,9 @@ struct hpux_acl_types { * aclp - Array of ACL structures. * acl_type_count - Pointer to acl_types structure. Should already be * allocated. - * Output: + * Output: * - * acl_type_count - This structure is filled up with counts of various + * acl_type_count - This structure is filled up with counts of various * acl types. */ @@ -1123,28 +1123,28 @@ static void hpux_count_obj(int acl_count, struct acl *aclp, struct hpux_acl_type for (i = 0; i < acl_count; i++) { switch (aclp[i].a_type) { - case USER: + case USER: acl_type_count->n_user++; break; - case USER_OBJ: + case USER_OBJ: acl_type_count->n_user_obj++; break; - case DEF_USER_OBJ: + case DEF_USER_OBJ: acl_type_count->n_def_user_obj++; break; - case GROUP: + case GROUP: acl_type_count->n_group++; break; - case GROUP_OBJ: + case GROUP_OBJ: acl_type_count->n_group_obj++; break; - case DEF_GROUP_OBJ: + case DEF_GROUP_OBJ: acl_type_count->n_def_group_obj++; break; - case OTHER_OBJ: + case OTHER_OBJ: acl_type_count->n_other_obj++; break; - case DEF_OTHER_OBJ: + case DEF_OTHER_OBJ: acl_type_count->n_def_other_obj++; break; case CLASS_OBJ: @@ -1159,14 +1159,14 @@ static void hpux_count_obj(int acl_count, struct acl *aclp, struct hpux_acl_type case DEF_GROUP: acl_type_count->n_def_group++; break; - default: + default: acl_type_count->n_illegal_obj++; break; } } } -/* swap_acl_entries: Swaps two ACL entries. +/* swap_acl_entries: Swaps two ACL entries. * * Inputs: aclp0, aclp1 - ACL entries to be swapped. */ @@ -1189,25 +1189,25 @@ static void hpux_swap_acl_entries(struct acl *aclp0, struct acl *aclp1) } /* prohibited_duplicate_type - * Identifies if given ACL type can have duplicate entries or + * Identifies if given ACL type can have duplicate entries or * not. * * Inputs: acl_type - ACL Type. * - * Outputs: + * Outputs: * - * Return.. + * Return.. * * True - If the ACL type matches any of the prohibited types. * False - If the ACL type doesn't match any of the prohibited types. - */ + */ static BOOL hpux_prohibited_duplicate_type(int acl_type) { switch (acl_type) { case USER: case GROUP: - case DEF_USER: + case DEF_USER: case DEF_GROUP: return True; default: @@ -1217,7 +1217,7 @@ static BOOL hpux_prohibited_duplicate_type(int acl_type) /* get_needed_class_perm * Returns the permissions of a ACL structure only if the ACL - * type matches one of the pre-determined types for computing + * type matches one of the pre-determined types for computing * CLASS_OBJ permissions. * * Inputs: aclp - Pointer to ACL structure. @@ -1226,17 +1226,17 @@ static BOOL hpux_prohibited_duplicate_type(int acl_type) static int hpux_get_needed_class_perm(struct acl *aclp) { switch (aclp->a_type) { - case USER: - case GROUP_OBJ: - case GROUP: - case DEF_USER_OBJ: + case USER: + case GROUP_OBJ: + case GROUP: + case DEF_USER_OBJ: case DEF_USER: - case DEF_GROUP_OBJ: + case DEF_GROUP_OBJ: case DEF_GROUP: case DEF_CLASS_OBJ: - case DEF_OTHER_OBJ: + case DEF_OTHER_OBJ: return aclp->a_perm; - default: + default: return 0; } } @@ -1267,15 +1267,15 @@ static int hpux_acl_sort(int acl_count, int calclass, struct acl *aclp) #if !defined(HAVE_HPUX_ACLSORT) /* * The aclsort() system call is available on the latest HPUX General - * Patch Bundles. So for HPUX, we developed our version of acl_sort - * function. Because, we don't want to update to a new + * Patch Bundles. So for HPUX, we developed our version of acl_sort + * function. Because, we don't want to update to a new * HPUX GR bundle just for aclsort() call. */ struct hpux_acl_types acl_obj_count; int n_class_obj_perm = 0; int i, j; - + if (!acl_count) { DEBUG(10, ("Zero acl count passed. Returning Success\n")); return 0; @@ -1290,8 +1290,8 @@ static int hpux_acl_sort(int acl_count, int calclass, struct acl *aclp) hpux_count_obj(acl_count, aclp, &acl_obj_count); - /* There should be only one entry each of type USER_OBJ, GROUP_OBJ, - * CLASS_OBJ and OTHER_OBJ + /* There should be only one entry each of type USER_OBJ, GROUP_OBJ, + * CLASS_OBJ and OTHER_OBJ */ if (acl_obj_count.n_user_obj != 1 @@ -1313,15 +1313,15 @@ or DEF_USER_OBJ or DEF_GROUP_OBJ or DEF_OTHER_OBJ\n")); return -1; } - /* We now have proper number of OBJ and DEF_OBJ entries. Now sort the acl - * structures. + /* We now have proper number of OBJ and DEF_OBJ entries. Now sort the acl + * structures. * * Sorting crieteria - First sort by ACL type. If there are multiple entries of * same ACL type, sort by ACL id. * - * I am using the trivial kind of sorting method here because, performance isn't + * I am using the trivial kind of sorting method here because, performance isn't * really effected by the ACLs feature. More over there aren't going to be more - * than 17 entries on HPUX. + * than 17 entries on HPUX. */ for (i = 0; i < acl_count; i++) { @@ -1390,7 +1390,7 @@ static int acl_sort(SMB_ACL_T acl_d) } return 0; } - + int sys_acl_valid(SMB_ACL_T acl_d) { return acl_sort(acl_d); @@ -1405,11 +1405,11 @@ int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T type, SMB_ACL_T acl_d) int ret; if (hpux_acl_call_presence() == False) { - /* Looks like we don't have the acl() system call on HPUX. + /* Looks like we don't have the acl() system call on HPUX. * May be the system doesn't have the latest version of JFS. */ errno=ENOSYS; - return -1; + return -1; } if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) { @@ -1538,11 +1538,11 @@ int sys_acl_delete_def_file(const char *path) ret = acl(path, ACL_SET, acl_d->count, acl_d->acl); sys_acl_free_acl(acl_d); - + return ret; } -int sys_acl_free_acl(SMB_ACL_T acl_d) +int sys_acl_free_acl(SMB_ACL_T acl_d) { free(acl_d); return 0; @@ -1723,7 +1723,7 @@ int sys_acl_delete_def_file(const char *name) return acl_delete_def_file(name); } -int sys_acl_free_acl(SMB_ACL_T acl_d) +int sys_acl_free_acl(SMB_ACL_T acl_d) { if (acl_d->freeaclp) { acl_free(acl_d->aclp); @@ -1834,12 +1834,12 @@ SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type) } /* Get the acl using statacl */ - + DEBUG(10, ("Entering sys_acl_get_file\n")); DEBUG(10, ("path_p is %s\n", path_p)); file_acl = (struct acl *)SMB_MALLOC(BUFSIZ); - + if (file_acl == NULL) { errno=ENOMEM; DEBUG(0, ("Error in AIX sys_acl_get_file: %d\n", errno)); @@ -1931,9 +1931,9 @@ SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type) * to be specified but, it's better than leaving it 0 */ acl_entry_link->entryp->ace_type = acl_entry->ace_type; - + acl_entry_link->entryp->ace_access = acl_entry->ace_access; - + memcpy(acl_entry_link->entryp->ace_id, idp, sizeof (struct ace_id)); /* The access in the acl entries must be left shifted by * @@ -1962,7 +1962,7 @@ SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type) DEBUG(10, ("acl_entry = %d\n", acl_entry)); DEBUG(10, ("The ace_type is %d\n", acl_entry->ace_type)); - + acl_entry = acl_nxt(acl_entry); } } /* end of if enabled */ @@ -2014,12 +2014,12 @@ SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type) new_acl_entry->ace_access = file_acl->o_access << 6; idp->id_type = SMB_ACL_OTHER; break; - + case 1: new_acl_entry->ace_access = file_acl->u_access << 6; idp->id_type = SMB_ACL_USER_OBJ; break; - + default: return NULL; @@ -2048,7 +2048,7 @@ SMB_ACL_T sys_acl_get_fd(int fd) int rc = 0; /* Get the acl using fstatacl */ - + DEBUG(10, ("Entering sys_acl_get_fd\n")); DEBUG(10, ("fd is %d\n", fd)); file_acl = (struct acl *)SMB_MALLOC(BUFSIZ); @@ -2095,12 +2095,12 @@ SMB_ACL_T sys_acl_get_fd(int fd) DEBUG(10, ("acl_entry is %d\n", acl_entry)); DEBUG(10, ("acl_last(file_acl) id %d\n", acl_last(file_acl))); - + /* Check if the extended acl bit is on. * * If it isn't, do not show the * * contents of the acl since AIX intends * * the extended info to remain unused */ - + if (file_acl->acl_mode & S_IXACL){ /* while we are not pointing to the very end */ while (acl_entry < acl_last(file_acl)) { @@ -2115,7 +2115,7 @@ SMB_ACL_T sys_acl_get_fd(int fd) } idp = acl_entry->ace_id; - + /* Check if this is the first entry in the linked list. * * The first entry needs to keep prevp pointing to NULL * * and already has entryp allocated. */ @@ -2177,7 +2177,7 @@ SMB_ACL_T sys_acl_get_fd(int fd) DEBUG(10, ("acl_entry = %d\n", acl_entry)); DEBUG(10, ("The ace_type is %d\n", acl_entry->ace_type)); - + acl_entry = acl_nxt(acl_entry); } } /* end of if enabled */ @@ -2210,43 +2210,43 @@ SMB_ACL_T sys_acl_get_fd(int fd) } acl_entry_link->nextp = NULL; - + new_acl_entry = acl_entry_link->entryp; idp = new_acl_entry->ace_id; - + new_acl_entry->ace_len = sizeof (struct acl_entry); new_acl_entry->ace_type = ACC_PERMIT; idp->id_len = sizeof (struct ace_id); DEBUG(10, ("idp->id_len = %d\n", idp->id_len)); memset(idp->id_data, 0, sizeof (uid_t)); - + switch (i) { case 2: new_acl_entry->ace_access = file_acl->g_access << 6; idp->id_type = SMB_ACL_GROUP_OBJ; break; - + case 3: new_acl_entry->ace_access = file_acl->o_access << 6; idp->id_type = SMB_ACL_OTHER; break; - + case 1: new_acl_entry->ace_access = file_acl->u_access << 6; idp->id_type = SMB_ACL_USER_OBJ; break; - + default: return NULL; } - + acl_entry_link_head->count++; DEBUG(10, ("new_acl_entry->ace_access = %d\n", new_acl_entry->ace_access)); } acl_entry_link_head->count = 0; SAFE_FREE(file_acl); - + return acl_entry_link_head; } #endif @@ -2274,7 +2274,7 @@ int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *b SMB_ACL_T sys_acl_init(int count) { struct acl_entry_link *theacl = NULL; - + if (count < 0) { errno = EINVAL; return NULL; @@ -2383,9 +2383,9 @@ int sys_acl_valid(SMB_ACL_T theacl) } DEBUG(10, ("user_obj=%d, group_obj=%d, other_obj=%d\n", user_obj, group_obj, other_obj)); - + if (user_obj != 1 || group_obj != 1 || other_obj != 1) - return -1; + return -1; return 0; } @@ -2404,7 +2404,7 @@ int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl) DEBUG(10, ("Entering sys_acl_set_file\n")); DEBUG(10, ("File name is %s\n", name)); - + /* AIX has no default ACL */ if (acltype == SMB_ACL_TYPE_DEFAULT) return 0; @@ -2449,7 +2449,7 @@ int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl) errno = ENOMEM; DEBUG(0, ("Error in sys_acl_set_file is %d\n", errno)); return -1; - } + } memcpy(file_acl_temp, file_acl, file_acl->acl_len); SAFE_FREE(file_acl); @@ -2460,15 +2460,15 @@ int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl) file_acl->acl_len += sizeof (struct acl_entry); acl_entry->ace_len = acl_entry_link->entryp->ace_len; acl_entry->ace_access = acl_entry_link->entryp->ace_access; - + /* In order to use this, we'll need to wait until we can get denies */ /* if (!acl_entry->ace_access && acl_entry->ace_type == ACC_PERMIT) acl_entry->ace_type = ACC_SPECIFY; */ acl_entry->ace_type = ACC_SPECIFY; - + ace_id = acl_entry->ace_id; - + ace_id->id_type = acl_entry_link->entryp->ace_id->id_type; DEBUG(10, ("The id type is %d\n", ace_id->id_type)); ace_id->id_len = acl_entry_link->entryp->ace_id->id_len; @@ -2496,7 +2496,7 @@ int sys_acl_set_fd(int fd, SMB_ACL_T theacl) uint user_id; uint acl_length; uint rc; - + DEBUG(10, ("Entering sys_acl_set_fd\n")); acl_length = BUFSIZ; file_acl = (struct acl *)SMB_MALLOC(BUFSIZ); @@ -2508,7 +2508,7 @@ int sys_acl_set_fd(int fd, SMB_ACL_T theacl) } memset(file_acl, 0, BUFSIZ); - + file_acl->acl_len = ACL_SIZ; file_acl->acl_mode = S_IXACL; @@ -2550,22 +2550,22 @@ int sys_acl_set_fd(int fd, SMB_ACL_T theacl) file_acl->acl_len += sizeof (struct acl_entry); acl_entry->ace_len = acl_entry_link->entryp->ace_len; acl_entry->ace_access = acl_entry_link->entryp->ace_access; - + /* In order to use this, we'll need to wait until we can get denies */ /* if (!acl_entry->ace_access && acl_entry->ace_type == ACC_PERMIT) acl_entry->ace_type = ACC_SPECIFY; */ - + acl_entry->ace_type = ACC_SPECIFY; - + ace_id = acl_entry->ace_id; - + ace_id->id_type = acl_entry_link->entryp->ace_id->id_type; DEBUG(10, ("The id type is %d\n", ace_id->id_type)); ace_id->id_len = acl_entry_link->entryp->ace_id->id_len; memcpy(&user_id, acl_entry_link->entryp->ace_id->id_data, sizeof (uid_t)); memcpy(ace_id->id_data, &user_id, sizeof (uid_t)); } - + rc = fchacl(fd, file_acl, file_acl->acl_len); DEBUG(10, ("errno is %d\n", errno)); DEBUG(10, ("return code is %d\n", rc)); @@ -2594,7 +2594,7 @@ int sys_acl_free_acl(SMB_ACL_T posix_acl) SAFE_FREE(acl_entry_link->prevp); SAFE_FREE(acl_entry_link->entryp); SAFE_FREE(acl_entry_link); - + return 0; } diff --git a/lib/sysacls.h b/lib/sysacls.h index 8865dae43..c06959742 100644 --- a/lib/sysacls.h +++ b/lib/sysacls.h @@ -3,7 +3,7 @@ * Version 2.2.x * Portable SMB ACL interface * Copyright (C) Jeremy Allison 2000 - * Copyright (C) 2007-2020 Wayne Davison + * Copyright (C) 2007-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 as published by @@ -232,7 +232,7 @@ struct new_acl_entry{ #define SMB_ACL_ENTRY_T struct new_acl_entry* #define SMB_ACL_T struct acl_entry_link* - + #define SMB_ACL_TAG_T unsigned short #define SMB_ACL_TYPE_T int diff --git a/lib/sysxattrs.c b/lib/sysxattrs.c index d403caf57..ca08d1319 100644 --- a/lib/sysxattrs.c +++ b/lib/sysxattrs.c @@ -2,7 +2,7 @@ * Extended attribute support for rsync. * * Copyright (C) 2004 Red Hat, Inc. - * Copyright (C) 2003-2019 Wayne Davison + * Copyright (C) 2003-2022 Wayne Davison * Written by Jay Fenlason. * * This program is free software; you can redistribute it and/or modify diff --git a/log.c b/log.c index 45ae5c1bb..e4ba1cce2 100644 --- a/log.c +++ b/log.c @@ -3,7 +3,7 @@ * * Copyright (C) 1998-2001 Andrew Tridgell * Copyright (C) 2000-2001 Martin Pool - * Copyright (C) 2003-2020 Wayne Davison + * 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 as published by @@ -36,8 +36,6 @@ extern int protocol_version; extern int always_checksum; extern int preserve_mtimes; extern int msgs2stderr; -extern int xfersum_type; -extern int checksum_type; extern int stdout_format_has_i; extern int stdout_format_has_o_or_i; extern int logfile_format_has_i; @@ -62,6 +60,8 @@ extern unsigned int module_dirlen; extern char sender_file_sum[MAX_DIGEST_LEN]; extern const char undetermined_hostname[]; +extern struct name_num_item *xfer_sum_nni, *file_sum_nni; + static int log_initialised; static int logfile_was_closed; static FILE *logfile_fp; @@ -680,12 +680,12 @@ static void log_formatted(enum logcode code, const char *format, const char *op, n = NULL; if (S_ISREG(file->mode)) { if (always_checksum) - n = sum_as_hex(checksum_type, F_SUM(file), 1); + n = sum_as_hex(file_sum_nni->num, F_SUM(file), 1); else if (iflags & ITEM_TRANSFER) - n = sum_as_hex(xfersum_type, sender_file_sum, 0); + n = sum_as_hex(xfer_sum_nni->num, sender_file_sum, 0); } if (!n) { - int sum_len = csum_len_for_type(always_checksum ? checksum_type : xfersum_type, + int sum_len = csum_len_for_type(always_checksum ? file_sum_nni->num : xfer_sum_nni->num, always_checksum); memset(buf2, ' ', sum_len*2); buf2[sum_len*2] = '\0'; diff --git a/m4/have_type.m4 b/m4/have_type.m4 index 704ca33b5..12fc719e8 100644 --- a/m4/have_type.m4 +++ b/m4/have_type.m4 @@ -1,6 +1,5 @@ dnl AC_HAVE_TYPE(TYPE,INCLUDES) AC_DEFUN([AC_HAVE_TYPE], [ -AC_REQUIRE([AC_HEADER_STDC]) cv=`echo "$1" | sed 'y%./+- %__p__%'` AC_MSG_CHECKING(for $1) AC_CACHE_VAL([ac_cv_type_$cv], diff --git a/main.c b/main.c index 361dbc4e5..4f070accc 100644 --- a/main.c +++ b/main.c @@ -4,7 +4,7 @@ * Copyright (C) 1996-2001 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2001, 2002 Martin Pool - * Copyright (C) 2003-2020 Wayne Davison + * 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 as published by @@ -48,6 +48,7 @@ extern int called_from_signal_handler; extern int need_messages_from_generator; extern int kluge_around_eof; extern int got_xfer_error; +extern int old_style_args; extern int msgs2stderr; extern int module_id; extern int read_only; @@ -65,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; @@ -88,6 +89,8 @@ extern int backup_dir_len; extern int basis_dir_cnt; extern int default_af_hint; extern int stdout_format_has_i; +extern int trust_sender_filter; +extern int trust_sender_args; extern struct stats stats; extern char *stdout_format; extern char *logfile_format; @@ -103,7 +106,7 @@ extern char curr_dir[MAXPATHLEN]; extern char backup_dir_buf[MAXPATHLEN]; extern char *basis_dir[MAX_BASIS_DIRS+1]; extern struct file_list *first_flist; -extern filter_rule_list daemon_filter_list; +extern filter_rule_list daemon_filter_list, implied_filter_list; uid_t our_uid; gid_t our_gid; @@ -467,38 +470,33 @@ static void output_summary(void) **/ static void show_malloc_stats(void) { -#ifdef HAVE_MALLINFO - struct mallinfo mi; - - mi = mallinfo(); +#ifdef MEM_ALLOC_INFO + struct MEM_ALLOC_INFO mi = MEM_ALLOC_INFO(); /* mallinfo or mallinfo2 */ rprintf(FCLIENT, "\n"); rprintf(FINFO, RSYNC_NAME "[%d] (%s%s%s) heap statistics:\n", (int)getpid(), am_server ? "server " : "", am_daemon ? "daemon " : "", who_am_i()); - rprintf(FINFO, " arena: %10ld (bytes from sbrk)\n", - (long)mi.arena); - rprintf(FINFO, " ordblks: %10ld (chunks not in use)\n", - (long)mi.ordblks); - rprintf(FINFO, " smblks: %10ld\n", - (long)mi.smblks); - rprintf(FINFO, " hblks: %10ld (chunks from mmap)\n", - (long)mi.hblks); - rprintf(FINFO, " hblkhd: %10ld (bytes from mmap)\n", - (long)mi.hblkhd); - rprintf(FINFO, " allmem: %10ld (bytes from sbrk + mmap)\n", - (long)mi.arena + mi.hblkhd); - rprintf(FINFO, " usmblks: %10ld\n", - (long)mi.usmblks); - rprintf(FINFO, " fsmblks: %10ld\n", - (long)mi.fsmblks); - rprintf(FINFO, " uordblks: %10ld (bytes used)\n", - (long)mi.uordblks); - rprintf(FINFO, " fordblks: %10ld (bytes free)\n", - (long)mi.fordblks); - rprintf(FINFO, " keepcost: %10ld (bytes in releasable chunk)\n", - (long)mi.keepcost); -#endif /* HAVE_MALLINFO */ + +#define PRINT_ALLOC_NUM(title, descr, num) \ + rprintf(FINFO, " %-11s%10" SIZE_T_FMT_MOD "d (" descr ")\n", \ + title ":", (SIZE_T_FMT_CAST)(num)); + + PRINT_ALLOC_NUM("arena", "bytes from sbrk", mi.arena); + PRINT_ALLOC_NUM("ordblks", "chunks not in use", mi.ordblks); + PRINT_ALLOC_NUM("smblks", "free fastbin blocks", mi.smblks); + PRINT_ALLOC_NUM("hblks", "chunks from mmap", mi.hblks); + PRINT_ALLOC_NUM("hblkhd", "bytes from mmap", mi.hblkhd); + PRINT_ALLOC_NUM("allmem", "bytes from sbrk + mmap", mi.arena + mi.hblkhd); + PRINT_ALLOC_NUM("usmblks", "always 0", mi.usmblks); + PRINT_ALLOC_NUM("fsmblks", "bytes in freed fastbin blocks", mi.fsmblks); + PRINT_ALLOC_NUM("uordblks", "bytes used", mi.uordblks); + PRINT_ALLOC_NUM("fordblks", "bytes free", mi.fordblks); + PRINT_ALLOC_NUM("keepcost", "bytes in releasable chunk", mi.keepcost); + +#undef PRINT_ALLOC_NUM + +#endif /* MEM_ALLOC_INFO */ } @@ -612,11 +610,7 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n"); exit_cleanup(RERR_SYNTAX); } - if (**remote_argv == '-') { - if (asprintf(args + argc++, "./%s", *remote_argv++) < 0) - out_of_memory("do_cmd"); - } else - args[argc++] = *remote_argv++; + args[argc++] = safe_arg(NULL, *remote_argv++); remote_argc--; } } @@ -668,6 +662,16 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in return pid; } +/* Older versions turn an empty string as a reference to the current directory. + * We now treat this as an error unless --old-args was used. */ +static char *dot_dir_or_error() +{ + if (old_style_args || am_server) + return "."; + rprintf(FERROR, "Empty destination arg specified (use \".\" or see --old-args).\n"); + exit_cleanup(RERR_SYNTAX); +} + /* The receiving side operates in one of two modes: * * 1. it receives any number of files into a destination directory, @@ -695,9 +699,8 @@ static char *get_local_name(struct file_list *flist, char *dest_path) if (!dest_path || list_only) return NULL; - /* Treat an empty string as a copy into the current directory. */ if (!*dest_path) - dest_path = "."; + dest_path = dot_dir_or_error(); if (daemon_filter_list.head) { char *slash = strrchr(dest_path, '/'); @@ -1084,6 +1087,7 @@ static int do_recv(int f_in, int f_out, char *local_name) } am_generator = 1; + implied_filter_list.head = implied_filter_list.tail = NULL; flist_receiving_enabled = True; io_end_multiplex_in(MPLX_SWITCHING); @@ -1379,15 +1383,6 @@ int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[]) return MAX(exit_code, exit_code2); } -static void dup_argv(char *argv[]) -{ - int i; - - for (i = 0; argv[i]; i++) - argv[i] = strdup(argv[i]); -} - - /* Start a client for either type of remote connection. Work out * whether the arguments request a remote shell or rsyncd connection, * and call the appropriate connection function, then run_client. @@ -1403,10 +1398,6 @@ static int start_client(int argc, char *argv[]) int ret; pid_t pid; - /* Don't clobber argv[] so that ps(1) can still show the right - * command line. */ - dup_argv(argv); - if (!read_batch) { /* for read_batch, NO source is specified */ char *path = check_for_hostspec(argv[0], &shell_machine, &rsync_port); if (path) { /* source is remote */ @@ -1439,6 +1430,8 @@ static int start_client(int argc, char *argv[]) if (argc > 1) { p = argv[--argc]; + if (!*p) + p = dot_dir_or_error(); remote_argv = argv + argc; } else { static char *dotarg[1] = { "." }; @@ -1479,6 +1472,12 @@ static int start_client(int argc, char *argv[]) rsync_port = 0; } + /* A local transfer doesn't unbackslash anything, so leave the args alone. */ + if (local_server) { + old_style_args = 2; + trust_sender_args = trust_sender_filter = 1; + } + if (!rsync_port && remote_argc && !**remote_argv) /* Turn an empty arg into a dot dir. */ *remote_argv = "."; @@ -1504,6 +1503,8 @@ static int start_client(int argc, char *argv[]) char *dummy_host; int dummy_port = rsync_port; int i; + if (filesfrom_fd < 0) + add_implied_include(remote_argv[0], daemon_connection); /* For remote source, any extra source args must have either * the same hostname or an empty hostname. */ for (i = 1; i < remote_argc; i++) { @@ -1527,6 +1528,7 @@ static int start_client(int argc, char *argv[]) if (!rsync_port && !*arg) /* Turn an empty arg into a dot dir. */ arg = "."; remote_argv[i] = arg; + add_implied_include(arg, daemon_connection); } } @@ -1743,6 +1745,17 @@ int main(int argc,char *argv[]) unset_env_var("DISPLAY"); +#if defined USE_OPENSSL && defined SET_OPENSSL_CONF +#define TO_STR2(x) #x +#define TO_STR(x) TO_STR2(x) + /* ./configure --with-openssl-conf=/etc/ssl/openssl-rsync.cnf + * defines SET_OPENSSL_CONF as that unquoted pathname. */ + if (!getenv("OPENSSL_CONF")) /* Don't override it if it's already set. */ + set_env_str("OPENSSL_CONF", TO_STR(SET_OPENSSL_CONF)); +#undef TO_STR +#undef TO_STR2 +#endif + memset(&stats, 0, sizeof(stats)); /* Even a non-daemon runs needs the default config values to be set, e.g. @@ -1761,6 +1774,7 @@ int main(int argc,char *argv[]) #if defined CONFIG_LOCALE && defined HAVE_SETLOCALE setlocale(LC_CTYPE, ""); + setlocale(LC_NUMERIC, ""); #endif if (!parse_arguments(&argc, (const char ***) &argv)) { diff --git a/match.c b/match.c index 9d5c92594..dfd6af2c9 100644 --- a/match.c +++ b/match.c @@ -3,7 +3,7 @@ * * Copyright (C) 1996 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras - * Copyright (C) 2003-2020 Wayne Davison + * Copyright (C) 2003-2023 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,7 +24,9 @@ extern int checksum_seed; extern int append_mode; -extern int xfersum_type; + +extern struct name_num_item *xfer_sum_nni; +extern int xfer_sum_len; int updating_basis_file; char sender_file_sum[MAX_DIGEST_LEN]; @@ -140,11 +142,14 @@ static void hash_search(int f,struct sum_struct *s, { OFF_T offset, aligned_offset, end; int32 k, want_i, aligned_i, backup; - char sum2[SUM_LENGTH]; + char sum2[MAX_DIGEST_LEN]; uint32 s1, s2, sum; 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; @@ -230,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; } @@ -250,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; } @@ -269,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. */ @@ -288,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; @@ -356,15 +361,13 @@ static void hash_search(int f,struct sum_struct *s, **/ void match_sums(int f, struct sum_struct *s, struct map_struct *buf, OFF_T len) { - int sum_len; - last_match = 0; false_alarms = 0; hash_hits = 0; matches = 0; data_transfer = 0; - sum_init(xfersum_type, checksum_seed); + sum_init(xfer_sum_nni, checksum_seed); if (append_mode > 0) { if (append_mode == 2) { @@ -405,22 +408,22 @@ void match_sums(int f, struct sum_struct *s, struct map_struct *buf, OFF_T len) matched(f, s, buf, len, -1); } - sum_len = sum_end(sender_file_sum); + sum_end(sender_file_sum); /* If we had a read error, send a bad checksum. We use all bits * off as long as the checksum doesn't happen to be that, in * which case we turn the last 0 bit into a 1. */ if (buf && buf->status != 0) { int i; - for (i = 0; i < sum_len && sender_file_sum[i] == 0; i++) {} - memset(sender_file_sum, 0, sum_len); - if (i == sum_len) + for (i = 0; i < xfer_sum_len && sender_file_sum[i] == 0; i++) {} + memset(sender_file_sum, 0, xfer_sum_len); + if (i == xfer_sum_len) sender_file_sum[i-1]++; } if (DEBUG_GTE(DELTASUM, 2)) rprintf(FINFO,"sending file_sum\n"); - write_buf(f, sender_file_sum, sum_len); + write_buf(f, sender_file_sum, xfer_sum_len); if (DEBUG_GTE(DELTASUM, 2)) { rprintf(FINFO, "false_alarms=%d hash_hits=%d matches=%d\n", diff --git a/maybe-make-man b/maybe-make-man index b7f0a9f1a..c7af73934 100755 --- a/maybe-make-man +++ b/maybe-make-man @@ -1,22 +1,18 @@ #!/bin/sh -if [ x"$2" = x ]; then - echo "Usage: $0 SRC_DIR NAME.NUM.md" 1>&2 +if [ $# != 1 ]; then + echo "Usage: $0 NAME.NUM.md" 1>&2 exit 1 fi -srcdir="$1" -inname="$2" +inname="$1" +srcdir=`dirname "$0"` flagfile="$srcdir/.md2man-works" - -if [ ! -d "$srcdir" ]; then - echo "The specified SRC_DIR is not a directory: $srcdir" 1>&2 - exit 1 -fi +force_flagfile="$srcdir/.md2man-force" if [ ! -f "$flagfile" ]; then # We test our smallest manpage just to see if the python setup works. - if "$srcdir/md2man" --test "$srcdir/rsync-ssl.1.md" >/dev/null 2>&1; then + if "$srcdir/md-convert" --test "$srcdir/rsync-ssl.1.md" >/dev/null 2>&1; then touch $flagfile else outname=`echo "$inname" | sed 's/\.md$//'` @@ -37,4 +33,10 @@ if [ ! -f "$flagfile" ]; then fi fi -"$srcdir/md2man" "$srcdir/$inname" +if [ -f "$force_flagfile" ]; then + opt='--force-link-text' +else + opt='' +fi + +"$srcdir/md-convert" $opt "$srcdir/$inname" diff --git a/md-convert b/md-convert new file mode 100755 index 000000000..fb2bc993f --- /dev/null +++ b/md-convert @@ -0,0 +1,664 @@ +#!/usr/bin/env python3 + +# This script transforms markdown files into html and (optionally) nroff. The +# output files are written into the current directory named for the input file +# without the .md suffix and either the .html suffix or no suffix. +# +# If the input .md file has a section number at the end of the name (e.g., +# rsync.1.md) a nroff file is also output (PROJ.NUM.md -> PROJ.NUM). +# +# The markdown input format has one extra extension: if a numbered list starts +# at 0, it is turned into a description list. The dl's dt tag is taken from the +# contents of the first tag inside the li, which is usually a p, code, or +# strong tag. +# +# The cmarkgfm or commonmark lib is used to transforms the input file into +# html. Then, the html.parser is used as a state machine that lets us tweak +# the html and (optionally) output nroff data based on the html tags. +# +# If the string @USE_GFM_PARSER@ exists in the file, the string is removed and +# a github-flavored-markup parser is used to parse the file. +# +# The man-page .md files also get the vars @VERSION@, @BINDIR@, and @LIBDIR@ +# substituted. Some of these values depend on the Makefile $(prefix) (see the +# generated Makefile). If the maintainer wants to build files for /usr/local +# while creating release-ready man-page files for /usr, use the environment to +# set RSYNC_OVERRIDE_PREFIX=/usr. + +# Copyright (C) 2020 - 2021 Wayne Davison +# +# This program is freely redistributable. + +import os, sys, re, argparse, subprocess, time +from html.parser import HTMLParser + +VALID_PAGES = 'README INSTALL COPYING rsync.1 rrsync.1 rsync-ssl.1 rsyncd.conf.5'.split() + +CONSUMES_TXT = set('h1 h2 h3 p li pre'.split()) + +HTML_START = """\ + +%TITLE% + + + + +""" + +TABLE_STYLE = """\ +table { + border-color: grey; + border-spacing: 0; +} +tr { + border-top: 1px solid grey; +} +tr:nth-child(2n) { + background-color: #f6f8fa; +} +th, td { + border: 1px solid #dfe2e5; + text-align: center; + padding-left: 1em; + padding-right: 1em; +} +""" + +MAN_HTML_END = """\ +

%s

+""" + +HTML_END = """\ + +""" + +MAN_START = r""" +.TH "%s" "%s" "%s" "%s" "User Commands" +.\" prefix=%s +""".lstrip() + +MAN_END = """\ +""" + +NORM_FONT = ('\1', r"\fP") +BOLD_FONT = ('\2', r"\fB") +UNDR_FONT = ('\3', r"\fI") +NBR_DASH = ('\4', r"\-") +NBR_SPACE = ('\xa0', r"\ ") + +FILENAME_RE = re.compile(r'^(?P(?P.+/)?(?P(?P[^/]+?)(\.(?P\d+))?)\.md)$') +ASSIGNMENT_RE = re.compile(r'^(\w+)=(.+)') +VER_RE = re.compile(r'^#define\s+RSYNC_VERSION\s+"(\d.+?)"', re.M) +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') +CODE_BLOCK_RE = re.compile(r'[%s]([^=%s]+)[=%s]' % (BOLD_FONT[0], NORM_FONT[0], NORM_FONT[0])) +NBR_DASH_RE = re.compile(r'[%s]' % NBR_DASH[0]) +INVALID_TARGET_CHARS_RE = re.compile(r'[^-A-Za-z0-9._]') +INVALID_START_CHAR_RE = re.compile(r'^([^A-Za-z0-9])') +MANIFY_LINESTART_RE = re.compile(r"^(['.])", flags=re.M) + +md_parser = None +env_subs = { } + +warning_count = 0 + +def main(): + for mdfn in args.mdfiles: + parse_md_file(mdfn) + + if args.test: + print("The test was successful.") + + +def parse_md_file(mdfn): + fi = FILENAME_RE.match(mdfn) + if not fi: + die('Failed to parse a md input file name:', mdfn) + fi = argparse.Namespace(**fi.groupdict()) + fi.want_manpage = not not fi.sect + if fi.want_manpage: + fi.title = fi.prog + '(' + fi.sect + ') manpage' + else: + fi.title = fi.prog + ' for rsync' + + if fi.want_manpage: + if not env_subs: + find_man_substitutions() + prog_ver = 'rsync ' + env_subs['VERSION'] + if fi.prog != 'rsync': + prog_ver = fi.prog + ' from ' + prog_ver + fi.man_headings = (fi.prog, fi.sect, env_subs['date'], prog_ver, env_subs['prefix']) + + with open(mdfn, 'r', encoding='utf-8') as fh: + txt = fh.read() + + use_gfm_parser = '@USE_GFM_PARSER@' in txt + if use_gfm_parser: + txt = txt.replace('@USE_GFM_PARSER@', '') + + if fi.want_manpage: + txt = (txt.replace('@VERSION@', env_subs['VERSION']) + .replace('@BINDIR@', env_subs['bindir']) + .replace('@LIBDIR@', env_subs['libdir'])) + + if use_gfm_parser: + if not gfm_parser: + die('Input file requires cmarkgfm parser:', mdfn) + fi.html_in = gfm_parser(txt) + else: + fi.html_in = md_parser(txt) + txt = None + + TransformHtml(fi) + + if args.test: + return + + output_list = [ (fi.name + '.html', fi.html_out) ] + if fi.want_manpage: + output_list += [ (fi.name, fi.man_out) ] + for fn, txt in output_list: + if args.dest and args.dest != '.': + fn = os.path.join(args.dest, fn) + if os.path.lexists(fn): + os.unlink(fn) + print("Wrote:", fn) + with open(fn, 'w', encoding='utf-8') as fh: + fh.write(txt) + + +def find_man_substitutions(): + srcdir = os.path.dirname(sys.argv[0]) + '/' + mtime = 0 + + git_dir = srcdir + '.git' + if os.path.lexists(git_dir): + mtime = int(subprocess.check_output(['git', '--git-dir', git_dir, 'log', '-1', '--format=%at'])) + + # Allow "prefix" to be overridden via the environment: + env_subs['prefix'] = os.environ.get('RSYNC_OVERRIDE_PREFIX', None) + + if args.test: + env_subs['VERSION'] = '1.0.0' + env_subs['bindir'] = '/usr/bin' + env_subs['libdir'] = '/usr/lib/rsync' + tz_offset = 0 + else: + for fn in (srcdir + 'version.h', 'Makefile'): + try: + st = os.lstat(fn) + except OSError: + die('Failed to find', srcdir + fn) + if not mtime: + mtime = st.st_mtime + + with open(srcdir + 'version.h', 'r', encoding='utf-8') as fh: + txt = fh.read() + m = VER_RE.search(txt) + env_subs['VERSION'] = m.group(1) + m = TZ_RE.search(txt) # the tzdata lib may not be installed, so we use a simple hour offset + tz_offset = float(m.group(1)) * 60 * 60 + + with open('Makefile', 'r', encoding='utf-8') as fh: + for line in fh: + m = ASSIGNMENT_RE.match(line) + if not m: + continue + var, val = (m.group(1), m.group(2)) + if var == 'prefix' and env_subs[var] is not None: + continue + while VAR_REF_RE.search(val): + val = VAR_REF_RE.sub(lambda m: env_subs[m.group(1)], val) + env_subs[var] = val + if var == 'srcdir': + break + + env_subs['date'] = time.strftime('%d %b %Y', time.gmtime(mtime + tz_offset)).lstrip('0') + + +def html_via_commonmark(txt): + return commonmark.HtmlRenderer().render(commonmark.Parser().parse(txt)) + + +class TransformHtml(HTMLParser): + def __init__(self, fi): + HTMLParser.__init__(self, convert_charrefs=True) + + self.fn = fi.fn + + st = self.state = argparse.Namespace( + list_state = [ ], + p_macro = ".P\n", + at_first_tag_in_li = False, + at_first_tag_in_dd = False, + dt_from = None, + in_pre = False, + in_code = False, + html_out = [ HTML_START.replace('%TITLE%', fi.title) ], + man_out = [ ], + txt = '', + want_manpage = fi.want_manpage, + created_hashtags = set(), + derived_hashtags = set(), + referenced_hashtags = set(), + bad_hashtags = set(), + latest_targets = [ ], + opt_prefix = 'opt', + a_href = None, + a_href_external = False, + a_txt_start = None, + after_a_tag = False, + target_suf = '', + ) + + if st.want_manpage: + st.man_out.append(MAN_START % fi.man_headings) + + if '' in fi.html_in: + st.html_out[0] = st.html_out[0].replace('', TABLE_STYLE + '') + + self.feed(fi.html_in) + fi.html_in = None + + if st.want_manpage: + st.html_out.append(MAN_HTML_END % env_subs['date']) + st.html_out.append(HTML_END) + st.man_out.append(MAN_END) + + fi.html_out = ''.join(st.html_out) + st.html_out = None + + fi.man_out = ''.join(st.man_out) + st.man_out = None + + for tgt, txt in st.derived_hashtags: + derived = txt2target(txt, tgt) + if derived not in st.created_hashtags: + txt = BIN_CHARS_RE.sub('', txt.replace(NBR_DASH[0], '-').replace(NBR_SPACE[0], ' ')) + warn('Unknown derived hashtag link in', self.fn, 'based on:', (tgt, txt)) + + for bad in st.bad_hashtags: + if bad in st.created_hashtags: + warn('Missing "#" in hashtag link in', self.fn + ':', bad) + else: + warn('Unknown non-hashtag link in', self.fn + ':', bad) + + for bad in st.referenced_hashtags - st.created_hashtags: + warn('Unknown hashtag link in', self.fn + ':', '#' + bad) + + def handle_UE(self): + st = self.state + if st.txt.startswith(('.', ',', '!', '?', ';', ':')): + st.man_out[-1] = ".UE " + st.txt[0] + "\n" + st.txt = st.txt[1:] + st.after_a_tag = False + + def handle_starttag(self, tag, attrs_list): + st = self.state + if args.debug: + self.output_debug('START', (tag, attrs_list)) + if st.at_first_tag_in_li: + if st.list_state[-1] == 'dl': + st.dt_from = tag + if tag == 'p': + tag = 'dt' + else: + st.html_out.append('
') + elif tag == 'p': + st.at_first_tag_in_dd = True # Kluge to suppress a .P at the start of an li. + st.at_first_tag_in_li = False + if tag == 'p': + if not st.at_first_tag_in_dd: + st.man_out.append(st.p_macro) + elif tag == 'li': + st.at_first_tag_in_li = True + lstate = st.list_state[-1] + if lstate == 'dl': + return + if lstate == 'o': + st.man_out.append(".IP o\n") + else: + st.man_out.append(".IP " + str(lstate) + ".\n") + st.list_state[-1] += 1 + elif tag == 'blockquote': + st.man_out.append(".RS 4\n") + elif tag == 'pre': + st.in_pre = True + st.man_out.append(st.p_macro + ".nf\n") + elif tag == 'code' and not st.in_pre: + st.in_code = True + st.txt += BOLD_FONT[0] + elif tag == 'strong' or tag == 'b': + st.txt += BOLD_FONT[0] + elif tag == 'em' or tag == 'i': + if st.want_manpage: + tag = 'u' # Change it into underline to be more like the manpage + st.txt += UNDR_FONT[0] + elif tag == 'ol': + start = 1 + for var, val in attrs_list: + if var == 'start': + start = int(val) # We only support integers. + break + if st.list_state: + st.man_out.append(".RS\n") + if start == 0: + tag = 'dl' + attrs_list = [ ] + st.list_state.append('dl') + else: + st.list_state.append(start) + st.man_out.append(st.p_macro) + st.p_macro = ".IP\n" + elif tag == 'ul': + st.man_out.append(st.p_macro) + if st.list_state: + st.man_out.append(".RS\n") + st.p_macro = ".IP\n" + st.list_state.append('o') + elif tag == 'hr': + st.man_out.append(".l\n") + st.html_out.append("
") + return + elif tag == 'a': + st.a_href = None + for var, val in attrs_list: + if var == 'href': + if val.startswith(('https://', 'http://', 'mailto:', 'ftp:')): + if st.after_a_tag: + self.handle_UE() + st.man_out.append(manify(st.txt.strip()) + "\n") + st.man_out.append(".UR " + val + "\n") + st.txt = '' + st.a_href = val + st.a_href_external = True + elif '#' in val: + pg, tgt = val.split('#', 1) + if pg and pg not in VALID_PAGES or '#' in tgt: + st.bad_hashtags.add(val) + elif tgt in ('', 'opt', 'dopt'): + st.a_href = val + st.a_href_external = False + elif pg == '': + st.referenced_hashtags.add(tgt) + if tgt in st.latest_targets: + warn('Found link to the current section in', self.fn + ':', val) + elif val not in VALID_PAGES: + st.bad_hashtags.add(val) + st.a_txt_start = len(st.txt) + st.html_out.append('<' + tag + ''.join(' ' + var + '="' + htmlify(val) + '"' for var, val in attrs_list) + '>') + st.at_first_tag_in_dd = False + + + def handle_endtag(self, tag): + st = self.state + if args.debug: + self.output_debug('END', (tag,)) + if st.after_a_tag: + self.handle_UE() + if tag in CONSUMES_TXT or st.dt_from == tag: + txt = st.txt.strip() + st.txt = '' + else: + txt = None + add_to_txt = None + if tag == 'h1': + tgt = txt + target_suf = '' + if tgt.startswith('NEWS for '): + m = VERSION_RE.search(tgt) + if m: + tgt = m.group(1) + st.target_suf = '-' + tgt + self.add_targets(tag, tgt) + elif tag == 'h2': + st.man_out.append(st.p_macro + '.SH "' + manify(txt) + '"\n') + self.add_targets(tag, txt, st.target_suf) + st.opt_prefix = 'dopt' if txt == 'DAEMON OPTIONS' else 'opt' + elif tag == 'h3': + st.man_out.append(st.p_macro + '.SS "' + manify(txt) + '"\n') + self.add_targets(tag, txt, st.target_suf) + elif tag == 'p': + if st.dt_from == 'p': + tag = 'dt' + st.man_out.append('.IP "' + manify(txt) + '"\n') + if txt.startswith(BOLD_FONT[0]): + self.add_targets(tag, txt) + st.dt_from = None + elif txt != '': + st.man_out.append(manify(txt) + "\n") + elif tag == 'li': + if st.list_state[-1] == 'dl': + if st.at_first_tag_in_li: + die("Invalid 0. -> td translation") + tag = 'dd' + if txt != '': + st.man_out.append(manify(txt) + "\n") + st.at_first_tag_in_li = False + elif tag == 'blockquote': + st.man_out.append(".RE\n") + elif tag == 'pre': + st.in_pre = False + st.man_out.append(manify(txt) + "\n.fi\n") + elif (tag == 'code' and not st.in_pre): + st.in_code = False + add_to_txt = NORM_FONT[0] + elif tag == 'strong' or tag == 'b': + add_to_txt = NORM_FONT[0] + elif tag == 'em' or tag == 'i': + if st.want_manpage: + tag = 'u' # Change it into underline to be more like the manpage + add_to_txt = NORM_FONT[0] + elif tag == 'ol' or tag == 'ul': + if st.list_state.pop() == 'dl': + tag = 'dl' + if st.list_state: + st.man_out.append(".RE\n") + else: + st.p_macro = ".P\n" + st.at_first_tag_in_dd = False + elif tag == 'hr': + return + elif tag == 'a': + if st.a_href_external: + st.txt = st.txt.strip() + if args.force_link_text or st.a_href != st.txt: + st.man_out.append(manify(st.txt) + "\n") + st.man_out.append(".UE\n") # This might get replaced with a punctuation version in handle_UE() + st.after_a_tag = True + st.a_href_external = False + st.txt = '' + elif st.a_href: + atxt = st.txt[st.a_txt_start:] + find = 'href="' + st.a_href + '"' + for j in range(len(st.html_out)-1, 0, -1): + if find in st.html_out[j]: + pg, tgt = st.a_href.split('#', 1) + derived = txt2target(atxt, tgt) + if pg == '': + if derived in st.latest_targets: + warn('Found link to the current section in', self.fn + ':', st.a_href) + st.derived_hashtags.add((tgt, atxt)) + st.html_out[j] = st.html_out[j].replace(find, 'href="' + pg + '#' + derived + '"') + break + else: + die('INTERNAL ERROR: failed to find href in html data:', find) + st.html_out.append('') + if add_to_txt: + if txt is None: + st.txt += add_to_txt + else: + txt += add_to_txt + if st.dt_from == tag: + st.man_out.append('.IP "' + manify(txt) + '"\n') + st.html_out.append('
') + st.at_first_tag_in_dd = True + st.dt_from = None + elif tag == 'dt': + st.html_out.append('
') + st.at_first_tag_in_dd = True + + + def handle_data(self, txt): + st = self.state + if '](' in txt: + warn('Malformed link in', self.fn + ':', txt) + if args.debug: + self.output_debug('DATA', (txt,)) + 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) + if st.in_code: + txt = WHITESPACE_RE.sub(NBR_SPACE[0], txt) + html = html.replace(NBR_DASH[0], '-').replace(NBR_SPACE[0], ' ') # is non-breaking in CSS + st.html_out.append(html.replace(NBR_SPACE[0], ' ').replace(NBR_DASH[0], '-⁠')) + st.txt += txt + + + def add_targets(self, tag, txt, suf=None): + st = self.state + tag = '<' + tag + '>' + targets = CODE_BLOCK_RE.findall(txt) + if not targets: + targets = [ txt ] + tag_pos = 0 + for txt in targets: + txt = txt2target(txt, st.opt_prefix) + if not txt: + continue + if suf: + txt += suf + if txt in st.created_hashtags: + for j in range(2, 1000): + chk = txt + '-' + str(j) + if chk not in st.created_hashtags: + print('Made link target unique:', chk) + txt = chk + break + if tag_pos == 0: + tag_pos -= 1 + while st.html_out[tag_pos] != tag: + tag_pos -= 1 + st.html_out[tag_pos] = tag[:-1] + ' id="' + txt + '">' + st.html_out.append('') + tag_pos -= 1 # take into account the append + else: + st.html_out[tag_pos] = '' + st.html_out[tag_pos] + st.created_hashtags.add(txt) + st.latest_targets = targets + + + def output_debug(self, event, extra): + import pprint + st = self.state + if args.debug < 2: + st = argparse.Namespace(**vars(st)) + if len(st.html_out) > 2: + st.html_out = ['...'] + st.html_out[-2:] + if len(st.man_out) > 2: + st.man_out = ['...'] + st.man_out[-2:] + print(event, extra) + pprint.PrettyPrinter(indent=2).pprint(vars(st)) + + +def txt2target(txt, opt_prefix): + txt = txt.strip().rstrip(':') + m = CODE_BLOCK_RE.search(txt) + if m: + txt = m.group(1) + txt = NBR_DASH_RE.sub('-', txt) + txt = BIN_CHARS_RE.sub('', txt) + txt = INVALID_TARGET_CHARS_RE.sub('_', txt) + if opt_prefix and txt.startswith('-'): + txt = opt_prefix + txt + else: + txt = INVALID_START_CHAR_RE.sub(r't\1', txt) + return txt + + +def manify(txt): + return MANIFY_LINESTART_RE.sub(r'\&\1', txt.replace('\\', '\\\\') + .replace(NBR_SPACE[0], NBR_SPACE[1]) + .replace(NBR_DASH[0], NBR_DASH[1]) + .replace(NORM_FONT[0], NORM_FONT[1]) + .replace(BOLD_FONT[0], BOLD_FONT[1]) + .replace(UNDR_FONT[0], UNDR_FONT[1])) + + +def htmlify(txt): + return txt.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') + + +def warn(*msg): + print(*msg, file=sys.stderr) + global warning_count + warning_count += 1 + + +def die(*msg): + warn(*msg) + sys.exit(1) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Convert markdown into html and (optionally) nroff. Each input filename must have a .md suffix, which is changed to .html for the output filename. If the input filename ends with .num.md (e.g. foo.1.md) then a nroff file is also output with the input filename's .md suffix removed (e.g. foo.1).", add_help=False) + parser.add_argument('--test', action='store_true', help="Just test the parsing without outputting any files.") + parser.add_argument('--dest', metavar='DIR', help="Create files in DIR instead of the current directory.") + parser.add_argument('--force-link-text', action='store_true', help="Don't remove the link text if it matches the link href. Useful when nroff doesn't understand .UR and .UE.") + parser.add_argument('--debug', '-D', action='count', default=0, help='Output copious info on the html parsing. Repeat for even more.') + parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.") + parser.add_argument("mdfiles", metavar='FILE.md', nargs='+', help="One or more .md files to convert.") + args = parser.parse_args() + + try: + import cmarkgfm + md_parser = cmarkgfm.markdown_to_html + gfm_parser = cmarkgfm.github_flavored_markdown_to_html + except: + try: + import commonmark + md_parser = html_via_commonmark + except: + die("Failed to find cmarkgfm or commonmark for python3.") + gfm_parser = None + + main() + if warning_count: + sys.exit(1) diff --git a/md2man b/md2man deleted file mode 100755 index fa1d2e824..000000000 --- a/md2man +++ /dev/null @@ -1,393 +0,0 @@ -#!/usr/bin/env python3 - -# This script takes a manpage written in markdown and turns it into an html web -# page and a nroff man page. The input file must have the name of the program -# and the section in this format: NAME.NUM.md. The output files are written -# into the current directory named NAME.NUM.html and NAME.NUM. The input -# format has one extra extension: if a numbered list starts at 0, it is turned -# into a description list. The dl's dt tag is taken from the contents of the -# first tag inside the li, which is usually a p, code, or strong tag. The -# cmarkgfm or commonmark lib is used to transforms the input file into html. -# The html.parser is used as a state machine that both tweaks the html and -# outputs the nroff data based on the html tags. -# -# We normally grab the prefix from the generated Makefile, which is then used -# in the various other grabbed values (see the Makefile for its ${prefix} -# paths). However, the maintainer can choose to override this prefix by -# exporting RSYNC_OVERRIDE_PREFIX=/usr. This allows the man pages to refer to -# /usr paths (and are thus compatible with the release-rsync script) while -# still having the built rsync get installed into /usr/local for local testing. -# -# Copyright (C) 2020 Wayne Davison -# -# This program is freely redistributable. - -import sys, os, re, argparse, subprocess, time -from html.parser import HTMLParser - -CONSUMES_TXT = set('h1 h2 p li pre'.split()) - -HTML_START = """\ - -%s - - - -""" - -HTML_END = """\ -

%s

- -""" - -MAN_START = r""" -.TH "%s" "%s" "%s" "%s" "User Commands" -.\" prefix=%s -""".lstrip() - -MAN_END = """\ -""" - -NORM_FONT = ('\1', r"\fP") -BOLD_FONT = ('\2', r"\fB") -UNDR_FONT = ('\3', r"\fI") -NBR_DASH = ('\4', r"\-") -NBR_SPACE = ('\xa0', r"\ ") - -md_parser = None - -def main(): - fi = re.match(r'^(?P(?P.+/)?(?P(?P[^/]+)\.(?P\d+))\.md)$', args.mdfile) - if not fi: - die('Failed to parse NAME.NUM.md out of input file:', args.mdfile) - fi = argparse.Namespace(**fi.groupdict()) - - if not fi.srcdir: - fi.srcdir = './' - - fi.title = fi.prog + '(' + fi.sect + ') man page' - fi.mtime = 0 - - git_dir = fi.srcdir + '.git' - if os.path.lexists(git_dir): - fi.mtime = int(subprocess.check_output(['git', '--git-dir', git_dir, 'log', '-1', '--format=%at'])) - - env_subs = { 'prefix': os.environ.get('RSYNC_OVERRIDE_PREFIX', None) } - - if args.test: - env_subs['VERSION'] = '1.0.0' - env_subs['bindir'] = '/usr/bin' - env_subs['libdir'] = '/usr/lib/rsync' - else: - for fn in (fi.srcdir + 'version.h', 'Makefile'): - try: - st = os.lstat(fn) - except: - die('Failed to find', fi.srcdir + fn) - if not fi.mtime: - fi.mtime = st.st_mtime - - with open(fi.srcdir + 'version.h', 'r', encoding='utf-8') as fh: - txt = fh.read() - m = re.search(r'"(.+?)"', txt) - env_subs['VERSION'] = m.group(1) - - with open('Makefile', 'r', encoding='utf-8') as fh: - for line in fh: - m = re.match(r'^(\w+)=(.+)', line) - if not m: - continue - var, val = (m.group(1), m.group(2)) - if var == 'prefix' and env_subs[var] is not None: - continue - while re.search(r'\$\{', val): - val = re.sub(r'\$\{(\w+)\}', lambda m: env_subs[m.group(1)], val) - env_subs[var] = val - if var == 'srcdir': - break - - with open(fi.fn, 'r', encoding='utf-8') as fh: - txt = fh.read() - - txt = re.sub(r'@VERSION@', env_subs['VERSION'], txt) - txt = re.sub(r'@BINDIR@', env_subs['bindir'], txt) - txt = re.sub(r'@LIBDIR@', env_subs['libdir'], txt) - - fi.html_in = md_parser(txt) - txt = None - - fi.date = time.strftime('%d %b %Y', time.localtime(fi.mtime)) - fi.man_headings = (fi.prog, fi.sect, fi.date, fi.prog + ' ' + env_subs['VERSION'], env_subs['prefix']) - - HtmlToManPage(fi) - - if args.test: - print("The test was successful.") - return - - for fn, txt in ((fi.name + '.html', fi.html_out), (fi.name, fi.man_out)): - print("Wrote:", fn) - with open(fn, 'w', encoding='utf-8') as fh: - fh.write(txt) - - -def html_via_commonmark(txt): - return commonmark.HtmlRenderer().render(commonmark.Parser().parse(txt)) - - -class HtmlToManPage(HTMLParser): - def __init__(self, fi): - HTMLParser.__init__(self, convert_charrefs=True) - - st = self.state = argparse.Namespace( - list_state = [ ], - p_macro = ".P\n", - at_first_tag_in_li = False, - at_first_tag_in_dd = False, - dt_from = None, - in_pre = False, - in_code = False, - html_out = [ HTML_START % fi.title ], - man_out = [ MAN_START % fi.man_headings ], - txt = '', - ) - - self.feed(fi.html_in) - fi.html_in = None - - st.html_out.append(HTML_END % fi.date) - st.man_out.append(MAN_END) - - fi.html_out = ''.join(st.html_out) - st.html_out = None - - fi.man_out = ''.join(st.man_out) - st.man_out = None - - - def handle_starttag(self, tag, attrs_list): - st = self.state - if args.debug: - self.output_debug('START', (tag, attrs_list)) - if st.at_first_tag_in_li: - if st.list_state[-1] == 'dl': - st.dt_from = tag - if tag == 'p': - tag = 'dt' - else: - st.html_out.append('
') - elif tag == 'p': - st.at_first_tag_in_dd = True # Kluge to suppress a .P at the start of an li. - st.at_first_tag_in_li = False - if tag == 'p': - if not st.at_first_tag_in_dd: - st.man_out.append(st.p_macro) - elif tag == 'li': - st.at_first_tag_in_li = True - lstate = st.list_state[-1] - if lstate == 'dl': - return - if lstate == 'o': - st.man_out.append(".IP o\n") - else: - st.man_out.append(".IP " + str(lstate) + ".\n") - st.list_state[-1] += 1 - elif tag == 'blockquote': - st.man_out.append(".RS 4\n") - elif tag == 'pre': - st.in_pre = True - st.man_out.append(st.p_macro + ".nf\n") - elif tag == 'code' and not st.in_pre: - st.in_code = True - st.txt += BOLD_FONT[0] - elif tag == 'strong' or tag == 'b': - st.txt += BOLD_FONT[0] - elif tag == 'em' or tag == 'i': - tag = 'u' # Change it into underline to be more like the man page - st.txt += UNDR_FONT[0] - elif tag == 'ol': - start = 1 - for var, val in attrs_list: - if var == 'start': - start = int(val) # We only support integers. - break - if st.list_state: - st.man_out.append(".RS\n") - if start == 0: - tag = 'dl' - attrs_list = [ ] - st.list_state.append('dl') - else: - st.list_state.append(start) - st.man_out.append(st.p_macro) - st.p_macro = ".IP\n" - elif tag == 'ul': - st.man_out.append(st.p_macro) - if st.list_state: - st.man_out.append(".RS\n") - st.p_macro = ".IP\n" - st.list_state.append('o') - st.html_out.append('<' + tag + ''.join(' ' + var + '="' + htmlify(val) + '"' for var, val in attrs_list) + '>') - st.at_first_tag_in_dd = False - - - def handle_endtag(self, tag): - st = self.state - if args.debug: - self.output_debug('END', (tag,)) - if tag in CONSUMES_TXT or st.dt_from == tag: - txt = st.txt.strip() - st.txt = '' - else: - txt = None - add_to_txt = None - if tag == 'h1': - st.man_out.append(st.p_macro + '.SH "' + manify(txt) + '"\n') - elif tag == 'h2': - st.man_out.append(st.p_macro + '.SS "' + manify(txt) + '"\n') - elif tag == 'p': - if st.dt_from == 'p': - tag = 'dt' - st.man_out.append('.IP "' + manify(txt) + '"\n') - st.dt_from = None - elif txt != '': - st.man_out.append(manify(txt) + "\n") - elif tag == 'li': - if st.list_state[-1] == 'dl': - if st.at_first_tag_in_li: - die("Invalid 0. -> td translation") - tag = 'dd' - if txt != '': - st.man_out.append(manify(txt) + "\n") - st.at_first_tag_in_li = False - elif tag == 'blockquote': - st.man_out.append(".RE\n") - elif tag == 'pre': - st.in_pre = False - st.man_out.append(manify(txt) + "\n.fi\n") - elif (tag == 'code' and not st.in_pre): - st.in_code = False - add_to_txt = NORM_FONT[0] - elif tag == 'strong' or tag == 'b': - add_to_txt = NORM_FONT[0] - elif tag == 'em' or tag == 'i': - tag = 'u' # Change it into underline to be more like the man page - add_to_txt = NORM_FONT[0] - elif tag == 'ol' or tag == 'ul': - if st.list_state.pop() == 'dl': - tag = 'dl' - if st.list_state: - st.man_out.append(".RE\n") - else: - st.p_macro = ".P\n" - st.at_first_tag_in_dd = False - st.html_out.append('') - if add_to_txt: - if txt is None: - st.txt += add_to_txt - else: - txt += add_to_txt - if st.dt_from == tag: - st.man_out.append('.IP "' + manify(txt) + '"\n') - st.html_out.append('
') - st.at_first_tag_in_dd = True - st.dt_from = None - elif tag == 'dt': - st.html_out.append('
') - st.at_first_tag_in_dd = True - - - def handle_data(self, txt): - st = self.state - if args.debug: - self.output_debug('DATA', (txt,)) - if st.in_pre: - html = htmlify(txt) - else: - txt = re.sub(r'\s--(\s)', NBR_SPACE[0] + r'--\1', txt).replace('--', NBR_DASH[0]*2) - txt = re.sub(r'(^|\W)-', r'\1' + NBR_DASH[0], txt) - html = htmlify(txt) - if st.in_code: - txt = re.sub(r'\s', NBR_SPACE[0], txt) - html = html.replace(NBR_DASH[0], '-').replace(NBR_SPACE[0], ' ') # is non-breaking in CSS - st.html_out.append(html.replace(NBR_SPACE[0], ' ').replace(NBR_DASH[0], '-⁠')) - st.txt += txt - - - def output_debug(self, event, extra): - import pprint - st = self.state - if args.debug < 2: - st = argparse.Namespace(**vars(st)) - if len(st.html_out) > 2: - st.html_out = ['...'] + st.html_out[-2:] - if len(st.man_out) > 2: - st.man_out = ['...'] + st.man_out[-2:] - print(event, extra) - pprint.PrettyPrinter(indent=2).pprint(vars(st)) - - -def manify(txt): - return re.sub(r"^(['.])", r'\&\1', txt.replace('\\', '\\\\') - .replace(NBR_SPACE[0], NBR_SPACE[1]) - .replace(NBR_DASH[0], NBR_DASH[1]) - .replace(NORM_FONT[0], NORM_FONT[1]) - .replace(BOLD_FONT[0], BOLD_FONT[1]) - .replace(UNDR_FONT[0], UNDR_FONT[1]), flags=re.M) - - -def htmlify(txt): - return txt.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') - - -def warn(*msg): - print(*msg, file=sys.stderr) - - -def die(*msg): - warn(*msg) - sys.exit(1) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Transform a NAME.NUM.md markdown file into a NAME.NUM.html web page & a NAME.NUM man page.', add_help=False) - parser.add_argument('--test', action='store_true', help='Test if we can parse the input w/o updating any files.') - parser.add_argument('--debug', '-D', action='count', default=0, help='Output copious info on the html parsing. Repeat for even more.') - parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.") - parser.add_argument('mdfile', help="The NAME.NUM.md file to parse.") - args = parser.parse_args() - - try: - import cmarkgfm - md_parser = cmarkgfm.markdown_to_html - except: - try: - import commonmark - md_parser = html_via_commonmark - except: - die("Failed to find cmarkgfm or commonmark for python3.") - - main() diff --git a/md2man b/md2man new file mode 120000 index 000000000..5d1a8fcd9 --- /dev/null +++ b/md2man @@ -0,0 +1 @@ +md-convert \ No newline at end of file diff --git a/mkgitver b/mkgitver index 49aa150be..0102b0894 100755 --- a/mkgitver +++ b/mkgitver @@ -1,14 +1,16 @@ #!/bin/sh srcdir=`dirname $0` -gitver=`git describe --abbrev=8 2>/dev/null` if [ ! -f git-version.h ]; then touch git-version.h fi -case "$gitver" in - *.*) +if test -d "$srcdir/.git" || test -f "$srcdir/.git"; then + gitver=`git describe --abbrev=8 2>/dev/null` + # NOTE: I'm avoiding "|" in sed since I'm not sure if sed -r is portable and "\|" fails on some OSes. + verchk=`echo "$gitver-" | sed -n '/^v3\.[0-9][0-9]*\.[0-9][0-9]*\(pre[0-9]*\)*-/p'` + if [ -n "$verchk" ]; then echo "#define RSYNC_GITVER \"$gitver\"" >git-version.h.new if ! diff git-version.h.new git-version.h >/dev/null; then echo "Updating git-version.h" @@ -16,5 +18,5 @@ case "$gitver" in else rm git-version.h.new fi - ;; -esac + fi +fi diff --git a/options.c b/options.c index 3f0354624..578507c6e 100644 --- a/options.c +++ b/options.c @@ -3,7 +3,7 @@ * * Copyright (C) 1998-2001 Andrew Tridgell * Copyright (C) 2000, 2001, 2002 Martin Pool - * Copyright (C) 2002-2020 Wayne Davison + * Copyright (C) 2002-2023 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,6 +27,8 @@ extern int module_id; extern int local_server; extern int sanitize_paths; +extern int trust_sender_args; +extern int trust_sender_filter; extern unsigned int module_dirlen; extern filter_rule_list filter_list; extern filter_rule_list daemon_filter_list; @@ -47,6 +49,7 @@ int append_mode = 0; int keep_dirlinks = 0; int copy_dirlinks = 0; int copy_links = 0; +int copy_devices = 0; int write_devices = 0; int preserve_links = 0; int preserve_hard_links = 0; @@ -63,6 +66,7 @@ int preserve_atimes = 0; int preserve_crtimes = 0; int omit_dir_times = 0; int omit_link_times = 0; +int trust_sender = 0; int update_only = 0; int open_noatime = 0; int cvs_exclude = 0; @@ -91,6 +95,7 @@ int implied_dirs = 1; int missing_args = 0; /* 0 = FERROR_XFER, 1 = ignore, 2 = delete */ int numeric_ids = 0; int msgs2stderr = 2; /* Default: send errors to stderr for local & remote-shell transfers */ +int saw_stderr_opt = 0; int allow_8bit_chars = 0; int force_delete = 0; int io_timeout = 0; @@ -101,6 +106,7 @@ int filesfrom_fd = -1; char *filesfrom_host = NULL; int eol_nulls = 0; int protect_args = -1; +int old_style_args = -1; int human_readable = 1; int recurse = 0; int mkpath_dest_arg = 0; @@ -194,6 +200,7 @@ int remote_option_cnt = 0; const char **remote_options = NULL; const char *checksum_choice = NULL; const char *compress_choice = NULL; +static const char *empty_argv[1]; int quiet = 0; int output_motd = 1; @@ -230,7 +237,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] = { - /*0*/ NULL, + /*0*/ "NONREG", /*1*/ "COPY,DEL,FLIST,MISC,NAME,STATS,SYMSAFE", /*2*/ "BACKUP,MISC2,MOUNT,NAME2,REMOVE,SKIP", }; @@ -268,9 +275,10 @@ static struct output_struct info_words[COUNT_INFO+1] = { INFO_WORD(MISC, W_SND|W_REC, "Mention miscellaneous information (levels 1-2)"), INFO_WORD(MOUNT, W_SND|W_REC, "Mention mounts that were found or skipped"), INFO_WORD(NAME, W_SND|W_REC, "Mention 1) updated file/dir names, 2) unchanged names"), + INFO_WORD(NONREG, W_REC, "Mention skipped non-regular files (default 1, 0 disables)"), INFO_WORD(PROGRESS, W_CLI, "Mention 1) per-file progress or 2) total transfer progress"), INFO_WORD(REMOVE, W_SND, "Mention files removed on the sending side"), - INFO_WORD(SKIP, W_REC, "Mention files that are skipped due to options used (levels 1-2)"), + INFO_WORD(SKIP, W_REC, "Mention files skipped due to transfer overrides (levels 1-2)"), INFO_WORD(STATS, W_CLI|W_SRV, "Mention statistics at end of run (levels 1-3)"), INFO_WORD(SYMSAFE, W_SND|W_REC, "Mention symlinks that are unsafe"), { NULL, "--info", 0, 0, 0, 0 } @@ -289,7 +297,7 @@ static struct output_struct debug_words[COUNT_DEBUG+1] = { DEBUG_WORD(DELTASUM, W_SND|W_REC, "Debug delta-transfer checksumming (levels 1-4)"), DEBUG_WORD(DUP, W_REC, "Debug weeding of duplicate names"), DEBUG_WORD(EXIT, W_CLI|W_SRV, "Debug exit events (levels 1-3)"), - DEBUG_WORD(FILTER, W_SND|W_REC, "Debug filter actions (levels 1-2)"), + DEBUG_WORD(FILTER, W_SND|W_REC, "Debug filter actions (levels 1-3)"), DEBUG_WORD(FLIST, W_SND|W_REC, "Debug file-list operations (levels 1-4)"), DEBUG_WORD(FUZZY, W_REC, "Debug fuzzy scoring (levels 1-2)"), DEBUG_WORD(GENR, W_REC, "Debug generator functions"), @@ -488,9 +496,9 @@ static void output_item_help(struct output_struct *words) rprintf(FINFO, fmt, "HELP", "Output this help message"); rprintf(FINFO, "\n"); - rprintf(FINFO, "Options added for each increase in verbose level:\n"); + rprintf(FINFO, "Options added at each level of verbosity:\n"); - for (j = 1; j <= MAX_VERBOSITY; j++) { + for (j = 0; j <= MAX_VERBOSITY; j++) { parse_output_words(words, levels, verbosity[j], HELP_PRIORITY); opt = make_output_option(words, levels, W_CLI|W_SRV|W_SND|W_REC); if (opt) { @@ -509,7 +517,7 @@ static void set_output_verbosity(int level, uchar priority) if (level > MAX_VERBOSITY) level = MAX_VERBOSITY; - for (j = 1; j <= level; j++) { + for (j = 0; j <= level; j++) { parse_output_words(info_words, info_levels, info_verbosity[j], priority); parse_output_words(debug_words, debug_levels, debug_verbosity[j], priority); } @@ -528,7 +536,7 @@ void limit_output_verbosity(int level) memset(debug_limits, 0, sizeof debug_limits); /* Compute the level limits in the above arrays. */ - for (j = 1; j <= level; j++) { + for (j = 0; j <= level; j++) { parse_output_words(info_words, info_limits, info_verbosity[j], LIMIT_PRIORITY); parse_output_words(debug_words, debug_limits, debug_verbosity[j], LIMIT_PRIORITY); } @@ -575,7 +583,7 @@ enum {OPT_SERVER = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM, OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE, OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE, OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR, - OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS, + OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS, OPT_OLD_ARGS, OPT_STOP_AFTER, OPT_STOP_AT, OPT_REFUSED_BASE = 9000}; @@ -653,6 +661,7 @@ static struct poptOption long_options[] = { {"no-D", 0, POPT_ARG_NONE, 0, OPT_NO_D, 0, 0 }, {"devices", 0, POPT_ARG_VAL, &preserve_devices, 1, 0, 0 }, {"no-devices", 0, POPT_ARG_VAL, &preserve_devices, 0, 0, 0 }, + {"copy-devices", 0, POPT_ARG_NONE, ©_devices, 0, 0, 0 }, {"write-devices", 0, POPT_ARG_VAL, &write_devices, 1, 0, 0 }, {"no-write-devices", 0, POPT_ARG_VAL, &write_devices, 0, 0, 0 }, {"specials", 0, POPT_ARG_VAL, &preserve_specials, 1, 0, 0 }, @@ -778,9 +787,14 @@ static struct poptOption long_options[] = { {"files-from", 0, POPT_ARG_STRING, &files_from, 0, 0, 0 }, {"from0", '0', POPT_ARG_VAL, &eol_nulls, 1, 0, 0}, {"no-from0", 0, POPT_ARG_VAL, &eol_nulls, 0, 0, 0}, - {"protect-args", 's', POPT_ARG_VAL, &protect_args, 1, 0, 0}, + {"old-args", 0, POPT_ARG_NONE, 0, OPT_OLD_ARGS, 0, 0}, + {"no-old-args", 0, POPT_ARG_VAL, &old_style_args, 0, 0, 0}, + {"secluded-args", 's', POPT_ARG_VAL, &protect_args, 1, 0, 0}, + {"no-secluded-args", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0}, + {"protect-args", 0, POPT_ARG_VAL, &protect_args, 1, 0, 0}, {"no-protect-args", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0}, {"no-s", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0}, + {"trust-sender", 0, POPT_ARG_VAL, &trust_sender, 1, 0, 0}, {"numeric-ids", 0, POPT_ARG_VAL, &numeric_ids, 1, 0, 0 }, {"no-numeric-ids", 0, POPT_ARG_VAL, &numeric_ids, 0, 0, 0 }, {"usermap", 0, POPT_ARG_STRING, 0, OPT_USERMAP, 0, 0 }, @@ -939,11 +953,12 @@ static void set_refuse_options(void) if (!am_daemon || op->shortName == 'e' /* Required for compatibility flags */ || op->shortName == '0' /* --from0 just modifies --files-from, so refuse that instead (or not) */ - || op->shortName == 's' /* --protect-args is always OK */ + || op->shortName == 's' /* --secluded-args is always OK */ || op->shortName == 'n' /* --dry-run is always OK */ || strcmp("iconv", longName) == 0 || strcmp("no-iconv", longName) == 0 || strcmp("checksum-seed", longName) == 0 + || strcmp("copy-devices", longName) == 0 /* disable wild-match (it gets refused below) */ || strcmp("write-devices", longName) == 0 /* disable wild-match (it gets refused below) */ || strcmp("log-format", longName) == 0 /* aka out-format (NOT log-file-format) */ || strcmp("sender", longName) == 0 @@ -955,6 +970,7 @@ static void set_refuse_options(void) assert(list_end != NULL); if (am_daemon) { /* Refused by default, but can be accepted via a negated exact match. */ + parse_one_refuse_match(0, "copy-devices", list_end); parse_one_refuse_match(0, "write-devices", list_end); } @@ -1332,7 +1348,7 @@ char *alt_dest_opt(int type) **/ int parse_arguments(int *argc_p, const char ***argv_p) { - static poptContext pc; + poptContext pc; const char *arg, **argv = *argv_p; int argc = *argc_p; int opt, want_dest_type; @@ -1352,10 +1368,6 @@ int parse_arguments(int *argc_p, const char ***argv_p) /* TODO: Call poptReadDefaultConfig; handle errors. */ - /* The context leaks in case of an error, but if there's a - * problem we always exit anyhow. */ - if (pc) - poptFreeContext(pc); pc = poptGetContext(RSYNC_NAME, argc, argv, long_options, 0); if (!am_server) { poptReadDefaultConfig(pc, 0); @@ -1398,7 +1410,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) strlcpy(err_buf, "Attempt to hack rsync thwarted!\n", sizeof err_buf); - return 0; + goto cleanup; } #ifdef ICONV_OPTION iconv_opt = NULL; @@ -1444,7 +1456,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) { snprintf(err_buf, sizeof err_buf, "the --temp-dir path is WAY too long.\n"); - return 0; + goto cleanup; } if (!daemon_opt) { @@ -1454,8 +1466,16 @@ int parse_arguments(int *argc_p, const char ***argv_p) exit_cleanup(RERR_SYNTAX); } - *argv_p = argv = poptGetArgs(pc); - *argc_p = argc = count_args(argv); + argv = poptGetArgs(pc); + argc = count_args(argv); + if (!argc) { + *argv_p = empty_argv; + *argc_p = 0; + } else if (poptDupArgv(argc, argv, argc_p, argv_p) != 0) + out_of_memory("parse_arguments"); + argv = *argv_p; + poptFreeContext(pc); + am_starting_up = 0; daemon_opt = 0; am_daemon = 1; @@ -1510,7 +1530,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) case 'a': if (refused_archive_part) { create_refuse_error(refused_archive_part); - return 0; + goto cleanup; } if (!recurse) /* preserve recurse == 2 */ recurse = 1; @@ -1580,7 +1600,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) case 'P': if (refused_partial || refused_progress) { create_refuse_error(refused_partial ? refused_partial : refused_progress); - return 0; + goto cleanup; } do_progress = 1; keep_partial = 1; @@ -1603,12 +1623,19 @@ int parse_arguments(int *argc_p, const char ***argv_p) compress_choice = NULL; break; + case OPT_OLD_ARGS: + if (old_style_args <= 0) + old_style_args = 1; + else + old_style_args++; + break; + case 'M': arg = poptGetOptArg(pc); if (*arg != '-') { snprintf(err_buf, sizeof err_buf, "Remote option must start with a dash: %s\n", arg); - return 0; + goto cleanup; } if (remote_option_cnt+2 >= remote_option_alloc) { remote_option_alloc += 16; @@ -1650,27 +1677,27 @@ int parse_arguments(int *argc_p, const char ***argv_p) ssize_t size; arg = poptGetOptArg(pc); if ((size = parse_size_arg(arg, 'b', "block-size", 0, max_blength, False)) < 0) - return 0; + goto cleanup; block_size = (int32)size; break; } case OPT_MAX_SIZE: if ((max_size = parse_size_arg(max_size_arg, 'b', "max-size", 0, -1, False)) < 0) - return 0; + goto cleanup; max_size_arg = strdup(do_big_num(max_size, 0, NULL)); break; case OPT_MIN_SIZE: if ((min_size = parse_size_arg(min_size_arg, 'b', "min-size", 0, -1, False)) < 0) - return 0; + goto cleanup; min_size_arg = strdup(do_big_num(min_size, 0, NULL)); break; case OPT_BWLIMIT: { ssize_t size = parse_size_arg(bwlimit_arg, 'K', "bwlimit", 512, -1, True); if (size < 0) - return 0; + goto cleanup; bwlimit_arg = strdup(do_big_num(size, 0, NULL)); bwlimit = (size + 512) / 1024; break; @@ -1699,7 +1726,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) snprintf(err_buf, sizeof err_buf, "ERROR: the %s option conflicts with the %s option\n", alt_dest_opt(want_dest_type), alt_dest_opt(0)); - return 0; + goto cleanup; } alt_dest_type = want_dest_type; @@ -1707,7 +1734,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) snprintf(err_buf, sizeof err_buf, "ERROR: at most %d %s args may be specified\n", MAX_BASIS_DIRS, alt_dest_opt(0)); - return 0; + goto cleanup; } /* We defer sanitizing this arg until we know what * our destination directory is going to be. */ @@ -1720,7 +1747,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) snprintf(err_buf, sizeof err_buf, "Invalid argument passed to --chmod (%s)\n", arg); - return 0; + goto cleanup; } break; @@ -1739,11 +1766,11 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (usermap_via_chown) { snprintf(err_buf, sizeof err_buf, "--usermap conflicts with prior --chown.\n"); - return 0; + goto cleanup; } snprintf(err_buf, sizeof err_buf, "You can only specify --usermap once.\n"); - return 0; + goto cleanup; } usermap = (char *)poptGetOptArg(pc); usermap_via_chown = False; @@ -1755,11 +1782,11 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (groupmap_via_chown) { snprintf(err_buf, sizeof err_buf, "--groupmap conflicts with prior --chown.\n"); - return 0; + goto cleanup; } snprintf(err_buf, sizeof err_buf, "You can only specify --groupmap once.\n"); - return 0; + goto cleanup; } groupmap = (char *)poptGetOptArg(pc); groupmap_via_chown = False; @@ -1778,11 +1805,11 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (!usermap_via_chown) { snprintf(err_buf, sizeof err_buf, "--chown conflicts with prior --usermap.\n"); - return 0; + goto cleanup; } snprintf(err_buf, sizeof err_buf, "You can only specify a user-affecting --chown once.\n"); - return 0; + goto cleanup; } if (asprintf(&usermap, "*:%.*s", len, chown) < 0) out_of_memory("parse_arguments"); @@ -1794,11 +1821,11 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (!groupmap_via_chown) { snprintf(err_buf, sizeof err_buf, "--chown conflicts with prior --groupmap.\n"); - return 0; + goto cleanup; } snprintf(err_buf, sizeof err_buf, "You can only specify a group-affecting --chown once.\n"); - return 0; + goto cleanup; } if (asprintf(&groupmap, "*:%s", arg) < 0) out_of_memory("parse_arguments"); @@ -1826,7 +1853,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) snprintf(err_buf,sizeof(err_buf), "ACLs are not supported on this %s\n", am_server ? "server" : "client"); - return 0; + goto cleanup; #endif case 'X': @@ -1837,7 +1864,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) snprintf(err_buf,sizeof(err_buf), "extended attributes are not supported on this %s\n", am_server ? "server" : "client"); - return 0; + goto cleanup; #endif case OPT_STOP_AFTER: { @@ -1846,7 +1873,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) stop_at_utime = time(NULL); if ((val = atol(arg) * 60) <= 0 || LONG_MAX - val < stop_at_utime || (long)(time_t)val != val) { snprintf(err_buf, sizeof err_buf, "invalid --stop-after value: %s\n", arg); - return 0; + goto cleanup; } stop_at_utime += val; break; @@ -1857,11 +1884,11 @@ int parse_arguments(int *argc_p, const char ***argv_p) arg = poptGetOptArg(pc); if ((stop_at_utime = parse_time(arg)) == (time_t)-1) { snprintf(err_buf, sizeof err_buf, "invalid --stop-at format: %s\n", arg); - return 0; + goto cleanup; } if (stop_at_utime <= time(NULL)) { snprintf(err_buf, sizeof err_buf, "--stop-at time is not in the future: %s\n", arg); - return 0; + goto cleanup; } break; #endif @@ -1879,8 +1906,9 @@ int parse_arguments(int *argc_p, const char ***argv_p) else { snprintf(err_buf, sizeof err_buf, "--stderr mode \"%s\" is not one of errors, all, or client\n", arg); - return 0; + goto cleanup; } + saw_stderr_opt = 1; break; } @@ -1889,18 +1917,21 @@ int parse_arguments(int *argc_p, const char ***argv_p) * turned this option off. */ if (opt >= OPT_REFUSED_BASE) { create_refuse_error(opt); - return 0; + goto cleanup; } snprintf(err_buf, sizeof err_buf, "%s%s: %s\n", am_server ? "on remote machine: " : "", poptBadOption(pc, POPT_BADOPTION_NOALIAS), poptStrerror(opt)); - return 0; + goto cleanup; } } + if (msgs2stderr != 2) + saw_stderr_opt = 1; + if (version_opt_cnt) { - print_rsync_version(FINFO); + print_rsync_version(version_opt_cnt > 1 && !am_server ? FNONE : FINFO); exit_cleanup(0); } @@ -1912,9 +1943,26 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (max_alloc_arg) { ssize_t size = parse_size_arg(max_alloc_arg, 'B', "max-alloc", 1024*1024, -1, True); if (size < 0) - return 0; + goto cleanup; max_alloc = size; } + if (!max_alloc) + max_alloc = SIZE_MAX; + + if (old_style_args < 0) { + if (!am_server && protect_args <= 0 && (arg = getenv("RSYNC_OLD_ARGS")) != NULL && *arg) { + protect_args = 0; + old_style_args = atoi(arg); + } else + old_style_args = 0; + } else if (old_style_args) { + if (protect_args > 0) { + snprintf(err_buf, sizeof err_buf, + "--secluded-args conflicts with --old-args.\n"); + goto cleanup; + } + protect_args = 0; + } if (protect_args < 0) { if (am_server) @@ -1922,7 +1970,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) else if ((arg = getenv("RSYNC_PROTECT_ARGS")) != NULL && *arg) protect_args = atoi(arg) ? 1 : 0; else { -#ifdef RSYNC_USE_PROTECTED_ARGS +#ifdef RSYNC_USE_SECLUDED_ARGS protect_args = 1; #else protect_args = 0; @@ -1956,7 +2004,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) do_compression = CPRES_AUTO; if (do_compression && refused_compress) { create_refuse_error(refused_compress); - return 0; + goto cleanup; } } @@ -1981,7 +2029,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) default: snprintf(err_buf, sizeof err_buf, "Invalid --outbuf setting -- specify N, L, or B.\n"); - return 0; + goto cleanup; } setvbuf(stdout, (char *)NULL, mode, 0); } @@ -2009,7 +2057,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) } if (refused_no_iconv && !iconv_opt) { create_refuse_error(refused_no_iconv); - return 0; + goto cleanup; } #endif @@ -2020,18 +2068,30 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (orig_protect_args == 2 && am_server) protect_args = orig_protect_args; - if (protect_args == 1 && am_server) + if (protect_args == 1 && am_server) { + poptFreeContext(pc); return 1; + } - *argv_p = argv = poptGetArgs(pc); - *argc_p = argc = count_args(argv); + /* Because popt 1.19 has started to free the returned args data, we now + * make a copy of the array and then do an immediate cleanup. */ + argv = poptGetArgs(pc); + argc = count_args(argv); + if (!argc) { + *argv_p = empty_argv; + *argc_p = 0; + } else if (poptDupArgv(argc, argv, argc_p, argv_p) != 0) + out_of_memory("parse_arguments"); + argv = *argv_p; + poptFreeContext(pc); + pc = NULL; #ifndef SUPPORT_LINKS if (preserve_links && !am_sender) { snprintf(err_buf, sizeof err_buf, "symlinks are not supported on this %s\n", am_server ? "server" : "client"); - return 0; + goto cleanup; } #endif @@ -2040,7 +2100,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) snprintf(err_buf, sizeof err_buf, "hard links are not supported on this %s\n", am_server ? "server" : "client"); - return 0; + goto cleanup; } #endif @@ -2048,20 +2108,20 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (am_root < 0 && preserve_xattrs > 1) { snprintf(err_buf, sizeof err_buf, "--fake-super conflicts with -XX\n"); - return 0; + goto cleanup; } #else if (am_root < 0) { snprintf(err_buf, sizeof err_buf, "--fake-super requires an rsync with extended attributes enabled\n"); - return 0; + goto cleanup; } #endif if (write_batch && read_batch) { snprintf(err_buf, sizeof err_buf, "--write-batch and --read-batch can not be used together\n"); - return 0; + goto cleanup; } if (write_batch > 0 || read_batch) { if (am_server) { @@ -2080,25 +2140,25 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (read_batch && files_from) { snprintf(err_buf, sizeof err_buf, "--read-batch cannot be used with --files-from\n"); - return 0; + goto cleanup; } if (read_batch && remove_source_files) { snprintf(err_buf, sizeof err_buf, "--read-batch cannot be used with --remove-%s-files\n", remove_source_files == 1 ? "source" : "sent"); - return 0; + goto cleanup; } if (batch_name && strlen(batch_name) > MAX_BATCH_NAME_LEN) { snprintf(err_buf, sizeof err_buf, "the batch-file name must be %d characters or less.\n", MAX_BATCH_NAME_LEN); - return 0; + goto cleanup; } if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) { snprintf(err_buf, sizeof err_buf, "the --temp-dir path is WAY too long.\n"); - return 0; + goto cleanup; } if (max_delete < 0 && max_delete != INT_MIN) { @@ -2132,7 +2192,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (delete_before + !!delete_during + delete_after > 1) { snprintf(err_buf, sizeof err_buf, "You may not combine multiple --delete-WHEN options.\n"); - return 0; + goto cleanup; } if (delete_before || delete_during || delete_after) delete_mode = 1; @@ -2143,7 +2203,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) delete_during = 1; else { create_refuse_error(refused_delete_before); - return 0; + goto cleanup; } } else if (refused_delete_during) delete_before = 1; @@ -2152,14 +2212,14 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (!xfer_dirs && delete_mode) { snprintf(err_buf, sizeof err_buf, "--delete does not work without --recursive (-r) or --dirs (-d).\n"); - return 0; + goto cleanup; } if (missing_args == 3) /* simplify if both options were specified */ missing_args = 2; if (refused_delete && (delete_mode || missing_args == 2)) { create_refuse_error(refused_delete); - return 0; + goto cleanup; } if (remove_source_files) { @@ -2168,7 +2228,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) * options. */ if (refused_delete && am_sender) { create_refuse_error(refused_delete); - return 0; + goto cleanup; } need_messages_from_generator = 1; } @@ -2222,7 +2282,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) snprintf(err_buf, sizeof err_buf, "--suffix cannot contain slashes: %s\n", backup_suffix); - return 0; + goto cleanup; } if (backup_dir) { size_t len; @@ -2235,7 +2295,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (len > sizeof backup_dir_buf - 128) { snprintf(err_buf, sizeof err_buf, "the --backup-dir path is WAY too long.\n"); - return 0; + goto cleanup; } backup_dir_len = (int)len; if (!backup_dir_len) { @@ -2254,7 +2314,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) "--suffix cannot be empty %s\n", backup_dir_len < 0 ? "when --backup-dir is the same as the dest dir" : "without a --backup-dir"); - return 0; + goto cleanup; } else if (make_backups && delete_mode && !delete_excluded && !am_server) { snprintf(backup_dir_buf, sizeof backup_dir_buf, "P *%s", backup_suffix); @@ -2282,7 +2342,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (do_progress && !am_server) { if (!log_before_transfer && INFO_EQ(NAME, 0)) parse_output_words(info_words, info_levels, "name", DEFAULT_PRIORITY); - parse_output_words(info_words, info_levels, "flist2,progress", DEFAULT_PRIORITY); + parse_output_words(info_words, info_levels, "FLIST2,PROGRESS", DEFAULT_PRIORITY); } if (dry_run) @@ -2323,11 +2383,11 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (whole_file > 0) { snprintf(err_buf, sizeof err_buf, "--append cannot be used with --whole-file\n"); - return 0; + goto cleanup; } if (refused_inplace) { create_refuse_error(refused_inplace); - return 0; + goto cleanup; } inplace = 1; } @@ -2335,7 +2395,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (write_devices) { if (refused_inplace) { create_refuse_error(refused_inplace); - return 0; + goto cleanup; } inplace = 1; } @@ -2350,13 +2410,13 @@ int parse_arguments(int *argc_p, const char ***argv_p) "--%s cannot be used with --%s\n", append_mode ? "append" : "inplace", delay_updates ? "delay-updates" : "partial-dir"); - return 0; + goto cleanup; } /* --inplace implies --partial for refusal purposes, but we * clear the keep_partial flag for internal logic purposes. */ if (refused_partial) { create_refuse_error(refused_partial); - return 0; + goto cleanup; } keep_partial = 0; #else @@ -2364,7 +2424,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) "--%s is not supported on this %s\n", append_mode ? "append" : "inplace", am_server ? "server" : "client"); - return 0; + goto cleanup; #endif } else { if (keep_partial && !partial_dir && !am_server) { @@ -2378,7 +2438,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) partial_dir = NULL; if (!partial_dir && refused_partial) { create_refuse_error(refused_partial); - return 0; + goto cleanup; } keep_partial = 1; } @@ -2399,14 +2459,14 @@ int parse_arguments(int *argc_p, const char ***argv_p) if (am_server) { snprintf(err_buf, sizeof err_buf, "The --files-from sent to the server cannot specify a host.\n"); - return 0; + goto cleanup; } files_from = p; filesfrom_host = h; if (strcmp(files_from, "-") == 0) { snprintf(err_buf, sizeof err_buf, "Invalid --files-from remote filename\n"); - return 0; + goto cleanup; } } else { if (sanitize_paths) @@ -2425,11 +2485,16 @@ int parse_arguments(int *argc_p, const char ***argv_p) snprintf(err_buf, sizeof err_buf, "failed to open files-from file %s: %s\n", files_from, strerror(errno)); - return 0; + goto cleanup; } } } + if (trust_sender || am_server || read_batch) + trust_sender_args = trust_sender_filter = 1; + else if (old_style_args || filesfrom_host != NULL) + trust_sender_args = 1; + am_starting_up = 0; return 1; @@ -2437,10 +2502,80 @@ int parse_arguments(int *argc_p, const char ***argv_p) options_rejected: snprintf(err_buf, sizeof err_buf, "Your options have been rejected by the server.\n"); + cleanup: + if (pc) + poptFreeContext(pc); return 0; } +static char SPLIT_ARG_WHEN_OLD[1]; + +/** + * Do backslash quoting of any weird chars in "arg", append the resulting + * string to the end of the "opt" (which gets a "=" appended if it is not + * an empty or NULL string), and return the (perhaps malloced) result. + * If opt is NULL, arg is considered a filename arg that allows wildcards. + * If it is "" or any other value, it is considered an option. + **/ +char *safe_arg(const char *opt, const char *arg) +{ +#define SHELL_CHARS "!#$&;|<>(){}\"'` \t\\" +#define WILD_CHARS "*?[]" /* We don't allow remote brace expansion */ + BOOL is_filename_arg = !opt; + char *escapes = is_filename_arg ? SHELL_CHARS : WILD_CHARS SHELL_CHARS; + BOOL escape_leading_dash = is_filename_arg && *arg == '-'; + BOOL escape_leading_tilde = 0; + int len1 = opt && *opt ? strlen(opt) + 1 : 0; + int len2 = strlen(arg); + int extras = escape_leading_dash ? 2 : 0; + char *ret; + if (!protect_args && old_style_args < 2 && (!old_style_args || (!is_filename_arg && opt != SPLIT_ARG_WHEN_OLD))) { + const char *f; + if (*arg == '~' && is_filename_arg && !am_sender && !trust_sender_args + && ((relative_paths && !strstr(arg, "/./")) + || !strchr(arg, '/'))) { + extras++; + escape_leading_tilde = 1; + } + for (f = arg; *f; f++) { + if (strchr(escapes, *f)) + extras++; + } + } + if (!len1 && !extras) + return (char*)arg; + ret = new_array(char, len1 + len2 + extras + 1); + if (len1) { + memcpy(ret, opt, len1-1); + ret[len1-1] = '='; + } + if (escape_leading_dash) { + ret[len1++] = '.'; + ret[len1++] = '/'; + extras -= 2; + } + if (!extras) + memcpy(ret + len1, arg, len2); + else { + const char *f = arg; + char *t = ret + len1; + if (escape_leading_tilde) + *t++ = '\\'; + while (*f) { + if (*f == '\\') { + if (!is_filename_arg || !strchr(WILD_CHARS, f[1])) + *t++ = '\\'; + } else if (strchr(escapes, *f)) + *t++ = '\\'; + *t++ = *f++; + } + } + ret[len1+len2+extras] = '\0'; + return ret; +} + + /** * Construct a filtered list of options to pass through from the * client to the server. @@ -2584,9 +2719,7 @@ void server_options(char **args, int *argc_p) set++; else set = iconv_opt; - if (asprintf(&arg, "--iconv=%s", set) < 0) - goto oom; - args[ac++] = arg; + args[ac++] = safe_arg("--iconv", set); } #endif @@ -2652,33 +2785,24 @@ void server_options(char **args, int *argc_p) } if (backup_dir) { + /* This split idiom allows for ~/path expansion via the shell. */ args[ac++] = "--backup-dir"; - args[ac++] = backup_dir; + args[ac++] = safe_arg("", backup_dir); } /* Only send --suffix if it specifies a non-default value. */ - if (strcmp(backup_suffix, backup_dir ? "" : BACKUP_SUFFIX) != 0) { - /* We use the following syntax to avoid weirdness with '~'. */ - if (asprintf(&arg, "--suffix=%s", backup_suffix) < 0) - goto oom; - args[ac++] = arg; - } + if (strcmp(backup_suffix, backup_dir ? "" : BACKUP_SUFFIX) != 0) + args[ac++] = safe_arg("--suffix", backup_suffix); - if (checksum_choice) { - if (asprintf(&arg, "--checksum-choice=%s", checksum_choice) < 0) - goto oom; - args[ac++] = arg; - } + if (checksum_choice) + args[ac++] = safe_arg("--checksum-choice", checksum_choice); if (do_compression == CPRES_ZLIBX) args[ac++] = "--new-compress"; else if (compress_choice && do_compression == CPRES_ZLIB) args[ac++] = "--old-compress"; - else if (compress_choice) { - if (asprintf(&arg, "--compress-choice=%s", compress_choice) < 0) - goto oom; - args[ac++] = arg; - } + else if (compress_choice) + args[ac++] = safe_arg("--compress-choice", compress_choice); if (am_sender) { if (max_delete > 0) { @@ -2687,14 +2811,10 @@ void server_options(char **args, int *argc_p) args[ac++] = arg; } else if (max_delete == 0) args[ac++] = "--max-delete=-1"; - if (min_size >= 0) { - args[ac++] = "--min-size"; - args[ac++] = min_size_arg; - } - if (max_size >= 0) { - args[ac++] = "--max-size"; - args[ac++] = max_size_arg; - } + if (min_size >= 0) + args[ac++] = safe_arg("--min-size", min_size_arg); + if (max_size >= 0) + args[ac++] = safe_arg("--max-size", max_size_arg); if (delete_before) args[ac++] = "--delete-before"; else if (delete_during == 2) @@ -2718,17 +2838,12 @@ void server_options(char **args, int *argc_p) if (do_stats) args[ac++] = "--stats"; } else { - if (skip_compress) { - if (asprintf(&arg, "--skip-compress=%s", skip_compress) < 0) - goto oom; - args[ac++] = arg; - } + if (skip_compress) + args[ac++] = safe_arg("--skip-compress", skip_compress); } - if (max_alloc_arg && max_alloc != DEFAULT_MAX_ALLOC) { - args[ac++] = "--max-alloc"; - args[ac++] = max_alloc_arg; - } + if (max_alloc_arg && max_alloc != DEFAULT_MAX_ALLOC) + args[ac++] = safe_arg("--max-alloc", max_alloc_arg); /* --delete-missing-args needs the cooperation of both sides, but * the sender can handle --ignore-missing-args by itself. */ @@ -2753,7 +2868,7 @@ void server_options(char **args, int *argc_p) if (partial_dir && am_sender) { if (partial_dir != tmp_partialdir) { args[ac++] = "--partial-dir"; - args[ac++] = partial_dir; + args[ac++] = safe_arg("", partial_dir); } if (delay_updates) args[ac++] = "--delay-updates"; @@ -2776,17 +2891,11 @@ void server_options(char **args, int *argc_p) args[ac++] = "--use-qsort"; if (am_sender) { - if (usermap) { - if (asprintf(&arg, "--usermap=%s", usermap) < 0) - goto oom; - args[ac++] = arg; - } + if (usermap) + args[ac++] = safe_arg("--usermap", usermap); - if (groupmap) { - if (asprintf(&arg, "--groupmap=%s", groupmap) < 0) - goto oom; - args[ac++] = arg; - } + if (groupmap) + args[ac++] = safe_arg("--groupmap", groupmap); if (ignore_existing) args[ac++] = "--ignore-existing"; @@ -2797,7 +2906,7 @@ void server_options(char **args, int *argc_p) if (tmpdir) { args[ac++] = "--temp-dir"; - args[ac++] = tmpdir; + args[ac++] = safe_arg("", tmpdir); } if (do_fsync) @@ -2810,7 +2919,7 @@ void server_options(char **args, int *argc_p) */ for (i = 0; i < basis_dir_cnt; i++) { args[ac++] = alt_dest_opt(0); - args[ac++] = basis_dir[i]; + args[ac++] = safe_arg("", basis_dir[i]); } } } @@ -2828,14 +2937,14 @@ void server_options(char **args, int *argc_p) } else if (inplace) { args[ac++] = "--inplace"; /* Work around a bug in older rsync versions (on the remote side) for --inplace --sparse */ - if (sparse_files && !whole_file) + if (sparse_files && !whole_file && am_sender) args[ac++] = "--no-W"; } if (files_from && (!am_sender || filesfrom_host)) { if (filesfrom_host) { args[ac++] = "--files-from"; - args[ac++] = files_from; + args[ac++] = safe_arg("", files_from); if (eol_nulls) args[ac++] = "--from0"; } else { @@ -2857,6 +2966,9 @@ void server_options(char **args, int *argc_p) else if (remove_source_files) args[ac++] = "--remove-sent-files"; + if (copy_devices && !am_sender) + args[ac++] = "--copy-devices"; + if (preallocate_files && am_sender) args[ac++] = "--preallocate"; @@ -2878,7 +2990,7 @@ void server_options(char **args, int *argc_p) exit_cleanup(RERR_SYNTAX); } for (j = 1; j <= remote_option_cnt; j++) - args[ac++] = (char*)remote_options[j]; + args[ac++] = safe_arg(SPLIT_ARG_WHEN_OLD, remote_options[j]); } *argc_p = ac; diff --git a/packaging/auto-Makefile b/packaging/auto-Makefile index 29d2d6889..032913d57 100644 --- a/packaging/auto-Makefile +++ b/packaging/auto-Makefile @@ -1,6 +1,6 @@ -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 + doxygen doxygen-upload finddead rrsync .PHONY: $(TARGETS) auto-prep diff --git a/packaging/branch-from-patch b/packaging/branch-from-patch index 440b5835a..40e5653c4 100755 --- a/packaging/branch-from-patch +++ b/packaging/branch-from-patch @@ -154,7 +154,7 @@ def create_branch(patch): s = cmd_run(['git', 'commit', '-a', '-m', f"Creating branch from {patch.name}.diff."]) if not s.returncode: break - s = cmd_run(['/bin/zsh']) + s = cmd_run([os.environ.get('SHELL', '/bin/sh')]) if s.returncode: die('Aborting due to shell error code') diff --git a/packaging/cull-options b/packaging/cull-options new file mode 100755 index 000000000..e71818cdb --- /dev/null +++ b/packaging/cull-options @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# This script outputs either perl or python code that parses all possible options +# that the code in options.c might send to the server. The resulting code is then +# included in the rrsync script. + +import re, argparse + +short_no_arg = { } +short_with_num = { '@': 1 } +long_opts = { # These include some extra long-args that BackupPC uses: + 'block-size': 1, + 'daemon': -1, + 'debug': 1, + 'fake-super': 0, + 'fuzzy': 0, + 'group': 0, + 'hard-links': 0, + 'ignore-times': 0, + 'info': 1, + 'links': 0, + 'log-file': 3, + 'munge-links': 0, + 'no-munge-links': -1, + 'one-file-system': 0, + 'owner': 0, + 'perms': 0, + 'recursive': 0, + 'stderr': 1, + 'times': 0, + 'copy-devices': -1, + 'write-devices': -1, + } + +def main(): + last_long_opt = None + + with open('../options.c') as fh: + for line in fh: + m = re.search(r"argstr\[x\+\+\] = '([^.ie])'", line) + if m: + short_no_arg[m.group(1)] = 1 + last_long_opt = None + continue + + m = re.search(r'asprintf\([^,]+, "-([a-zA-Z0-9])\%l?[ud]"', line) + if m: + short_with_num[m.group(1)] = 1 + last_long_opt = None + continue + + m = re.search(r'args\[ac\+\+\] = "--([^"=]+)"', line) + if m: + last_long_opt = m.group(1) + if last_long_opt not in long_opts: + long_opts[last_long_opt] = 0 + else: + last_long_opt = None + continue + + if last_long_opt: + m = re.search(r'args\[ac\+\+\] = safe_arg\("", ([^[("\s]+)\);', line) + if m: + long_opts[last_long_opt] = 2 + last_long_opt = None + continue + if 'args[ac++] = ' in line: + last_long_opt = None + + m = re.search(r'return "--([^"]+-dest)";', line) + if m: + long_opts[m.group(1)] = 2 + last_long_opt = None + continue + + m = re.search(r'asprintf\([^,]+, "--([^"=]+)=', line) + if not m: + m = re.search(r'args\[ac\+\+\] = "--([^"=]+)=', line) + if not m: + m = re.search(r'args\[ac\+\+\] = safe_arg\("--([^"=]+)"', line) + if not m: + m = re.search(r'fmt = .*: "--([^"=]+)=', line) + if m: + long_opts[m.group(1)] = 1 + last_long_opt = None + + long_opts['files-from'] = 3 + + txt = """\ +### START of options data produced by the cull-options script. ### + +# To disable a short-named option, add its letter to this string: +""" + + txt += str_assign('short_disabled', 's') + "\n" + txt += '# These are also disabled when the restricted dir is not "/":\n' + txt += str_assign('short_disabled_subdir', 'KLk') + "\n" + txt += '# These are all possible short options that we will accept (when not disabled above):\n' + txt += str_assign('short_no_arg', ''.join(sorted(short_no_arg)), 'DO NOT REMOVE ANY') + txt += str_assign('short_with_num', ''.join(sorted(short_with_num)), 'DO NOT REMOVE ANY') + + txt += """ +# To disable a long-named option, change its value to a -1. The values mean: +# 0 = the option has no arg; 1 = the arg doesn't need any checking; 2 = only +# check the arg when receiving; and 3 = always check the arg. +""" + + print(txt, end='') + + if args.python: + print("long_opts = {") + sep = ':' + else: + print("our %long_opt = (") + sep = ' =>' + + for opt in sorted(long_opts): + if opt.startswith(('min-', 'max-')): + val = 1 + else: + val = long_opts[opt] + print(' ', repr(opt) + sep, str(val) + ',') + + if args.python: + print("}") + else: + print(");") + print("\n### END of options data produced by the cull-options script. ###") + + +def str_assign(name, val, comment=None): + comment = ' # ' + comment if comment else '' + if args.python: + return name + ' = ' + repr(val) + comment + "\n" + return 'our $' + name + ' = ' + repr(val) + ';' + comment + "\n" + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Output culled rsync options for rrsync.", add_help=False) + out_group = parser.add_mutually_exclusive_group() + out_group.add_argument('--perl', action='store_true', help="Output perl code.") + out_group.add_argument('--python', action='store_true', help="Output python code (the default).") + parser.add_argument('--help', '-h', action='help', help="Output this help message and exit.") + args = parser.parse_args() + if not args.perl: + args.python = True + main() + +# vim: sw=4 et diff --git a/packaging/cull_options b/packaging/cull_options deleted file mode 100755 index 7588002be..000000000 --- a/packaging/cull_options +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env perl -# This script outputs some perl code that parses all possible options -# that the code in options.c might send to the server. This perl code -# is included in the rrsync script. -use strict; - -our %short_no_arg; -our %short_with_num = ( '@' => 1 ); -our %long_opt = ( # These include some extra long-args that BackupPC uses: - 'block-size' => 1, - 'daemon' => -1, - 'debug' => 1, - 'fake-super' => 0, - 'fuzzy' => 0, - 'group' => 0, - 'hard-links' => 0, - 'ignore-times' => 0, - 'info' => 1, - 'links' => 0, - 'log-file' => 3, - 'one-file-system' => 0, - 'owner' => 0, - 'perms' => 0, - 'recursive' => 0, - 'times' => 0, - 'write-devices' => -1, -); -our $last_long_opt; - -open(IN, '../options.c') or die "Unable to open ../options.c: $!\n"; - -while () { - if (/\Qargstr[x++]\E = '([^.ie])'/) { - $short_no_arg{$1} = 1; - undef $last_long_opt; - } elsif (/\Qasprintf(\E[^,]+, "-([a-zA-Z0-9])\%l?[ud]"/) { - $short_with_num{$1} = 1; - undef $last_long_opt; - } elsif (/\Qargs[ac++]\E = "--([^"=]+)"/) { - $last_long_opt = $1; - $long_opt{$1} = 0 unless exists $long_opt{$1}; - } elsif (defined($last_long_opt) - && /\Qargs[ac++]\E = ([^["\s]+);/) { - $long_opt{$last_long_opt} = 2; - undef $last_long_opt; - } elsif (/return "--([^"]+-dest)";/) { - $long_opt{$1} = 2; - undef $last_long_opt; - } elsif (/\Qasprintf(\E[^,]+, "--([^"=]+)=/ || /\Qargs[ac++]\E = "--([^"=]+)=/ || /fmt = .*: "--([^"=]+)=/) { - $long_opt{$1} = 1; - undef $last_long_opt; - } -} -close IN; - -my $short_no_arg = join('', sort keys %short_no_arg); -my $short_with_num = join('', sort keys %short_with_num); - -print < $val,\n"; -} - -print ");\n\n"; diff --git a/packaging/lsb/rsync.spec b/packaging/lsb/rsync.spec index 04163c72b..845b188a9 100644 --- a/packaging/lsb/rsync.spec +++ b/packaging/lsb/rsync.spec @@ -1,6 +1,6 @@ Summary: A fast, versatile, remote (and local) file-copying tool Name: rsync -Version: 3.2.3 +Version: 3.4.1 %define fullversion %{version} Release: 1 %define srcdir src @@ -79,9 +79,5 @@ rm -rf $RPM_BUILD_ROOT %dir /etc/rsync-ssl/certs %changelog -* Thu Aug 06 2020 Wayne Davison -Released 3.2.3. - -* Fri Mar 21 2008 Wayne Davison -Added installation of /etc/xinetd.d/rsync file and some commented-out -lines that demonstrate how to use the rsync-patches tar file. +* Thu Jan 16 2025 Rsync Project +Released 3.4.1. diff --git a/packaging/md2html b/packaging/md2html deleted file mode 100755 index 21e42c66a..000000000 --- a/packaging/md2html +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright (C) 2020 Wayne Davison -# -# This program is freely redistributable. - -import os, re, argparse - -HTML_START = """\ - -%s - - - -""" - -HTML_END = """\ - -""" - -md_parser = None - -def main(): - for mdfn in args.mdfiles: - if not mdfn.endswith('.md'): - print('Ignoring non-md input file:', mdfn) - continue - title = re.sub(r'.*/', '', mdfn).replace('.md', '') - htfn = mdfn.replace('.md', '.html') - - print("Parsing", mdfn, '->', htfn) - - with open(mdfn, 'r', encoding='utf-8') as fh: - txt = fh.read() - - txt = re.sub(r'\s--\s', '\xa0-- ', txt) - - html = md_parser(txt) - - html = re.sub(r'(?)()([\s\S]*?)()', lambda m: m[1] + re.sub(r'\s', '\xa0', m[2]) + m[3], html) - html = html.replace('--', '‑‑').replace("\xa0-", ' ‑').replace("\xa0", ' ') - html = re.sub(r'(\W)-', r'\1‑', html) - - if os.path.lexists(htfn): - os.unlink(htfn) - - with open(htfn, 'w', encoding='utf-8') as fh: - fh.write(HTML_START % title) - fh.write(html) - fh.write(HTML_END) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Output html for md pages.', add_help=False) - parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.") - parser.add_argument("mdfiles", nargs='+', help="The .md files to turn into .html files.") - args = parser.parse_args() - - try: - import cmarkgfm - # Our NEWS.md file has a gfm table in it. - md_parser = cmarkgfm.github_flavored_markdown_to_html - except: - die("Failed to find cmarkgfm for python3.") - - main() diff --git a/packaging/openssl-rsync.cnf b/packaging/openssl-rsync.cnf new file mode 100644 index 000000000..7432285df --- /dev/null +++ b/packaging/openssl-rsync.cnf @@ -0,0 +1,18 @@ +# This config file can be used with rsync to enable legacy digests +# (such as MD4) by using the OPENSSL_CONF environment variable. +# See rsync's configure --with-openssl-conf=/path/name option. + +openssl_conf = openssl_init + +[openssl_init] +providers = provider_sect + +[provider_sect] +default = default_sect +legacy = legacy_sect + +[default_sect] +activate = 1 + +[legacy_sect] +activate = 1 diff --git a/packaging/pkglib.py b/packaging/pkglib.py index 57cc2012d..c2b293074 100644 --- a/packaging/pkglib.py +++ b/packaging/pkglib.py @@ -32,7 +32,7 @@ def _maybe_set(o, **msa): # Only set a value if the user didn't already set it. opts = opts.copy() _maybe_set(opts, **maybe_set_args) - if type(cmd) == str: + if isinstance(cmd, str): _maybe_set(opts, shell=True) want_raw = opts.pop('raw', False) @@ -170,18 +170,7 @@ 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(['fgrep', '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.in file and return them as a list. +# 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') @@ -189,7 +178,7 @@ def get_gen_files(want_dir_plus_list=False): auto_dir = os.path.join('auto-build-save', cmd_txt('git rev-parse --abbrev-ref HEAD').out.strip().replace('/', '%')) - with open('Makefile.in', 'r', encoding='utf-8') as fh: + with open(auto_dir + '/Makefile', 'r', encoding='utf-8') as fh: for line in fh: if not gen_files: chk = re.sub(r'^GENFILES=', '', line) diff --git a/packaging/pre-push b/packaging/pre-push deleted file mode 100755 index 8a713695e..000000000 --- a/packaging/pre-push +++ /dev/null @@ -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 &2 diff --git a/packaging/release-rsync b/packaging/release-rsync index fa1da2349..2d1486b8d 100755 --- a/packaging/release-rsync +++ b/packaging/release-rsync @@ -3,7 +3,24 @@ # 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. +# ~/samba-rsync-ftp dir will be ready to be rsynced to samba.org. See the +# script samba-rsync for an easy way to initialize the local ftp copy and to +# thereafter update the remote files from your local copy. + +# This script also expects to be able to gpg sign the resulting tar files +# using your default gpg key. Make sure that the html download.html file +# has a link to the relevant keys that are authorized to sign the tar files +# and also make sure that the following commands work as expected: +# +# touch TeMp +# gpg --sign TeMp +# gpg --verify TeMp.gpg +# gpg --sign TeMp +# rm TeMp* +# +# The second time you sign the file it should NOT prompt you for your password +# (unless the timeout period has passed). It will prompt about overriding the +# existing TeMp.gpg file, though. import os, sys, re, argparse, glob, shutil, signal from datetime import datetime @@ -21,13 +38,15 @@ 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() + now = datetime.now().astimezone() # Requires python 3.6 or later 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() + # The MAINTAINER_TZ_OFFSET is a float number of hours vs UTC. It can start with '-' but not '+'. + tz_now = now.strftime('%z') + tz_num = tz_now[0:1].replace('+', '') + str(float(tz_now[1:3]) + float(tz_now[3:]) / 60) curdir = os.getcwd() @@ -105,6 +124,8 @@ def main(): 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'): @@ -185,7 +206,7 @@ About to: '%define srcdir': srcdir, } - tweak_files = 'version.h rsync.h NEWS.md'.split() + tweak_files = 'version.h rsync.h'.split() tweak_files += glob.glob('packaging/*.spec') tweak_files += glob.glob('packaging/*/*.spec') @@ -193,7 +214,12 @@ About to: with open(fn, 'r', encoding='utf-8') as fh: old_txt = txt = fh.read() if fn == 'version.h': - txt = f'#define RSYNC_VERSION "{version}"\n' + 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) + x_re = re.compile(r'^(#define MAINTAINER_TZ_OFFSET).*', re.M) + msg = f"Unable to update MAINTAINER_TZ_OFFSET in {fn}" + txt = replace_or_die(x_re, r'\1 ' + tz_num, txt, msg) elif '.spec' in fn: for var, val in specvars.items(): x_re = re.compile(r'^%s .*' % re.escape(var), re.M) @@ -201,15 +227,15 @@ About to: 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+)') + 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 define in {fn}") elif fn == 'NEWS.md': efv = re.escape(finalversion) - x_re = re.compile(r'^<.+>\s+# NEWS for rsync %s \(UNRELEASED\)\s+## Changes in this version:\n' % efv + 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'\n\n# NEWS for rsync {finalversion} ({rel_day})\n\n' + 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' @@ -230,7 +256,7 @@ About to: cmd_chk(['packaging/year-tweak']) print(dash_line) - cmd_run("git diff --color | less -p '^diff .*'") + cmd_run("git diff".split()) srctar_name = f"{rsync_ver}.tar.gz" pattar_name = f"rsync-patches-{version}.tar.gz" @@ -245,20 +271,20 @@ About to: About to: - git commit all changes - - generate the manpages + - 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(" ") - s = cmd_run(['git', 'commit', '-a', '-m', f'Preparing for release of {version}']) + s = cmd_run(['git', 'commit', '-a', '-m', f'Preparing for release of {version} [buildall]']) if s.returncode: die('Aborting') - cmd_chk('make gen') + cmd_chk('touch configure.ac && packaging/smart-make && make gen') - print(f'Creating any missing patch branches.') + 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') @@ -341,7 +367,7 @@ About to: 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(["packaging/md2html"] + [ dest +'/'+ fn for fn in md_files ]) + cmd_chk(["./md-convert", "--dest", dest, *md_files]) cmd_chk(f"git log --name-status | gzip -9 >{dest}/ChangeLog.gz") diff --git a/packaging/samba-rsync b/packaging/samba-rsync new file mode 100755 index 000000000..b291b276d --- /dev/null +++ b/packaging/samba-rsync @@ -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/" diff --git a/packaging/send-news b/packaging/send-news new file mode 100755 index 000000000..c83a74c03 --- /dev/null +++ b/packaging/send-news @@ -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/" diff --git a/packaging/systemd/rsync.service b/packaging/systemd/rsync.service index 8a0b5820a..8a867ca64 100644 --- a/packaging/systemd/rsync.service +++ b/packaging/systemd/rsync.service @@ -7,6 +7,7 @@ Documentation=man:rsync(1) man:rsyncd.conf(5) [Service] ExecStart=/usr/bin/rsync --daemon --no-detach RestartSec=1 +Restart=on-failure # Citing README.md: # diff --git a/packaging/var-checker b/packaging/var-checker index f17c69a29..1573895c0 100755 --- a/packaging/var-checker +++ b/packaging/var-checker @@ -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\w+)(?P\[.*?\])?$', item) + line = re.sub(r'\s*=\s*[^,]*', '', line) + m = re.search(r'^(?:(?:static|extern)\s+)?(?P[^\[,]+?)(?P\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\*+\s*)?(?P\w+)(?P\[.*?\])?$', 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 diff --git a/packaging/year-tweak b/packaging/year-tweak index 69d2f2ff9..8a7fb98e4 100755 --- a/packaging/year-tweak +++ b/packaging/year-tweak @@ -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
.*Copyright\s+\S+\s+)(?P\d\d\d\d(?:-\d\d\d\d)?(,\s+\d\d\d\d)*)(?P.+)', 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()
 
diff --git a/popt/findme.c b/popt/findme.c
deleted file mode 100644
index ac4cbaed2..000000000
--- a/popt/findme.c
+++ /dev/null
@@ -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;
-}
diff --git a/popt/findme.h b/popt/findme.h
deleted file mode 100644
index a016b867e..000000000
--- a/popt/findme.h
+++ /dev/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
diff --git a/popt/lookup3.c b/popt/lookup3.c
new file mode 100644
index 000000000..e974cad87
--- /dev/null
+++ b/popt/lookup3.c
@@ -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 
+
+#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 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>(8-j));
+			c[0] = jlu32l(m, a, hlen);
+			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; lz) 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
-#endif
 #include 
+#include 
+#include 
+#include 
 
-#include "findme.h"
 #include "poptint.h"
 
-#ifndef DBL_EPSILON
-#define DBL_EPSILON 2.2204460492503131e-16
+#ifdef HAVE_STDALIGN_H
+#include 
+#define ALIGNOF(x) alignof(x)
+#elif defined __GNUC__
+#define ALIGNOF(x) __alignof__(x)
+#else
+#define ALIGNOF(x) sizeof(x)
 #endif
 
 #ifdef	MYDEBUG
-/*@unchecked@*/
 int _popt_debug = 0;
 #endif
 
-#if !defined(HAVE_STRERROR) && !defined(__LCLINT__)
+unsigned int _poptArgMask = POPT_ARG_MASK;
+unsigned int _poptGroupMask = POPT_GROUP_MASK;
+
+#if !defined(HAVE_STRERROR)
 static char * strerror(int errno)
 {
     extern int sys_nerr;
@@ -41,7 +48,6 @@ static char * strerror(int errno)
 #endif
 
 #ifdef MYDEBUG
-/*@unused@*/
 static void prtcon(const char *msg, poptContext con)
 {
     if (msg) fprintf(stderr, "%s", msg);
@@ -60,119 +66,93 @@ void poptSetExecPath(poptContext con, const char * path, int allowAbsolute)
     con->execPath = _free(con->execPath);
     con->execPath = xstrdup(path);
     con->execAbsolute = allowAbsolute;
-    /*@-nullstate@*/ /* LCL: con->execPath not NULL */
     return;
-    /*@=nullstate@*/
 }
 
 static void invokeCallbacksPRE(poptContext con, const struct poptOption * opt)
-	/*@globals internalState@*/
-	/*@modifies internalState@*/
 {
     if (opt != NULL)
     for (; opt->longName || opt->shortName || opt->arg; opt++) {
-	if (opt->arg == NULL) continue;		/* XXX program error. */
-	if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
-	    void * arg = opt->arg;
-/*@-branchstate@*/
-	    /* XXX sick hack to preserve pretense of ABI. */
-	    if (arg == poptHelpOptions) arg = poptHelpOptionsI18N;
-/*@=branchstate@*/
-	    /* Recurse on included sub-tables. */
-	    invokeCallbacksPRE(con, arg);
-	} else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK &&
-		   (opt->argInfo & POPT_CBFLAG_PRE))
-	{   /*@-castfcnptr@*/
-	    poptCallbackType cb = (poptCallbackType)opt->arg;
-	    /*@=castfcnptr@*/
-	    /* Perform callback. */
-	    /*@-noeffectuncon @*/
-	    cb(con, POPT_CALLBACK_REASON_PRE, NULL, NULL, opt->descrip);
-	    /*@=noeffectuncon @*/
+	poptArg arg = { .ptr = opt->arg };
+	if (arg.ptr)
+	switch (poptArgType(opt)) {
+	case POPT_ARG_INCLUDE_TABLE:	/* Recurse on included sub-tables. */
+	    poptSubstituteHelpI18N(arg.opt);	/* XXX side effects */
+	    invokeCallbacksPRE(con, arg.opt);
+	    break;
+	case POPT_ARG_CALLBACK:		/* Perform callback. */
+	    if (!CBF_ISSET(opt, PRE))
+		break;
+	    arg.cb(con, POPT_CALLBACK_REASON_PRE, NULL, NULL, opt->descrip);
+	    break;
 	}
     }
 }
 
 static void invokeCallbacksPOST(poptContext con, const struct poptOption * opt)
-	/*@globals internalState@*/
-	/*@modifies internalState@*/
 {
     if (opt != NULL)
     for (; opt->longName || opt->shortName || opt->arg; opt++) {
-	if (opt->arg == NULL) continue;		/* XXX program error. */
-	if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
-	    void * arg = opt->arg;
-/*@-branchstate@*/
-	    /* XXX sick hack to preserve pretense of ABI. */
-	    if (arg == poptHelpOptions) arg = poptHelpOptionsI18N;
-/*@=branchstate@*/
-	    /* Recurse on included sub-tables. */
-	    invokeCallbacksPOST(con, arg);
-	} else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK &&
-		   (opt->argInfo & POPT_CBFLAG_POST))
-	{   /*@-castfcnptr@*/
-	    poptCallbackType cb = (poptCallbackType)opt->arg;
-	    /*@=castfcnptr@*/
-	    /* Perform callback. */
-	    /*@-noeffectuncon @*/
-	    cb(con, POPT_CALLBACK_REASON_POST, NULL, NULL, opt->descrip);
-	    /*@=noeffectuncon @*/
+	poptArg arg = { .ptr = opt->arg };
+	if (arg.ptr)
+	switch (poptArgType(opt)) {
+	case POPT_ARG_INCLUDE_TABLE:	/* Recurse on included sub-tables. */
+	    poptSubstituteHelpI18N(arg.opt);	/* XXX side effects */
+	    invokeCallbacksPOST(con, arg.opt);
+	    break;
+	case POPT_ARG_CALLBACK:		/* Perform callback. */
+	    if (!CBF_ISSET(opt, POST))
+		break;
+	    arg.cb(con, POPT_CALLBACK_REASON_POST, NULL, NULL, opt->descrip);
+	    break;
 	}
     }
 }
 
 static void invokeCallbacksOPTION(poptContext con,
-				  const struct poptOption * opt,
-				  const struct poptOption * myOpt,
-				  /*@null@*/ const void * myData, int shorty)
-	/*@globals internalState@*/
-	/*@modifies internalState@*/
+				const struct poptOption * opt,
+				const struct poptOption * myOpt,
+				const void * myData, int shorty)
 {
     const struct poptOption * cbopt = NULL;
+    poptArg cbarg = { .ptr = NULL };
 
     if (opt != NULL)
     for (; opt->longName || opt->shortName || opt->arg; opt++) {
-	if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
-	    void * arg = opt->arg;
-/*@-branchstate@*/
-	    /* XXX sick hack to preserve pretense of ABI. */
-	    if (arg == poptHelpOptions) arg = poptHelpOptionsI18N;
-/*@=branchstate@*/
-	    /* Recurse on included sub-tables. */
-	    if (opt->arg != NULL)	/* XXX program error */
+	poptArg arg = { .ptr = opt->arg };
+	switch (poptArgType(opt)) {
+	case POPT_ARG_INCLUDE_TABLE:	/* Recurse on included sub-tables. */
+	    poptSubstituteHelpI18N(arg.opt);	/* XXX side effects */
+	    if (opt->arg != NULL)
 		invokeCallbacksOPTION(con, opt->arg, myOpt, myData, shorty);
-	} else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK &&
-		  !(opt->argInfo & POPT_CBFLAG_SKIPOPTION)) {
-	    /* Save callback info. */
+	    break;
+	case POPT_ARG_CALLBACK:		/* Save callback info. */
+	    if (CBF_ISSET(opt, SKIPOPTION))
+		break;
 	    cbopt = opt;
-	} else if (cbopt != NULL &&
-		   ((myOpt->shortName && opt->shortName && shorty &&
-			myOpt->shortName == opt->shortName) ||
-		    (myOpt->longName && opt->longName &&
-		/*@-nullpass@*/		/* LCL: opt->longName != NULL */
+	    cbarg.ptr = opt->arg;
+	    break;
+	default:		/* Perform callback on matching option. */
+	    if (cbopt == NULL || cbarg.cb == NULL)
+		break;
+	    if ((myOpt->shortName && opt->shortName && shorty &&
+			myOpt->shortName == opt->shortName)
+	     || (myOpt->longName != NULL && opt->longName != NULL &&
 			!strcmp(myOpt->longName, opt->longName)))
-		/*@=nullpass@*/
-		   )
-	{   /*@-castfcnptr@*/
-	    poptCallbackType cb = (poptCallbackType)cbopt->arg;
-	    /*@=castfcnptr@*/
-	    const void * cbData = (cbopt->descrip ? cbopt->descrip : myData);
-	    /* Perform callback. */
-	    if (cb != NULL) {	/* XXX program error */
-		/*@-noeffectuncon @*/
-		cb(con, POPT_CALLBACK_REASON_OPTION, myOpt,
-			con->os->nextArg, cbData);
-		/*@=noeffectuncon @*/
+	    {	const void *cbData = (cbopt->descrip ? cbopt->descrip : myData);
+		cbarg.cb(con, POPT_CALLBACK_REASON_OPTION,
+			myOpt, con->os->nextArg, cbData);
+		/* Terminate (unless explcitly continuing). */
+		if (!CBF_ISSET(cbopt, CONTINUE))
+		    return;
 	    }
-	    /* Terminate (unless explcitly continuing). */
-	    if (!(cbopt->argInfo & POPT_CBFLAG_CONTINUE))
-		return;
+	    break;
 	}
     }
 }
 
 poptContext poptGetContext(const char * name, int argc, const char ** argv,
-			   const struct poptOption * options, int flags)
+			const struct poptOption * options, unsigned int flags)
 {
     poptContext con = malloc(sizeof(*con));
 
@@ -181,58 +161,44 @@ poptContext poptGetContext(const char * name, int argc, const char ** argv,
 
     con->os = con->optionStack;
     con->os->argc = argc;
-    /*@-dependenttrans -assignexpose@*/	/* FIX: W2DO? */
     con->os->argv = argv;
-    /*@=dependenttrans =assignexpose@*/
     con->os->argb = NULL;
 
     if (!(flags & POPT_CONTEXT_KEEP_FIRST))
-	con->os->next = 1;			/* skip argv[0] */
+	con->os->next = 1;		/* skip argv[0] */
 
-    con->leftovers = calloc( (argc + 1), sizeof(*con->leftovers) );
-    /*@-dependenttrans -assignexpose@*/	/* FIX: W2DO? */
+    con->leftovers = calloc( (size_t)(argc + 1), sizeof(*con->leftovers) );
+    con->allocLeftovers = argc + 1;
     con->options = options;
-    /*@=dependenttrans =assignexpose@*/
     con->aliases = NULL;
     con->numAliases = 0;
     con->flags = flags;
     con->execs = NULL;
     con->numExecs = 0;
+    con->execFail = NULL;
     con->finalArgvAlloced = argc * 2;
-    con->finalArgv = calloc( con->finalArgvAlloced, sizeof(*con->finalArgv) );
+    con->finalArgv = calloc( (size_t)con->finalArgvAlloced, sizeof(*con->finalArgv) );
     con->execAbsolute = 1;
     con->arg_strip = NULL;
 
     if (getenv("POSIXLY_CORRECT") || getenv("POSIX_ME_HARDER"))
 	con->flags |= POPT_CONTEXT_POSIXMEHARDER;
 
-    if (name) {
-	size_t bufsize = strlen(name) + 1;
-	char * t = malloc(bufsize);
-	if (t) {
-	    strlcpy(t, name, bufsize);
-	    con->appName = t;
-	}
-    }
+    if (name)
+	con->appName = xstrdup(name);
 
-    /*@-internalglobs@*/
     invokeCallbacksPRE(con, con->options);
-    /*@=internalglobs@*/
 
     return con;
 }
 
-static void cleanOSE(/*@special@*/ struct optionStackEntry *os)
-	/*@uses os @*/
-	/*@releases os->nextArg, os->argv, os->argb @*/
-	/*@modifies os @*/
+static void cleanOSE(struct optionStackEntry *os)
 {
     os->nextArg = _free(os->nextArg);
     os->argv = _free(os->argv);
     os->argb = PBM_FREE(os->argb);
 }
 
-/*@-boundswrite@*/
 void poptResetContext(poptContext con)
 {
     int i;
@@ -244,36 +210,34 @@ void poptResetContext(poptContext con)
     con->os->argb = PBM_FREE(con->os->argb);
     con->os->currAlias = NULL;
     con->os->nextCharArg = NULL;
-    con->os->nextArg = NULL;
-    con->os->next = 1;			/* skip argv[0] */
+    con->os->nextArg = _free(con->os->nextArg);
+    if (!(con->flags & POPT_CONTEXT_KEEP_FIRST))
+	con->os->next = 1;		/* skip argv[0] */
+    else
+	con->os->next = 0;
 
+    for (i = 0; i < con->numLeftovers; i++) {
+        con->leftovers[i] = _free(con->leftovers[i]);
+    }
     con->numLeftovers = 0;
     con->nextLeftover = 0;
     con->restLeftover = 0;
     con->doExec = NULL;
+    con->execFail = _free(con->execFail);
 
     if (con->finalArgv != NULL)
     for (i = 0; i < con->finalArgvCount; i++) {
-	/*@-unqualifiedtrans@*/		/* FIX: typedef double indirection. */
 	con->finalArgv[i] = _free(con->finalArgv[i]);
-	/*@=unqualifiedtrans@*/
     }
 
     con->finalArgvCount = 0;
     con->arg_strip = PBM_FREE(con->arg_strip);
-    /*@-nullstate@*/	/* FIX: con->finalArgv != NULL */
     return;
-    /*@=nullstate@*/
 }
-/*@=boundswrite@*/
 
 /* Only one of longName, shortName should be set, not both. */
-/*@-boundswrite@*/
-static int handleExec(/*@special@*/ poptContext con,
-		/*@null@*/ const char * longName, char shortName)
-	/*@uses con->execs, con->numExecs, con->flags, con->doExec,
-		con->finalArgv, con->finalArgvAlloced, con->finalArgvCount @*/
-	/*@modifies con @*/
+static int handleExec(poptContext con,
+		const char * longName, char shortName)
 {
     poptItem item;
     int i;
@@ -311,40 +275,75 @@ static int handleExec(/*@special@*/ poptContext con,
 
     i = con->finalArgvCount++;
     if (con->finalArgv != NULL)	/* XXX can't happen */
-    {	size_t bufsize = (longName ? strlen(longName) : 0) + 3;
-	char *s  = malloc(bufsize);
+    {	char *s  = malloc((longName ? strlen(longName) : 0) + sizeof("--"));
 	if (s != NULL) {	/* XXX can't happen */
+	    con->finalArgv[i] = s;
+	    *s++ = '-';
 	    if (longName)
-		snprintf(s, bufsize, "--%s", longName);
+		s = stpcpy( stpcpy(s, "-"), longName);
 	    else
-		snprintf(s, bufsize, "-%c", shortName);
-	    con->finalArgv[i] = s;
+		*s++ = shortName;
+	    *s = '\0';
 	} else
 	    con->finalArgv[i] = NULL;
     }
 
-    /*@-nullstate@*/	/* FIX: con->finalArgv[] == NULL */
     return 1;
-    /*@=nullstate@*/
 }
-/*@=boundswrite@*/
+
+/**
+ * Compare long option for equality, adjusting for POPT_ARGFLAG_TOGGLE.
+ * @param opt           option
+ * @param longName	arg option
+ * @param longNameLen	arg option length
+ * @return		does long option match?
+ */
+static int
+longOptionStrcmp(const struct poptOption * opt,
+		const char * longName, size_t longNameLen)
+{
+    const char * optLongName = opt->longName;
+    int rc;
+
+    if (optLongName == NULL || longName == NULL)	/* XXX can't heppen */
+	return 0;
+
+    if (F_ISSET(opt, TOGGLE)) {
+	if (optLongName[0] == 'n' && optLongName[1] == 'o') {
+	    optLongName += sizeof("no") - 1;
+	    if (optLongName[0] == '-')
+		optLongName++;
+	}
+	if (longName[0] == 'n' && longName[1] == 'o') {
+	    longName += sizeof("no") - 1;
+	    longNameLen -= sizeof("no") - 1;
+	    if (longName[0] == '-') {
+		longName++;
+		longNameLen--;
+	    }
+	}
+    }
+    rc = (int)(strlen(optLongName) == longNameLen);
+    if (rc)
+	rc = (int)(strncmp(optLongName, longName, longNameLen) == 0);
+    return rc;
+}
 
 /* Only one of longName, shortName may be set at a time */
-static int handleAlias(/*@special@*/ poptContext con,
-		/*@null@*/ const char * longName, char shortName,
-		/*@exposed@*/ /*@null@*/ const char * nextCharArg)
-	/*@uses con->aliases, con->numAliases, con->optionStack, con->os,
-		con->os->currAlias, con->os->currAlias->option.longName @*/
-	/*@modifies con @*/
+static int handleAlias(poptContext con,
+		const char * longName, size_t longNameLen,
+		char shortName,
+		const char * nextArg)
 {
     poptItem item = con->os->currAlias;
     int rc;
     int i;
 
     if (item) {
-	if (longName && (item->option.longName &&
-		!strcmp(longName, item->option.longName)))
+	if (longName && item->option.longName != NULL
+	 && longOptionStrcmp(&item->option, longName, longNameLen))
 	    return 0;
+	else
 	if (shortName && shortName == item->option.shortName)
 	    return 0;
     }
@@ -354,10 +353,12 @@ static int handleAlias(/*@special@*/ poptContext con,
 
     for (i = con->numAliases - 1; i >= 0; i--) {
 	item = con->aliases + i;
-	if (longName && !(item->option.longName &&
-			!strcmp(longName, item->option.longName)))
-	    continue;
-	else if (shortName != item->option.shortName)
+	if (longName) {
+	    if (item->option.longName == NULL)
+		continue;
+	    if (!longOptionStrcmp(&item->option, longName, longNameLen))
+		continue;
+	} else if (shortName != item->option.shortName)
 	    continue;
 	break;
     }
@@ -366,10 +367,8 @@ static int handleAlias(/*@special@*/ poptContext con,
     if ((con->os - con->optionStack + 1) == POPT_OPTION_DEPTH)
 	return POPT_ERROR_OPTSTOODEEP;
 
-/*@-boundsread@*/
-    if (nextCharArg && *nextCharArg)
-	con->os->nextCharArg = nextCharArg;
-/*@=boundsread@*/
+    if (longName == NULL && nextArg != NULL && *nextArg != '\0')
+	con->os->nextCharArg = nextArg;
 
     con->os++;
     con->os->next = 0;
@@ -377,21 +376,82 @@ static int handleAlias(/*@special@*/ poptContext con,
     con->os->nextArg = NULL;
     con->os->nextCharArg = NULL;
     con->os->currAlias = con->aliases + i;
-    rc = poptDupArgv(con->os->currAlias->argc, con->os->currAlias->argv,
-		&con->os->argc, &con->os->argv);
+    {	const char ** av;
+	int ac = con->os->currAlias->argc;
+	/* Append --foo=bar arg to alias argv array (if present). */ 
+	if (longName && nextArg != NULL && *nextArg != '\0') {
+	    av = malloc((ac + 1 + 1) * sizeof(*av));
+	    if (av != NULL) {	/* XXX won't happen. */
+		for (i = 0; i < ac; i++) {
+		    av[i] = con->os->currAlias->argv[i];
+		}
+		av[ac++] = nextArg;
+		av[ac] = NULL;
+	    } else	/* XXX revert to old popt behavior if malloc fails. */
+		av = con->os->currAlias->argv;
+	} else
+	    av = con->os->currAlias->argv;
+	rc = poptDupArgv(ac, av, &con->os->argc, &con->os->argv);
+	if (av != NULL && av != con->os->currAlias->argv)
+	    free(av);
+    }
     con->os->argb = NULL;
 
     return (rc ? rc : 1);
 }
 
-/*@-bounds -boundswrite @*/
+/**
+ * Return absolute path to executable by searching PATH.
+ * @param argv0		name of executable
+ * @return		(malloc'd) absolute path to executable (or NULL)
+ */
+static
+const char * findProgramPath(const char * argv0)
+{
+    char *path = NULL, *s = NULL, *se;
+    char *t = NULL;
+
+    if (argv0 == NULL) return NULL;	/* XXX can't happen */
+
+    /* If there is a / in argv[0], it has to be an absolute path. */
+    /* XXX Hmmm, why not if (argv0[0] == '/') ... instead? */
+    if (strchr(argv0, '/'))
+	return xstrdup(argv0);
+
+    if ((path = getenv("PATH")) == NULL || (path = xstrdup(path)) == NULL)
+	return NULL;
+
+    /* The return buffer in t is big enough for any path. */
+    if ((t = malloc(strlen(path) + strlen(argv0) + sizeof("/"))) != NULL)
+    for (s = path; s && *s; s = se) {
+
+	/* Snip PATH element into [s,se). */
+	if ((se = strchr(s, ':')))
+	    *se++ = '\0';
+
+	/* Append argv0 to PATH element. */
+	(void) stpcpy(stpcpy(stpcpy(t, s), "/"), argv0);
+
+	/* If file is executable, bingo! */
+	if (!access(t, X_OK))
+	    break;
+    }
+
+    /* If no executable was found in PATH, return NULL. */
+    if (!(s && *s) && t != NULL)
+	t = _free(t);
+    path = _free(path);
+
+    return t;
+}
+
 static int execCommand(poptContext con)
-	/*@globals internalState @*/
-	/*@modifies internalState @*/
 {
     poptItem item = con->doExec;
-    const char ** argv;
+    poptArgv argv = NULL;
     int argc = 0;
+    int rc;
+    int ec = POPT_ERROR_ERRNO;
 
     if (item == NULL) /*XXX can't happen*/
 	return POPT_ERROR_NOARG;
@@ -405,13 +465,17 @@ static int execCommand(poptContext con)
     if (argv == NULL) return POPT_ERROR_MALLOC;
 
     if (!strchr(item->argv[0], '/') && con->execPath != NULL) {
-	size_t bufsize = strlen(con->execPath) + strlen(item->argv[0]) + sizeof "/";
-	char *s = alloca(bufsize);
-	snprintf(s, bufsize, "%s/%s", con->execPath, item->argv[0]);
+	char *s = malloc(strlen(con->execPath) + strlen(item->argv[0]) + sizeof("/"));
+	if (s)
+	    (void)stpcpy(stpcpy(stpcpy(s, con->execPath), "/"), item->argv[0]);
+
 	argv[argc] = s;
     } else
 	argv[argc] = findProgramPath(item->argv[0]);
-    if (argv[argc++] == NULL) return POPT_ERROR_NOARG;
+    if (argv[argc++] == NULL) {
+	ec = POPT_ERROR_NOARG;
+	goto exit;
+    }
 
     if (item->argc > 1) {
 	memcpy(argv + argc, item->argv + 1, sizeof(*argv) * (item->argc - 1));
@@ -431,12 +495,11 @@ static int execCommand(poptContext con)
 
     argv[argc] = NULL;
 
-  {
-#ifdef __hpux
-    int rc = setresgid(getgid(), getgid(),-1);
-    if (rc) return POPT_ERROR_ERRNO;
+#if defined(hpux) || defined(__hpux)
+    rc = setresgid(getgid(), getgid(),-1);
+    if (rc) goto exit;
     rc = setresuid(getuid(), getuid(),-1);
-    if (rc) return POPT_ERROR_ERRNO;
+    if (rc) goto exit;
 #else
 /*
  * XXX " ... on BSD systems setuid() should be preferred over setreuid()"
@@ -444,27 +507,27 @@ static int execCommand(poptContext con)
  * XXX	from Norbert Warmuth 
  */
 #if defined(HAVE_SETUID)
-    int rc = setgid(getgid());
-    if (rc) return POPT_ERROR_ERRNO;
+    rc = setgid(getgid());
+    if (rc) goto exit;
     rc = setuid(getuid());
-    if (rc) return POPT_ERROR_ERRNO;
+    if (rc) goto exit;
 #elif defined (HAVE_SETREUID)
-    int rc = setregid(getgid(), getgid());
-    if (rc) return POPT_ERROR_ERRNO;
+    rc = setregid(getgid(), getgid());
+    if (rc) goto exit;
     rc = setreuid(getuid(), getuid());
-    if (rc) return POPT_ERROR_ERRNO;
+    if (rc) goto exit;
 #else
-    ; /* Can't drop privileges */
+    /* refuse to exec if we cannot drop suid/sgid privileges */
+    if (getuid() != geteuid() || getgid() != getegid()) {
+	errno = ENOTSUP;
+	goto exit;
+    }
 #endif
 #endif
-  }
-
-    if (argv[0] == NULL)
-	return POPT_ERROR_NOARG;
 
 #ifdef	MYDEBUG
 if (_popt_debug)
-    {	const char ** avp;
+    {	poptArgv avp;
 	fprintf(stderr, "==> execvp(%s) argv[%d]:", argv[0], argc);
 	for (avp = argv; *avp; avp++)
 	    fprintf(stderr, " '%s'", *avp);
@@ -472,56 +535,65 @@ if (_popt_debug)
     }
 #endif
 
-    execvp(argv[0], (char *const *)argv);
+    rc = execvp(argv[0], (char *const *)argv);
 
-    return POPT_ERROR_ERRNO;
+    /* only reached on execvp() failure */
+    con->execFail = xstrdup(argv[0]);
+
+exit:
+    if (argv) {
+        if (argv[0])
+            free((void *)argv[0]);
+        free(argv);
+    }
+    return ec;
 }
-/*@=bounds =boundswrite @*/
 
-/*@-boundswrite@*/
-/*@observer@*/ /*@null@*/ static const struct poptOption *
-findOption(const struct poptOption * opt, /*@null@*/ const char * longName,
+static const struct poptOption *
+findOption(const struct poptOption * opt,
+		const char * longName, size_t longNameLen,
 		char shortName,
-		/*@null@*/ /*@out@*/ poptCallbackType * callback,
-		/*@null@*/ /*@out@*/ const void ** callbackData,
-		int singleDash)
-	/*@modifies *callback, *callbackData */
+		poptCallbackType * callback,
+		const void ** callbackData,
+		unsigned int argInfo)
 {
     const struct poptOption * cb = NULL;
+    poptArg cbarg = { .ptr = NULL };
 
     /* This happens when a single - is given */
-    if (singleDash && !shortName && (longName && *longName == '\0'))
+    if (LF_ISSET(ONEDASH) && !shortName && (longName && *longName == '\0'))
 	shortName = '-';
 
     for (; opt->longName || opt->shortName || opt->arg; opt++) {
+	poptArg arg = { .ptr = opt->arg };
 
-	if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
-	    const struct poptOption * opt2;
-	    void * arg = opt->arg;
-
-/*@-branchstate@*/
-	    /* XXX sick hack to preserve pretense of ABI. */
-	    if (arg == poptHelpOptions) arg = poptHelpOptionsI18N;
-/*@=branchstate@*/
-	    /* Recurse on included sub-tables. */
-	    if (arg == NULL) continue;	/* XXX program error */
-	    opt2 = findOption(arg, longName, shortName, callback,
-			      callbackData, singleDash);
+	switch (poptArgType(opt)) {
+	case POPT_ARG_INCLUDE_TABLE:	/* Recurse on included sub-tables. */
+	{   const struct poptOption * opt2;
+
+	    poptSubstituteHelpI18N(arg.opt);	/* XXX side effects */
+	    if (arg.ptr == NULL) continue;	/* XXX program error */
+	    opt2 = findOption(arg.opt, longName, longNameLen, shortName, callback,
+			      callbackData, argInfo);
 	    if (opt2 == NULL) continue;
 	    /* Sub-table data will be inheirited if no data yet. */
-	    if (!(callback && *callback)) return opt2;
-	    if (!(callbackData && *callbackData == NULL)) return opt2;
-	    /*@-observertrans -dependenttrans @*/
-	    *callbackData = opt->descrip;
-	    /*@=observertrans =dependenttrans @*/
+	    if (callback && *callback
+	     && callbackData && *callbackData == NULL)
+		*callbackData = opt->descrip;
 	    return opt2;
-	} else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK) {
+	}   break;
+	case POPT_ARG_CALLBACK:
 	    cb = opt;
-	} else if (longName && opt->longName &&
-		   (!singleDash || (opt->argInfo & POPT_ARGFLAG_ONEDASH)) &&
-		/*@-nullpass@*/		/* LCL: opt->longName != NULL */
-		   !strcmp(longName, opt->longName))
-		/*@=nullpass@*/
+	    cbarg.ptr = opt->arg;
+	    continue;
+	    break;
+	default:
+	    break;
+	}
+
+	if (longName != NULL && opt->longName != NULL &&
+		   (!LF_ISSET(ONEDASH) || F_ISSET(opt, ONEDASH)) &&
+		   longOptionStrcmp(opt, longName, longNameLen))
 	{
 	    break;
 	} else if (shortName && shortName == opt->shortName) {
@@ -529,34 +601,19 @@ findOption(const struct poptOption * opt, /*@null@*/ const char * longName,
 	}
     }
 
-    if (!opt->longName && !opt->shortName)
+    if (opt->longName == NULL && !opt->shortName)
 	return NULL;
-    /*@-modobserver -mods @*/
-    if (callback) *callback = NULL;
-    if (callbackData) *callbackData = NULL;
-    if (cb) {
-	if (callback)
-	/*@-castfcnptr@*/
-	    *callback = (poptCallbackType)cb->arg;
-	/*@=castfcnptr@*/
-	if (!(cb->argInfo & POPT_CBFLAG_INC_DATA)) {
-	    if (callbackData)
-		/*@-observertrans@*/	/* FIX: typedef double indirection. */
-		*callbackData = cb->descrip;
-		/*@=observertrans@*/
-	}
-    }
-    /*@=modobserver =mods @*/
+
+    if (callback)
+	*callback = (cb ? cbarg.cb : NULL);
+    if (callbackData)
+	*callbackData = (cb && !CBF_ISSET(cb, INC_DATA) ? cb->descrip : NULL);
 
     return opt;
 }
-/*@=boundswrite@*/
 
-static const char * findNextArg(/*@special@*/ poptContext con,
+static const char * findNextArg(poptContext con,
 		unsigned argx, int delete_arg)
-	/*@uses con->optionStack, con->os,
-		con->os->next, con->os->argb, con->os->argc, con->os->argv @*/
-	/*@modifies con @*/
 {
     struct optionStackEntry * os = con->os;
     const char * arg;
@@ -568,151 +625,586 @@ static const char * findNextArg(/*@special@*/ poptContext con,
 	if (os->next == os->argc && os == con->optionStack) break;
 	if (os->argv != NULL)
 	for (i = os->next; i < os->argc; i++) {
-	    /*@-sizeoftype@*/
 	    if (os->argb && PBM_ISSET(i, os->argb))
-		/*@innercontinue@*/ continue;
+		continue;
 	    if (*os->argv[i] == '-')
-		/*@innercontinue@*/ continue;
+		continue;
 	    if (--argx > 0)
-		/*@innercontinue@*/ continue;
+		continue;
 	    arg = os->argv[i];
 	    if (delete_arg) {
 		if (os->argb == NULL) os->argb = PBM_ALLOC(os->argc);
 		if (os->argb != NULL)	/* XXX can't happen */
-		PBM_SET(i, os->argb);
+		    PBM_SET(i, os->argb);
 	    }
-	    /*@innerbreak@*/ break;
-	    /*@=sizeoftype@*/
+	    break;
 	}
 	if (os > con->optionStack) os--;
     } while (arg == NULL);
     return arg;
 }
 
-/*@-boundswrite@*/
-static /*@only@*/ /*@null@*/ const char *
-expandNextArg(/*@special@*/ poptContext con, const char * s)
-	/*@uses con->optionStack, con->os,
-		con->os->next, con->os->argb, con->os->argc, con->os->argv @*/
-	/*@modifies con @*/
+static const char *
+expandNextArg(poptContext con, const char * s)
 {
     const char * a = NULL;
-    size_t alen, pos;
-    char *t, *te;
+    char *t, *t_tmp, *te;
     size_t tn = strlen(s) + 1;
     char c;
 
-    te = t = malloc(tn);;
+    te = t = malloc(tn);
     if (t == NULL) return NULL;		/* XXX can't happen */
+    *t = '\0';
     while ((c = *s++) != '\0') {
 	switch (c) {
 #if 0	/* XXX can't do this */
 	case '\\':	/* escape */
 	    c = *s++;
-	    /*@switchbreak@*/ break;
+	    break;
 #endif
 	case '!':
 	    if (!(s[0] == '#' && s[1] == ':' && s[2] == '+'))
-		/*@switchbreak@*/ break;
+		break;
 	    /* XXX Make sure that findNextArg deletes only next arg. */
 	    if (a == NULL) {
-		if ((a = findNextArg(con, 1, 1)) == NULL)
-		    /*@switchbreak@*/ break;
+		if ((a = findNextArg(con, 1U, 1)) == NULL)
+		    break;
+	    }
+	    s += sizeof("#:+") - 1;
+
+	    tn += strlen(a);
+	    {   size_t pos = (size_t) (te - t);
+		if ((t_tmp = realloc(t, tn)) == NULL) {	/* XXX can't happen */
+		    free(t);
+		    return NULL;
+		}
+		t = t_tmp;
+		te = stpcpy(t + pos, a);
 	    }
-	    s += 3;
-
-	    alen = strlen(a);
-	    tn += alen;
-	    pos = te - t;
-	    t = realloc(t, tn);
-	    te = t + pos;
-	    memcpy(te, a, alen+1); te += alen;
 	    continue;
-	    /*@notreached@*/ /*@switchbreak@*/ break;
+	    break;
 	default:
-	    /*@switchbreak@*/ break;
+	    break;
 	}
 	*te++ = c;
     }
-    *te = '\0';
-    t = realloc(t, strlen(t) + 1);	/* XXX memory leak, hard to plug */
+    *te++ = '\0';
+    /* If the new string is longer than needed, shorten. */
+    if ((t + tn) > te) {
+	if ((te = realloc(t, (size_t)(te - t))) == NULL)
+	    free(t);
+	t = te;
+    }
     return t;
 }
-/*@=boundswrite@*/
 
-static void poptStripArg(/*@special@*/ poptContext con, int which)
-	/*@uses con->arg_strip, con->optionStack @*/
-	/*@defines con->arg_strip @*/
-	/*@modifies con @*/
+static void poptStripArg(poptContext con, int which)
 {
-    /*@-sizeoftype@*/
     if (con->arg_strip == NULL)
 	con->arg_strip = PBM_ALLOC(con->optionStack[0].argc);
     if (con->arg_strip != NULL)		/* XXX can't happen */
     PBM_SET(which, con->arg_strip);
-    /*@=sizeoftype@*/
-    /*@-compdef@*/ /* LCL: con->arg_strip udefined? */
     return;
-    /*@=compdef@*/
 }
 
-int poptSaveLong(long * arg, int argInfo, long aLong)
+unsigned int _poptBitsN = _POPT_BITS_N;
+unsigned int _poptBitsM = _POPT_BITS_M;
+unsigned int _poptBitsK = _POPT_BITS_K;
+
+static int _poptBitsNew(poptBits *bitsp)
+{
+    if (bitsp == NULL)
+	return POPT_ERROR_NULLARG;
+
+    /* XXX handle negated initialization. */
+    if (*bitsp == NULL) {
+	if (_poptBitsN == 0) {
+	    _poptBitsN = _POPT_BITS_N;
+	    _poptBitsM = _POPT_BITS_M;
+	}
+	if (_poptBitsM == 0U) _poptBitsM = (3 * _poptBitsN) / 2;
+	if (_poptBitsK == 0U || _poptBitsK > 32U) _poptBitsK = _POPT_BITS_K;
+	*bitsp = PBM_ALLOC(_poptBitsM-1);
+    }
+    return 0;
+}
+
+int poptBitsAdd(poptBits bits, const char * s)
+{
+    size_t ns = (s ? strlen(s) : 0);
+    uint32_t h0 = 0;
+    uint32_t h1 = 0;
+
+    if (bits == NULL || ns == 0)
+	return POPT_ERROR_NULLARG;
+
+    poptJlu32lpair(s, ns, &h0, &h1);
+
+    for (ns = 0; ns < (size_t)_poptBitsK; ns++) {
+        uint32_t h = h0 + ns * h1;
+        uint32_t ix = (h % _poptBitsM);
+        PBM_SET(ix, bits);
+    }
+    return 0;
+}
+
+int poptBitsChk(poptBits bits, const char * s)
+{
+    size_t ns = (s ? strlen(s) : 0);
+    uint32_t h0 = 0;
+    uint32_t h1 = 0;
+    int rc = 1;
+
+    if (bits == NULL || ns == 0)
+	return POPT_ERROR_NULLARG;
+
+    poptJlu32lpair(s, ns, &h0, &h1);
+
+    for (ns = 0; ns < (size_t)_poptBitsK; ns++) {
+        uint32_t h = h0 + ns * h1;
+        uint32_t ix = (h % _poptBitsM);
+        if (PBM_ISSET(ix, bits))
+            continue;
+        rc = 0;
+        break;
+    }
+    return rc;
+}
+
+int poptBitsClr(poptBits bits)
+{
+    static size_t nbw = (__PBM_NBITS/8);
+    size_t nw = (__PBM_IX(_poptBitsM-1) + 1);
+
+    if (bits == NULL)
+	return POPT_ERROR_NULLARG;
+    memset(bits, 0, nw * nbw);
+    return 0;
+}
+
+int poptBitsDel(poptBits bits, const char * s)
+{
+    size_t ns = (s ? strlen(s) : 0);
+    uint32_t h0 = 0;
+    uint32_t h1 = 0;
+
+    if (bits == NULL || ns == 0)
+	return POPT_ERROR_NULLARG;
+
+    poptJlu32lpair(s, ns, &h0, &h1);
+
+    for (ns = 0; ns < (size_t)_poptBitsK; ns++) {
+        uint32_t h = h0 + ns * h1;
+        uint32_t ix = (h % _poptBitsM);
+        PBM_CLR(ix, bits);
+    }
+    return 0;
+}
+
+int poptBitsIntersect(poptBits *ap, const poptBits b)
+{
+    __pbm_bits *abits;
+    __pbm_bits *bbits;
+    __pbm_bits rc = 0;
+    size_t nw = (__PBM_IX(_poptBitsM-1) + 1);
+    size_t i;
+
+    if (ap == NULL || b == NULL || _poptBitsNew(ap))
+	return POPT_ERROR_NULLARG;
+    abits = __PBM_BITS(*ap);
+    bbits = __PBM_BITS(b);
+
+    for (i = 0; i < nw; i++) {
+        abits[i] &= bbits[i];
+	rc |= abits[i];
+    }
+    return (rc ? 1 : 0);
+}
+
+int poptBitsUnion(poptBits *ap, const poptBits b)
+{
+    __pbm_bits *abits;
+    __pbm_bits *bbits;
+    __pbm_bits rc = 0;
+    size_t nw = (__PBM_IX(_poptBitsM-1) + 1);
+    size_t i;
+
+    if (ap == NULL || b == NULL || _poptBitsNew(ap))
+	return POPT_ERROR_NULLARG;
+    abits = __PBM_BITS(*ap);
+    bbits = __PBM_BITS(b);
+
+    for (i = 0; i < nw; i++) {
+        abits[i] |= bbits[i];
+	rc |= abits[i];
+    }
+    return (rc ? 1 : 0);
+}
+
+int poptBitsArgs(poptContext con, poptBits *ap)
+{
+    const char ** av;
+    int rc = 0;
+
+    if (con == NULL || ap == NULL || _poptBitsNew(ap) ||
+	con->leftovers == NULL || con->numLeftovers == con->nextLeftover)
+	return POPT_ERROR_NULLARG;
+
+    /* some apps like [like RPM ;-) ] need this NULL terminated */
+    con->leftovers[con->numLeftovers] = NULL;
+
+    for (av = con->leftovers + con->nextLeftover; *av != NULL; av++) {
+	if ((rc = poptBitsAdd(*ap, *av)) != 0)
+	    break;
+    }
+    return rc;
+}
+
+int poptSaveBits(poptBits * bitsp,
+		UNUSED(unsigned int argInfo), const char * s)
+{
+    char *tbuf = NULL;
+    char *t, *te;
+    int rc = 0;
+
+    if (bitsp == NULL || s == NULL || *s == '\0' || _poptBitsNew(bitsp))
+	return POPT_ERROR_NULLARG;
+
+    /* Parse comma separated attributes. */
+    te = tbuf = xstrdup(s);
+    while ((t = te) != NULL && *t) {
+	while (*te != '\0' && *te != ',')
+	    te++;
+	if (*te != '\0')
+	    *te++ = '\0';
+	/* XXX Ignore empty strings. */
+	if (*t == '\0')
+	    continue;
+	/* XXX Permit negated attributes. caveat emptor: false negatives. */
+	if (*t == '!') {
+	    t++;
+	    if ((rc = poptBitsChk(*bitsp, t)) > 0)
+		rc = poptBitsDel(*bitsp, t);
+	} else
+	    rc = poptBitsAdd(*bitsp, t);
+	if (rc)
+	    break;
+    }
+    tbuf = _free(tbuf);
+    return rc;
+}
+
+int poptSaveString(const char *** argvp,
+		UNUSED(unsigned int argInfo), const char * val)
+{
+    int argc = 0;
+
+    if (argvp == NULL || val == NULL)
+	return POPT_ERROR_NULLARG;
+
+    /* XXX likely needs an upper bound on argc. */
+    if (*argvp != NULL)
+    while ((*argvp)[argc] != NULL)
+	argc++;
+ 
+    if ((*argvp = xrealloc(*argvp, (argc + 1 + 1) * sizeof(**argvp))) != NULL) {
+	(*argvp)[argc++] = xstrdup(val);
+	(*argvp)[argc  ] = NULL;
+    }
+    return 0;
+}
+
+static long long poptRandomValue(long long limit)
+{
+#if defined(HAVE_SRANDOM)
+    static int seed = 1;
+
+    if (seed) {
+	srandom((unsigned)getpid());
+	srandom((unsigned)random());
+	seed = 0;
+    }
+
+    return random() % limit + 1;
+#else
+    /* XXX avoid adding POPT_ERROR_UNIMPLEMENTED to minimize i18n churn. */
+    return POPT_ERROR_BADOPERATION;
+#endif
+}
+
+int poptSaveLongLong(long long * arg, unsigned int argInfo, long long aLongLong)
 {
     /* XXX Check alignment, may fail on funky platforms. */
-    if (arg == NULL || (((unsigned long)arg) & (sizeof(*arg)-1)))
+    if (arg == NULL || (((unsigned long)arg) & (ALIGNOF(*arg)-1)))
 	return POPT_ERROR_NULLARG;
 
-    if (argInfo & POPT_ARGFLAG_NOT)
-	aLong = ~aLong;
-    switch (argInfo & POPT_ARGFLAG_LOGICALOPS) {
+    if (aLongLong != 0 && LF_ISSET(RANDOM)) {
+	aLongLong = poptRandomValue(aLongLong);
+	if (aLongLong < 0)
+	    return aLongLong;
+    }
+    if (LF_ISSET(NOT))
+	aLongLong = ~aLongLong;
+    switch (LF_ISSET(LOGICALOPS)) {
     case 0:
-	*arg = aLong;
+	*arg = aLongLong;
 	break;
     case POPT_ARGFLAG_OR:
-	*arg |= aLong;
+	*(unsigned long long *)arg |= (unsigned long long)aLongLong;
 	break;
     case POPT_ARGFLAG_AND:
-	*arg &= aLong;
+	*(unsigned long long *)arg &= (unsigned long long)aLongLong;
 	break;
     case POPT_ARGFLAG_XOR:
-	*arg ^= aLong;
+	*(unsigned long long *)arg ^= (unsigned long long)aLongLong;
 	break;
     default:
 	return POPT_ERROR_BADOPERATION;
-	/*@notreached@*/ break;
+	break;
     }
     return 0;
 }
 
-int poptSaveInt(/*@null@*/ int * arg, int argInfo, long aLong)
+int poptSaveLong(long * arg, unsigned int argInfo, long aLong)
 {
     /* XXX Check alignment, may fail on funky platforms. */
-    if (arg == NULL || (((unsigned long)arg) & (sizeof(*arg)-1)))
+    if (arg == NULL || (((unsigned long)arg) & (ALIGNOF(*arg)-1)))
 	return POPT_ERROR_NULLARG;
 
-    if (argInfo & POPT_ARGFLAG_NOT)
+    if (aLong != 0 && LF_ISSET(RANDOM)) {
+	aLong = (long)poptRandomValue(aLong);
+	if (aLong < 0)
+	    return aLong;
+    }
+    if (LF_ISSET(NOT))
 	aLong = ~aLong;
-    switch (argInfo & POPT_ARGFLAG_LOGICALOPS) {
-    case 0:
-	*arg = aLong;
+    switch (LF_ISSET(LOGICALOPS)) {
+    case 0:		   *arg = aLong; break;
+    case POPT_ARGFLAG_OR:  *(unsigned long *)arg |= (unsigned long)aLong; break;
+    case POPT_ARGFLAG_AND: *(unsigned long *)arg &= (unsigned long)aLong; break;
+    case POPT_ARGFLAG_XOR: *(unsigned long *)arg ^= (unsigned long)aLong; break;
+    default:
+	return POPT_ERROR_BADOPERATION;
 	break;
-    case POPT_ARGFLAG_OR:
-	*arg |= aLong;
+    }
+    return 0;
+}
+
+int poptSaveInt(int * arg, unsigned int argInfo, long aLong)
+{
+    /* XXX Check alignment, may fail on funky platforms. */
+    if (arg == NULL || (((unsigned long)arg) & (ALIGNOF(*arg)-1)))
+	return POPT_ERROR_NULLARG;
+
+    if (aLong != 0 && LF_ISSET(RANDOM)) {
+	aLong = (int)poptRandomValue(aLong);
+	if (aLong < 0)
+	    return aLong;
+    }
+    if (LF_ISSET(NOT))
+	aLong = ~aLong;
+    switch (LF_ISSET(LOGICALOPS)) {
+    case 0:		   *arg = (int) aLong;				break;
+    case POPT_ARGFLAG_OR:  *(unsigned int *)arg |= (unsigned int) aLong; break;
+    case POPT_ARGFLAG_AND: *(unsigned int *)arg &= (unsigned int) aLong; break;
+    case POPT_ARGFLAG_XOR: *(unsigned int *)arg ^= (unsigned int) aLong; break;
+    default:
+	return POPT_ERROR_BADOPERATION;
 	break;
-    case POPT_ARGFLAG_AND:
-	*arg &= aLong;
+    }
+    return 0;
+}
+
+int poptSaveShort(short * arg, unsigned int argInfo, long aLong)
+{
+    /* XXX Check alignment, may fail on funky platforms. */
+    if (arg == NULL || (((unsigned long)arg) & (ALIGNOF(*arg)-1)))
+	return POPT_ERROR_NULLARG;
+
+    if (aLong != 0 && LF_ISSET(RANDOM)) {
+	aLong = (short)poptRandomValue(aLong);
+	if (aLong < 0)
+	    return aLong;
+    }
+    if (LF_ISSET(NOT))
+	aLong = ~aLong;
+    switch (LF_ISSET(LOGICALOPS)) {
+    case 0:		   *arg = (short) aLong;
 	break;
-    case POPT_ARGFLAG_XOR:
-	*arg ^= aLong;
+    case POPT_ARGFLAG_OR:  *(unsigned short *)arg |= (unsigned short) aLong;
+	break;
+    case POPT_ARGFLAG_AND: *(unsigned short *)arg &= (unsigned short) aLong;
+	break;
+    case POPT_ARGFLAG_XOR: *(unsigned short *)arg ^= (unsigned short) aLong;
+	break;
+    default: return POPT_ERROR_BADOPERATION;
 	break;
-    default:
-	return POPT_ERROR_BADOPERATION;
-	/*@notreached@*/ break;
     }
     return 0;
 }
 
-/*@-boundswrite@*/
+/**
+ * Return argInfo field, handling POPT_ARGFLAG_TOGGLE overrides.
+ * @param con		context
+ * @param opt           option
+ * @return		argInfo
+ */
+static unsigned int poptArgInfo(poptContext con, const struct poptOption * opt)
+{
+    unsigned int argInfo = opt->argInfo;
+
+    if (con->os->argv != NULL && con->os->next > 0 && opt->longName != NULL)
+    if (LF_ISSET(TOGGLE)) {
+	const char * longName = con->os->argv[con->os->next-1];
+	while (*longName == '-') longName++;
+	/* XXX almost good enough but consider --[no]nofoo corner cases. */
+	if (longName[0] != opt->longName[0] || longName[1] != opt->longName[1])
+	{
+	    if (!LF_ISSET(XOR)) {	/* XXX dont toggle with XOR */
+		/* Toggle POPT_BIT_SET <=> POPT_BIT_CLR. */
+		if (LF_ISSET(LOGICALOPS))
+		    argInfo ^= (POPT_ARGFLAG_OR|POPT_ARGFLAG_AND);
+		argInfo ^= POPT_ARGFLAG_NOT;
+	    }
+	}
+    }
+    return argInfo;
+}
+
+/**
+ * Parse an integer expression.
+ * @retval *llp		integer expression value
+ * @param argInfo	integer expression type
+ * @param val		integer expression string
+ * @return		0 on success, otherwise POPT_* error.
+ */
+static int poptParseInteger(long long * llp,
+		UNUSED(unsigned int argInfo),
+		const char * val)
+{
+    if (val) {
+	char *end = NULL;
+	*llp = strtoll(val, &end, 0);
+
+	/* XXX parse scaling suffixes here. */
+
+	if (!(end && *end == '\0'))
+	    return POPT_ERROR_BADNUMBER;
+    } else
+	*llp = 0;
+    return 0;
+}
+
+/**
+ * Save the option argument through the (*opt->arg) pointer.
+ * @param con		context
+ * @param opt           option
+ * @return		0 on success, otherwise POPT_* error.
+ */
+static int poptSaveArg(poptContext con, const struct poptOption * opt)
+{
+    poptArg arg = { .ptr = opt->arg };
+    int rc = 0;		/* assume success */
+
+    switch (poptArgType(opt)) {
+    case POPT_ARG_BITSET:
+	/* XXX memory leak, application is responsible for free. */
+	rc = poptSaveBits(arg.ptr, opt->argInfo, con->os->nextArg);
+	break;
+    case POPT_ARG_ARGV:
+	/* XXX memory leak, application is responsible for free. */
+	rc = poptSaveString(arg.ptr, opt->argInfo, con->os->nextArg);
+	break;
+    case POPT_ARG_STRING:
+	/* XXX memory leak, application is responsible for free. */
+	arg.argv[0] = (con->os->nextArg) ? xstrdup(con->os->nextArg) : NULL;
+	break;
+
+    case POPT_ARG_INT:
+    case POPT_ARG_SHORT:
+    case POPT_ARG_LONG:
+    case POPT_ARG_LONGLONG:
+    {	unsigned int argInfo = poptArgInfo(con, opt);
+	long long aNUM = 0;
+
+	if ((rc = poptParseInteger(&aNUM, argInfo, con->os->nextArg)) != 0)
+	    break;
+
+	switch (poptArgType(opt)) {
+	case POPT_ARG_LONGLONG:
+/* XXX let's not demand C99 compiler flags for  quite yet. */
+#if !defined(LLONG_MAX)
+#   define LLONG_MAX    9223372036854775807LL
+#   define LLONG_MIN    (-LLONG_MAX - 1LL)
+#endif
+	    rc = !(aNUM == LLONG_MIN || aNUM == LLONG_MAX)
+		? poptSaveLongLong(arg.longlongp, argInfo, aNUM)
+		: POPT_ERROR_OVERFLOW;
+	    break;
+	case POPT_ARG_LONG:
+	    rc = !(aNUM < (long long)LONG_MIN || aNUM > (long long)LONG_MAX)
+		? poptSaveLong(arg.longp, argInfo, (long)aNUM)
+		: POPT_ERROR_OVERFLOW;
+	    break;
+	case POPT_ARG_INT:
+	    rc = !(aNUM < (long long)INT_MIN || aNUM > (long long)INT_MAX)
+		? poptSaveInt(arg.intp, argInfo, (long)aNUM)
+		: POPT_ERROR_OVERFLOW;
+	    break;
+	case POPT_ARG_SHORT:
+	    rc = !(aNUM < (long long)SHRT_MIN || aNUM > (long long)SHRT_MAX)
+		? poptSaveShort(arg.shortp, argInfo, (long)aNUM)
+		: POPT_ERROR_OVERFLOW;
+	    break;
+	}
+    }   break;
+
+    case POPT_ARG_FLOAT:
+    case POPT_ARG_DOUBLE:
+    {	char *end = NULL;
+	double aDouble = 0.0;
+
+	if (con->os->nextArg) {
+	    int saveerrno = errno;
+	    errno = 0;
+	    aDouble = strtod(con->os->nextArg, &end);
+	    if (errno == ERANGE) {
+		rc = POPT_ERROR_OVERFLOW;
+		break;
+	    }
+	    errno = saveerrno;
+	    if (*end != '\0') {
+		rc = POPT_ERROR_BADNUMBER;
+		break;
+	    }
+	}
+
+	switch (poptArgType(opt)) {
+	case POPT_ARG_DOUBLE:
+	    arg.doublep[0] = aDouble;
+	    break;
+	case POPT_ARG_FLOAT:
+#define POPT_ABS(a)	((((a) - 0.0) < DBL_EPSILON) ? -(a) : (a))
+	    if ((FLT_MIN - POPT_ABS(aDouble)) > DBL_EPSILON
+	     || (POPT_ABS(aDouble) - FLT_MAX) > DBL_EPSILON)
+		rc = POPT_ERROR_OVERFLOW;
+	    else
+		arg.floatp[0] = (float) aDouble;
+	    break;
+	}
+    }   break;
+    case POPT_ARG_MAINCALL:
+	con->maincall = opt->arg;
+	break;
+    default:
+	fprintf(stdout, POPT_("option type (%u) not implemented in popt\n"),
+		poptArgType(opt));
+	exit(EXIT_FAILURE);
+	break;
+    }
+    return rc;
+}
+
 /* returns 'val' element, -1 on last item, POPT_ERROR_* on error */
 int poptGetNextOpt(poptContext con)
 {
@@ -734,24 +1226,27 @@ int poptGetNextOpt(poptContext con)
 	    cleanOSE(con->os--);
 	}
 	if (!con->os->nextCharArg && con->os->next == con->os->argc) {
-	    /*@-internalglobs@*/
 	    invokeCallbacksPOST(con, con->options);
-	    /*@=internalglobs@*/
+
+	    if (con->maincall) {
+		(void) (*con->maincall) (con->finalArgvCount, con->finalArgv);
+		return -1;
+	    }
+
 	    if (con->doExec) return execCommand(con);
 	    return -1;
 	}
 
 	/* Process next long option */
 	if (!con->os->nextCharArg) {
-	    char * localOptString, * optString;
+	    const char * optString;
+            size_t optStringLen;
 	    int thisopt;
 
-	    /*@-sizeoftype@*/
 	    if (con->os->argb && PBM_ISSET(con->os->next, con->os->argb)) {
 		con->os->next++;
 		continue;
 	    }
-	    /*@=sizeoftype@*/
 	    thisopt = con->os->next;
 	    if (con->os->argv != NULL)	/* XXX can't happen */
 	    origOptString = con->os->argv[con->os->next++];
@@ -759,25 +1254,35 @@ int poptGetNextOpt(poptContext con)
 	    if (origOptString == NULL)	/* XXX can't happen */
 		return POPT_ERROR_BADOPT;
 
-	    if (con->restLeftover || *origOptString != '-') {
+	    if (con->restLeftover || *origOptString != '-' ||
+		(*origOptString == '-' && origOptString[1] == '\0'))
+	    {
 		if (con->flags & POPT_CONTEXT_POSIXMEHARDER)
 		    con->restLeftover = 1;
 		if (con->flags & POPT_CONTEXT_ARG_OPTS) {
 		    con->os->nextArg = xstrdup(origOptString);
 		    return 0;
 		}
-		if (con->leftovers != NULL)	/* XXX can't happen */
-		    con->leftovers[con->numLeftovers++] = origOptString;
+		if (con->leftovers != NULL) {	/* XXX can't happen */
+		    /* One might think we can never overflow the leftovers
+		       array.  Actually, that's true, as long as you don't
+		       use poptStuffArgs()... */
+		    if ((con->numLeftovers + 1) >= (con->allocLeftovers)) {
+			con->allocLeftovers += 10;
+			con->leftovers =
+			    realloc(con->leftovers,
+				    sizeof(*con->leftovers) * con->allocLeftovers);
+		    }
+		    con->leftovers[con->numLeftovers++]
+			= xstrdup(origOptString); /* so a free of a stuffed
+						     argv doesn't give us a
+						     dangling pointer */
+		}
 		continue;
 	    }
 
 	    /* Make a copy we can hack at */
-	    {   size_t bufsize = strlen(origOptString) + 1;
-		localOptString = optString = alloca(bufsize);
-		if (optString == NULL) /* XXX can't happen */
-		    return POPT_ERROR_BADOPT;
-		strlcpy(optString, origOptString, bufsize);
-	    }
+	    optString = origOptString;
 
 	    if (optString[0] == '\0')
 		return POPT_ERROR_BADOPT;
@@ -786,46 +1291,42 @@ int poptGetNextOpt(poptContext con)
 		con->restLeftover = 1;
 		continue;
 	    } else {
-		char *oe;
-		int singleDash;
+		const char *oe;
+		unsigned int argInfo = 0;
 
 		optString++;
 		if (*optString == '-')
-		    singleDash = 0, optString++;
+		    optString++;
 		else
-		    singleDash = 1;
+		    argInfo |= POPT_ARGFLAG_ONEDASH;
+
+		/* Check for "--long=arg" option. */
+		for (oe = optString; *oe && *oe != '='; oe++)
+		    {};
+		optStringLen = (size_t)(oe - optString);
+		if (*oe == '=')
+		    longArg = oe + 1;
 
 		/* XXX aliases with arg substitution need "--alias=arg" */
-		if (handleAlias(con, optString, '\0', NULL))
+		if (handleAlias(con, optString, optStringLen, '\0', longArg)) {
+		    longArg = NULL;
 		    continue;
+		}
 
 		if (handleExec(con, optString, '\0'))
 		    continue;
 
-		/* Check for "--long=arg" option. */
-		for (oe = optString; *oe && *oe != '='; oe++)
-		    {};
-		if (*oe == '=') {
-		    *oe++ = '\0';
-		    /* XXX longArg is mapped back to persistent storage. */
-		    longArg = origOptString + (oe - localOptString);
-		} else
-		    oe = NULL;
-
-		opt = findOption(con->options, optString, '\0', &cb, &cbData,
-				 singleDash);
-		if (!opt && !singleDash)
+		opt = findOption(con->options, optString, optStringLen, '\0', &cb, &cbData,
+				 argInfo);
+		if (!opt && !LF_ISSET(ONEDASH))
 		    return POPT_ERROR_BADOPT;
-		if (!opt && oe)
-		    oe[-1] = '='; /* restore overwritten '=' */
 	    }
 
 	    if (!opt) {
 		con->os->nextCharArg = origOptString + 1;
 		longArg = NULL;
 	    } else {
-		if (con->os == con->optionStack &&
-		   opt->argInfo & POPT_ARGFLAG_STRIP)
+		if (con->os == con->optionStack && F_ISSET(opt, STRIP))
 		{
 		    canstrip = 1;
 		    poptStripArg(con, thisopt);
@@ -835,66 +1336,63 @@ int poptGetNextOpt(poptContext con)
 	}
 
 	/* Process next short option */
-	/*@-branchstate@*/		/* FIX: W2DO? */
 	if (con->os->nextCharArg) {
-	    origOptString = con->os->nextCharArg;
+	    const char * nextCharArg = con->os->nextCharArg;
 
 	    con->os->nextCharArg = NULL;
 
-	    if (handleAlias(con, NULL, *origOptString, origOptString + 1))
+	    if (handleAlias(con, NULL, 0, *nextCharArg, nextCharArg + 1))
 		continue;
 
-	    if (handleExec(con, NULL, *origOptString)) {
+	    if (handleExec(con, NULL, *nextCharArg)) {
 		/* Restore rest of short options for further processing */
-		origOptString++;
-		if (*origOptString != '\0')
-		    con->os->nextCharArg = origOptString;
+		nextCharArg++;
+		if (*nextCharArg != '\0')
+		    con->os->nextCharArg = nextCharArg;
 		continue;
 	    }
 
-	    opt = findOption(con->options, NULL, *origOptString, &cb,
+	    opt = findOption(con->options, NULL, 0, *nextCharArg, &cb,
 			     &cbData, 0);
 	    if (!opt)
 		return POPT_ERROR_BADOPT;
 	    shorty = 1;
 
-	    origOptString++;
-	    if (*origOptString != '\0')
-		con->os->nextCharArg = origOptString;
+	    nextCharArg++;
+	    if (*nextCharArg != '\0')
+		con->os->nextCharArg = nextCharArg;
 	}
-	/*@=branchstate@*/
 
 	if (opt == NULL) return POPT_ERROR_BADOPT;	/* XXX can't happen */
-	if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_NONE
-	 || (opt->argInfo & POPT_ARG_MASK) == POPT_ARG_VAL) {
+	if (poptArgType(opt) == POPT_ARG_NONE || poptArgType(opt) == POPT_ARG_VAL) {
 	    if (longArg || (con->os->nextCharArg && con->os->nextCharArg[0] == '='))
 		return POPT_ERROR_UNWANTEDARG;
 	    if (opt->arg) {
-		long val = (opt->argInfo & POPT_ARG_MASK) == POPT_ARG_VAL ? opt->val : 1;
-		if (poptSaveInt((int *)opt->arg, opt->argInfo, val))
+		long val = poptArgType(opt) == POPT_ARG_VAL ? opt->val : 1;
+		unsigned int argInfo = poptArgInfo(con, opt);
+		if (poptSaveInt((int *)opt->arg, argInfo, val))
 		    return POPT_ERROR_BADOPERATION;
 	    }
 	} else {
+	    int rc;
+
 	    con->os->nextArg = _free(con->os->nextArg);
-	    /*@-usedef@*/	/* FIX: W2DO? */
 	    if (longArg) {
-	    /*@=usedef@*/
 		longArg = expandNextArg(con, longArg);
-		con->os->nextArg = longArg;
+		con->os->nextArg = (char *) longArg;
 	    } else if (con->os->nextCharArg) {
-		longArg = expandNextArg(con, con->os->nextCharArg + (con->os->nextCharArg[0] == '='));
-		con->os->nextArg = longArg;
+		longArg = expandNextArg(con, con->os->nextCharArg + (int)(*con->os->nextCharArg == '='));
+		con->os->nextArg = (char *) longArg;
 		con->os->nextCharArg = NULL;
 	    } else {
 		while (con->os->next == con->os->argc &&
-		       con->os > con->optionStack) {
+			con->os > con->optionStack)
+		{
 		    cleanOSE(con->os--);
 		}
 		if (con->os->next == con->os->argc) {
-		    if (!(opt->argInfo & POPT_ARGFLAG_OPTIONAL))
-			/*@-compdef@*/	/* FIX: con->os->argv not defined */
+		    if (!F_ISSET(opt, OPTIONAL))
 			return POPT_ERROR_NOARG;
-			/*@=compdef@*/
 		    con->os->nextArg = NULL;
 		} else {
 
@@ -902,98 +1400,35 @@ int poptGetNextOpt(poptContext con)
 		     * Make sure this isn't part of a short arg or the
 		     * result of an alias expansion.
 		     */
-		    if (con->os == con->optionStack &&
-			(opt->argInfo & POPT_ARGFLAG_STRIP) &&
-			canstrip) {
+		    if (con->os == con->optionStack
+		     && F_ISSET(opt, STRIP) && canstrip)
+		    {
 			poptStripArg(con, con->os->next);
 		    }
 		
 		    if (con->os->argv != NULL) {	/* XXX can't happen */
-			/* XXX watchout: subtle side-effects live here. */
-			longArg = con->os->argv[con->os->next++];
-			longArg = expandNextArg(con, longArg);
-			con->os->nextArg = longArg;
+			if (F_ISSET(opt, OPTIONAL) &&
+			    con->os->argv[con->os->next][0] == '-') {
+			    con->os->nextArg = NULL;
+			} else {
+			    /* XXX watchout: subtle side-effects live here. */
+			    longArg = con->os->argv[con->os->next++];
+			    longArg = expandNextArg(con, longArg);
+			    con->os->nextArg = (char *) longArg;
+			}
 		    }
 		}
 	    }
 	    longArg = NULL;
 
-	    if (opt->arg) {
-		switch (opt->argInfo & POPT_ARG_MASK) {
-		case POPT_ARG_STRING:
-		    /* XXX memory leak, hard to plug */
-		    *((const char **) opt->arg) = (con->os->nextArg)
-			? xstrdup(con->os->nextArg) : NULL;
-		    /*@switchbreak@*/ break;
-
-		case POPT_ARG_INT:
-		case POPT_ARG_LONG:
-		{   long aLong = 0;
-		    char *end;
-
-		    if (con->os->nextArg) {
-			aLong = strtol(con->os->nextArg, &end, 0);
-			if (!(end && *end == '\0'))
-			    return POPT_ERROR_BADNUMBER;
-		    }
-
-		    if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_LONG) {
-			if (aLong == LONG_MIN || aLong == LONG_MAX)
-			    return POPT_ERROR_OVERFLOW;
-			if (poptSaveLong((long *)opt->arg, opt->argInfo, aLong))
-			    return POPT_ERROR_BADOPERATION;
-		    } else {
-			if (aLong > INT_MAX || aLong < INT_MIN)
-			    return POPT_ERROR_OVERFLOW;
-			if (poptSaveInt((int *)opt->arg, opt->argInfo, aLong))
-			    return POPT_ERROR_BADOPERATION;
-		    }
-		}   /*@switchbreak@*/ break;
-
-		case POPT_ARG_FLOAT:
-		case POPT_ARG_DOUBLE:
-		{   double aDouble = 0.0;
-		    char *end;
-
-		    if (con->os->nextArg) {
-			/*@-mods@*/
-			int saveerrno = errno;
-			errno = 0;
-			aDouble = strtod(con->os->nextArg, &end);
-			if (errno == ERANGE)
-			    return POPT_ERROR_OVERFLOW;
-			errno = saveerrno;
-			/*@=mods@*/
-			if (*end != '\0')
-			    return POPT_ERROR_BADNUMBER;
-		    }
-
-		    if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_DOUBLE) {
-			*((double *) opt->arg) = aDouble;
-		    } else {
-#define MY_ABS(a) ((((a) - 0.0) < DBL_EPSILON) ? -(a) : (a))
-			if ((MY_ABS(aDouble) - FLT_MAX) > DBL_EPSILON)
-			    return POPT_ERROR_OVERFLOW;
-			if ((FLT_MIN - MY_ABS(aDouble)) > DBL_EPSILON)
-			    return POPT_ERROR_OVERFLOW;
-			*((float *) opt->arg) = aDouble;
-		    }
-		}   /*@switchbreak@*/ break;
-		default:
-		    fprintf(stdout,
-			POPT_("option type (%d) not implemented in popt\n"),
-			(opt->argInfo & POPT_ARG_MASK));
-		    exit(EXIT_FAILURE);
-		    /*@notreached@*/ /*@switchbreak@*/ break;
-		}
-	    }
+	   /* Save the option argument through a (*opt->arg) pointer. */
+	    if (opt->arg != NULL && (rc = poptSaveArg(con, opt)) != 0)
+		return rc;
 	}
 
-	if (cb) {
-	    /*@-internalglobs@*/
+	if (cb)
 	    invokeCallbacksOPTION(con, con->options, opt, cbData, shorty);
-	    /*@=internalglobs@*/
-	} else if (opt->val && ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_VAL))
+	else if (opt->val && (poptArgType(opt) != POPT_ARG_VAL))
 	    done = 1;
 
 	if ((con->finalArgvCount + 2) >= (con->finalArgvAlloced)) {
@@ -1003,46 +1438,43 @@ int poptGetNextOpt(poptContext con)
 	}
 
 	if (con->finalArgv != NULL)
-	{   ssize_t bufsize = (opt->longName ? strlen(opt->longName) : 0) + 3;
-	    char *s = malloc(bufsize);
+	{   char *s = malloc((opt->longName ? strlen(opt->longName) : 0) + sizeof("--"));
 	    if (s != NULL) {	/* XXX can't happen */
-		if (opt->longName)
-		    snprintf(s, bufsize, "%s%s",
-			((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"),
-			opt->longName);
-		else
-		    snprintf(s, bufsize, "-%c", opt->shortName);
 		con->finalArgv[con->finalArgvCount++] = s;
+		*s++ = '-';
+		if (opt->longName) {
+		    if (!F_ISSET(opt, ONEDASH))
+			*s++ = '-';
+		    s = stpcpy(s, opt->longName);
+		} else {
+		    *s++ = opt->shortName;
+		    *s = '\0';
+		}
 	    } else
 		con->finalArgv[con->finalArgvCount++] = NULL;
 	}
 
-	if (opt->arg && (opt->argInfo & POPT_ARG_MASK) == POPT_ARG_NONE)
-	    /*@-ifempty@*/ ; /*@=ifempty@*/
-	else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_VAL)
-	    /*@-ifempty@*/ ; /*@=ifempty@*/
-	else if ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_NONE) {
-	    if (con->finalArgv != NULL && con->os->nextArg)
+	if (opt->arg && poptArgType(opt) == POPT_ARG_NONE)
+	    ;
+	else if (poptArgType(opt) == POPT_ARG_VAL)
+	    ;
+	else if (poptArgType(opt) != POPT_ARG_NONE) {
+	    if (con->finalArgv != NULL && con->os->nextArg != NULL)
 	        con->finalArgv[con->finalArgvCount++] =
-			/*@-nullpass@*/	/* LCL: con->os->nextArg != NULL */
 			xstrdup(con->os->nextArg);
-			/*@=nullpass@*/
 	}
     }
 
     return (opt ? opt->val : -1);	/* XXX can't happen */
 }
-/*@=boundswrite@*/
 
-const char * poptGetOptArg(poptContext con)
+char * poptGetOptArg(poptContext con)
 {
-    const char * ret = NULL;
-    /*@-branchstate@*/
+    char * ret = NULL;
     if (con) {
 	ret = con->os->nextArg;
 	con->os->nextArg = NULL;
     }
-    /*@=branchstate@*/
     return ret;
 }
 
@@ -1062,7 +1494,6 @@ const char * poptPeekArg(poptContext con)
     return ret;
 }
 
-/*@-boundswrite@*/
 const char ** poptGetArgs(poptContext con)
 {
     if (con == NULL ||
@@ -1072,46 +1503,44 @@ const char ** poptGetArgs(poptContext con)
     /* some apps like [like RPM ;-) ] need this NULL terminated */
     con->leftovers[con->numLeftovers] = NULL;
 
-    /*@-nullret -nullstate @*/	/* FIX: typedef double indirection. */
     return (con->leftovers + con->nextLeftover);
-    /*@=nullret =nullstate @*/
 }
-/*@=boundswrite@*/
+
+static
+poptItem poptFreeItems(poptItem items, int nitems)
+{
+    if (items != NULL) {
+	poptItem item = items;
+	while (--nitems >= 0) {
+	    item->option.longName = _free(item->option.longName);
+	    item->option.descrip = _free(item->option.descrip);
+	    item->option.argDescrip = _free(item->option.argDescrip);
+	    item->argv = _free(item->argv);
+	    item++;
+	}
+	_free(items);
+    }
+    return NULL;
+}
 
 poptContext poptFreeContext(poptContext con)
 {
-    poptItem item;
     int i;
 
     if (con == NULL) return con;
     poptResetContext(con);
-    con->os->argb = _free(con->os->argb);
 
-    if (con->aliases != NULL)
-    for (i = 0; i < con->numAliases; i++) {
-	item = con->aliases + i;
-	/*@-modobserver -observertrans -dependenttrans@*/
-	item->option.longName = _free(item->option.longName);
-	item->option.descrip = _free(item->option.descrip);
-	item->option.argDescrip = _free(item->option.argDescrip);
-	/*@=modobserver =observertrans =dependenttrans@*/
-	item->argv = _free(item->argv);
-    }
-    con->aliases = _free(con->aliases);
+    con->aliases = poptFreeItems(con->aliases, con->numAliases);
+    con->numAliases = 0;
 
-    if (con->execs != NULL)
-    for (i = 0; i < con->numExecs; i++) {
-	item = con->execs + i;
-	/*@-modobserver -observertrans -dependenttrans@*/
-	item->option.longName = _free(item->option.longName);
-	item->option.descrip = _free(item->option.descrip);
-	item->option.argDescrip = _free(item->option.argDescrip);
-	/*@=modobserver =observertrans =dependenttrans@*/
-	item->argv = _free(item->argv);
-    }
-    con->execs = _free(con->execs);
+    con->execs = poptFreeItems(con->execs, con->numExecs);
+    con->numExecs = 0;
 
+    for (i = 0; i < con->numLeftovers; i++) {
+        con->leftovers[i] = _free(con->leftovers[i]);
+    }
     con->leftovers = _free(con->leftovers);
+
     con->finalArgv = _free(con->finalArgv);
     con->appName = _free(con->appName);
     con->otherHelp = _free(con->otherHelp);
@@ -1123,9 +1552,10 @@ poptContext poptFreeContext(poptContext con)
 }
 
 int poptAddAlias(poptContext con, struct poptAlias alias,
-		/*@unused@*/ UNUSED(int flags))
+		UNUSED(int flags))
 {
-    poptItem item = (poptItem) alloca(sizeof(*item));
+    struct poptItem_s item_buf;
+    poptItem item = &item_buf;
     memset(item, 0, sizeof(*item));
     item->option.longName = alias.longName;
     item->option.shortName = alias.shortName;
@@ -1139,11 +1569,9 @@ int poptAddAlias(poptContext con, struct poptAlias alias,
     return poptAddItem(con, item, 0);
 }
 
-/*@-boundswrite@*/
-/*@-mustmod@*/ /* LCL: con not modified? */
 int poptAddItem(poptContext con, poptItem newItem, int flags)
 {
-    poptItem * items, item;
+    poptItem * items, item_tmp, item;
     int * nitems;
 
     switch (flags) {
@@ -1157,12 +1585,13 @@ int poptAddItem(poptContext con, poptItem newItem, int flags)
 	break;
     default:
 	return 1;
-	/*@notreached@*/ break;
+	break;
     }
 
-    *items = realloc((*items), ((*nitems) + 1) * sizeof(**items));
-    if ((*items) == NULL)
+    item_tmp = realloc((*items), ((*nitems) + 1) * sizeof(**items));
+    if (item_tmp == NULL)
 	return 1;
+    *items = item_tmp;
 
     item = (*items) + (*nitems);
 
@@ -1183,19 +1612,23 @@ int poptAddItem(poptContext con, poptItem newItem, int flags)
 
     return 0;
 }
-/*@=mustmod@*/
-/*@=boundswrite@*/
 
-const char * poptBadOption(poptContext con, int flags)
+const char * poptBadOption(poptContext con, unsigned int flags)
 {
     struct optionStackEntry * os = NULL;
+    const char *badOpt = NULL;
+
+    if (con != NULL) {
+       /* Stupid hack to return something semi-meaningful from exec failure */
+       if (con->execFail) {
+           badOpt = con->execFail;
+       } else {
+           os = (flags & POPT_BADOPTION_NOALIAS) ? con->optionStack : con->os;
+           badOpt = os->argv[os->next - 1];
+       }
+    }
 
-    if (con != NULL)
-	os = (flags & POPT_BADOPTION_NOALIAS) ? con->optionStack : con->os;
-
-    /*@-nullderef@*/	/* LCL: os->argv != NULL */
-    return (os && os->argv ? os->argv[os->next - 1] : NULL);
-    /*@=nullderef@*/
+    return badOpt;
 }
 
 const char * poptStrerror(const int error)
@@ -1221,6 +1654,8 @@ const char * poptStrerror(const int error)
 	return POPT_("number too large or too small");
       case POPT_ERROR_MALLOC:
 	return POPT_("memory allocation failed");
+      case POPT_ERROR_BADCONFIG:
+	return POPT_("config file failed sanity test");
       case POPT_ERROR_ERRNO:
 	return strerror(errno);
       default:
@@ -1256,14 +1691,12 @@ const char * poptGetInvocationName(poptContext con)
     return (con->os->argv ? con->os->argv[0] : "");
 }
 
-/*@-boundswrite@*/
 int poptStrippedArgv(poptContext con, int argc, char ** argv)
 {
     int numargs = argc;
     int j = 1;
     int i;
     
-    /*@-sizeoftype@*/
     if (con->arg_strip)
     for (i = 1; i < argc; i++) {
 	if (PBM_ISSET(i, con->arg_strip))
@@ -1276,8 +1709,6 @@ int poptStrippedArgv(poptContext con, int argc, char ** argv)
 	argv[j] = (j < numargs) ? argv[i] : NULL;
 	j++;
     }
-    /*@=sizeoftype@*/
     
     return numargs;
 }
-/*@=boundswrite@*/
diff --git a/popt/popt.h b/popt/popt.h
index 8d85f7312..bd1606110 100644
--- a/popt/popt.h
+++ b/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_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 */
 
-#define POPT_ARG_MASK		0x0000FFFF
-/*@}*/
+#define POPT_ARG_MASK		0x000000FFU
+#define POPT_GROUP_MASK		0x0000FF00U
 
-/** \ingroup popt
+/**
  * \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_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_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		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)
-	/*@*/;
-
-/** \ingroup popt
- * Destroy context.
- * @param con		context
- * @return		NULL always
- */
-/*@null@*/
-poptContext poptFreeContext( /*@only@*/ /*@null@*/ poptContext con)
-	/*@modifies con @*/;
+const char * poptBadOption(poptContext con, unsigned int flags);
 
-/** \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
diff --git a/popt/poptconfig.c b/popt/poptconfig.c
index 9733d1529..bf201e26f 100644
--- a/popt/poptconfig.c
+++ b/popt/poptconfig.c
@@ -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 
+#include 
+#include 
+#include 
 
-/*@-compmempass@*/	/* FIX: item->option.longName kept, not dependent. */
-static void configLine(poptContext con, char * line)
-	/*@modifies con @*/
+#if defined(HAVE_FNMATCH_H)
+#include 
+
+#endif
+
+#if defined(HAVE_GLOB_H)
+#include 
+
+#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)
+{
+    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)
 {
-    size_t nameLength;
+    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;
+
+	/* 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(line, &item->argc, &item->argv)) return;
+    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;
-    }
+    char * b = NULL, *be;
+    size_t nb = 0;
+    const char *se;
+    char *t = NULL, *te;
+    int rc;
 
-    file = alloca(fileLength + 1);
-    if (read(fd, (char *)file, fileLength) != fileLength) {
-	rc = errno;
-	(void) close(fd);
-	errno = rc;
-	return POPT_ERROR_ERRNO;
+    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;
     }
-    if (close(fd) == -1)
-	return POPT_ERROR_ERRNO;
 
-/*@-boundswrite@*/
-    dst = buf = alloca(fileLength + 1);
+    if ((t = malloc(nb + 1)) == NULL)
+	goto exit;
+    te = t;
 
-    chptr = file;
-    end = (file + fileLength);
-    /*@-infloops@*/	/* LCL: can't detect chptr++ */
-    while (chptr < end) {
-	switch (*chptr) {
+    be = (b + nb);
+    for (se = b; se < be; se++) {
+	switch (*se) {
 	  case '\n':
-	    *dst = '\0';
-	    dst = buf;
-	    while (*dst && isSpace(dst)) dst++;
-	    if (*dst && *dst != '#')
-		configLine(con, dst);
-	    chptr++;
-	    /*@switchbreak@*/ break;
+	    *te = '\0';
+	    te = t;
+	    while (*te && _isspaceptr(te)) te++;
+	    if (*te && *te != '#')
+		if ((rc = poptConfigLine(con, te)) != 0)
+		    goto exit;
+	    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++;
+	    *te = *se++;
+	    /* \ at the end of a line does not insert a \n */
+	    if (se < be && *se != '\n') {
+		te++;
+		*te++ = *se;
 	    }
-	    /*@switchbreak@*/ break;
+	    break;
 	  default:
-	    *dst++ = *chptr++;
-	    /*@switchbreak@*/ break;
+	    *te++ = *se;
+	    break;
 	}
     }
-    /*@=infloops@*/
-/*@=boundswrite@*/
+    rc = 0;
 
-    return 0;
+exit:
+    free(t);
+    if (b)
+	free(b);
+    return rc;
 }
 
-int poptReadDefaultConfig(poptContext con, /*@unused@*/ UNUSED(int useEnv))
+int poptReadConfigFiles(poptContext con, const char * paths)
 {
-    char * fn, * home;
-    int rc;
+    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 (con->appName == NULL) return 0;
+    if (buf)
+	free(buf);
 
-    rc = poptReadConfigFile(con, "/etc/popt");
-    if (rc) return rc;
+    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"))) {
-	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;
+	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;
     }
 
-    return 0;
+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;
 }
diff --git a/popt/popthelp.c b/popt/popthelp.c
index 6a009766d..6738f6add 100644
--- a/popt/popthelp.c
+++ b/popt/popthelp.c
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 
 /** \ingroup popt
- * \file popt/popthelp.c
+ * @file
  */
 
 /* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING
@@ -10,14 +10,16 @@
 
 #include "system.h"
 
-/*#define POPT_WCHAR_HACK*/
-#ifdef 	POPT_WCHAR_HACK
+#define        POPT_USE_TIOCGWINSZ
+#ifdef POPT_USE_TIOCGWINSZ
+#include 
+#endif
+
+#ifdef HAVE_MBSRTOWCS
 #include 			/* for mbsrtowcs */
-/*@access mbstate_t @*/
 #endif
 #include "poptint.h"
 
-/*@access poptContext@*/
 
 /**
  * Display arguments.
@@ -27,29 +29,29 @@
  * @param arg		(unused)
  * @param data		(unused)
  */
+NORETURN
 static void displayArgs(poptContext con,
-		/*@unused@*/ UNUSED(enum poptCallbackReason foo),
+		UNUSED(enum poptCallbackReason foo),
 		struct poptOption * key, 
-		/*@unused@*/ UNUSED(const char * arg), /*@unused@*/ UNUSED(void * data))
-	/*@globals fileSystem@*/
-	/*@modifies fileSystem@*/
+		UNUSED(const char * arg),
+		UNUSED(void * data))
 {
     if (key->shortName == '?')
 	poptPrintHelp(con, stdout, 0);
     else
 	poptPrintUsage(con, stdout, 0);
+
+    poptFreeContext(con);
     exit(0);
 }
 
 #ifdef	NOTYET
-/*@unchecked@*/
 static int show_option_defaults = 0;
 #endif
 
 /**
  * Empty table marker to enable displaying popt alias/exec options.
  */
-/*@observer@*/ /*@unchecked@*/
 struct poptOption poptAliasOptions[] = {
     POPT_TABLEEND
 };
@@ -57,45 +59,88 @@ struct poptOption poptAliasOptions[] = {
 /**
  * Auto help table options.
  */
-/*@-castfcnptr@*/
-/*@observer@*/ /*@unchecked@*/
 struct poptOption poptHelpOptions[] = {
-  { NULL, '\0', POPT_ARG_CALLBACK, (void *)&displayArgs, '\0', NULL, NULL },
-  { "help", '?', 0, NULL, '?', N_("Show this help message"), NULL },
-  { "usage", '\0', 0, NULL, 'u', N_("Display brief usage message"), NULL },
+  { NULL, '\0', POPT_ARG_CALLBACK, (void *)displayArgs, 0, NULL, NULL },
+  { "help", '?', 0, NULL, (int)'?', N_("Show this help message"), NULL },
+  { "usage", '\0', 0, NULL, (int)'u', N_("Display brief usage message"), NULL },
     POPT_TABLEEND
 } ;
 
-/*@observer@*/ /*@unchecked@*/
 static struct poptOption poptHelpOptions2[] = {
-/*@-readonlytrans@*/
-  { NULL, '\0', POPT_ARG_INTL_DOMAIN, PACKAGE, 0, NULL, NULL},
-/*@=readonlytrans@*/
-  { NULL, '\0', POPT_ARG_CALLBACK, (void *)&displayArgs, '\0', NULL, NULL },
-  { "help", '?', 0, NULL, '?', N_("Show this help message"), NULL },
-  { "usage", '\0', 0, NULL, 'u', N_("Display brief usage message"), NULL },
+  { NULL, '\0', POPT_ARG_INTL_DOMAIN, (void *)PACKAGE, 0, NULL, NULL},
+  { NULL, '\0', POPT_ARG_CALLBACK, (void *)displayArgs, 0, NULL, NULL },
+  { "help", '?', 0, NULL, (int)'?', N_("Show this help message"), NULL },
+  { "usage", '\0', 0, NULL, (int)'u', N_("Display brief usage message"), NULL },
 #ifdef	NOTYET
   { "defaults", '\0', POPT_ARG_NONE, &show_option_defaults, 0,
 	N_("Display option defaults in message"), NULL },
 #endif
+  { NULL, '\0',	0, NULL, 0, N_("Terminate options"), NULL },
     POPT_TABLEEND
 } ;
 
-/*@observer@*/ /*@unchecked@*/
 struct poptOption * poptHelpOptionsI18N = poptHelpOptions2;
-/*@=castfcnptr@*/
+
+#define        _POPTHELP_MAXLINE       ((size_t)79)
+
+typedef struct columns_s {
+    size_t cur;
+    size_t max;
+} * columns_t;
+
+/** 
+ * Return no. of columns in output window.
+ * @param fp           FILE
+ * @return             no. of columns 
+ */ 
+static size_t maxColumnWidth(FILE *fp)
+{   
+    size_t maxcols = _POPTHELP_MAXLINE;
+#if defined(TIOCGWINSZ)
+    struct winsize ws;
+    int fdno = fileno(fp ? fp : stdout);
+
+    memset(&ws, 0, sizeof(ws));
+    if (fdno >= 0 && !ioctl(fdno, (unsigned long)TIOCGWINSZ, &ws)) {
+	size_t ws_col = (size_t)ws.ws_col;
+	if (ws_col > maxcols && ws_col < (size_t)256)
+	    maxcols = ws_col - 1;
+    }
+#endif
+    return maxcols;
+}   
 
 /**
- * @param table		option(s)
+ * Determine number of display characters in a string.
+ * @param s		string
+ * @return		no. of display characters.
  */
-/*@observer@*/ /*@null@*/ static const char *
-getTableTranslationDomain(/*@null@*/ const struct poptOption *table)
-	/*@*/
+static inline size_t stringDisplayWidth(const char *s)
 {
-    const struct poptOption *opt;
+    size_t n = strlen(s);
+#ifdef HAVE_MBSRTOWCS
+    mbstate_t t;
 
-    if (table != NULL)
-    for (opt = table; opt->longName || opt->shortName || opt->arg; opt++) {
+    memset ((void *)&t, 0, sizeof (t));	/* In initial state.  */
+    /* Determine number of display characters.  */
+    n = mbsrtowcs (NULL, &s, n, &t);
+#else
+    n = 0;
+    for (; *s; s = POPT_next_char(s))
+	n++;
+#endif
+
+    return n;
+}
+
+/**
+ * @param opt		option(s)
+ */
+static const char *
+getTableTranslationDomain(const struct poptOption *opt)
+{
+    if (opt != NULL)
+    for (; opt->longName || opt->shortName || opt->arg; opt++) {
 	if (opt->argInfo == POPT_ARG_INTL_DOMAIN)
 	    return opt->arg;
     }
@@ -106,32 +151,46 @@ getTableTranslationDomain(/*@null@*/ const struct poptOption *table)
  * @param opt		option(s)
  * @param translation_domain	translation domain
  */
-/*@observer@*/ /*@null@*/ static const char *
+static const char *
 getArgDescrip(const struct poptOption * opt,
-		/*@-paramuse@*/ /* FIX: i18n macros disabled with lclint */
-		/*@null@*/ UNUSED(const char * translation_domain))
-		/*@=paramuse@*/
-	/*@*/
+		/* FIX: i18n macros disabled with lclint */
+		const char * translation_domain)
 {
-    if (!(opt->argInfo & POPT_ARG_MASK)) return NULL;
-
-    if (opt == (poptHelpOptions + 1) || opt == (poptHelpOptions + 2))
-	if (opt->argDescrip) return POPT_(opt->argDescrip);
-
-    if (opt->argDescrip) return D_(translation_domain, opt->argDescrip);
+    if (!poptArgType(opt)) return NULL;
+
+    if (poptArgType(opt) == POPT_ARG_MAINCALL)
+	return opt->argDescrip;
+    if (poptArgType(opt) == POPT_ARG_ARGV)
+	return opt->argDescrip;
+
+    if (opt->argDescrip) {
+	/* Some strings need popt library, not application, i18n domain. */
+	if (opt == (poptHelpOptions + 1)
+	 || opt == (poptHelpOptions + 2)
+	 || !strcmp(opt->argDescrip, N_("Help options:"))
+	 || !strcmp(opt->argDescrip, N_("Options implemented via popt alias/exec:")))
+	    return POPT_(opt->argDescrip);
+
+	/* Use the application i18n domain. */
+	return D_(translation_domain, opt->argDescrip);
+    }
 
-    switch (opt->argInfo & POPT_ARG_MASK) {
-    /*case POPT_ARG_NONE:	return POPT_("NONE");*/ /* impossible */
+    switch (poptArgType(opt)) {
+    case POPT_ARG_NONE:		return POPT_("NONE");
 #ifdef	DYING
     case POPT_ARG_VAL:		return POPT_("VAL");
 #else
     case POPT_ARG_VAL:		return NULL;
 #endif
     case POPT_ARG_INT:		return POPT_("INT");
+    case POPT_ARG_SHORT:	return POPT_("SHORT");
     case POPT_ARG_LONG:		return POPT_("LONG");
+    case POPT_ARG_LONGLONG:	return POPT_("LONGLONG");
     case POPT_ARG_STRING:	return POPT_("STRING");
     case POPT_ARG_FLOAT:	return POPT_("FLOAT");
     case POPT_ARG_DOUBLE:	return POPT_("DOUBLE");
+    case POPT_ARG_MAINCALL:	return NULL;
+    case POPT_ARG_ARGV:		return NULL;
     default:			return POPT_("ARG");
     }
 }
@@ -143,59 +202,62 @@ getArgDescrip(const struct poptOption * opt,
  * @param translation_domain	translation domain
  * @return
  */
-static /*@only@*/ /*@null@*/ char *
+static char *
 singleOptionDefaultValue(size_t lineLength,
 		const struct poptOption * opt,
-		/*@-paramuse@*/ /* FIX: i18n macros disabled with lclint */
-		/*@null@*/ UNUSED(const char * translation_domain))
-		/*@=paramuse@*/
-	/*@*/
+		/* FIX: i18n macros disabled with lclint */
+		const char * translation_domain)
 {
     const char * defstr = D_(translation_domain, "default");
-    size_t limit, bufsize = 4*lineLength + 1;
-    char * le = malloc(bufsize);
+    char * le = malloc(4*lineLength + 1);
     char * l = le;
 
     if (le == NULL) return NULL;	/* XXX can't happen */
-/*@-boundswrite@*/
+    *le = '\0';
     *le++ = '(';
-    le += strlcpy(le, defstr, bufsize - 3);
+    le = stpcpy(le, defstr);
     *le++ = ':';
     *le++ = ' ';
-    limit = bufsize - (le - l) - 1; /* -1 for closing paren */
-    if (opt->arg)	/* XXX programmer error */
-    switch (opt->argInfo & POPT_ARG_MASK) {
+  if (opt->arg) {	/* XXX programmer error */
+    poptArg arg = { .ptr = opt->arg };
+    switch (poptArgType(opt)) {
     case POPT_ARG_VAL:
     case POPT_ARG_INT:
-    {	long aLong = *((int *)opt->arg);
-	le += snprintf(le, limit, "%ld", aLong);
-    }	break;
+	le += sprintf(le, "%d", arg.intp[0]);
+	break;
+    case POPT_ARG_SHORT:
+	le += sprintf(le, "%hd", arg.shortp[0]);
+	break;
     case POPT_ARG_LONG:
-    {	long aLong = *((long *)opt->arg);
-	le += snprintf(le, limit, "%ld", aLong);
-    }	break;
+	le += sprintf(le, "%ld", arg.longp[0]);
+	break;
+    case POPT_ARG_LONGLONG:
+	le += sprintf(le, "%lld", arg.longlongp[0]);
+	break;
     case POPT_ARG_FLOAT:
-    {	double aDouble = *((float *)opt->arg);
-	le += snprintf(le, limit, "%g", aDouble);
+    {	double aDouble = (double) arg.floatp[0];
+	le += sprintf(le, "%g", aDouble);
     }	break;
     case POPT_ARG_DOUBLE:
-    {	double aDouble = *((double *)opt->arg);
-	le += snprintf(le, limit, "%g", aDouble);
-    }	break;
+	le += sprintf(le, "%g", arg.doublep[0]);
+	break;
+    case POPT_ARG_MAINCALL:
+	le += sprintf(le, "%p", opt->arg);
+	break;
+    case POPT_ARG_ARGV:
+	le += sprintf(le, "%p", opt->arg);
+	break;
     case POPT_ARG_STRING:
-    {	const char * s = *(const char **)opt->arg;
-	if (s == NULL) {
-	    le += strlcpy(le, "null", limit);
-	} else {
-	    size_t len;
-	    limit -= 2; /* make room for quotes */
+    {	const char * s = arg.argv[0];
+	if (s == NULL)
+	    le = stpcpy(le, "null");
+	else {
+	    size_t limit = 4*lineLength - (le - l) - sizeof("\"\")");
+	    size_t slen;
 	    *le++ = '"';
-	    len = strlcpy(le, s, limit);
-	    if (len >= limit) {
-		le += limit - 3 - 1;
-		*le++ = '.'; *le++ = '.'; *le++ = '.';
-	    } else
-		le += len;
+	    strncpy(le, s, limit); le[limit] = '\0'; le += (slen = strlen(le));
+	    if (slen == limit && s[limit])
+		le[-1] = le[-2] = le[-3] = '.';
 	    *le++ = '"';
 	}
     }	break;
@@ -203,11 +265,11 @@ singleOptionDefaultValue(size_t lineLength,
     default:
 	l = _free(l);
 	return NULL;
-	/*@notreached@*/ break;
+	break;
     }
+  }
     *le++ = ')';
     *le = '\0';
-/*@=boundswrite@*/
 
     return l;
 }
@@ -215,80 +277,101 @@ singleOptionDefaultValue(size_t lineLength,
 /**
  * Display help text for an option.
  * @param fp		output file handle
- * @param maxLeftCol	largest argument display width
+ * @param columns	output display width control
  * @param opt		option(s)
  * @param translation_domain	translation domain
  */
-static void singleOptionHelp(FILE * fp, size_t maxLeftCol, 
+static void singleOptionHelp(FILE * fp, columns_t columns,
 		const struct poptOption * opt,
-		/*@null@*/ UNUSED(const char * translation_domain))
-	/*@globals fileSystem @*/
-	/*@modifies *fp, fileSystem @*/
+		const char * translation_domain)
 {
+    size_t maxLeftCol = columns->cur;
     size_t indentLength = maxLeftCol + 5;
-    size_t lineLength = 79 - indentLength;
+    size_t lineLength = columns->max - indentLength;
     const char * help = D_(translation_domain, opt->descrip);
     const char * argDescrip = getArgDescrip(opt, translation_domain);
+    /* Display shortName iff printable non-space. */
+    int prtshort = (int)(isprint((int)opt->shortName) && opt->shortName != ' ');
     size_t helpLength;
     char * defs = NULL;
     char * left;
-    size_t lelen, limit;
     size_t nb = maxLeftCol + 1;
     int displaypad = 0;
 
     /* Make sure there's more than enough room in target buffer. */
     if (opt->longName)	nb += strlen(opt->longName);
+    if (F_ISSET(opt, TOGGLE)) nb += sizeof("[no]") - 1;
     if (argDescrip)	nb += strlen(argDescrip);
 
-/*@-boundswrite@*/
     left = malloc(nb);
     if (left == NULL) return;	/* XXX can't happen */
     left[0] = '\0';
     left[maxLeftCol] = '\0';
 
-    if (opt->longName && opt->shortName)
-	snprintf(left, nb, "-%c, %s%s", opt->shortName,
-		((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"),
-		opt->longName);
-    else if (opt->shortName != '\0') 
-	snprintf(left, nb, "-%c", opt->shortName);
-    else if (opt->longName)
-	snprintf(left, nb, "%s%s",
-		((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"),
-		opt->longName);
-    if (!*left) goto out;
+#define	prtlong	(opt->longName != NULL)	/* XXX splint needs a clue */
+    if (!(prtshort || prtlong))
+	goto out;
+    if (prtshort && prtlong) {
+	const char *dash = F_ISSET(opt, ONEDASH) ? "-" : "--";
+	left[0] = '-';
+	left[1] = opt->shortName;
+	(void) stpcpy(stpcpy(stpcpy(left+2, ", "), dash), opt->longName);
+    } else if (prtshort) {
+	left[0] = '-';
+	left[1] = opt->shortName;
+	left[2] = '\0';
+    } else if (prtlong) {
+	/* XXX --long always padded for alignment with/without "-X, ". */
+	const char *dash = poptArgType(opt) == POPT_ARG_MAINCALL ? ""
+			 : (F_ISSET(opt, ONEDASH) ? "-" : "--");
+	const char *longName = opt->longName;
+	const char *toggle;
+	if (F_ISSET(opt, TOGGLE)) {
+	    toggle = "[no]";
+	    if (longName[0] == 'n' && longName[1] == 'o') {
+		longName += sizeof("no") - 1;
+		if (longName[0] == '-')
+		    longName++;
+	    }
+	} else
+	    toggle = "";
+	(void) stpcpy(stpcpy(stpcpy(stpcpy(left, "    "), dash), toggle), longName);
+    }
+#undef	prtlong
 
     if (argDescrip) {
 	char * le = left + strlen(left);
 
-	if (opt->argInfo & POPT_ARGFLAG_OPTIONAL)
+	if (F_ISSET(opt, OPTIONAL))
 	    *le++ = '[';
 
 	/* Choose type of output */
-	/*@-branchstate@*/
-	if (opt->argInfo & POPT_ARGFLAG_SHOW_DEFAULT) {
+	if (F_ISSET(opt, SHOW_DEFAULT)) {
 	    defs = singleOptionDefaultValue(lineLength, opt, translation_domain);
 	    if (defs) {
-		size_t bufsize = (help ? strlen(help) : 0) + sizeof " " + strlen(defs);
-		char * t = malloc(bufsize);
+		char * t = malloc((help ? strlen(help) : 0) +
+				strlen(defs) + sizeof(" "));
 		if (t) {
-		    snprintf(t, bufsize, "%s %s", help ? help : "", defs);
+		    char * te = t;
+		    if (help)
+			te = stpcpy(te, help);
+		    *te++ = ' ';
+		    strcpy(te, defs);
 		    defs = _free(defs);
+		    defs = t;
 		}
-		defs = t;
 	    }
 	}
-	/*@=branchstate@*/
 
 	if (opt->argDescrip == NULL) {
-	    switch (opt->argInfo & POPT_ARG_MASK) {
+	    switch (poptArgType(opt)) {
 	    case POPT_ARG_NONE:
 		break;
 	    case POPT_ARG_VAL:
 #ifdef	NOTNOW	/* XXX pug ugly nerdy output */
 	    {	long aLong = opt->val;
-		int ops = (opt->argInfo & POPT_ARGFLAG_LOGICALOPS);
-		int negate = (opt->argInfo & POPT_ARGFLAG_NOT);
+		int ops = F_ISSET(opt, LOGICALOPS);
+		int negate = F_ISSET(opt, NOT);
 
 		/* Don't bother displaying typical values */
 		if (!ops && (aLong == 0L || aLong == 1L || aLong == -1L))
@@ -297,111 +380,102 @@ static void singleOptionHelp(FILE * fp, size_t maxLeftCol,
 		switch (ops) {
 		case POPT_ARGFLAG_OR:
 		    *le++ = '|';
-		    /*@innerbreak@*/ break;
+		    break;
 		case POPT_ARGFLAG_AND:
 		    *le++ = '&';
-		    /*@innerbreak@*/ break;
+		    break;
 		case POPT_ARGFLAG_XOR:
 		    *le++ = '^';
-		    /*@innerbreak@*/ break;
+		    break;
 		default:
-		    /*@innerbreak@*/ break;
+		    break;
 		}
 		*le++ = (opt->longName != NULL ? '=' : ' ');
 		if (negate) *le++ = '~';
-		/*@-formatconst@*/
-		limit = nb - (le - left);
-		lelen = snprintf(le, limit, (ops ? "0x%lx" : "%ld"), aLong);
-		le += lelen >= limit ? limit - 1 : lelen;
-		/*@=formatconst@*/
+		le += sprintf(le, (ops ? "0x%lx" : "%ld"), aLong);
 		*le++ = ']';
 	    }
 #endif
 		break;
 	    case POPT_ARG_INT:
+	    case POPT_ARG_SHORT:
 	    case POPT_ARG_LONG:
+	    case POPT_ARG_LONGLONG:
 	    case POPT_ARG_FLOAT:
 	    case POPT_ARG_DOUBLE:
 	    case POPT_ARG_STRING:
 		*le++ = (opt->longName != NULL ? '=' : ' ');
-		limit = nb - (le - left);
-		lelen = strlcpy(le, argDescrip, limit);
-		le += lelen >= limit ? limit - 1 : lelen;
+		le = stpcpy(le, argDescrip);
 		break;
 	    default:
 		break;
 	    }
 	} else {
+	    char *leo;
 
-	    *le++ = '=';
-	    limit = nb - (le - left);
-	    lelen = strlcpy(le, argDescrip, limit);
-	    if (lelen >= limit)
-		lelen = limit - 1;
-	    le += lelen;
-
-#ifdef	POPT_WCHAR_HACK
-	    {	const char * scopy = argDescrip;
-		mbstate_t t;
-		size_t n;
-
-		memset ((void *)&t, '\0', sizeof (t));	/* In initial state.  */
-		/* Determine number of characters.  */
-		n = mbsrtowcs (NULL, &scopy, strlen(scopy), &t);
+	    /* XXX argDescrip[0] determines "--foo=bar" or "--foo bar". */
+	    if (!strchr(" =(", argDescrip[0]))
+		*le++ = ((poptArgType(opt) == POPT_ARG_MAINCALL) ? ' ' :
+			 (poptArgType(opt) == POPT_ARG_ARGV) ? ' ' :
+			 opt->longName == NULL ? ' ' : '=');
+	    le = stpcpy(leo = le, argDescrip);
 
-		displaypad = (int) (lelen-n);
-	    }
-#endif
+	    /* Adjust for (possible) wide characters. */
+	    displaypad = (int)((le - leo) - stringDisplayWidth(argDescrip));
 	}
-	if (opt->argInfo & POPT_ARGFLAG_OPTIONAL)
+	if (F_ISSET(opt, OPTIONAL))
 	    *le++ = ']';
 	*le = '\0';
     }
-/*@=boundswrite@*/
 
     if (help)
-	fprintf(fp,"  %-*s   ", (int)maxLeftCol+displaypad, left);
+	POPT_fprintf(fp,"  %-*s   ", (int)(maxLeftCol+displaypad), left);
     else {
-	fprintf(fp,"  %s\n", left); 
+	POPT_fprintf(fp,"  %s\n", left);
 	goto out;
     }
 
     left = _free(left);
-/*@-branchstate@*/
-    if (defs) {
+    if (defs)
 	help = defs;
-	defs = NULL;
-    }
-/*@=branchstate@*/
 
     helpLength = strlen(help);
-/*@-boundsread@*/
     while (helpLength > lineLength) {
 	const char * ch;
 	char format[16];
 
 	ch = help + lineLength - 1;
-	while (ch > help && !isSpace(ch)) ch--;
+	while (ch > help && !_isspaceptr(ch))
+	    ch = POPT_prev_char(ch);
 	if (ch == help) break;		/* give up */
-	while (ch > (help + 1) && isSpace(ch)) ch--;
-	ch++;
+	while (ch > (help + 1) && _isspaceptr(ch))
+	    ch = POPT_prev_char (ch);
+	ch = POPT_next_char(ch);
+
+	/*
+	 *  XXX strdup is necessary to add NUL terminator so that an unknown
+	 *  no. of (possible) multi-byte characters can be displayed.
+	 */
+	{   char * fmthelp = xstrdup(help);
+	    if (fmthelp) {
+		fmthelp[ch - help] = '\0';
+		sprintf(format, "%%s\n%%%ds", (int) indentLength);
+		POPT_fprintf(fp, format, fmthelp, " ");
+		free(fmthelp);
+	    }
+	}
 
-	snprintf(format, sizeof format, "%%.%ds\n%%%ds", (int) (ch - help), (int) indentLength);
-	/*@-formatconst@*/
-	fprintf(fp, format, help, " ");
-	/*@=formatconst@*/
 	help = ch;
-	while (isSpace(help) && *help) help++;
+	while (_isspaceptr(help) && *help)
+	    help = POPT_next_char(help);
 	helpLength = strlen(help);
     }
-/*@=boundsread@*/
 
     if (helpLength) fprintf(fp, "%s\n", help);
+    help = NULL;
 
 out:
-    /*@-dependenttrans@*/
     defs = _free(defs);
-    /*@=dependenttrans@*/
     left = _free(left);
 }
 
@@ -412,54 +486,45 @@ static void singleOptionHelp(FILE * fp, size_t maxLeftCol,
  * @return		display width
  */
 static size_t maxArgWidth(const struct poptOption * opt,
-		       /*@null@*/ UNUSED(const char * translation_domain))
-	/*@*/
+		       const char * translation_domain)
 {
     size_t max = 0;
     size_t len = 0;
-    const char * s;
+    const char * argDescrip;
     
     if (opt != NULL)
     while (opt->longName || opt->shortName || opt->arg) {
-	if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
-	    if (opt->arg)	/* XXX program error */
-	    len = maxArgWidth(opt->arg, translation_domain);
+	if (poptArgType(opt) == POPT_ARG_INCLUDE_TABLE) {
+	    void * arg = opt->arg;
+	    /* XXX sick hack to preserve pretense of ABI. */
+	    if (arg == poptHelpOptions)
+		arg = poptHelpOptionsI18N;
+	    if (arg)	/* XXX program error */
+		len = maxArgWidth(arg, translation_domain);
 	    if (len > max) max = len;
-	} else if (!(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) {
+	} else if (!F_ISSET(opt, DOC_HIDDEN)) {
 	    len = sizeof("  ")-1;
-	    if (opt->shortName != '\0') len += sizeof("-X")-1;
-	    if (opt->shortName != '\0' && opt->longName) len += sizeof(", ")-1;
+	    /* XXX --long always padded for alignment with/without "-X, ". */
+	    len += sizeof("-X, ")-1;
 	    if (opt->longName) {
-		len += ((opt->argInfo & POPT_ARGFLAG_ONEDASH)
-			? sizeof("-")-1 : sizeof("--")-1);
+		len += (F_ISSET(opt, ONEDASH) ? sizeof("-") : sizeof("--")) - 1;
 		len += strlen(opt->longName);
 	    }
 
-	    s = getArgDescrip(opt, translation_domain);
-
-#ifdef POPT_WCHAR_HACK
-	    /* XXX Calculate no. of display characters. */
-	    if (s) {
-		const char * scopy = s;
-		mbstate_t t;
-		size_t n;
-
-/*@-boundswrite@*/
-		memset ((void *)&t, '\0', sizeof (t));	/* In initial state.  */
-/*@=boundswrite@*/
-		/* Determine number of characters.  */
-		n = mbsrtowcs (NULL, &scopy, strlen(scopy), &t);
-		len += sizeof("=")-1 + n;
+	    argDescrip = getArgDescrip(opt, translation_domain);
+
+	    if (argDescrip) {
+
+		/* XXX argDescrip[0] determines "--foo=bar" or "--foo bar". */
+		if (!strchr(" =(", argDescrip[0])) len += sizeof("=")-1;
+
+		/* Adjust for (possible) wide characters. */
+		len += stringDisplayWidth(argDescrip);
 	    }
-#else
-	    if (s)
-		len += sizeof("=")-1 + strlen(s);
-#endif
 
-	    if (opt->argInfo & POPT_ARGFLAG_OPTIONAL) len += sizeof("[]")-1;
+	    if (F_ISSET(opt, OPTIONAL)) len += sizeof("[]")-1;
 	    if (len > max) max = len;
 	}
-
 	opt++;
     }
     
@@ -471,14 +536,13 @@ static size_t maxArgWidth(const struct poptOption * opt,
  * @param fp		output file handle
  * @param items		alias/exec array
  * @param nitems	no. of alias/exec entries
- * @param left		largest argument display width
+ * @param columns	output display width control
  * @param translation_domain	translation domain
  */
 static void itemHelp(FILE * fp,
-		/*@null@*/ poptItem items, int nitems, size_t left,
-		/*@null@*/ UNUSED(const char * translation_domain))
-	/*@globals fileSystem @*/
-	/*@modifies *fp, fileSystem @*/
+		poptItem items, int nitems,
+		columns_t columns,
+		const char * translation_domain)
 {
     poptItem item;
     int i;
@@ -487,9 +551,8 @@ static void itemHelp(FILE * fp,
     for (i = 0, item = items; i < nitems; i++, item++) {
 	const struct poptOption * opt;
 	opt = &item->option;
-	if ((opt->longName || opt->shortName) && 
-	    !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN))
-	    singleOptionHelp(fp, left, opt, translation_domain);
+	if ((opt->longName || opt->shortName) && !F_ISSET(opt, DOC_HIDDEN))
+	    singleOptionHelp(fp, columns, opt, translation_domain);
     }
 }
 
@@ -498,43 +561,48 @@ static void itemHelp(FILE * fp,
  * @param con		context
  * @param fp		output file handle
  * @param table		option(s)
- * @param left		largest argument display width
+ * @param columns	output display width control
  * @param translation_domain	translation domain
  */
 static void singleTableHelp(poptContext con, FILE * fp,
-		/*@null@*/ const struct poptOption * table, size_t left,
-		/*@null@*/ UNUSED(const char * translation_domain))
-	/*@globals fileSystem @*/
-	/*@modifies *fp, fileSystem @*/
+		const struct poptOption * table,
+		columns_t columns,
+		const char * translation_domain)
 {
     const struct poptOption * opt;
     const char *sub_transdom;
 
     if (table == poptAliasOptions) {
-	itemHelp(fp, con->aliases, con->numAliases, left, NULL);
-	itemHelp(fp, con->execs, con->numExecs, left, NULL);
+	itemHelp(fp, con->aliases, con->numAliases, columns, NULL);
+	itemHelp(fp, con->execs, con->numExecs, columns, NULL);
 	return;
     }
 
     if (table != NULL)
-    for (opt = table; (opt->longName || opt->shortName || opt->arg); opt++) {
-	if ((opt->longName || opt->shortName) && 
-	    !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN))
-	    singleOptionHelp(fp, left, opt, translation_domain);
+    for (opt = table; opt->longName || opt->shortName || opt->arg; opt++) {
+	if ((opt->longName || opt->shortName) && !F_ISSET(opt, DOC_HIDDEN))
+	    singleOptionHelp(fp, columns, opt, translation_domain);
     }
 
     if (table != NULL)
-    for (opt = table; (opt->longName || opt->shortName || opt->arg); opt++) {
-	if ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_INCLUDE_TABLE)
+    for (opt = table; opt->longName || opt->shortName || opt->arg; opt++) {
+	void * arg = opt->arg;
+	if (poptArgType(opt) != POPT_ARG_INCLUDE_TABLE)
 	    continue;
-	sub_transdom = getTableTranslationDomain(opt->arg);
+	/* XXX sick hack to preserve pretense of ABI. */
+	if (arg == poptHelpOptions)
+	    arg = poptHelpOptionsI18N;
+	sub_transdom = getTableTranslationDomain(arg);
 	if (sub_transdom == NULL)
 	    sub_transdom = translation_domain;
 	    
+	/* If no popt aliases/execs, skip poptAliasOption processing. */
+	if (arg == poptAliasOptions && !(con->numAliases || con->numExecs))
+	    continue;
 	if (opt->descrip)
-	    fprintf(fp, "\n%s\n", D_(sub_transdom, opt->descrip));
+	    POPT_fprintf(fp, "\n%s\n", D_(sub_transdom, opt->descrip));
 
-	singleTableHelp(con, fp, opt->arg, left, sub_transdom);
+	singleTableHelp(con, fp, arg, columns, sub_transdom);
     }
 }
 
@@ -542,22 +610,18 @@ static void singleTableHelp(poptContext con, FILE * fp,
  * @param con		context
  * @param fp		output file handle
  */
-static int showHelpIntro(poptContext con, FILE * fp)
-	/*@globals fileSystem @*/
-	/*@modifies *fp, fileSystem @*/
+static size_t showHelpIntro(poptContext con, FILE * fp)
 {
-    int len = 6;
-    const char * fn;
+    const char *usage_str = POPT_("Usage:");
+    size_t len = strlen(usage_str);
+    POPT_fprintf(fp, "%s", usage_str);
 
-    fprintf(fp, POPT_("Usage:"));
     if (!(con->flags & POPT_CONTEXT_KEEP_FIRST)) {
-/*@-boundsread@*/
-	/*@-nullderef -type@*/	/* LCL: wazzup? */
-	fn = con->optionStack->argv[0];
-	/*@=nullderef =type@*/
-/*@=boundsread@*/
+	struct optionStackEntry * os = con->optionStack;
+	const char * fn = (os->argv ? os->argv[0] : NULL);
 	if (fn == NULL) return len;
 	if (strchr(fn, '/')) fn = strrchr(fn, '/') + 1;
+	/* XXX POPT_fprintf not needed for argv[0] display. */
 	fprintf(fp, " %s", fn);
 	len += strlen(fn) + 1;
     }
@@ -565,126 +629,114 @@ static int showHelpIntro(poptContext con, FILE * fp)
     return len;
 }
 
-void poptPrintHelp(poptContext con, FILE * fp, /*@unused@*/ UNUSED(int flags))
+void poptPrintHelp(poptContext con, FILE * fp, UNUSED(int flags))
 {
-    size_t leftColWidth;
+    columns_t columns = calloc((size_t)1, sizeof(*columns));
 
     (void) showHelpIntro(con, fp);
     if (con->otherHelp)
-	fprintf(fp, " %s\n", con->otherHelp);
+	POPT_fprintf(fp, " %s\n", con->otherHelp);
     else
-	fprintf(fp, " %s\n", POPT_("[OPTION...]"));
+	POPT_fprintf(fp, " %s\n", POPT_("[OPTION...]"));
 
-    leftColWidth = maxArgWidth(con->options, NULL);
-    singleTableHelp(con, fp, con->options, leftColWidth, NULL);
+    if (columns) {
+	columns->cur = maxArgWidth(con->options, NULL);
+	columns->max = maxColumnWidth(fp);
+	singleTableHelp(con, fp, con->options, columns, NULL);
+	free(columns);
+    }
 }
 
 /**
  * Display usage text for an option.
  * @param fp		output file handle
- * @param cursor	current display position
+ * @param columns	output display width control
  * @param opt		option(s)
  * @param translation_domain	translation domain
  */
-static size_t singleOptionUsage(FILE * fp, size_t cursor, 
+static size_t singleOptionUsage(FILE * fp, columns_t columns,
 		const struct poptOption * opt,
-		/*@null@*/ const char *translation_domain)
-	/*@globals fileSystem @*/
-	/*@modifies *fp, fileSystem @*/
+		const char *translation_domain)
 {
-    size_t len = 4;
-    char shortStr[2] = { '\0', '\0' };
-    const char * item = shortStr;
+    size_t len = sizeof(" []")-1;
     const char * argDescrip = getArgDescrip(opt, translation_domain);
-
-    if (opt->shortName != '\0' && opt->longName != NULL) {
-	len += 2;
-	if (!(opt->argInfo & POPT_ARGFLAG_ONEDASH)) len++;
+    /* Display shortName iff printable non-space. */
+    int prtshort = (int)(isprint((int)opt->shortName) && opt->shortName != ' ');
+
+#define	prtlong	(opt->longName != NULL)	/* XXX splint needs a clue */
+    if (!(prtshort || prtlong))
+	return columns->cur;
+
+    len = sizeof(" []")-1;
+    if (prtshort)
+	len += sizeof("-c")-1;
+    if (prtlong) {
+	if (prtshort) len += sizeof("|")-1;
+	len += (F_ISSET(opt, ONEDASH) ? sizeof("-") : sizeof("--")) - 1;
 	len += strlen(opt->longName);
-    } else if (opt->shortName != '\0') {
-	len++;
-	shortStr[0] = opt->shortName;
-	shortStr[1] = '\0';
-    } else if (opt->longName) {
-	len += strlen(opt->longName);
-	if (!(opt->argInfo & POPT_ARGFLAG_ONEDASH)) len++;
-	item = opt->longName;
     }
 
-    if (len == 4) return cursor;
-
-#ifdef POPT_WCHAR_HACK
-    /* XXX Calculate no. of display characters. */
     if (argDescrip) {
-	const char * scopy = argDescrip;
-	mbstate_t t;
-	size_t n;
-
-/*@-boundswrite@*/
-	memset ((void *)&t, '\0', sizeof (t));	/* In initial state.  */
-/*@=boundswrite@*/
-	/* Determine number of characters.  */
-	n = mbsrtowcs (NULL, &scopy, strlen(scopy), &t);
-	len += sizeof("=")-1 + n;
+
+	/* XXX argDescrip[0] determines "--foo=bar" or "--foo bar". */
+	if (!strchr(" =(", argDescrip[0])) len += sizeof("=")-1;
+
+	/* Adjust for (possible) wide characters. */
+	len += stringDisplayWidth(argDescrip);
     }
-#else
-    if (argDescrip) 
-	len += sizeof("=")-1 + strlen(argDescrip);
-#endif
 
-    if ((cursor + len) > 79) {
+    if ((columns->cur + len) > columns->max) {
 	fprintf(fp, "\n       ");
-	cursor = 7;
+	columns->cur = (size_t)7;
     } 
 
-    if (opt->longName && opt->shortName) {
-	fprintf(fp, " [-%c|-%s%s%s%s]",
-	    opt->shortName, ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "" : "-"),
-	    opt->longName,
-	    (argDescrip ? " " : ""),
-	    (argDescrip ? argDescrip : ""));
-    } else {
-	fprintf(fp, " [-%s%s%s%s]",
-	    ((opt->shortName || (opt->argInfo & POPT_ARGFLAG_ONEDASH)) ? "" : "-"),
-	    item,
-	    (argDescrip ? (opt->shortName != '\0' ? " " : "=") : ""),
-	    (argDescrip ? argDescrip : ""));
+    fprintf(fp, " [");
+    if (prtshort)
+	fprintf(fp, "-%c", opt->shortName);
+    if (prtlong)
+	fprintf(fp, "%s%s%s",
+		(prtshort ? "|" : ""),
+		(F_ISSET(opt, ONEDASH) ? "-" : "--"),
+		opt->longName);
+#undef	prtlong
+
+    if (argDescrip) {
+	/* XXX argDescrip[0] determines "--foo=bar" or "--foo bar". */
+	if (!strchr(" =(", argDescrip[0])) fputc(opt->longName == NULL ? ' ' : '=', fp);
+	fprintf(fp, "%s", argDescrip);
     }
+    fprintf(fp, "]");
 
-    return cursor + len + 1;
+    return columns->cur + len + 1;
 }
 
 /**
  * Display popt alias and exec usage.
  * @param fp		output file handle
- * @param cursor	current display position
+ * @param columns	output display width control
  * @param item		alias/exec array
  * @param nitems	no. of ara/exec entries
  * @param translation_domain	translation domain
  */
-static size_t itemUsage(FILE * fp, size_t cursor,
-		/*@null@*/ poptItem item, int nitems,
-		/*@null@*/ UNUSED(const char * translation_domain))
-	/*@globals fileSystem @*/
-	/*@modifies *fp, fileSystem @*/
+static size_t itemUsage(FILE * fp, columns_t columns,
+		poptItem item, int nitems,
+		const char * translation_domain)
 {
     int i;
 
-    /*@-branchstate@*/		/* FIX: W2DO? */
     if (item != NULL)
     for (i = 0; i < nitems; i++, item++) {
 	const struct poptOption * opt;
 	opt = &item->option;
-        if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INTL_DOMAIN) {
+        if (poptArgType(opt) == POPT_ARG_INTL_DOMAIN) {
 	    translation_domain = (const char *)opt->arg;
-	} else if ((opt->longName || opt->shortName) &&
-		 !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) {
-	    cursor = singleOptionUsage(fp, cursor, opt, translation_domain);
+	} else
+	if ((opt->longName || opt->shortName) && !F_ISSET(opt, DOC_HIDDEN)) {
+	    columns->cur = singleOptionUsage(fp, columns, opt, translation_domain);
 	}
     }
-    /*@=branchstate@*/
 
-    return cursor;
+    return columns->cur;
 }
 
 /**
@@ -700,53 +752,51 @@ typedef struct poptDone_s {
  * Display usage text for a table of options.
  * @param con		context
  * @param fp		output file handle
- * @param cursor	current display position
+ * @param columns	output display width control
  * @param opt		option(s)
  * @param translation_domain	translation domain
  * @param done		tables already processed
  * @return
  */
-static size_t singleTableUsage(poptContext con, FILE * fp, size_t cursor,
-		/*@null@*/ const struct poptOption * opt,
-		/*@null@*/ UNUSED(const char * translation_domain),
-		/*@null@*/ poptDone done)
-	/*@globals fileSystem @*/
-	/*@modifies *fp, done, fileSystem @*/
+static size_t singleTableUsage(poptContext con, FILE * fp, columns_t columns,
+		const struct poptOption * opt,
+		const char * translation_domain,
+		poptDone done)
 {
-    /*@-branchstate@*/		/* FIX: W2DO? */
     if (opt != NULL)
     for (; (opt->longName || opt->shortName || opt->arg) ; opt++) {
-        if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INTL_DOMAIN) {
+        if (poptArgType(opt) == POPT_ARG_INTL_DOMAIN) {
 	    translation_domain = (const char *)opt->arg;
-	} else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
+	} else
+	if (poptArgType(opt) == POPT_ARG_INCLUDE_TABLE) {
+	    void * arg = opt->arg;
+	    /* XXX sick hack to preserve pretense of ABI. */
+	    if (arg == poptHelpOptions)
+		arg = poptHelpOptionsI18N;
 	    if (done) {
 		int i = 0;
+		if (done->opts != NULL)
 		for (i = 0; i < done->nopts; i++) {
-/*@-boundsread@*/
 		    const void * that = done->opts[i];
-/*@=boundsread@*/
-		    if (that == NULL || that != opt->arg)
-			/*@innercontinue@*/ continue;
-		    /*@innerbreak@*/ break;
+		    if (that == NULL || that != arg)
+			continue;
+		    break;
 		}
 		/* Skip if this table has already been processed. */
-		if (opt->arg == NULL || i < done->nopts)
+		if (arg == NULL || i < done->nopts)
 		    continue;
-/*@-boundswrite@*/
-		if (done->nopts < done->maxopts)
-		    done->opts[done->nopts++] = (const void *) opt->arg;
-/*@=boundswrite@*/
+		if (done->opts != NULL && done->nopts < done->maxopts)
+		    done->opts[done->nopts++] = (const void *) arg;
 	    }
-	    cursor = singleTableUsage(con, fp, cursor, opt->arg,
+	    columns->cur = singleTableUsage(con, fp, columns, opt->arg,
 			translation_domain, done);
-	} else if ((opt->longName || opt->shortName) &&
-		 !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) {
-	    cursor = singleOptionUsage(fp, cursor, opt, translation_domain);
+	} else
+	if ((opt->longName || opt->shortName) && !F_ISSET(opt, DOC_HIDDEN)) {
+	    columns->cur = singleOptionUsage(fp, columns, opt, translation_domain);
 	}
     }
-    /*@=branchstate@*/
 
-    return cursor;
+    return columns->cur;
 }
 
 /**
@@ -757,66 +807,78 @@ static size_t singleTableUsage(poptContext con, FILE * fp, size_t cursor,
  * @retval str		concatenation of short options
  * @return		length of display string
  */
-static int showShortOptions(const struct poptOption * opt, FILE * fp,
-		/*@null@*/ char * str)
-	/*@globals fileSystem @*/
-	/*@modifies *str, *fp, fileSystem @*/
-	/*@requires maxRead(str) >= 0 @*/
+static size_t showShortOptions(const struct poptOption * opt, FILE * fp,
+		char * str)
 {
-    /* bufsize larger then the ascii set, lazy alloca on top level call. */
-    char * s = (str != NULL ? str : memset(alloca(300), 0, 300));
-    int len = 0;
+    /* bufsize larger then the ascii set, lazy allocation on top level call. */
+    size_t nb = (size_t)300;
+    char * s = (str != NULL ? str : calloc((size_t)1, nb));
+    size_t len = (size_t)0;
 
     if (s == NULL)
 	return 0;
 
-/*@-boundswrite@*/
     if (opt != NULL)
     for (; (opt->longName || opt->shortName || opt->arg); opt++) {
-	if (opt->shortName && !(opt->argInfo & POPT_ARG_MASK))
-	    s[strlen(s)] = opt->shortName;
-	else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE)
-	    if (opt->arg)	/* XXX program error */
-		len = showShortOptions(opt->arg, fp, s);
+	if (!F_ISSET(opt, DOC_HIDDEN) && opt->shortName && !poptArgType(opt))
+	{
+	    /* Display shortName iff unique printable non-space. */
+	    if (!strchr(s, opt->shortName) && isprint((int)opt->shortName)
+	     && opt->shortName != ' ')
+		s[strlen(s)] = opt->shortName;
+	} else if (poptArgType(opt) == POPT_ARG_INCLUDE_TABLE) {
+	    void * arg = opt->arg;
+	    /* XXX sick hack to preserve pretense of ABI. */
+	    if (arg == poptHelpOptions)
+		arg = poptHelpOptionsI18N;
+	    if (arg)	/* XXX program error */
+		len = showShortOptions(arg, fp, s);
+	}
     } 
-/*@=boundswrite@*/
 
     /* On return to top level, print the short options, return print length. */
-    if (s == str && *s != '\0') {
+    if (s != str && *s != '\0') {
 	fprintf(fp, " [-%s]", s);
 	len = strlen(s) + sizeof(" [-]")-1;
     }
+    if (s != str)
+	free(s);
     return len;
 }
 
-void poptPrintUsage(poptContext con, FILE * fp, /*@unused@*/ UNUSED(int flags))
+void poptPrintUsage(poptContext con, FILE * fp, UNUSED(int flags))
 {
-    poptDone done = memset(alloca(sizeof(*done)), 0, sizeof(*done));
-    size_t cursor;
+    columns_t columns = calloc((size_t)1, sizeof(*columns));
+    struct poptDone_s done_buf;
+    poptDone done = &done_buf;
 
+    memset(done, 0, sizeof(*done));
     done->nopts = 0;
     done->maxopts = 64;
-    cursor = done->maxopts * sizeof(*done->opts);
-/*@-boundswrite@*/
-    done->opts = memset(alloca(cursor), 0, cursor);
-    /*@-keeptrans@*/
-    done->opts[done->nopts++] = (const void *) con->options;
-    /*@=keeptrans@*/
-/*@=boundswrite@*/
-
-    cursor = showHelpIntro(con, fp);
-    cursor += showShortOptions(con->options, fp, NULL);
-    cursor = singleTableUsage(con, fp, cursor, con->options, NULL, done);
-    cursor = itemUsage(fp, cursor, con->aliases, con->numAliases, NULL);
-    cursor = itemUsage(fp, cursor, con->execs, con->numExecs, NULL);
+  if (columns) {
+    columns->cur = done->maxopts * sizeof(*done->opts);
+    columns->max = maxColumnWidth(fp);
+    done->opts = calloc((size_t)1, columns->cur);
+    if (done->opts != NULL)
+	done->opts[done->nopts++] = (const void *) con->options;
+
+    columns->cur = showHelpIntro(con, fp);
+    columns->cur += showShortOptions(con->options, fp, NULL);
+    columns->cur = singleTableUsage(con, fp, columns, con->options, NULL, done);
+    columns->cur = itemUsage(fp, columns, con->aliases, con->numAliases, NULL);
+    columns->cur = itemUsage(fp, columns, con->execs, con->numExecs, NULL);
 
     if (con->otherHelp) {
-	cursor += strlen(con->otherHelp) + 1;
-	if (cursor > 79) fprintf(fp, "\n       ");
+	columns->cur += strlen(con->otherHelp) + 1;
+	if (columns->cur > columns->max) fprintf(fp, "\n       ");
 	fprintf(fp, " %s", con->otherHelp);
     }
 
     fprintf(fp, "\n");
+    if (done->opts != NULL)
+	free(done->opts);
+    free(columns);
+  }
 }
 
 void poptSetOtherOptionHelp(poptContext con, const char * text)
diff --git a/popt/poptint.c b/popt/poptint.c
new file mode 100644
index 000000000..b8dc90f42
--- /dev/null
+++ b/popt/poptint.c
@@ -0,0 +1,194 @@
+#include "system.h"
+#include 
+#include 
+#ifdef HAVE_LANGINFO_H
+#include 
+#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) */
diff --git a/popt/poptint.h b/popt/poptint.h
index bec7c9769..001c5c35d 100644
--- a/popt/poptint.h
+++ b/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 
+
 /**
  * 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 
+#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 
 #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
diff --git a/popt/poptparse.c b/popt/poptparse.c
index e003a04a9..5afc6c551 100644
--- a/popt/poptparse.c
+++ b/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;
diff --git a/popt/system.h b/popt/system.h
index 25c22daee..f731d206d 100644
--- a/popt/system.h
+++ b/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 
-#endif
-
 #include 
 
-#include 
-#include 
-#include 
+/* 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 
 #endif
 
-#include 
-#ifdef HAVE_SYS_TYPES_H
-# include 
-#endif
-#ifdef STDC_HEADERS
-# include 
-# include 
-#else
-# ifdef HAVE_STDLIB_H
-#  include 
-# endif
-#endif
-#ifdef HAVE_STRING_H
-# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
-#  include 
-# endif
-# include 
-#endif
-#ifdef HAVE_STRINGS_H
-# include 
-#endif
-#ifdef HAVE_UNISTD_H
-# include 
-#endif
+#include 
+#include 
+#include 
 
-#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 
-#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 
-# 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);
+    do
+	*d++ = *s;
+    while (*s++ != '\0');
+    return d - 1;
+}
 #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;
-}
+/* 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  /* HAVE_MCHECK_H && defined(__GNUC__) */
+#endif  /* defined(HAVE_MCHECK_H) && defined(__GNUC__) */
 
-#if HAVE___SECURE_GETENV && !defined(__LCLINT__)
+#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"
diff --git a/prepare-source b/prepare-source
index f5b7b46ca..a4e78e61e 100755
--- a/prepare-source
+++ b/prepare-source
@@ -6,7 +6,7 @@
 #
 #   build     build the config files [the default w/no arg]
 #   fetch     fetch the latest dev autoconfig files
-#   fetchgen  fetch all the latest dev generated files (including man pages)
+#   fetchgen  fetch all the latest dev generated files (including manpages)
 #   fetchSRC  fetch the latest dev source files [NON-GENERATED FILES]
 #
 # The script stops after the first successful action.
@@ -32,7 +32,7 @@ if test "$dir" != '.'; then
 	fi
     done
     for fn in configure.sh config.h.in aclocal.m4; do
-	test ! -f $fn -a -f "$dir/$fn" && cp -p "$dir/$fn" $fn
+	test ! -f $fn && test -f "$dir/$fn" && cp -p "$dir/$fn" $fn
     done
 fi
 
diff --git a/progress.c b/progress.c
index 8da528622..87207fbfa 100644
--- a/progress.c
+++ b/progress.c
@@ -4,7 +4,7 @@
  * Copyright (C) 1996-2000 Andrew Tridgell
  * Copyright (C) 1996 Paul Mackerras
  * Copyright (C) 2001, 2002 Martin Pool 
- * Copyright (C) 2003-2020 Wayne Davison
+ * 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 as published by
@@ -115,13 +115,13 @@ static void rprint_progress(OFF_T ofs, OFF_T size, struct timeval *now, int is_l
 		units = "kB/s";
 	}
 
-	if (remain < 0)
+	if (remain < 0 || remain > 9999.0 * 3600.0)
 		strlcpy(rembuf, "  ??:??:??", sizeof rembuf);
 	else {
-		snprintf(rembuf, sizeof rembuf, "%4d:%02d:%02d",
-			 (int) (remain / 3600.0),
-			 (int) (remain / 60.0) % 60,
-			 (int) remain % 60);
+		snprintf(rembuf, sizeof rembuf, "%4u:%02u:%02u",
+			 (unsigned int) (remain / 3600.0),
+			 (unsigned int) (remain / 60.0) % 60,
+			 (unsigned int) remain % 60);
 	}
 
 	output_needs_newline = 0;
diff --git a/receiver.c b/receiver.c
index 2cd843517..edfbb2106 100644
--- a/receiver.c
+++ b/receiver.c
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 1996-2000 Andrew Tridgell
  * Copyright (C) 1996 Paul Mackerras
- * Copyright (C) 2003-2020 Wayne Davison
+ * Copyright (C) 2003-2023 Wayne Davison
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -56,7 +56,6 @@ extern int inplace;
 extern int inplace_partial;
 extern int allowed_lull;
 extern int delay_updates;
-extern int xfersum_type;
 extern BOOL want_progress_now;
 extern mode_t orig_umask;
 extern struct stats stats;
@@ -67,6 +66,10 @@ 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;
 
 static struct bitbag *delayed_bits = NULL;
 static int phase = 0, redoing = 0;
@@ -240,7 +243,6 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
 	static char file_sum1[MAX_DIGEST_LEN];
 	struct map_struct *mapbuf;
 	struct sum_struct sum;
-	int sum_len;
 	int32 len;
 	OFF_T total_size = F_LENGTH(file);
 	OFF_T offset = 0;
@@ -280,7 +282,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
 	} else
 		mapbuf = NULL;
 
-	sum_init(xfersum_type, checksum_seed);
+	sum_init(xfer_sum_nni, checksum_seed);
 
 	if (append_mode > 0) {
 		OFF_T j;
@@ -371,7 +373,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
 
 	if (fd != -1 && offset > 0) {
 		if (sparse_files > 0) {
-			if (sparse_end(fd, offset) != 0)
+			if (sparse_end(fd, offset, updating_basis_or_equiv) != 0)
 				goto report_write_error;
 		} else if (flush_write_file(fd) < 0) {
 		    report_write_error:
@@ -393,7 +395,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
 	if (INFO_GTE(PROGRESS, 1))
 		end_progress(total_size);
 
-	sum_len = sum_end(file_sum1);
+	sum_end(file_sum1);
 
 	if (do_fsync && fd != -1 && fsync(fd) != 0) {
 		rsyserr(FERROR, errno, "fsync failed on %s", full_fname(fname));
@@ -403,10 +405,10 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
 	if (mapbuf)
 		unmap_file(mapbuf);
 
-	read_buf(f_in, sender_file_sum, sum_len);
+	read_buf(f_in, sender_file_sum, xfer_sum_len);
 	if (DEBUG_GTE(DELTASUM, 2))
 		rprintf(FINFO,"got file_sum\n");
-	if (fd != -1 && memcmp(file_sum1, sender_file_sum, sum_len) != 0)
+	if (fd != -1 && memcmp(file_sum1, sender_file_sum, xfer_sum_len) != 0)
 		return 0;
 	return 1;
 }
@@ -439,9 +441,8 @@ static void handle_delayed_updates(char *local_name)
 					"rename failed for %s (from %s)",
 					full_fname(fname), partialptr);
 			} else {
-				if (remove_source_files
-				 || (preserve_hard_links && F_IS_HLINKED(file)))
-					send_msg_int(MSG_SUCCESS, ndx);
+				if (remove_source_files || (preserve_hard_links && F_IS_HLINKED(file)))
+					send_msg_success(fname, ndx);
 				handle_partial_dir(partialptr, PDIR_DELETE);
 			}
 		}
@@ -551,6 +552,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. */
@@ -593,10 +596,13 @@ int recv_files(int f_in, int f_out, char *local_name)
 		if (DEBUG_GTE(RECV, 1))
 			rprintf(FINFO, "recv_files(%s)\n", fname);
 
-		if (daemon_filter_list.head && (*fname != '.' || fname[1] != '\0')
-		 && check_filter(&daemon_filter_list, FLOG, fname, 0) < 0) {
-			rprintf(FERROR, "attempt to hack rsync failed.\n");
-			exit_cleanup(RERR_PROTOCOL);
+		if (daemon_filter_list.head && (*fname != '.' || fname[1] != '\0')) {
+			int filt_flags = S_ISDIR(file->mode) ? NAME_IS_DIR : NAME_IS_FILE;
+			if (check_filter(&daemon_filter_list, FLOG, fname, filt_flags) < 0) {
+				rprintf(FERROR, "ERROR: rejecting file transfer request for daemon excluded file: %s\n",
+					fname);
+				exit_cleanup(RERR_PROTOCOL);
+			}
 		}
 
 #ifdef SUPPORT_XATTRS
@@ -695,7 +701,7 @@ int recv_files(int f_in, int f_out, char *local_name)
 			if (!am_server)
 				discard_receive_data(f_in, file);
 			if (inc_recurse)
-				send_msg_int(MSG_SUCCESS, ndx);
+				send_msg_success(fname, ndx);
 			continue;
 		}
 
@@ -713,28 +719,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
@@ -757,25 +769,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));
@@ -808,14 +826,16 @@ int recv_files(int f_in, int f_out, char *local_name)
 			continue;
 		}
 
-		if (fd1 != -1 && !(S_ISREG(st.st_mode) || (write_devices && IS_DEVICE(st.st_mode)))) {
+		if (write_devices && IS_DEVICE(st.st_mode)) {
+			if (fd1 != -1 && st.st_size == 0)
+				st.st_size = get_device_size(fd1, fname);
+			/* Mark the file entry as a device so that we don't try to truncate it later on. */
+			file->mode = S_IFBLK | (file->mode & ACCESSPERMS);
+		} else if (fd1 != -1 && !(S_ISREG(st.st_mode))) {
 			close(fd1);
 			fd1 = -1;
 		}
 
-		if (fd1 != -1 && IS_DEVICE(st.st_mode) && st.st_size == 0)
-			st.st_size = get_device_size(fd1, fname);
-
 		/* If we're not preserving permissions, change the file-list's
 		 * mode based on the local permissions and some heuristics. */
 		if (!preserve_perms) {
@@ -921,9 +941,8 @@ int recv_files(int f_in, int f_out, char *local_name)
 		case 2:
 			break;
 		case 1:
-			if (remove_source_files || inc_recurse
-			 || (preserve_hard_links && F_IS_HLINKED(file)))
-				send_msg_int(MSG_SUCCESS, ndx);
+			if (remove_source_files || inc_recurse || (preserve_hard_links && F_IS_HLINKED(file)))
+				send_msg_success(fname, ndx);
 			break;
 		case 0: {
 			enum logcode msgtype = redoing ? FERROR_XFER : FWARNING;
diff --git a/rsync-ssl.1.md b/rsync-ssl.1.md
index 32ab31e2c..a6f1e3d39 100644
--- a/rsync-ssl.1.md
+++ b/rsync-ssl.1.md
@@ -1,14 +1,17 @@
-# NAME
+## NAME
 
 rsync-ssl - a helper script for connecting to an ssl rsync daemon
 
-# SYNOPSIS
+## SYNOPSIS
 
 ```
 rsync-ssl [--type=SSL_TYPE] RSYNC_ARGS
 ```
 
-# DESCRIPTION
+The online version of this manpage (that includes cross-linking of topics)
+is available at .
+
+## DESCRIPTION
 
 The rsync-ssl script helps you to run an rsync copy to/from an rsync daemon
 that requires ssl connections.
@@ -20,7 +23,7 @@ environment.  You can specify an overriding port via `--port` or by including
 it in the normal spot in the URL format, though both of those require your
 rsync version to be at least 3.2.0.
 
-# OPTIONS
+## OPTIONS
 
 If the **first** arg is a `--type=SSL_TYPE` option, the script will only use
 that particular program to open an ssl connection instead of trying to find an
@@ -32,35 +35,56 @@ required for this particular option.
 All the other options are passed through to the rsync command, so consult the
 **rsync**(1) manpage for more information on how it works.
 
-# ENVIRONMENT VARIABLES
+## ENVIRONMENT VARIABLES
 
 The ssl helper scripts are affected by the following environment variables:
 
-0.  `RSYNC_SSL_TYPE` Specifies the program type that should be used to open the
-    ssl connection.  It must be one of `openssl` or `stunnel`.  The
-    `--type=SSL_TYPE` option overrides this, when specified.
-0.  `RSYNC_SSL_PORT` If specified, the value is the port number that is used as
-    the default when the user does not specify a port in their rsync command.
-    When not specified, the default port number is 874.  (Note that older rsync
-    versions (prior to 3.2.0) did not communicate an overriding port number
-    value to the helper script.)
-0.  `RSYNC_SSL_CERT` If specified, the value is a filename that contains a
+0.  `RSYNC_SSL_TYPE`
+
+    Specifies the program type that should be used to open the ssl connection.
+    It must be one of `openssl` or `stunnel`.  The `--type=SSL_TYPE` option
+    overrides this, when specified.
+
+0.  `RSYNC_SSL_PORT`
+
+    If specified, the value is the port number that is used as the default when
+    the user does not specify a port in their rsync command.  When not
+    specified, the default port number is 874.  (Note that older rsync versions
+    (prior to 3.2.0) did not communicate an overriding port number value to the
+    helper script.)
+
+0.  `RSYNC_SSL_CERT`
+
+    If specified, the value is a filename that contains a certificate to use
+    for the connection.
+
+0.  `RSYNC_SSL_KEY`
+
+    If specified, the value is a filename that contains a key for the provided
     certificate to use for the connection.
-0.  `RSYNC_SSL_KEY` If specified, the value is a filename that contains a
-    key for the provided certificate to use for the connection.
-0.  `RSYNC_SSL_CA_CERT` If specified, the value is a filename that contains a
-    certificate authority certificate that is used to validate the connection.
-0.  `RSYNC_SSL_OPENSSL` Specifies the openssl executable to run when the
-    connection type is set to openssl.  If unspecified, the $PATH is searched
-    for "openssl".
-0.  `RSYNC_SSL_GNUTLS` Specifies the gnutls-cli executable to run when the
-    connection type is set to gnutls.  If unspecified, the $PATH is searched
-    for "gnutls-cli".
-0.  `RSYNC_SSL_STUNNEL` Specifies the stunnel executable to run when the
-    connection type is set to stunnel.  If unspecified, the $PATH is searched
-    first for "stunnel4" and then for "stunnel".
-
-# EXAMPLES
+
+0.  `RSYNC_SSL_CA_CERT`
+
+    If specified, the value is a filename that contains a certificate authority
+    certificate that is used to validate the connection.
+
+0.  `RSYNC_SSL_OPENSSL`
+
+    Specifies the openssl executable to run when the connection type is set to
+    openssl.  If unspecified, the $PATH is searched for "openssl".
+
+0.  `RSYNC_SSL_GNUTLS`
+
+    Specifies the gnutls-cli executable to run when the connection type is set
+    to gnutls.  If unspecified, the $PATH is searched for "gnutls-cli".
+
+0.  `RSYNC_SSL_STUNNEL`
+
+    Specifies the stunnel executable to run when the connection type is set to
+    stunnel.  If unspecified, the $PATH is searched first for "stunnel4" and
+    then for "stunnel".
+
+## EXAMPLES
 
 >     rsync-ssl -aiv example.com::mod/ dest
 
@@ -70,11 +94,16 @@ The ssl helper scripts are affected by the following environment variables:
 
 >     rsync-ssl -aiv rsync://example.com:9874/mod/ dest
 
-# SEE ALSO
+## THE SERVER SIDE
+
+For help setting up an SSL/TLS supporting rsync, see the [instructions in
+rsyncd.conf](rsyncd.conf.5#SSL_TLS_Daemon_Setup).
+
+## SEE ALSO
 
-**rsync**(1), **rsyncd.conf**(5)
+[**rsync**(1)](rsync.1), [**rsyncd.conf**(5)](rsyncd.conf.5)
 
-# CAVEATS
+## CAVEATS
 
 Note that using an stunnel connection requires at least version 4 of stunnel,
 which should be the case on modern systems.  Also, it does not verify a
@@ -87,23 +116,23 @@ release the gnutls-cli command was dropping output, making it unusable.  If
 that bug has been fixed in your version, feel free to put gnutls into an
 exported RSYNC_SSL_TYPE environment variable to make its use the default.
 
-# BUGS
+## BUGS
 
 Please report bugs! See the web site at .
 
-# VERSION
+## VERSION
 
-This man page is current for version @VERSION@ of rsync.
+This manpage is current for version @VERSION@ of rsync.
 
-# CREDITS
+## CREDITS
 
-rsync is distributed under the GNU General Public License.  See the file
-COPYING for details.
+Rsync is distributed under the GNU General Public License.  See the file
+[COPYING](COPYING) for details.
 
 A web site is available at .  The site includes an
 FAQ-O-Matic which may cover questions unanswered by this manual page.
 
-# AUTHOR
+## AUTHOR
 
 This manpage was written by Wayne Davison.
 
diff --git a/rsync.1.md b/rsync.1.md
index d154a98ca..7e40e3617 100644
--- a/rsync.1.md
+++ b/rsync.1.md
@@ -1,8 +1,8 @@
-# NAME
+## NAME
 
 rsync - a fast, versatile, remote (and local) file-copying tool
 
-# SYNOPSIS
+## SYNOPSIS
 
 ```
 Local:
@@ -26,7 +26,10 @@ Access via rsync daemon:
 Usages with just one SRC arg and no DEST arg will list the source files instead
 of copying.
 
-# DESCRIPTION
+The online version of this manpage (that includes cross-linking of topics)
+is available at .
+
+## DESCRIPTION
 
 Rsync is a fast and extraordinarily versatile file copying tool.  It can copy
 locally, to/from another host over any remote shell, or to/from a remote rsync
@@ -54,7 +57,7 @@ Some of the additional features of rsync are:
 - pipelining of file transfers to minimize latency costs
 - support for anonymous or authenticated rsync daemons (ideal for mirroring)
 
-# GENERAL
+## GENERAL
 
 Rsync copies files either to or from a remote host, or locally on the current
 host (it does not support copying files between two remote hosts).
@@ -65,21 +68,21 @@ rsync daemon directly via TCP.  The remote-shell transport is used whenever the
 source or destination path contains a single colon (:) separator after a host
 specification.  Contacting an rsync daemon directly happens when the source or
 destination path contains a double colon (::) separator after a host
-specification, OR when an rsync:// URL is specified (see also the "USING
-RSYNC-DAEMON FEATURES VIA A REMOTE-SHELL CONNECTION" section for an exception
-to this latter rule).
+specification, OR when an rsync:// URL is specified (see also the [USING
+RSYNC-DAEMON FEATURES VIA A REMOTE-SHELL CONNECTION](#) section for an
+exception to this latter rule).
 
 As a special case, if a single source arg is specified without a destination,
 the files are listed in an output format similar to "`ls -l`".
 
 As expected, if neither the source or destination path specify a remote host,
-the copy occurs locally (see also the `--list-only` option).
+the copy occurs locally (see also the [`--list-only`](#opt) option).
 
 Rsync refers to the local side as the client and the remote side as the server.
 Don't confuse server with an rsync daemon.  A daemon is always a server, but a
 server can be either a daemon or a remote-shell spawned process.
 
-# SETUP
+## SETUP
 
 See the file README.md for installation instructions.
 
@@ -89,12 +92,12 @@ protocol).  For remote transfers, a modern rsync uses ssh for its
 communications, but it may have been configured to use a different remote shell
 by default, such as rsh or remsh.
 
-You can also specify any remote shell you like, either by using the `-e`
-command line option, or by setting the RSYNC_RSH environment variable.
+You can also specify any remote shell you like, either by using the [`-e`](#opt)
+command line option, or by setting the [`RSYNC_RSH`](#) environment variable.
 
 Note that rsync must be installed on both the source and destination machines.
 
-# USAGE
+## USAGE
 
 You use rsync in the same way you use rcp.  You must specify a source and a
 destination, one of which may be remote.
@@ -149,55 +152,161 @@ rsync daemon by leaving off the module name:
 
 >     rsync somehost.mydomain.com::
 
-See the following section for more details.
+## COPYING TO A DIFFERENT NAME
+
+When you want to copy a directory to a different name, use a trailing slash on
+the source directory to put the contents of the directory into any destination
+directory you like:
+
+>     rsync -ai foo/ bar/
+
+Rsync also has the ability to customize a destination file's name when copying
+a single item.  The rules for this are:
+
+- The transfer list must consist of a single item (either a file or an empty
+  directory)
+- The final element of the destination path must not exist as a directory
+- The destination path must not have been specified with a trailing slash
+
+Under those circumstances, rsync will set the name of the destination's single
+item to the last element of the destination path.  Keep in mind that it is best
+to only use this idiom when copying a file and use the above trailing-slash
+idiom when copying a directory.
+
+The following example copies the `foo.c` file as `bar.c` in the `save` dir
+(assuming that `bar.c` isn't a directory):
+
+>     rsync -ai src/foo.c save/bar.c
+
+The single-item copy rule might accidentally bite you if you unknowingly copy a
+single item and specify a destination dir that doesn't exist (without using a
+trailing slash).  For example, if `src/*.c` matches one file and `save/dir`
+doesn't exist, this will confuse you by naming the destination file `save/dir`:
+
+>     rsync -ai src/*.c save/dir
+
+To prevent such an accident, either make sure the destination dir exists or
+specify the destination path with a trailing slash:
+
+>     rsync -ai src/*.c save/dir/
+
+## SORTED TRANSFER ORDER
+
+Rsync always sorts the specified filenames into its internal transfer list.
+This handles the merging together of the contents of identically named
+directories, makes it easy to remove duplicate filenames. It can, however,
+confuse someone when the files are transferred in a different order than what
+was given on the command-line.
+
+If you need a particular file to be transferred prior to another, either
+separate the files into different rsync calls, or consider using
+[`--delay-updates`](#opt) (which doesn't affect the sorted transfer order, but
+does make the final file-updating phase happen much more rapidly).
+
+## MULTI-HOST SECURITY
 
-# ADVANCED USAGE
+Rsync takes steps to ensure that the file requests that are shared in a
+transfer are protected against various security issues.  Most of the potential
+problems arise on the receiving side where rsync takes steps to ensure that the
+list of files being transferred remains within the bounds of what was
+requested.
+
+Toward this end, rsync 3.1.2 and later have aborted when a file list contains
+an absolute or relative path that tries to escape out of the top of the
+transfer.  Also, beginning with version 3.2.5, rsync does two more safety
+checks of the file list to (1) ensure that no extra source arguments were added
+into the transfer other than those that the client requested and (2) ensure
+that the file list obeys the exclude rules that were sent to the sender.
+
+For those that don't yet have a 3.2.5 client rsync (or those that want to be
+extra careful), it is safest to do a copy into a dedicated destination
+directory for the remote files when you don't trust the remote host.  For
+example, instead of doing an rsync copy into your home directory:
+
+>     rsync -aiv host1:dir1 ~
+
+Dedicate a "host1-files" dir to the remote content:
+
+>     rsync -aiv host1:dir1 ~/host1-files
+
+See the [`--trust-sender`](#opt) option for additional details.
+
+CAUTION: it is not particularly safe to use rsync to copy files from a
+case-preserving filesystem to a case-ignoring filesystem.  If you must perform
+such a copy, you should either disable symlinks via `--no-links` or enable the
+munging of symlinks via [`--munge-links`](#opt) (and make sure you use the
+right local or remote option).  This will prevent rsync from doing potentially
+dangerous things if a symlink name overlaps with a file or directory. It does
+not, however, ensure that you get a full copy of all the files (since that may
+not be possible when the names overlap). A potentially better solution is to
+list all the source files and create a safe list of filenames that you pass to
+the [`--files-from`](#opt) option.  Any files that conflict in name would need
+to be copied to different destination directories using more than one copy.
+
+While a copy of a case-ignoring filesystem to a case-ignoring filesystem can
+work out fairly well, if no `--delete-during` or `--delete-before` option is
+active, rsync can potentially update an existing file on the receiving side
+without noticing that the upper-/lower-case of the filename should be changed
+to match the sender.
+
+## ADVANCED USAGE
 
 The syntax for requesting multiple files from a remote host is done by
 specifying additional remote-host args in the same style as the first, or with
 the hostname omitted.  For instance, all these work:
 
->     rsync -av host:file1 :file2 host:file{3,4} /dest/
->     rsync -av host::modname/file{1,2} host::modname/file3 /dest/
->     rsync -av host::modname/file1 ::modname/file{3,4}
+>     rsync -aiv host:file1 :file2 host:file{3,4} /dest/
+>     rsync -aiv host::modname/file{1,2} host::modname/extra /dest/
+>     rsync -aiv host::modname/first ::extra-file{1,2} /dest/
 
-Older versions of rsync required using quoted spaces in the SRC, like these
-examples:
+Note that a daemon connection only supports accessing one module per copy
+command, so if the start of a follow-up path doesn't begin with the
+modname of the first path, it is assumed to be a path in the module (such as
+the extra-file1 & extra-file2 that are grabbed above).
 
->     rsync -av host:'dir1/file1 dir2/file2' /dest
->     rsync host::'modname/dir1/file1 modname/dir2/file2' /dest
+Really old versions of rsync (2.6.9 and before) only allowed specifying one
+remote-source arg, so some people have instead relied on the remote-shell
+performing space splitting to break up an arg into multiple paths. Such
+unintuitive behavior is no longer supported by default (though you can request
+it, as described below).
 
-This word-splitting still works (by default) in the latest rsync, but is not as
-easy to use as the first method.
+Starting in 3.2.4, filenames are passed to a remote shell in such a way as to
+preserve the characters you give it. Thus, if you ask for a file with spaces
+in the name, that's what the remote rsync looks for:
 
-If you need to transfer a filename that contains whitespace, you can either
-specify the `--protect-args` (`-s`) option, or you'll need to escape the
-whitespace in a way that the remote shell will understand.  For instance:
+>     rsync -aiv host:'a simple file.pdf' /dest/
 
->     rsync -av host:'file\ name\ with\ spaces' /dest
+If you use scripts that have been written to manually apply extra quoting to
+the remote rsync args (or to require remote arg splitting), you can ask rsync
+to let your script handle the extra escaping.  This is done by either adding
+the [`--old-args`](#opt) option to the rsync runs in the script (which requires
+a new rsync) or exporting [RSYNC_OLD_ARGS](#)=1 and [RSYNC_PROTECT_ARGS](#)=0
+(which works with old or new rsync versions).
 
-# CONNECTING TO AN RSYNC DAEMON
+## CONNECTING TO AN RSYNC DAEMON
 
 It is also possible to use rsync without a remote shell as the transport.  In
 this case you will directly connect to a remote rsync daemon, typically using
 TCP port 873. (This obviously requires the daemon to be running on the remote
-system, so refer to the STARTING AN RSYNC DAEMON TO ACCEPT CONNECTIONS section
-below for information on that.)
+system, so refer to the [STARTING AN RSYNC DAEMON TO ACCEPT CONNECTIONS](#)
+section below for information on that.)
 
 Using rsync in this way is the same as using it with a remote shell except
 that:
 
-- you either use a double colon :: instead of a single colon to separate the
-  hostname from the path, or you use an rsync:// URL.
-- the first word of the "path" is actually a module name.
-- the remote daemon may print a message of the day when you connect.
-- if you specify no path name on the remote daemon then the list of accessible
-  paths on the daemon will be shown.
-- if you specify no local destination then a listing of the specified files on
-  the remote daemon is provided.
-- you must not specify the `--rsh` (`-e`) option (since that overrides the
-  daemon connection to use ssh -- see USING RSYNC-DAEMON FEATURES VIA A
-  REMOTE-SHELL CONNECTION below).
+- Use either double-colon syntax or rsync:// URL syntax instead of the
+  single-colon (remote shell) syntax.
+- The first element of the "path" is actually a module name.
+- Additional remote source args can use an abbreviated syntax that omits the
+  hostname and/or the module name, as discussed in [ADVANCED USAGE](#).
+- The remote daemon may print a "message of the day" when you connect.
+- If you specify only the host (with no module or path) then a list of
+  accessible modules on the daemon is output.
+- If you specify a remote source path but no destination, a listing of the
+  matching files on the remote daemon is output.
+- The [`--rsh`](#opt) (`-e`) option must be omitted to avoid changing the
+  connection style from using a socket connection to [USING RSYNC-DAEMON
+  FEATURES VIA A REMOTE-SHELL CONNECTION](#).
 
 An example that copies all the files in a remote module named "src":
 
@@ -205,22 +314,23 @@ An example that copies all the files in a remote module named "src":
 
 Some modules on the remote daemon may require authentication.  If so, you will
 receive a password prompt when you connect.  You can avoid the password prompt
-by setting the environment variable RSYNC_PASSWORD to the password you want to
-use or using the `--password-file` option.  This may be useful when scripting
-rsync.
+by setting the environment variable [`RSYNC_PASSWORD`](#) to the password you
+want to use or using the [`--password-file`](#opt) option.  This may be useful
+when scripting rsync.
 
 WARNING: On some systems environment variables are visible to all users.  On
-those systems using `--password-file` is recommended.
+those systems using [`--password-file`](#opt) is recommended.
 
 You may establish the connection via a web proxy by setting the environment
-variable RSYNC_PROXY to a hostname:port pair pointing to your web proxy.  Note
-that your web proxy's configuration must support proxy connections to port 873.
+variable [`RSYNC_PROXY`](#) to a hostname:port pair pointing to your web proxy.
+Note that your web proxy's configuration must support proxy connections to port
+873.
 
 You may also establish a daemon connection using a program as a proxy by
-setting the environment variable RSYNC_CONNECT_PROG to the commands you wish to
-run in place of making a direct socket connection.  The string may contain the
-escape "%H" to represent the hostname specified in the rsync command (so use
-"%%" if you need a single "%" in your string).  For example:
+setting the environment variable [`RSYNC_CONNECT_PROG`](#) to the commands you
+wish to run in place of making a direct socket connection.  The string may
+contain the escape "%H" to represent the hostname specified in the rsync
+command (so use "%%" if you need a single "%" in your string).  For example:
 
 >     export RSYNC_CONNECT_PROG='ssh proxyhost nc %H 873'
 >     rsync -av targethost1::module/src/ /dest/
@@ -229,11 +339,11 @@ escape "%H" to represent the hostname specified in the rsync command (so use
 The command specified above uses ssh to run nc (netcat) on a proxyhost, which
 forwards all data to port 873 (the rsync daemon) on the targethost (%H).
 
-Note also that if the RSYNC_SHELL environment variable is set, that program
-will be used to run the RSYNC_CONNECT_PROG command instead of using the default
-shell of the **system()** call.
+Note also that if the [`RSYNC_SHELL`](#) environment variable is set, that
+program will be used to run the `RSYNC_CONNECT_PROG` command instead of using
+the default shell of the **system()** call.
 
-# USING RSYNC-DAEMON FEATURES VIA A REMOTE-SHELL CONNECTION
+## USING RSYNC-DAEMON FEATURES VIA A REMOTE-SHELL CONNECTION
 
 It is sometimes useful to use various features of an rsync daemon (such as
 named modules) without actually allowing any new socket connections into a
@@ -250,7 +360,7 @@ on that remote host to only allow connections from "localhost".)
 From the user's perspective, a daemon transfer via a remote-shell connection
 uses nearly the same command-line syntax as a normal rsync-daemon transfer,
 with the only exception being that you must explicitly set the remote shell
-program on the command-line with the `--rsh=COMMAND` option. (Setting the
+program on the command-line with the [`--rsh=COMMAND`](#opt) option. (Setting the
 RSYNC_RSH in the environment will not turn on this functionality.) For example:
 
 >     rsync -av --rsh=ssh host::module /dest
@@ -259,73 +369,47 @@ If you need to specify a different remote-shell user, keep in mind that the
 user@ prefix in front of the host is specifying the rsync-user value (for a
 module that requires user-based authentication).  This means that you must give
 the '-l user' option to ssh when specifying the remote-shell, as in this
-example that uses the short version of the `--rsh` option:
+example that uses the short version of the [`--rsh`](#opt) option:
 
 >     rsync -av -e "ssh -l ssh-user" rsync-user@host::module /dest
 
 The "ssh-user" will be used at the ssh level; the "rsync-user" will be used to
 log-in to the "module".
 
-# STARTING AN RSYNC DAEMON TO ACCEPT CONNECTIONS
+In this setup, the daemon is started by the ssh command that is accessing the
+system (which can be forced via the `~/.ssh/authorized_keys` file, if desired).
+However, when accessing a daemon directly, it needs to be started beforehand.
+
+## STARTING AN RSYNC DAEMON TO ACCEPT CONNECTIONS
 
 In order to connect to an rsync daemon, the remote system needs to have a
 daemon already running (or it needs to have configured something like inetd to
 spawn an rsync daemon for incoming connections on a particular port).  For full
 information on how to start a daemon that will handling incoming socket
-connections, see the **rsyncd.conf**(5) man page -- that is the config file for
-the daemon, and it contains the full details for how to run the daemon
-(including stand-alone and inetd configurations).
+connections, see the [**rsyncd.conf**(5)](rsyncd.conf.5) manpage -- that is
+the config file for the daemon, and it contains the full details for how to run
+the daemon (including stand-alone and inetd configurations).
 
 If you're using one of the remote-shell transports for the transfer, there is
 no need to manually start an rsync daemon.
 
-# SORTED TRANSFER ORDER
-
-Rsync always sorts the specified filenames into its internal transfer list.
-This handles the merging together of the contents of identically named
-directories, makes it easy to remove duplicate filenames, and may confuse
-someone when the files are transferred in a different order than what was given
-on the command-line.
-
-If you need a particular file to be transferred prior to another, either
-separate the files into different rsync calls, or consider using
-`--delay-updates` (which doesn't affect the sorted transfer order, but does
-make the final file-updating phase happen much more rapidly).
-
-# EXAMPLES
-
-Here are some examples of how I use rsync.
-
-To backup my wife's home directory, which consists of large MS Word files and
-mail folders, I use a cron job that runs
-
->     rsync -Cavz . arvidsjaur:backup
+## EXAMPLES
 
-each night over a PPP connection to a duplicate directory on my machine
-"arvidsjaur".
+Here are some examples of how rsync can be used.
 
-To synchronize my samba source trees I use the following Makefile targets:
+To backup a home directory, which consists of large MS Word files and mail
+folders, a per-user cron job can be used that runs this each day:
 
->     get:
->         rsync -avuzb --exclude '*~' samba:samba/ .
->     put:
->         rsync -Cavuzb . samba:samba/
->     sync: get put
+>     rsync -aiz . bkhost:backup/joe/
 
-This allows me to sync with a CVS directory at the other end of the connection.
-I then do CVS operations on the remote machine, which saves a lot of time as
-the remote CVS protocol isn't very efficient.
+To move some files from a remote host to the local host, you could run:
 
-I mirror a directory between my "old" and "new" ftp sites with the command:
+>     rsync -aiv --remove-source-files rhost:/tmp/{file1,file2}.c ~/src/
 
->     rsync -az -e ssh --delete ~ftp/pub/samba nimbus:"~ftp/pub/tridge"
+## OPTION SUMMARY
 
-This is launched from cron every few hours.
-
-# OPTION SUMMARY
-
-Here is a short summary of the options available in rsync.  Please refer to the
-detailed description below for a complete description.
+Here is a short summary of the options available in rsync.  Each option also
+has its own detailed description later in this manpage.
 
 [comment]: # (help-rsync.h)
 [comment]: # (Keep these short enough that they'll be under 80 chars when indented by 7 chars.)
@@ -351,7 +435,8 @@ detailed description below for a complete description.
 --append                 append data onto shorter files
 --append-verify          --append w/old data in file checksum
 --dirs, -d               transfer directories without recursing
---mkpath                 create the destination's path component
+--old-dirs, --old-d      works like --dirs when talking to old rsync
+--mkpath                 create destination's missing path components
 --links, -l              copy symlinks as symlinks
 --copy-links, -L         transform symlink into referent file/dir
 --copy-unsafe-links      only "unsafe" symlinks are transformed
@@ -368,6 +453,8 @@ detailed description below for a complete description.
 --owner, -o              preserve owner (super-user only)
 --group, -g              preserve group
 --devices                preserve device files (super-user only)
+--copy-devices           copy device contents as a regular file
+--write-devices          write to devices as files (implies --inplace)
 --specials               preserve special files
 -D                       same as --devices --specials
 --times, -t              preserve modification times
@@ -380,7 +467,6 @@ detailed description below for a complete description.
 --fake-super             store/recover privileged attrs using xattrs
 --sparse, -S             turn sequences of nulls into sparse blocks
 --preallocate            allocate dest files before writing them
---write-devices          write to devices as files (implies --inplace)
 --dry-run, -n            perform a trial run with no changes made
 --whole-file, -W         copy files whole (w/o delta-xfer algorithm)
 --checksum-choice=STR    choose the checksum algorithm (aka --cc)
@@ -438,7 +524,9 @@ detailed description below for a complete description.
 --include-from=FILE      read include patterns from FILE
 --files-from=FILE        read list of source-file names from FILE
 --from0, -0              all *-from/filter files are delimited by 0s
---protect-args, -s       no space-splitting; wildcard chars only
+--old-args               disable the modern arg-protection idiom
+--secluded-args, -s      use the protocol to safely send the args
+--trust-sender           trust the remote sender's file list
 --copy-as=USER[:GROUP]   specify user & optional group for the copy
 --address=ADDRESS        bind address for outgoing socket to daemon
 --port=PORT              specify double-colon alternate port number
@@ -496,35 +584,46 @@ accepted:
 --help, -h               show this help (when used with --daemon)
 ```
 
-# OPTIONS
+## OPTIONS
 
 Rsync accepts both long (double-dash + word) and short (single-dash + letter)
 options.  The full list of the available options are described below.  If an
 option can be specified in more than one way, the choices are comma-separated.
-Some options only have a long variant, not a short.  If the option takes a
-parameter, the parameter is only listed after the long variant, even though it
-must also be specified for the short.  When specifying a parameter, you can
-either use the form `--option=param` or replace the '=' with whitespace.  The
-parameter may need to be quoted in some manner for it to survive the shell's
-command-line parsing.  Keep in mind that a leading tilde (`~`) in a filename is
-substituted by your shell, so `--option=~/foo` will not change the tilde into
-your home directory (remove the '=' for that).
+Some options only have a long variant, not a short.
+
+If the option takes a parameter, the parameter is only listed after the long
+variant, even though it must also be specified for the short.  When specifying
+a parameter, you can either use the form `--option=param`, `--option param`,
+`-o=param`, `-o param`, or `-oparam` (the latter choices assume that your
+option has a short variant).
+
+The parameter may need to be quoted in some manner for it to survive the
+shell's command-line parsing.  Also keep in mind that a leading tilde (`~`) in
+a pathname is substituted by your shell, so make sure that you separate the
+option name from the pathname using a space if you want the local shell to
+expand it.
+
+[comment]: # (Some markup below uses a literal non-breakable space when a backtick string)
+[comment]: # (needs to contain a space since markdown strips spaces from the start/end)
 
 [comment]: # (An OL starting at 0 is converted into a DL by the parser.)
 
-0.  `--help`, `-h` `(*)`
+0.  `--help`
 
     Print a short help page describing the options available in rsync and exit.
-    (*) The `-h` short option will only invoke `--help` when used without other
-    options since it normally means `--human-readable`.
+    You can also use `-h` for `--help` when it is used without any other
+    options (since it normally means [`--human-readable`](#opt)).
 
 0.  `--version`, `-V`
 
-    Print the rsync version plus other info and exit.
+    Print the rsync version plus other info and exit.  When repeated, the
+    information is output is a JSON format that is still fairly readable
+    (client side only).
 
-    The output includes the default list of checksum algorithms, the default
-    list of compression algorithms, a list of compiled-in capabilities, a link
-    to the rsync web site, and some license/copyright info.
+    The output includes a list of compiled-in capabilities, a list of
+    optimizations, the default list of checksum algorithms, the default list of
+    compression algorithms, the default list of daemon auth digests, a link to
+    the rsync web site, and a few other items.
 
 0.  `--verbose`, `-v`
 
@@ -535,12 +634,24 @@ your home directory (remove the '=' for that).
     being skipped and slightly more information at the end.  More than two `-v`
     options should only be used if you are debugging rsync.
 
+    The end-of-run summary tells you the number of bytes sent to the remote
+    rsync (which is the receiving side on a local copy), the number of bytes
+    received from the remote host, and the average bytes per second of the
+    transferred data computed over the entire length of the rsync run. The
+    second line shows the total size (in bytes), which is the sum of all the
+    file sizes that rsync considered transferring.  It also shows a "speedup"
+    value, which is a ratio of the total file size divided by the sum of the
+    sent and received bytes (which is really just a feel-good bigger-is-better
+    number).  Note that these byte values can be made more (or less)
+    human-readable by using the [`--human-readable`](#opt) (or
+    `--no-human-readable`) options.
+
     In a modern rsync, the `-v` option is equivalent to the setting of groups
-    of `--info` and `--debug` options.  You can choose to use these newer
-    options in addition to, or in place of using `--verbose`, as any
-    fine-grained settings override the implied settings of `-v`.  Both `--info`
-    and `--debug` have a way to ask for help that tells you exactly what flags
-    are set for each increase in verbosity.
+    of [`--info`](#opt) and [`--debug`](#opt) options.  You can choose to use
+    these newer options in addition to, or in place of using `--verbose`, as
+    any fine-grained settings override the implied settings of `-v`.  Both
+    [`--info`](#opt) and [`--debug`](#opt) have a way to ask for help that
+    tells you exactly what flags are set for each increase in verbosity.
 
     However, do keep in mind that a daemon's "`max verbosity`" setting will limit
     how high of a level the various individual flags can be set on the daemon
@@ -561,9 +672,9 @@ your home directory (remove the '=' for that).
     >     rsync -a --info=progress2 src/ dest/
     >     rsync -avv --info=stats2,misc1,flist0 src/ dest/
 
-    Note that `--info=name`'s output is affected by the `--out-format` and
-    `--itemize-changes` (`-i`) options.  See those options for more information
-    on what is output and when.
+    Note that `--info=name`'s output is affected by the [`--out-format`](#opt)
+    and [`--itemize-changes`](#opt) (`-i`) options.  See those options for more
+    information on what is output and when.
 
     This option was added to 3.1.0, so an older rsync on the server side might
     reject your attempts at fine-grained control (if one or more flags needed
@@ -583,8 +694,8 @@ your home directory (remove the '=' for that).
     >     rsync -avvv --debug=none src/ dest/
     >     rsync -avA --del --debug=del2,acl src/ dest/
 
-    Note that some debug messages will only be output when `--stderr=all` is
-    specified, especially those pertaining to I/O and buffer debugging.
+    Note that some debug messages will only be output when the [`--stderr=all`](#opt)
+    option is specified, especially those pertaining to I/O and buffer debugging.
 
     Beginning in 3.2.0, this option is no longer auto-forwarded to the server
     side in order to allow you to specify different debug values for each side
@@ -614,8 +725,8 @@ your home directory (remove the '=' for that).
       divide up the info and error messages by file handle.  For those doing
       debugging or using several levels of verbosity, this option can help to
       avoid clogging up the transfer stream (which should prevent any chance of
-      a deadlock bug hanging things up).  It also allows `--debug` to enable
-      some extra I/O related messages.
+      a deadlock bug hanging things up).  It also allows [`--debug`](#opt) to
+      enable some extra I/O related messages.
 
     - `client` - causes all rsync messages to be sent to the client side
       via the protocol stream.  One client process outputs all messages, with
@@ -652,9 +763,9 @@ your home directory (remove the '=' for that).
     the same modification timestamp.  This option turns off this "quick check"
     behavior, causing all files to be updated.
 
-    This option can be a little confusing compared to `--ignore-existing` and
-    `--ignore-non-existing` in that that they cause rsync to transfer fewer
-    files, while this option causes rsync to transfer more files.
+    This option can be confusing compared to [`--ignore-existing`](#opt) and
+    [`--ignore-non-existing`](#opt) in that that they cause rsync to transfer
+    fewer files, while this option causes rsync to transfer more files.
 
 0.  `--size-only`
 
@@ -710,8 +821,9 @@ your home directory (remove the '=' for that).
     before-the-transfer "Does this file need to be updated?" check.
 
     The checksum used is auto-negotiated between the client and the server, but
-    can be overridden using either the `--checksum-choice` (`--cc`) option or an
-    environment variable that is discussed in that option's section.
+    can be overridden using either the [`--checksum-choice`](#opt) (`--cc`)
+    option or an environment variable that is discussed in that option's
+    section.
 
 0.  `--archive`, `-a`
 
@@ -720,52 +832,81 @@ your home directory (remove the '=' for that).
     **not** include preserving ACLs (`-A`), xattrs (`-X`), atimes (`-U`),
     crtimes (`-N`), nor the finding and preserving of hardlinks (`-H`).
 
-    The only exception to the above equivalence is when
-    `--files-from` is specified, in which case `-r` is not implied.
+    The only exception to the above equivalence is when [`--files-from`](#opt)
+    is specified, in which case [`-r`](#opt) is not implied.
 
 0.  `--no-OPTION`
 
     You may turn off one or more implied options by prefixing the option name
-    with "no-".  Not all options may be prefixed with a "no-": only options that
-    are implied by other options (e.g. `--no-D`, `--no-perms`) or have
-    different defaults in various circumstances (e.g. `--no-whole-file`,
-    `--no-blocking-io`, `--no-dirs`).  You may specify either the short or the
-    long option name after the "no-" prefix (e.g. `--no-R` is the same as
-    `--no-relative`).
-
-    For example: if you want to use `-a` (`--archive`) but don't want `-o`
-    (`--owner`), instead of converting `-a` into `-rlptgD`, you could specify
-    `-a --no-o` (or `-a --no-owner`).
-
-    The order of the options is important: if you specify `--no-r -a`, the
-    `-r` option would end up being turned on, the opposite of `-a --no-r`.
-    Note also that the side-effects of the `--files-from` option are NOT
+    with "no-".  Not all positive options have a negated opposite, but a lot
+    do, including those that can be used to disable an implied option (e.g.
+    `--no-D`, `--no-perms`) or have different defaults in various circumstances
+    (e.g. [`--no-whole-file`](#opt), `--no-blocking-io`, `--no-dirs`).  Every
+    valid negated option accepts both the short and the long option name after
+    the "no-" prefix (e.g. `--no-R` is the same as `--no-relative`).
+
+    As an example, if you want to use [`--archive`](#opt) (`-a`) but don't want
+    [`--owner`](#opt) (`-o`), instead of converting `-a` into `-rlptgD`, you
+    can specify `-a --no-o` (aka `--archive --no-owner`).
+
+    The order of the options is important: if you specify `--no-r -a`, the `-r`
+    option would end up being turned on, the opposite of `-a --no-r`.  Note
+    also that the side-effects of the [`--files-from`](#opt) option are NOT
     positional, as it affects the default state of several options and slightly
-    changes the meaning of `-a` (see the `--files-from` option for more
-    details).
+    changes the meaning of [`-a`](#opt) (see the [`--files-from`](#opt) option
+    for more details).
 
 0.  `--recursive`, `-r`
 
-    This tells rsync to copy directories recursively.  See also `--dirs` (`-d`).
+    This tells rsync to copy directories recursively.  See also
+    [`--dirs`](#opt) (`-d`) for an option that allows the scanning of a single
+    directory.
 
-    Beginning with rsync 3.0.0, the recursive algorithm used is now an
-    incremental scan that uses much less memory than before and begins the
-    transfer after the scanning of the first few directories have been
-    completed.  This incremental scan only affects our recursion algorithm, and
-    does not change a non-recursive transfer.  It is also only possible when
-    both ends of the transfer are at least version 3.0.0.
+    See the [`--inc-recursive`](#opt) option for a discussion of the
+    incremental recursion for creating the list of files to transfer.
 
-    Some options require rsync to know the full file list, so these options
-    disable the incremental recursion mode.  These include: `--delete-before`,
-    `--delete-after`, `--prune-empty-dirs`, and `--delay-updates`.  Because of
-    this, the default delete mode when you specify `--delete` is now
-    `--delete-during` when both ends of the connection are at least 3.0.0 (use
-    `--del` or `--delete-during` to request this improved deletion mode
-    explicitly).  See also the `--delete-delay` option that is a better choice
-    than using `--delete-after`.
+0. `--inc-recursive`, `--i-r`
 
-    Incremental recursion can be disabled using the `--no-inc-recursive` option
-    or its shorter `--no-i-r` alias.
+    This option explicitly enables on incremental recursion when scanning for
+    files, which is enabled by default when using the [`--recursive`](#opt)
+    option and both sides of the transfer are running rsync 3.0.0 or newer.
+
+    Incremental recursion uses much less memory than non-incremental, while
+    also beginning the transfer more quickly (since it doesn't need to scan the
+    entire transfer hierarchy before it starts transferring files).  If no
+    recursion is enabled in the source files, this option has no effect.
+
+    Some options require rsync to know the full file list, so these options
+    disable the incremental recursion mode.  These include:
+    - [`--delete-before`](#opt) (the old default of [`--delete`](#opt))
+    - [`--delete-after`](#opt)
+    - [`--prune-empty-dirs`](#opt)
+    - [`--delay-updates`](#opt)
+
+    In order to make [`--delete`](#opt) compatible with incremental recursion,
+    rsync 3.0.0 made [`--delete-during`](#opt) the default delete mode (which
+    was first added in 2.6.4).
+
+    One side-effect of incremental recursion is that any missing
+    sub-directories inside a recursively-scanned directory are (by default)
+    created prior to recursing into the sub-dirs.  This earlier creation point
+    (compared to a non-incremental recursion) allows rsync to then set the
+    modify time of the finished directory right away (without having to delay
+    that until a bunch of recursive copying has finished).  However, these
+    early directories don't yet have their completed mode, mtime, or ownership
+    set -- they have more restrictive rights until the subdirectory's copying
+    actually begins.  This early-creation idiom can be avoided by using the
+    [`--omit-dir-times`](#opt) option.
+
+    Incremental recursion can be disabled using the
+    [`--no-inc-recursive`](#opt) (`--no-i-r`) option.
+
+0. `--no-inc-recursive`, `--no-i-r`
+
+    Disables the new incremental recursion algorithm of the
+    [`--recursive`](#opt) option.  This makes rsync scan the full file list
+    before it begins to transfer files.  See [`--inc-recursive`](#opt) for more
+    info.
 
 0.  `--relative`, `-R`
 
@@ -794,7 +935,7 @@ your home directory (remove the '=' for that).
     in its path.  If you want to duplicate a server-side symlink, include both
     the symlink via its path, and referent directory via its real path.  If
     you're dealing with an older rsync on the sending side, you may need to use
-    the `--no-implied-dirs` option.
+    the [`--no-implied-dirs`](#opt) option.
 
     It is also possible to limit the amount of path information that is sent as
     implied directories for each path you specify.  With a modern rsync on the
@@ -820,7 +961,7 @@ your home directory (remove the '=' for that).
 
 0.  `--no-implied-dirs`
 
-    This option affects the default behavior of the `--relative` option.  When
+    This option affects the default behavior of the [`--relative`](#opt) option.  When
     it is specified, the attributes of the implied directories from the source
     names are not included in the transfer.  This means that the corresponding
     path elements on the destination system are left unchanged if they exist,
@@ -830,13 +971,13 @@ your home directory (remove the '=' for that).
 
     For instance, if a command-line arg or a files-from entry told rsync to
     transfer the file "path/foo/file", the directories "path" and "path/foo"
-    are implied when `--relative` is used.  If "path/foo" is a symlink to "bar"
+    are implied when [`--relative`](#opt) is used.  If "path/foo" is a symlink to "bar"
     on the destination system, the receiving rsync would ordinarily delete
     "path/foo", recreate it as a directory, and receive the file into the new
     directory.  With `--no-implied-dirs`, the receiving rsync updates
     "path/foo/file" using the existing path elements, which means that the file
     ends up being created in "path/bar".  Another way to accomplish this link
-    preservation is to use the `--keep-dirlinks` option (which will also affect
+    preservation is to use the [`--keep-dirlinks`](#opt) option (which will also affect
     symlinks to directories in the rest of the transfer).
 
     When pulling files from an rsync older than 3.0.0, you may need to use this
@@ -847,27 +988,31 @@ your home directory (remove the '=' for that).
 
     With this option, preexisting destination files are renamed as each file is
     transferred or deleted.  You can control where the backup file goes and
-    what (if any) suffix gets appended using the `--backup-dir` and `--suffix`
-    options.
-
-    Note that if you don't specify `--backup-dir`, (1) the `--omit-dir-times`
-    option will be forced on, and (2) if `--delete` is also in effect (without
-    `--delete-excluded`), rsync will add a "protect" filter-rule for the backup
-    suffix to the end of all your existing excludes (e.g. `-f "P *~"`).  This
-    will prevent previously backed-up files from being deleted.  Note that if
-    you are supplying your own filter rules, you may need to manually insert
-    your own exclude/protect rule somewhere higher up in the list so that it
-    has a high enough priority to be effective (e.g., if your rules specify a
-    trailing inclusion/exclusion of `*`, the auto-added rule would never be
-    reached).
+    what (if any) suffix gets appended using the [`--backup-dir`](#opt) and
+    [`--suffix`](#opt) options.
+
+    If you don't specify [`--backup-dir`](#opt):
+
+    1. the [`--omit-dir-times`](#opt) option will be forced on
+    2. the use of [`--delete`](#opt) (without [`--delete-excluded`](#opt)),
+       causes rsync to add a "protect" [filter-rule](#FILTER_RULES) for the
+       backup suffix to the end of all your existing filters that looks like
+       this: `-f "P *~"`.  This rule prevents previously backed-up files from
+       being deleted.
+
+    Note that if you are supplying your own filter rules, you may need to
+    manually insert your own exclude/protect rule somewhere higher up in the
+    list so that it has a high enough priority to be effective (e.g. if your
+    rules specify a trailing inclusion/exclusion of `*`, the auto-added rule
+    would never be reached).
 
 0.  `--backup-dir=DIR`
 
-    This implies the `--backup` option, and tells rsync to store all
+    This implies the [`--backup`](#opt) option, and tells rsync to store all
     backups in the specified directory on the receiving side.  This can be used
     for incremental backups.  You can additionally specify a backup suffix
-    using the `--suffix` option (otherwise the files backed up in the specified
-    directory will keep their original filenames).
+    using the [`--suffix`](#opt) option (otherwise the files backed up in the
+    specified directory will keep their original filenames).
 
     Note that if you specify a relative path, the backup directory will be
     relative to the destination directory, so you probably want to specify
@@ -878,8 +1023,8 @@ your home directory (remove the '=' for that).
 0.  `--suffix=SUFFIX`
 
     This option allows you to override the default backup suffix used with the
-    `--backup` (`-b`) option.  The default suffix is a `~` if no `--backup-dir`
-    was specified, otherwise it is an empty string.
+    [`--backup`](#opt) (`-b`) option.  The default suffix is a `~` if no
+    [`--backup-dir`](#opt) was specified, otherwise it is an empty string.
 
 0.  `--update`, `-u`
 
@@ -895,9 +1040,15 @@ your home directory (remove the '=' for that).
     directory where the destination has a file, the transfer would occur
     regardless of the timestamps.
 
-    This option is a transfer rule, not an exclude, so it doesn't affect the
-    data that goes into the file-lists, and thus it doesn't affect deletions.
-    It just limits the files that the receiver requests to be transferred.
+    This option is a [TRANSFER RULE](#TRANSFER_RULES), so don't expect any
+    exclude side effects.
+
+    A caution for those that choose to combine [`--inplace`](#opt) with
+    `--update`: an interrupted transfer will leave behind a partial file on the
+    receiving side that has a very recent modified time, so re-running the
+    transfer will probably **not** continue the interrupted file.  As such, it
+    is usually best to avoid combining this with[ `--inplace`](#opt) unless you
+    have implemented manual steps to handle any interrupted in-progress files.
 
 0.  `--inplace`
 
@@ -924,7 +1075,7 @@ your home directory (remove the '=' for that).
       for the open of the file for writing to be successful.
     - The efficiency of rsync's delta-transfer algorithm may be reduced if some
       data in the destination file is overwritten before it can be copied to a
-      position later in the file.  This does not apply if you use `--backup`,
+      position later in the file.  This does not apply if you use [`--backup`](#opt),
       since rsync is smart enough to use the backup file as the basis file for
       the transfer.
 
@@ -936,10 +1087,10 @@ your home directory (remove the '=' for that).
     bound.  It can also help keep a copy-on-write filesystem snapshot from
     diverging the entire contents of a file that only has minor changes.
 
-    The option implies `--partial` (since an interrupted transfer does not
-    delete the file), but conflicts with `--partial-dir` and `--delay-updates`.
-    Prior to rsync 2.6.4 `--inplace` was also incompatible with
-    `--compare-dest` and `--link-dest`.
+    The option implies [`--partial`](#opt) (since an interrupted transfer does
+    not delete the file), but conflicts with [`--partial-dir`](#opt) and
+    [`--delay-updates`](#opt).  Prior to rsync 2.6.4 `--inplace` was also
+    incompatible with [`--compare-dest`](#opt) and [`--link-dest`](#opt).
 
 0.  `--append`
 
@@ -965,13 +1116,13 @@ your home directory (remove the '=' for that).
 
 0.  `--append-verify`
 
-    This special copy mode works like `--append` except that all the data in
-    the file is included in the checksum verification (making it much less
+    This special copy mode works like [`--append`](#opt) except that all the
+    data in the file is included in the checksum verification (making it less
     efficient but also potentially safer).  This option **can be dangerous** if
     you aren't 100% sure that all the files in the transfer are shared, growing
-    files.  See the `--append` option for more details.
+    files.  See the [`--append`](#opt) option for more details.
 
-    Note: prior to rsync 3.0.0, the `--append` option worked like
+    Note: prior to rsync 3.0.0, the [`--append`](#opt) option worked like
     `--append-verify`, so if you are interacting with an older rsync (or the
     transfer is using a protocol prior to 30), specifying either append option
     will initiate an `--append-verify` transfer.
@@ -979,64 +1130,88 @@ your home directory (remove the '=' for that).
 0.  `--dirs`, `-d`
 
     Tell the sending side to include any directories that are encountered.
-    Unlike `--recursive`, a directory's contents are not copied unless the
-    directory name specified is "." or ends with a trailing slash (e.g. ".",
-    "dir/.", "dir/", etc.).  Without this option or the `--recursive` option,
-    rsync will skip all directories it encounters (and output a message to that
-    effect for each one).  If you specify both `--dirs` and `--recursive`,
-    `--recursive` takes precedence.
-
-    The `--dirs` option is implied by the `--files-from` option or the
-    `--list-only` option (including an implied `--list-only` usage) if
-    `--recursive` wasn't specified (so that directories are seen in the
-    listing).  Specify `--no-dirs` (or `--no-d`) if you want to turn this off.
-
-    There is also a backward-compatibility helper option, `--old-dirs` (or
-    `--old-d`) that tells rsync to use a hack of `-r --exclude='/*/*'` to get
+    Unlike [`--recursive`](#opt), a directory's contents are not copied unless
+    the directory name specified is "." or ends with a trailing slash (e.g.
+    ".", "dir/.", "dir/", etc.).  Without this option or the
+    [`--recursive`](#opt) option, rsync will skip all directories it encounters
+    (and output a message to that effect for each one).  If you specify both
+    `--dirs` and [`--recursive`](#opt), `--recursive` takes precedence.
+
+    The `--dirs` option is implied by the [`--files-from`](#opt) option or the
+    [`--list-only`](#opt) option (including an implied [`--list-only`](#opt)
+    usage) if [`--recursive`](#opt) wasn't specified (so that directories are
+    seen in the listing).  Specify `--no-dirs` (or `--no-d`) if you want to
+    turn this off.
+
+    There is also a backward-compatibility helper option, `--old-dirs`
+    (`--old-d`) that tells rsync to use a hack of `-r --exclude='/*/*'` to get
     an older rsync to list a single directory without recursing.
 
 0.  `--mkpath`
 
-    Create a missing path component of the destination arg.  This allows rsync
-    to create multiple levels of missing destination dirs and to create a path
-    in which to put a single renamed file.  Keep in mind that you'll need to
-    supply a trailing slash if you want the entire destination path to be
-    treated as a directory when copying a single arg (making rsync behave the
-    same way that it would if the path component of the destination had already
-    existed).
+    Create all missing path components of the destination path.
 
-    For example, the following creates a copy of file foo as bar in the sub/dir
-    directory, creating dirs "sub" and "sub/dir" if either do not yet exist:
+    By default, rsync allows only the final component of the destination path
+    to not exist, which is an attempt to help you to validate your destination
+    path.  With this option, rsync creates all the missing destination-path
+    components, just as if `mkdir -p $DEST_PATH` had been run on the receiving
+    side.
 
-    >     rsync -ai --mkpath foo sub/dir/bar
+    When specifying a destination path, including a trailing slash ensures that
+    the whole path is treated as directory names to be created, even when the
+    file list has a single item. See the [COPYING TO A DIFFERENT NAME](#)
+    section for full details on how rsync decides if a final destination-path
+    component should be created as a directory or not.
 
-    If you instead ran the following, it would have created file foo in the
-    sub/dir/bar directory:
+    If you would like the newly-created destination dirs to match the dirs on
+    the sending side, you should be using [`--relative`](#opt) (`-R`) instead
+    of `--mkpath`.  For instance, the following two commands result in the same
+    destination tree, but only the second command ensures that the
+    "some/extra/path" components match the dirs on the sending side:
 
-    >     rsync -ai --mkpath foo sub/dir/bar/
+    >     rsync -ai --mkpath host:some/extra/path/*.c some/extra/path/
+    >     rsync -aiR host:some/extra/path/*.c ./
 
 0.  `--links`, `-l`
 
-    When symlinks are encountered, recreate the symlink on the destination.
+    Add symlinks to the transferred files instead of noisily ignoring them with
+    a "non-regular file" warning for each symlink encountered.  You can
+    alternately silence the warning by specifying [`--info=nonreg0`](#opt).
+
+    The default handling of symlinks is to recreate each symlink's unchanged
+    value on the receiving side.
+
+    See the [SYMBOLIC LINKS](#) section for multi-option info.
 
 0.  `--copy-links`, `-L`
 
-    When symlinks are encountered, the item that they point to (the referent)
-    is copied, rather than the symlink.  In older versions of rsync, this
-    option also had the side-effect of telling the receiving side to follow
-    symlinks, such as symlinks to directories.  In a modern rsync such as this
-    one, you'll need to specify `--keep-dirlinks` (`-K`) to get this extra
-    behavior.  The only exception is when sending files to an rsync that is too
-    old to understand `-K` -- in that case, the `-L` option will still have the
-    side-effect of `-K` on that older receiving rsync.
+    The sender transforms each symlink encountered in the transfer into the
+    referent item, following the symlink chain to the file or directory that it
+    references.  If a symlink chain is broken, an error is output and the file
+    is dropped from the transfer.
+
+    This option supersedes any other options that affect symlinks in the
+    transfer, since there are no symlinks left in the transfer.
+
+    This option does not change the handling of existing symlinks on the
+    receiving side, unlike versions of rsync prior to 2.6.3 which had the
+    side-effect of telling the receiving side to also follow symlinks.  A
+    modern rsync won't forward this option to a remote receiver (since only the
+    sender needs to know about it), so this caveat should only affect someone
+    using an rsync client older than 2.6.7 (which is when `-L` stopped being
+    forwarded to the receiver).
+
+    See the [`--keep-dirlinks`](#opt) (`-K`) if you need a symlink to a
+    directory to be treated as a real directory on the receiving side.
+
+    See the [SYMBOLIC LINKS](#) section for multi-option info.
 
 0.  `--copy-unsafe-links`
 
     This tells rsync to copy the referent of symbolic links that point outside
     the copied tree.  Absolute symlinks are also treated like ordinary files,
-    and so are any symlinks in the source path itself when `--relative` is
-    used.  This option has no additional effect if `--copy-links` was also
-    specified.
+    and so are any symlinks in the source path itself when [`--relative`](#opt)
+    is used.
 
     Note that the cut-off point is the top of the transfer, which is the part
     of the path that rsync isn't mentioning in the verbose output.  If you copy
@@ -1047,50 +1222,88 @@ your home directory (remove the '=' for that).
     slash) to "/dest/subdir" that would not allow symlinks to any files outside
     of "subdir".
 
+    Note that safe symlinks are only copied if [`--links`](#opt) was also
+    specified or implied. The `--copy-unsafe-links` option has no extra effect
+    when combined with [`--copy-links`](#opt).
+
+    See the [SYMBOLIC LINKS](#) section for multi-option info.
+
 0.  `--safe-links`
 
-    This tells rsync to ignore any symbolic links which point outside the
-    copied tree.  All absolute symlinks are also ignored. Using this option in
-    conjunction with `--relative` may give unexpected results.
+    This tells the receiving rsync to ignore any symbolic links in the transfer
+    which point outside the copied tree.  All absolute symlinks are also
+    ignored.
 
-0.  `--munge-links`
+    Since this ignoring is happening on the receiving side, it will still be
+    effective even when the sending side has munged symlinks (when it is using
+    [`--munge-links`](#opt)). It also affects deletions, since the file being
+    present in the transfer prevents any matching file on the receiver from
+    being deleted when the symlink is deemed to be unsafe and is skipped.
 
-    This option tells rsync to (1) modify all symlinks on the receiving side in
-    a way that makes them unusable but recoverable (see below), or (2) to
-    unmunge symlinks on the sending side that had been stored in a munged
-    state.  This is useful if you don't quite trust the source of the data to
-    not try to slip in a symlink to a unexpected place.
+    This option must be combined with [`--links`](#opt) (or
+    [`--archive`](#opt)) to have any symlinks in the transfer to conditionally
+    ignore. Its effect is superseded by [`--copy-unsafe-links`](#opt).
 
-    The way rsync disables the use of symlinks is to prefix each one with the
-    string "/rsyncd-munged/".  This prevents the links from being used as long
-    as that directory does not exist.  When this option is enabled, rsync will
-    refuse to run if that path is a directory or a symlink to a directory.
+    Using this option in conjunction with [`--relative`](#opt) may give
+    unexpected results.
 
-    The option only affects the client side of the transfer, so if you need it
-    to affect the server, specify it via `--remote-option`. (Note that in a
-    local transfer, the client side is the sender.)
+    See the [SYMBOLIC LINKS](#) section for multi-option info.
 
-    This option has no affect on a daemon, since the daemon configures whether
-    it wants munged symlinks via its "`munge symlinks`" parameter.  See also the
-    "munge-symlinks" perl script in the support directory of the source code.
+0.  `--munge-links`
+
+    This option affects just one side of the transfer and tells rsync to munge
+    symlink values when it is receiving files or unmunge symlink values when it
+    is sending files.  The munged values make the symlinks unusable on disk but
+    allows the original contents of the symlinks to be recovered.
+
+    The server-side rsync often enables this option without the client's
+    knowledge, such as in an rsync daemon's configuration file or by an option
+    given to the rrsync (restricted rsync) script.  When specified on the
+    client side, specify the option normally if it is the client side that
+    has/needs the munged symlinks, or use `-M--munge-links` to give the option
+    to the server when it has/needs the munged symlinks.  Note that on a local
+    transfer, the client is the sender, so specifying the option directly
+    unmunges symlinks while specifying it as a remote option munges symlinks.
+
+    This option has no effect when sent to a daemon via [`--remote-option`](#opt)
+    because the daemon configures whether it wants munged symlinks via its
+    "`munge symlinks`" parameter.
+
+    The symlink value is munged/unmunged once it is in the transfer, so any
+    option that transforms symlinks into non-symlinks occurs prior to the
+    munging/unmunging **except** for [`--safe-links`](#opt), which is a choice
+    that the receiver makes, so it bases its decision on the munged/unmunged
+    value.  This does mean that if a receiver has munging enabled, that using
+    [`--safe-links`](#opt) will cause all symlinks to be ignored (since they
+    are all absolute).
+
+    The method that rsync uses to munge the symlinks is to prefix each one's
+    value with the string "/rsyncd-munged/".  This prevents the links from
+    being used as long as the directory does not exist.  When this option is
+    enabled, rsync will refuse to run if that path is a directory or a symlink
+    to a directory (though it only checks at startup).  See also the
+    "munge-symlinks" python script in the support directory of the source code
+    for a way to munge/unmunge one or more symlinks in-place.
 
 0.  `--copy-dirlinks`, `-k`
 
     This option causes the sending side to treat a symlink to a directory as
     though it were a real directory.  This is useful if you don't want symlinks
-    to non-directories to be affected, as they would be using `--copy-links`.
+    to non-directories to be affected, as they would be using
+    [`--copy-links`](#opt).
 
     Without this option, if the sending side has replaced a directory with a
     symlink to a directory, the receiving side will delete anything that is in
     the way of the new symlink, including a directory hierarchy (as long as
-    `--force` or `--delete` is in effect).
+    [`--force`](#opt) or [`--delete`](#opt) is in effect).
 
-    See also `--keep-dirlinks` for an analogous option for the receiving side.
+    See also [`--keep-dirlinks`](#opt) for an analogous option for the
+    receiving side.
 
     `--copy-dirlinks` applies to all symlinks to directories in the source.  If
     you want to follow only a few specified symlinks, a trick you can use is to
     pass them as additional source args with a trailing slash, using
-    `--relative` to make the paths match up right.  For example:
+    [`--relative`](#opt) to make the paths match up right.  For example:
 
     >     rsync -r --relative src/./ src/./follow-me/ dest/
 
@@ -1099,6 +1312,8 @@ your home directory (remove the '=' for that).
     directory in the file-list which overrides the symlink found during the
     scan of "src/./".
 
+    See the [SYMBOLIC LINKS](#) section for multi-option info.
+
 0.  `--keep-dirlinks`, `-K`
 
     This option causes the receiving side to treat a symlink to a directory as
@@ -1114,14 +1329,18 @@ your home directory (remove the '=' for that).
     "bar".
 
     One note of caution: if you use `--keep-dirlinks`, you must trust all the
-    symlinks in the copy! If it is possible for an untrusted user to create
-    their own symlink to any directory, the user could then (on a subsequent
+    symlinks in the copy or enable the [`--munge-links`](#opt) option on the
+    receiving side!  If it is possible for an untrusted user to create their
+    own symlink to any real directory, the user could then (on a subsequent
     copy) replace the symlink with a real directory and affect the content of
     whatever directory the symlink references.  For backup copies, you are
     better off using something like a bind mount instead of a symlink to modify
     your receiving hierarchy.
 
-    See also `--copy-dirlinks` for an analogous option for the sending side.
+    See also [`--copy-dirlinks`](#opt) for an analogous option for the sending
+    side.
+
+    See the [SYMBOLIC LINKS](#) section for multi-option info.
 
 0.  `--hard-links`, `-H`
 
@@ -1138,41 +1357,42 @@ your home directory (remove the '=' for that).
       is present in the source file list), the copying algorithm will not break
       them explicitly.  However, if one or more of the paths have content
       differences, the normal file-update process will break those extra links
-      (unless you are using the `--inplace` option).
-    - If you specify a `--link-dest` directory that contains hard links, the
-      linking of the destination files against the `--link-dest` files can
-      cause some paths in the destination to become linked together due to the
-      `--link-dest` associations.
+      (unless you are using the [`--inplace`](#opt) option).
+    - If you specify a [`--link-dest`](#opt) directory that contains hard
+      links, the linking of the destination files against the
+      [`--link-dest`](#opt) files can cause some paths in the destination to
+      become linked together due to the [`--link-dest`](#opt) associations.
 
     Note that rsync can only detect hard links between files that are inside
     the transfer set.  If rsync updates a file that has extra hard-link
     connections to files outside the transfer, that linkage will be broken.  If
-    you are tempted to use the `--inplace` option to avoid this breakage, be
+    you are tempted to use the [`--inplace`](#opt) option to avoid this breakage, be
     very careful that you know how your files are being updated so that you are
     certain that no unintended changes happen due to lingering hard links (and
-    see the `--inplace` option for more caveats).
+    see the [`--inplace`](#opt) option for more caveats).
 
-    If incremental recursion is active (see `--recursive`), rsync may transfer
-    a missing hard-linked file before it finds that another link for that
-    contents exists elsewhere in the hierarchy.  This does not affect the
-    accuracy of the transfer (i.e. which files are hard-linked together), just
-    its efficiency (i.e. copying the data for a new, early copy of a
+    If incremental recursion is active (see [`--inc-recursive`](#opt)), rsync
+    may transfer a missing hard-linked file before it finds that another link
+    for that contents exists elsewhere in the hierarchy.  This does not affect
+    the accuracy of the transfer (i.e. which files are hard-linked together),
+    just its efficiency (i.e. copying the data for a new, early copy of a
     hard-linked file that could have been found later in the transfer in
     another member of the hard-linked set of files).  One way to avoid this
     inefficiency is to disable incremental recursion using the
-    `--no-inc-recursive` option.
+    [`--no-inc-recursive`](#opt) option.
 
 0.  `--perms`, `-p`
 
     This option causes the receiving rsync to set the destination permissions
-    to be the same as the source permissions. (See also the `--chmod` option
-    for a way to modify what rsync considers to be the source permissions.)
+    to be the same as the source permissions. (See also the [`--chmod`](#opt)
+    option for a way to modify what rsync considers to be the source
+    permissions.)
 
     When this option is _off_, permissions are set as follows:
 
     - Existing files (including updated files) retain their existing
-      permissions, though the `--executability` option might change just the
-      execute permission for the file.
+      permissions, though the [`--executability`](#opt) option might change
+      just the execute permission for the file.
     - New files get their "normal" permission bits set to the source file's
       permissions masked with the receiving directory's default permissions
       (either the receiving process's umask, or the permissions specified via
@@ -1180,18 +1400,19 @@ your home directory (remove the '=' for that).
       bits disabled except in the case where a new directory inherits a setgid
       bit from its parent directory.
 
-    Thus, when `--perms` and `--executability` are both disabled, rsync's
+    Thus, when `--perms` and [`--executability`](#opt) are both disabled, rsync's
     behavior is the same as that of other file-copy utilities, such as **cp**(1)
     and **tar**(1).
 
     In summary: to give destination files (both old and new) the source
     permissions, use `--perms`.  To give new files the destination-default
     permissions (while leaving existing files unchanged), make sure that the
-    `--perms` option is off and use `--chmod=ugo=rwX` (which ensures that all
-    non-masked bits get enabled).  If you'd care to make this latter behavior
-    easier to type, you could define a popt alias for it, such as putting this
-    line in the file `~/.popt` (the following defines the `-Z` option, and
-    includes `--no-g` to use the default group of the destination dir):
+    `--perms` option is off and use [`--chmod=ugo=rwX`](#opt) (which ensures
+    that all non-masked bits get enabled).  If you'd care to make this latter
+    behavior easier to type, you could define a popt alias for it, such as
+    putting this line in the file `~/.popt` (the following defines the `-Z`
+    option, and includes `--no-g` to use the default group of the destination
+    dir):
 
     >      rsync alias -Z --no-p --no-g --chmod=ugo=rwX
 
@@ -1215,8 +1436,8 @@ your home directory (remove the '=' for that).
 0.  `--executability`, `-E`
 
     This option causes rsync to preserve the executability (or
-    non-executability) of regular files when `--perms` is not enabled.  A
-    regular file is considered to be executable if at least one 'x' is turned
+    non-executability) of regular files when [`--perms`](#opt) is not enabled.
+    A regular file is considered to be executable if at least one 'x' is turned
     on in its permissions.  When an existing destination file's executability
     differs from that of the corresponding source file, rsync modifies the
     destination file's permissions as follows:
@@ -1225,16 +1446,16 @@ your home directory (remove the '=' for that).
     - To make a file executable, rsync turns on each 'x' permission that has a
       corresponding 'r' permission enabled.
 
-    If `--perms` is enabled, this option is ignored.
+    If [`--perms`](#opt) is enabled, this option is ignored.
 
 0.  `--acls`, `-A`
 
     This option causes rsync to update the destination ACLs to be the same as
-    the source ACLs.  The option also implies `--perms`.
+    the source ACLs.  The option also implies [`--perms`](#opt).
 
     The source and destination systems must have compatible ACL entries for
-    this option to work properly.  See the `--fake-super` option for a way to
-    backup and restore ACLs that are not compatible.
+    this option to work properly.  See the [`--fake-super`](#opt) option for a
+    way to backup and restore ACLs that are not compatible.
 
 0.  `--xattrs`, `-X`
 
@@ -1244,7 +1465,7 @@ your home directory (remove the '=' for that).
     For systems that support extended-attribute namespaces, a copy being done
     by a super-user copies all namespaces except system.\*.  A normal user only
     copies the user.\* namespace.  To be able to backup and restore non-user
-    namespaces as a normal user, see the `--fake-super` option.
+    namespaces as a normal user, see the [`--fake-super`](#opt) option.
 
     The above name filtering can be overridden by using one or more filter
     options with the **x** modifier.  When you specify an xattr-affecting
@@ -1266,8 +1487,8 @@ your home directory (remove the '=' for that).
     >     --filter='-xr *'
 
     Note that the `-X` option does not copy rsync's special xattr values (e.g.
-    those used by `--fake-super`) unless you repeat the option (e.g. `-XX`).
-    This "copy all xattrs" mode cannot be used with `--fake-super`.
+    those used by [`--fake-super`](#opt)) unless you repeat the option (e.g. `-XX`).
+    This "copy all xattrs" mode cannot be used with [`--fake-super`](#opt).
 
 0.  `--chmod=CHMOD`
 
@@ -1275,7 +1496,7 @@ your home directory (remove the '=' for that).
     to the permission of the files in the transfer.  The resulting value is
     treated as though it were the permissions that the sending side supplied
     for the file, which means that this option can seem to have no effect on
-    existing files if `--perms` is not enabled.
+    existing files if [`--perms`](#opt) is not enabled.
 
     In addition to the normal parsing rules specified in the **chmod**(1)
     manpage, you can specify an item that should only apply to a directory by
@@ -1294,20 +1515,20 @@ your home directory (remove the '=' for that).
     It is also legal to specify multiple `--chmod` options, as each additional
     option is just appended to the list of changes to make.
 
-    See the `--perms` and `--executability` options for how the resulting
-    permission value can be applied to the files in the transfer.
+    See the [`--perms`](#opt) and [`--executability`](#opt) options for how the
+    resulting permission value can be applied to the files in the transfer.
 
 0.  `--owner`, `-o`
 
     This option causes rsync to set the owner of the destination file to be the
     same as the source file, but only if the receiving rsync is being run as
-    the super-user (see also the `--super` and `--fake-super` options).  Without
-    this option, the owner of new and/or transferred files are set to the
-    invoking user on the receiving side.
+    the super-user (see also the [`--super`](#opt) and [`--fake-super`](#opt)
+    options).  Without this option, the owner of new and/or transferred files
+    are set to the invoking user on the receiving side.
 
     The preservation of ownership will associate matching names by default, but
     may fall back to using the ID number in some circumstances (see also the
-    `--numeric-ids` option for a full discussion).
+    [`--numeric-ids`](#opt) option for a full discussion).
 
 0.  `--group`, `-g`
 
@@ -1320,60 +1541,88 @@ your home directory (remove the '=' for that).
 
     The preservation of group information will associate matching names by
     default, but may fall back to using the ID number in some circumstances
-    (see also the `--numeric-ids` option for a full discussion).
+    (see also the [`--numeric-ids`](#opt) option for a full discussion).
 
 0.  `--devices`
 
     This option causes rsync to transfer character and block device files to
-    the remote system to recreate these devices.  This option has no effect if
-    the receiving rsync is not run as the super-user (see also the `--super`
-    and `--fake-super` options).
+    the remote system to recreate these devices.  If the receiving rsync is not
+    being run as the super-user, rsync silently skips creating the device files
+    (see also the [`--super`](#opt) and [`--fake-super`](#opt) options).
+
+    By default, rsync generates a "non-regular file" warning for each device
+    file encountered when this option is not set.  You can silence the warning
+    by specifying [`--info=nonreg0`](#opt).
 
 0.  `--specials`
 
-    This option causes rsync to transfer special files such as named sockets
-    and fifos.
+    This option causes rsync to transfer special files, such as named sockets
+    and fifos.  If the receiving rsync is not being run as the super-user,
+    rsync silently skips creating the special files (see also the
+    [`--super`](#opt) and [`--fake-super`](#opt) options).
+
+    By default, rsync generates a "non-regular file" warning for each special
+    file encountered when this option is not set.  You can silence the warning
+    by specifying [`--info=nonreg0`](#opt).
 
 0.  `-D`
 
-    The `-D` option is equivalent to `--devices --specials`.
+    The `-D` option is equivalent to "[`--devices`](#opt)
+    [`--specials`](#opt)".
+
+0.  `--copy-devices`
+
+    This tells rsync to treat a device on the sending side as a regular file,
+    allowing it to be copied to a normal destination file (or another device
+    if `--write-devices` was also specified).
+
+    This option is refused by default by an rsync daemon.
 
 0.  `--write-devices`
 
     This tells rsync to treat a device on the receiving side as a regular file,
     allowing the writing of file data into a device.
 
-    This option implies the `--inplace` option.
+    This option implies the [`--inplace`](#opt) option.
 
     Be careful using this, as you should know what devices are present on the
-    receiving side of the transfer, especially if running rsync as root.
+    receiving side of the transfer, especially when running rsync as root.
 
-    This option is refused by an rsync daemon.
+    This option is refused by default by an rsync daemon.
 
 0.  `--times`, `-t`
 
     This tells rsync to transfer modification times along with the files and
     update them on the remote system.  Note that if this option is not used,
     the optimization that excludes files that have not been modified cannot be
-    effective; in other words, a missing `-t` or `-a` will cause the next
-    transfer to behave as if it used `-I`, causing all files to be updated
-    (though rsync's delta-transfer algorithm will make the update fairly
-    efficient if the files haven't actually changed, you're much better off
-    using `-t`).
+    effective; in other words, a missing `-t` (or [`-a`](#opt)) will cause the
+    next transfer to behave as if it used [`--ignore-times`](#opt) (`-I`),
+    causing all files to be updated (though rsync's delta-transfer algorithm
+    will make the update fairly efficient if the files haven't actually
+    changed, you're much better off using `-t`).
+
+    A modern rsync that is using transfer protocol 30 or 31 conveys a modify
+    time using up to 8-bytes. If rsync is forced to speak an older protocol
+    (perhaps due to the remote rsync being older than 3.0.0) a modify time is
+    conveyed using 4-bytes. Prior to 3.2.7, these shorter values could convey
+    a date range of 13-Dec-1901 to 19-Jan-2038.  Beginning with 3.2.7, these
+    4-byte values now convey a date range of 1-Jan-1970 to 7-Feb-2106.  If you
+    have files dated older than 1970, make sure your rsync executables are
+    upgraded so that the full range of dates can be conveyed.
 
 0.  `--atimes`, `-U`
 
     This tells rsync to set the access (use) times of the destination files to
     the same value as the source files.
 
-    If repeated, it also sets the `--open-noatime` option, which can help you
+    If repeated, it also sets the [`--open-noatime`](#opt) option, which can help you
     to make the sending and receiving systems have the same access times on the
     transferred files without needing to run rsync an extra time after a file
     is transferred.
 
     Note that some older rsync versions (prior to 3.2.0) may have been built
-    with a pre-release `--atimes` patch that does not imply `--open-noatime`
-    when this option is repeated.
+    with a pre-release `--atimes` patch that does not imply
+    [`--open-noatime`](#opt) when this option is repeated.
 
 0.  `--open-noatime`
 
@@ -1387,25 +1636,20 @@ your home directory (remove the '=' for that).
 0.  `--crtimes`, `-N,`
 
     This tells rsync to set the create times (newness) of the destination
-    files to the same value as the source files.
+    files to the same value as the source files. Your OS & filesystem must
+    support the setting of arbitrary creation (birth) times for this option
+    to be supported.
 
 0.  `--omit-dir-times`, `-O`
 
     This tells rsync to omit directories when it is preserving modification,
     access, and create times.  If NFS is sharing the directories on the receiving
     side, it is a good idea to use `-O`.  This option is inferred if you use
-    `--backup` without `--backup-dir`.
-
-    This option also has the side-effect of avoiding early creation of
-    directories in incremental recursion copies.  The default `--inc-recursive`
-    copying normally does an early-create pass of all the sub-directories in a
-    parent directory in order for it to be able to then set the modify time of
-    the parent directory right away (without having to delay that until a bunch
-    of recursive copying has finished).  This early-create idiom is not
-    necessary if directory modify times are not being preserved, so it is
-    skipped.  Since early-create directories don't have accurate mode, mtime,
-    or ownership, the use of this option can help when someone wants to avoid
-    these partially-finished directories.
+    [`--backup`](#opt) without [`--backup-dir`](#opt).
+
+    This option also has the side-effect of avoiding early creation of missing
+    sub-directories when incremental recursion is enabled, as discussed in the
+    [`--inc-recursive`](#opt) section.
 
 0.  `--omit-link-times`, `-J`
 
@@ -1416,12 +1660,13 @@ your home directory (remove the '=' for that).
 
     This tells the receiving side to attempt super-user activities even if the
     receiving rsync wasn't run by the super-user.  These activities include:
-    preserving users via the `--owner` option, preserving all groups (not just
-    the current user's groups) via the `--group` option, and copying devices
-    via the `--devices` option.  This is useful for systems that allow such
-    activities without being the super-user, and also for ensuring that you
-    will get errors if the receiving side isn't being run as the super-user.
-    To turn off super-user activities, the super-user can use `--no-super`.
+    preserving users via the [`--owner`](#opt) option, preserving all groups
+    (not just the current user's groups) via the [`--group`](#opt) option, and
+    copying devices via the [`--devices`](#opt) option.  This is useful for
+    systems that allow such activities without being the super-user, and also
+    for ensuring that you will get errors if the receiving side isn't being run
+    as the super-user.  To turn off super-user activities, the super-user can
+    use `--no-super`.
 
 0.  `--fake-super`
 
@@ -1434,15 +1679,15 @@ your home directory (remove the '=' for that).
     u-s,g-s,o-t for safety) or that would limit the owner's access (since the
     real super-user can always access/change a file, the files we create can
     always be accessed/changed by the creating user).  This option also handles
-    ACLs (if `--acls` was specified) and non-user extended attributes (if
-    `--xattrs` was specified).
+    ACLs (if [`--acls`](#opt) was specified) and non-user extended attributes
+    (if [`--xattrs`](#opt) was specified).
 
     This is a good way to backup data without using a super-user, and to store
     ACLs from incompatible systems.
 
     The `--fake-super` option only affects the side where the option is used.
     To affect the remote side of a remote-shell connection, use the
-    `--remote-option` (`-M`) option:
+    [`--remote-option`](#opt) (`-M`) option:
 
     >     rsync -av -M--fake-super /src/ host:/dest/
 
@@ -1451,21 +1696,22 @@ your home directory (remove the '=' for that).
     files, specify `-M--fake-super`.  If you wish a local copy to enable this
     option just for the source files, combine `--fake-super` with `-M--super`.
 
-    This option is overridden by both `--super` and `--no-super`.
+    This option is overridden by both [`--super`](#opt) and `--no-super`.
 
-    See also the "`fake super`" setting in the daemon's rsyncd.conf file.
+    See also the [`fake super`](rsyncd.conf.5#fake_super) setting in the
+    daemon's rsyncd.conf file.
 
 0.  `--sparse`, `-S`
 
     Try to handle sparse files efficiently so they take up less space on the
-    destination.  If combined with `--inplace` the file created might not end
-    up with sparse blocks with some combinations of kernel version and/or
-    filesystem type.  If `--whole-file` is in effect (e.g. for a local copy)
-    then it will always work because rsync truncates the file prior to writing
-    out the updated version.
+    destination.  If combined with [`--inplace`](#opt) the file created might
+    not end up with sparse blocks with some combinations of kernel version
+    and/or filesystem type.  If [`--whole-file`](#opt) is in effect (e.g. for a
+    local copy) then it will always work because rsync truncates the file prior
+    to writing out the updated version.
 
     Note that versions of rsync older than 3.1.3 will reject the combination of
-    `--sparse` and `--inplace`.
+    `--sparse` and [`--inplace`](#opt).
 
 0.  `--preallocate`
 
@@ -1480,26 +1726,26 @@ your home directory (remove the '=' for that).
     the destination is not an extent-supporting filesystem (such as ext4, xfs,
     NTFS, etc.), this option may have no positive effect at all.
 
-    If combined with `--sparse`, the file will only have sparse blocks (as
-    opposed to allocated sequences of null bytes) if the kernel version and
+    If combined with [`--sparse`](#opt), the file will only have sparse blocks
+    (as opposed to allocated sequences of null bytes) if the kernel version and
     filesystem type support creating holes in the allocated data.
 
 0.  `--dry-run`, `-n`
 
     This makes rsync perform a trial run that doesn't make any changes (and
     produces mostly the same output as a real run).  It is most commonly used
-    in combination with the `--verbose`, `-v` and/or `--itemize-changes`, `-i`
-    options to see what an rsync command is going to do before one actually
-    runs it.
-
-    The output of `--itemize-changes` is supposed to be exactly the same on a
-    dry run and a subsequent real run (barring intentional trickery and system
-    call failures); if it isn't, that's a bug.  Other output should be mostly
-    unchanged, but may differ in some areas.  Notably, a dry run does not send
-    the actual data for file transfers, so `--progress` has no effect, the
-    "bytes sent", "bytes received", "literal data", and "matched data"
-    statistics are too small, and the "speedup" value is equivalent to a run
-    where no file transfers were needed.
+    in combination with the [`--verbose`](#opt) (`-v`) and/or
+    [`--itemize-changes`](#opt) (`-i`) options to see what an rsync command is
+    going to do before one actually runs it.
+
+    The output of [`--itemize-changes`](#opt) is supposed to be exactly the
+    same on a dry run and a subsequent real run (barring intentional trickery
+    and system call failures); if it isn't, that's a bug.  Other output should
+    be mostly unchanged, but may differ in some areas.  Notably, a dry run does
+    not send the actual data for file transfers, so [`--progress`](#opt) has no
+    effect, the "bytes sent", "bytes received", "literal data", and "matched
+    data" statistics are too small, and the "speedup" value is equivalent to a
+    run where no file transfers were needed.
 
 0.  `--whole-file`, `-W`
 
@@ -1511,11 +1757,20 @@ your home directory (remove the '=' for that).
     source and destination are specified as local paths, but only if no
     batch-writing option is in effect.
 
+0. `--no-whole-file`, `--no-W`
+
+    Disable whole-file updating when it is enabled by default for a local
+    transfer.  This usually slows rsync down, but it can be useful if you are
+    trying to minimize the writes to the destination file (if combined with
+    [`--inplace`](#opt)) or for testing the checksum-based update algorithm.
+
+    See also the [`--whole-file`](#opt) option.
+
 0.  `--checksum-choice=STR`, `--cc=STR`
 
     This option overrides the checksum algorithms.  If one algorithm name is
     specified, it is used for both the transfer checksums and (assuming
-    `--checksum` is specified) the pre-transfer checksums.  If two
+    [`--checksum`](#opt) is specified) the pre-transfer checksums.  If two
     comma-separated names are supplied, the first name affects the transfer
     checksums, and the second name affects the pre-transfer checksums (`-c`).
 
@@ -1527,15 +1782,16 @@ your home directory (remove the '=' for that).
     - `xxh64` (aka `xxhash`)
     - `md5`
     - `md4`
+    - `sha1`
     - `none`
 
     Run `rsync --version` to see the default checksum list compiled into your
     version (which may differ from the list above).
 
-    If "none" is specified for the first (or only) name, the `--whole-file`
+    If "none" is specified for the first (or only) name, the [`--whole-file`](#opt)
     option is forced on and no checksum verification is performed on the
     transferred data.  If "none" is specified for the second (or only) name,
-    the `--checksum` option cannot be used.
+    the [`--checksum`](#opt) option cannot be used.
 
     The "auto" option is the default, where rsync bases its algorithm choice on
     a negotiation between the client and the server as follows:
@@ -1548,14 +1804,14 @@ your home directory (remove the '=' for that).
     and various flavors of MD4 based on protocol age).
 
     The default order can be customized by setting the environment variable
-    RSYNC_CHECKSUM_LIST to a space-separated list of acceptable checksum names.
-    If the string contains a "`&`" character, it is separated into the "client
-    string & server string", otherwise the same string
-    applies to both.  If the string (or string portion) contains no
-    non-whitespace characters, the default checksum list is used.  This method
-    does not allow you to specify the transfer checksum separately from the
-    pre-transfer checksum, and it discards "auto" and all unknown checksum
-    names.  A list with only invalid names results in a failed negotiation.
+    [`RSYNC_CHECKSUM_LIST`](#) to a space-separated list of acceptable checksum
+    names.  If the string contains a "`&`" character, it is separated into the
+    "client string & server string", otherwise the same string applies to both.
+    If the string (or string portion) contains no non-whitespace characters,
+    the default checksum list is used.  This method does not allow you to
+    specify the transfer checksum separately from the pre-transfer checksum,
+    and it discards "auto" and all unknown checksum names.  A list with only
+    invalid names results in a failed negotiation.
 
     The use of the `--checksum-choice` option overrides this environment list.
 
@@ -1573,45 +1829,45 @@ your home directory (remove the '=' for that).
     encounters (using the attributes of the mounted directory because those of
     the underlying mount-point directory are inaccessible).
 
-    If rsync has been told to collapse symlinks (via `--copy-links` or
-    `--copy-unsafe-links`), a symlink to a directory on another device is
-    treated like a mount-point.  Symlinks to non-directories are unaffected by
-    this option.
+    If rsync has been told to collapse symlinks (via [`--copy-links`](#opt) or
+    [`--copy-unsafe-links`](#opt)), a symlink to a directory on another device
+    is treated like a mount-point.  Symlinks to non-directories are unaffected
+    by this option.
 
-0.  `--existing`, `--ignore-non-existing`
+0.  `--ignore-non-existing`, `--existing`
 
     This tells rsync to skip creating files (including directories) that do not
     exist yet on the destination.  If this option is combined with the
-    `--ignore-existing` option, no files will be updated (which can be useful
-    if all you want to do is delete extraneous files).
+    [`--ignore-existing`](#opt) option, no files will be updated (which can be
+    useful if all you want to do is delete extraneous files).
 
-    This option is a transfer rule, not an exclude, so it doesn't affect the
-    data that goes into the file-lists, and thus it doesn't affect deletions.
-    It just limits the files that the receiver requests to be transferred.
+    This option is a [TRANSFER RULE](#TRANSFER_RULES), so don't expect any
+    exclude side effects.
 
 0.  `--ignore-existing`
 
     This tells rsync to skip updating files that already exist on the
     destination (this does _not_ ignore existing directories, or nothing would
-    get done).  See also `--existing`.
-
-    This option is a transfer rule, not an exclude, so it doesn't affect the
-    data that goes into the file-lists, and thus it doesn't affect deletions.
-    It just limits the files that the receiver requests to be transferred.
-
-    This option can be useful for those doing backups using the `--link-dest`
-    option when they need to continue a backup run that got interrupted.  Since
-    a `--link-dest` run is copied into a new directory hierarchy (when it is
-    used properly), using `--ignore-existing` will ensure that the
-    already-handled files don't get tweaked (which avoids a change in
-    permissions on the hard-linked files).  This does mean that this option is
-    only looking at the existing files in the destination hierarchy itself.
-
-    When `--info=skip2` is used rsync will output "FILENAME exists (INFO)"
-    messages where the INFO indicates one of "type change", "sum change"
-    (requires `-c`), "file change" (based on the quick check), "attr change",
-    or "uptodate".  Using `--info=skip1` (which is also implied by `-vv`)
-    outputs the exists message without the INFO suffix.
+    get done).  See also [`--ignore-non-existing`](#opt).
+
+    This option is a [TRANSFER RULE](#TRANSFER_RULES), so don't expect any
+    exclude side effects.
+
+    This option can be useful for those doing backups using the
+    [`--link-dest`](#opt) option when they need to continue a backup run that
+    got interrupted.  Since a [`--link-dest`](#opt) run is copied into a new
+    directory hierarchy (when it is used properly), using [`--ignore-existing`
+    will ensure that the already-handled files don't get tweaked (which avoids
+    a change in permissions on the hard-linked files).  This does mean that
+    this option is only looking at the existing files in the destination
+    hierarchy itself.
+
+    When [`--info=skip2`](#opt) is used rsync will output "FILENAME exists
+    (INFO)" messages where the INFO indicates one of "type change", "sum
+    change" (requires [`-c`](#opt)), "file change" (based on the quick check),
+    "attr change", or "uptodate".  Using [`--info=skip1`](#opt) (which is also
+    implied by 2 [`-v`](#opt) options) outputs the exists message without the
+    INFO suffix.
 
 0.  `--remove-source-files`
 
@@ -1627,12 +1883,16 @@ your home directory (remove the '=' for that).
     If you can't first write the files into a different directory, you should
     use a naming idiom that lets rsync avoid transferring files that are not
     yet finished (e.g. name the file "foo.new" when it is written, rename it to
-    "foo" when it is done, and then use the option `--exclude='*.new'` for the
-    rsync transfer).
+    "foo" when it is done, and then use the option [`--exclude='*.new'`](#opt)
+    for the rsync transfer).
 
     Starting with 3.1.0, rsync will skip the sender-side removal (and output an
     error) if the file's size or modify time has not stayed unchanged.
 
+    Starting with 3.2.6, a local rsync copy will ensure that the sender does
+    not remove a file the receiver just verified, such as when the user
+    accidentally makes the source and destination directory the same path.
+
 0.  `--delete`
 
     This tells rsync to delete extraneous files from the receiving side (ones
@@ -1642,70 +1902,71 @@ your home directory (remove the '=' for that).
     contents (e.g. "`dir/*`") since the wildcard is expanded by the shell and
     rsync thus gets a request to transfer individual files, not the files'
     parent directory.  Files that are excluded from the transfer are also
-    excluded from being deleted unless you use the `--delete-excluded` option
-    or mark the rules as only matching on the sending side (see the
-    include/exclude modifiers in the FILTER RULES section).
+    excluded from being deleted unless you use the [`--delete-excluded`](#opt)
+    option or mark the rules as only matching on the sending side (see the
+    include/exclude modifiers in the [FILTER RULES](#) section).
 
-    Prior to rsync 2.6.7, this option would have no effect unless `--recursive`
-    was enabled.  Beginning with 2.6.7, deletions will also occur when `--dirs`
-    (`-d`) is enabled, but only for directories whose contents are being
-    copied.
+    Prior to rsync 2.6.7, this option would have no effect unless
+    [`--recursive`](#opt) was enabled.  Beginning with 2.6.7, deletions will
+    also occur when [`--dirs`](#opt) (`-d`) is enabled, but only for
+    directories whose contents are being copied.
 
     This option can be dangerous if used incorrectly! It is a very good idea to
-    first try a run using the `--dry-run` option (`-n`) to see what files are
-    going to be deleted.
+    first try a run using the [`--dry-run`](#opt) (`-n`) option to see what
+    files are going to be deleted.
 
     If the sending side detects any I/O errors, then the deletion of any files
     at the destination will be automatically disabled.  This is to prevent
     temporary filesystem failures (such as NFS errors) on the sending side from
     causing a massive deletion of files on the destination.  You can override
-    this with the `--ignore-errors` option.
+    this with the [`--ignore-errors`](#opt) option.
 
     The `--delete` option may be combined with one of the --delete-WHEN options
-    without conflict, as well as `--delete-excluded`.  However, if none of the
-    `--delete-WHEN` options are specified, rsync will choose the
-    `--delete-during` algorithm when talking to rsync 3.0.0 or newer, and the
-    `--delete-before` algorithm when talking to an older rsync.  See also
-    `--delete-delay` and `--delete-after`.
+    without conflict, as well as [`--delete-excluded`](#opt).  However, if none
+    of the `--delete-WHEN` options are specified, rsync will choose the
+    [`--delete-during`](#opt) algorithm when talking to rsync 3.0.0 or newer,
+    or the [`--delete-before`](#opt) algorithm when talking to an older rsync.
+    See also [`--delete-delay`](#opt) and [`--delete-after`](#opt).
 
 0.  `--delete-before`
 
     Request that the file-deletions on the receiving side be done before the
-    transfer starts.  See `--delete` (which is implied) for more details on
-    file-deletion.
+    transfer starts.  See [`--delete`](#opt) (which is implied) for more
+    details on file-deletion.
 
     Deleting before the transfer is helpful if the filesystem is tight for
     space and removing extraneous files would help to make the transfer
     possible.  However, it does introduce a delay before the start of the
     transfer, and this delay might cause the transfer to timeout (if
-    `--timeout` was specified).  It also forces rsync to use the old,
+    [`--timeout`](#opt) was specified).  It also forces rsync to use the old,
     non-incremental recursion algorithm that requires rsync to scan all the
-    files in the transfer into memory at once (see `--recursive`).
+    files in the transfer into memory at once (see [`--recursive`](#opt)).
 
 0.  `--delete-during`, `--del`
 
     Request that the file-deletions on the receiving side be done incrementally
     as the transfer happens.  The per-directory delete scan is done right
     before each directory is checked for updates, so it behaves like a more
-    efficient `--delete-before`, including doing the deletions prior to any
-    per-directory filter files being updated.  This option was first added in
-    rsync version 2.6.4.  See `--delete` (which is implied) for more details on
-    file-deletion.
+    efficient [`--delete-before`](#opt), including doing the deletions prior to
+    any per-directory filter files being updated.  This option was first added
+    in rsync version 2.6.4.  See [`--delete`](#opt) (which is implied) for more
+    details on file-deletion.
 
 0.  `--delete-delay`
 
     Request that the file-deletions on the receiving side be computed during
-    the transfer (like `--delete-during`), and then removed after the transfer
-    completes.  This is useful when combined with `--delay-updates` and/or
-    `--fuzzy`, and is more efficient than using `--delete-after` (but can
-    behave differently, since `--delete-after` computes the deletions in a
-    separate pass after all updates are done).  If the number of removed files
-    overflows an internal buffer, a temporary file will be created on the
-    receiving side to hold the names (it is removed while open, so you
-    shouldn't see it during the transfer).  If the creation of the temporary
-    file fails, rsync will try to fall back to using `--delete-after` (which it
-    cannot do if `--recursive` is doing an incremental scan).  See `--delete`
-    (which is implied) for more details on file-deletion.
+    the transfer (like [`--delete-during`](#opt)), and then removed after the
+    transfer completes.  This is useful when combined with
+    [`--delay-updates`](#opt) and/or [`--fuzzy`](#opt), and is more efficient
+    than using [`--delete-after`](#opt) (but can behave differently, since
+    [`--delete-after`](#opt) computes the deletions in a separate pass after
+    all updates are done).  If the number of removed files overflows an
+    internal buffer, a temporary file will be created on the receiving side to
+    hold the names (it is removed while open, so you shouldn't see it during
+    the transfer).  If the creation of the temporary file fails, rsync will try
+    to fall back to using [`--delete-after`](#opt) (which it cannot do if
+    [`--recursive`](#opt) is doing an incremental scan).  See
+    [`--delete`](#opt) (which is implied) for more details on file-deletion.
 
 0.  `--delete-after`
 
@@ -1715,53 +1976,70 @@ your home directory (remove the '=' for that).
     exclusions to take effect for the delete phase of the current transfer.  It
     also forces rsync to use the old, non-incremental recursion algorithm that
     requires rsync to scan all the files in the transfer into memory at once
-    (see `--recursive`). See `--delete` (which is implied) for more details on
-    file-deletion.
+    (see [`--recursive`](#opt)). See [`--delete`](#opt) (which is implied) for
+    more details on file-deletion.
+
+    See also the [`--delete-delay`](#opt) option that might be a faster choice
+    for those that just want the deletions to occur at the end of the transfer.
 
 0.  `--delete-excluded`
 
-    In addition to deleting the files on the receiving side that are not on the
-    sending side, this tells rsync to also delete any files on the receiving
-    side that are excluded (see `--exclude`).  See the FILTER RULES section for
-    a way to make individual exclusions behave this way on the receiver, and
-    for a way to protect files from `--delete-excluded`.  See `--delete` (which
-    is implied) for more details on file-deletion.
+    This option turns any unqualified exclude/include rules into server-side
+    rules that do not affect the receiver's deletions.
+
+    By default, an exclude or include has both a server-side effect (to "hide"
+    and "show" files when building the server's file list) and a receiver-side
+    effect (to "protect" and "risk" files when deletions are occurring).  Any
+    rule that has no modifier to specify what sides it is executed on will be
+    instead treated as if it were a server-side rule only, avoiding any
+    "protect" effects of the rules.
+
+    A rule can still apply to both sides even with this option specified if the
+    rule is given both the sender & receiver modifier letters (e.g., `-f'-sr
+    foo'`).  Receiver-side protect/risk rules can also be explicitly specified
+    to limit the deletions.  This saves you from having to edit a bunch of
+    `-f'- foo'` rules into `-f'-s foo'` (aka `-f'H foo'`) rules (not to mention
+    the corresponding includes).
+
+    See the [FILTER RULES](#) section for more information.  See
+    [`--delete`](#opt) (which is implied) for more details on deletion.
 
 0.  `--ignore-missing-args`
 
     When rsync is first processing the explicitly requested source files (e.g.
-    command-line arguments or `--files-from` entries), it is normally an error
-    if the file cannot be found.  This option suppresses that error, and does
-    not try to transfer the file.  This does not affect subsequent
+    command-line arguments or [`--files-from`](#opt) entries), it is normally
+    an error if the file cannot be found.  This option suppresses that error,
+    and does not try to transfer the file.  This does not affect subsequent
     vanished-file errors if a file was initially found to be present and later
     is no longer there.
 
 0.  `--delete-missing-args`
 
-    This option takes the behavior of (the implied) `--ignore-missing-args`
-    option a step farther: each missing arg will become a deletion request of
-    the corresponding destination file on the receiving side (should it exist).
-    If the destination file is a non-empty directory, it will only be
-    successfully deleted if `--force` or `--delete` are in effect.  Other than
-    that, this option is independent of any other type of delete processing.
+    This option takes the behavior of the (implied)
+    [`--ignore-missing-args`](#opt) option a step farther: each missing arg
+    will become a deletion request of the corresponding destination file on the
+    receiving side (should it exist).  If the destination file is a non-empty
+    directory, it will only be successfully deleted if [`--force`](#opt) or
+    [`--delete`](#opt) are in effect.  Other than that, this option is
+    independent of any other type of delete processing.
 
     The missing source files are represented by special file-list entries which
-    display as a "`*missing`" entry in the `--list-only` output.
+    display as a "`*missing`" entry in the [`--list-only`](#opt) output.
 
 0.  `--ignore-errors`
 
-    Tells `--delete` to go ahead and delete files even when there are I/O
-    errors.
+    Tells [`--delete`](#opt) to go ahead and delete files even when there are
+    I/O errors.
 
 0.  `--force`
 
     This option tells rsync to delete a non-empty directory when it is to be
     replaced by a non-directory.  This is only relevant if deletions are not
-    active (see `--delete` for details).
+    active (see [`--delete`](#opt) for details).
 
     Note for older rsync versions: `--force` used to still be required when
-    using `--delete-after`, and it used to be non-functional unless the
-    `--recursive` option was also enabled.
+    using [`--delete-after`](#opt), and it used to be non-functional unless the
+    [`--recursive`](#opt) option was also enabled.
 
 0.  `--max-delete=NUM`
 
@@ -1785,9 +2063,8 @@ your home directory (remove the '=' for that).
     the numeric units or left unqualified to specify bytes.  Feel free to use a
     fractional value along with the units, such as `--max-size=1.5m`.
 
-    This option is a transfer rule, not an exclude, so it doesn't affect the
-    data that goes into the file-lists, and thus it doesn't affect deletions.
-    It just limits the files that the receiver requests to be transferred.
+    This option is a [TRANSFER RULE](#TRANSFER_RULES), so don't expect any
+    exclude side effects.
 
     The first letter of a units string can be `B` (bytes), `K` (kilo), `M`
     (mega), `G` (giga), `T` (tera), or `P` (peta).  If the string is a single
@@ -1809,7 +2086,7 @@ your home directory (remove the '=' for that).
 
     This tells rsync to avoid transferring any file that is smaller than the
     specified SIZE, which can help in not transferring small, junk files.  See
-    the `--max-size` option for a description of SIZE and other information.
+    the [`--max-size`](#opt) option for a description of SIZE and other info.
 
     Note that rsync versions prior to 3.1.0 did not allow `--min-size=0`.
 
@@ -1826,16 +2103,18 @@ your home directory (remove the '=' for that).
     Keep in mind that this is not a limit on the total size of allocated
     memory.  It is a sanity-check value for each individual allocation.
 
-    See the `--max-size` option for a description of how SIZE can be specified.
-    The default suffix if none is given is bytes.
+    See the [`--max-size`](#opt) option for a description of how SIZE can be
+    specified.  The default suffix if none is given is bytes.
 
-    Beginning in 3.2.3, a value of 0 specifies no limit.
+    Beginning in 3.2.7, a value of 0 is an easy way to specify SIZE_MAX (the
+    largest limit possible).
 
-    You can set a default value using the environment variable RSYNC_MAX_ALLOC
-    using the same SIZE values as supported by this option.  If the remote
-    rsync doesn't understand the `--max-alloc` option, you can override an
-    environmental value by specifying `--max-alloc=1g`, which will make rsync
-    avoid sending the option to the remote side (because "1G" is the default).
+    You can set a default value using the environment variable
+    [`RSYNC_MAX_ALLOC`](#) using the same SIZE values as supported by this
+    option.  If the remote rsync doesn't understand the `--max-alloc` option,
+    you can override an environmental value by specifying `--max-alloc=1g`,
+    which will make rsync avoid sending the option to the remote side (because
+    "1G" is the default).
 
 0.  `--block-size=SIZE`, `-B`
 
@@ -1844,7 +2123,7 @@ your home directory (remove the '=' for that).
     updated.  See the technical report for details.
 
     Beginning in 3.2.3 the SIZE can be specified with a suffix as detailed in
-    the `--max-size` option.  Older versions only accepted a byte count.
+    the [`--max-size`](#opt) option.  Older versions only accepted a byte count.
 
 0.  `--rsh=COMMAND`, `-e`
 
@@ -1857,17 +2136,17 @@ your home directory (remove the '=' for that).
     shell _COMMAND_ will be used to run an rsync daemon on the remote host, and
     all data will be transmitted through that remote shell connection, rather
     than through a direct socket connection to a running rsync daemon on the
-    remote host.  See the section "USING RSYNC-DAEMON FEATURES VIA A
-    REMOTE-SHELL CONNECTION" above.
-
-    Beginning with rsync 3.2.0, the RSYNC_PORT environment variable will be set
-    when a daemon connection is being made via a remote-shell connection.  It
-    is set to 0 if the default daemon port is being assumed, or it is set to
-    the value of the rsync port that was specified via either the `--port`
-    option or a non-empty port value in an rsync:// URL.  This allows the
-    script to discern if a non-default port is being requested, allowing for
-    things such as an SSL or stunnel helper script to connect to a default or
-    alternate port.
+    remote host.  See the [USING RSYNC-DAEMON FEATURES VIA A REMOTE-SHELL
+    CONNECTION](#) section above.
+
+    Beginning with rsync 3.2.0, the [`RSYNC_PORT`](#) environment variable will
+    be set when a daemon connection is being made via a remote-shell
+    connection.  It is set to 0 if the default daemon port is being assumed, or
+    it is set to the value of the rsync port that was specified via either the
+    [`--port`](#opt) option or a non-empty port value in an `rsync://` URL.
+    This allows the script to discern if a non-default port is being requested,
+    allowing for things such as an SSL or stunnel helper script to connect to a
+    default or alternate port.
 
     Command-line arguments are permitted in COMMAND provided that COMMAND is
     presented to rsync as a single argument.  You must use spaces (not tabs or
@@ -1884,10 +2163,11 @@ your home directory (remove the '=' for that).
     (Note that ssh users can alternately customize site-specific connect
     options in their .ssh/config file.)
 
-    You can also choose the remote shell program using the RSYNC_RSH
+    You can also choose the remote shell program using the [`RSYNC_RSH`](#)
     environment variable, which accepts the same range of values as `-e`.
 
-    See also the `--blocking-io` option which is affected by this option.
+    See also the [`--blocking-io`](#opt) option which is affected by this
+    option.
 
 0.  `--rsync-path=PROGRAM`
 
@@ -1899,7 +2179,7 @@ your home directory (remove the '=' for that).
     & standard-out that rsync is using to communicate.
 
     One tricky example is to set a different default directory on the remote
-    machine for use with the `--relative` option.  For instance:
+    machine for use with the [`--relative`](#opt) option.  For instance:
 
     >     rsync -avR --rsync-path="cd /a/b && rsync" host:c/d /e/
 
@@ -1907,8 +2187,8 @@ your home directory (remove the '=' for that).
 
     This option is used for more advanced situations where you want certain
     effects to be limited to one side of the transfer only.  For instance, if
-    you want to pass `--log-file=FILE` and `--fake-super` to the remote system,
-    specify it like this:
+    you want to pass [`--log-file=FILE`](#opt) and [`--fake-super`](#opt) to
+    the remote system, specify it like this:
 
     >     rsync -av -M --log-file=foo -M--fake-super src/ dest/
 
@@ -1922,11 +2202,10 @@ your home directory (remove the '=' for that).
     cause rsync to have a different idea about what data to expect next over
     the socket, and that will make it fail in a cryptic fashion.
 
-    Note that it is best to use a separate `--remote-option` for each option
-    you want to pass.  This makes your usage compatible with the
-    `--protect-args` option.  If that option is off, any spaces in your remote
-    options will be split by the remote shell unless you take steps to protect
-    them.
+    Note that you should use a separate `-M` option for each remote option you
+    want to pass.  On older rsync versions, the presence of any spaces in the
+    remote-option arg could cause it to be split into separate remote args, but
+    this requires the use of [`--old-args`](#opt) in a modern rsync.
 
     When performing a local transfer, the "local" side is the sender and the
     "remote" side is the receiver.
@@ -1944,7 +2223,8 @@ your home directory (remove the '=' for that).
     to CVS to determine if a file should be ignored.
 
     The exclude list is initialized to exclude the following items (these
-    initial items are marked as perishable -- see the FILTER RULES section):
+    initial items are marked as perishable -- see the [FILTER RULES](#)
+    section):
 
     [comment]: # (This list gets used for the default-cvsignore.h file.)
 
@@ -1994,17 +2274,17 @@ your home directory (remove the '=' for that).
     filter/exclude files, these patterns are split on whitespace.  See the
     **cvs**(1) manual for more information.
 
-    If you're combining `-C` with your own `--filter` rules, you should note
-    that these CVS excludes are appended at the end of your own rules,
+    If you're combining `-C` with your own [`--filter`](#opt) rules, you should
+    note that these CVS excludes are appended at the end of your own rules,
     regardless of where the `-C` was placed on the command-line.  This makes
     them a lower priority than any rules you specified explicitly.  If you want
     to control where these CVS excludes get inserted into your filter rules,
     you should omit the `-C` as a command-line option and use a combination of
-    `--filter=:C` and `--filter=-C` (either on your command-line or by putting
-    the ":C" and "-C" rules into a filter file with your other rules).  The
-    first option turns on the per-directory scanning for the .cvsignore file.
-    The second option does a one-time import of the CVS excludes mentioned
-    above.
+    [`--filter=:C`](#opt) and [`--filter=-C`](#opt) (either on your
+    command-line or by putting the ":C" and "-C" rules into a filter file with
+    your other rules).  The first option turns on the per-directory scanning
+    for the .cvsignore file.  The second option does a one-time import of the
+    CVS excludes mentioned above.
 
 0.  `--filter=RULE`, `-f`
 
@@ -2018,12 +2298,12 @@ your home directory (remove the '=' for that).
     argument.  The text below also mentions that you can use an underscore to
     replace the space that separates a rule from its arg.
 
-    See the FILTER RULES section for detailed information on this option.
+    See the [FILTER RULES](#) section for detailed information on this option.
 
 0.  `-F`
 
-    The `-F` option is a shorthand for adding two `--filter` rules to your
-    command.  The first time it is used is a shorthand for this rule:
+    The `-F` option is a shorthand for adding two [`--filter`](#opt) rules to
+    your command.  The first time it is used is a shorthand for this rule:
 
     >     --filter='dir-merge /.rsync-filter'
 
@@ -2036,41 +2316,55 @@ your home directory (remove the '=' for that).
 
     This filters out the .rsync-filter files themselves from the transfer.
 
-    See the FILTER RULES section for detailed information on how these options
-    work.
+    See the [FILTER RULES](#) section for detailed information on how these
+    options work.
 
 0.  `--exclude=PATTERN`
 
-    This option is a simplified form of the `--filter` option that defaults to
-    an exclude rule and does not allow the full rule-parsing syntax of normal
-    filter rules.
+    This option is a simplified form of the [`--filter`](#opt) option that
+    specifies an exclude rule and does not allow the full rule-parsing syntax
+    of normal filter rules.  This is equivalent to specifying `-f'- PATTERN'`.
 
-    See the FILTER RULES section for detailed information on this option.
+    See the [FILTER RULES](#) section for detailed information on this option.
 
 0.  `--exclude-from=FILE`
 
-    This option is related to the `--exclude` option, but it specifies a FILE
-    that contains exclude patterns (one per line).  Blank lines in the file are
-    ignored, as are whole-line comments that start with '`;`' or '`#`'
+    This option is related to the [`--exclude`](#opt) option, but it specifies
+    a FILE that contains exclude patterns (one per line).  Blank lines in the
+    file are ignored, as are whole-line comments that start with '`;`' or '`#`'
     (filename rules that contain those characters are unaffected).
 
+    If a line begins with "`- `" (dash, space) or "`+ `" (plus, space), then
+    the type of rule is being explicitly specified as an exclude or an include
+    (respectively).  Any rules without such a prefix are taken to be an exclude.
+
+    If a line consists of just "`!`", then the current filter rules are cleared
+    before adding any further rules.
+
     If _FILE_ is '`-`', the list will be read from standard input.
 
 0.  `--include=PATTERN`
 
-    This option is a simplified form of the `--filter` option that defaults to
-    an include rule and does not allow the full rule-parsing syntax of normal
-    filter rules.
+    This option is a simplified form of the [`--filter`](#opt) option that
+    specifies an include rule and does not allow the full rule-parsing syntax
+    of normal filter rules.  This is equivalent to specifying `-f'+ PATTERN'`.
 
-    See the FILTER RULES section for detailed information on this option.
+    See the [FILTER RULES](#) section for detailed information on this option.
 
 0.  `--include-from=FILE`
 
-    This option is related to the `--include` option, but it specifies a FILE
-    that contains include patterns (one per line).  Blank lines in the file are
-    ignored, as are whole-line comments that start with '`;`' or '`#`'
+    This option is related to the [`--include`](#opt) option, but it specifies
+    a FILE that contains include patterns (one per line).  Blank lines in the
+    file are ignored, as are whole-line comments that start with '`;`' or '`#`'
     (filename rules that contain those characters are unaffected).
 
+    If a line begins with "`- `" (dash, space) or "`+ `" (plus, space), then
+    the type of rule is being explicitly specified as an exclude or an include
+    (respectively).  Any rules without such a prefix are taken to be an include.
+
+    If a line consists of just "`!`", then the current filter rules are cleared
+    before adding any further rules.
+
     If _FILE_ is '`-`', the list will be read from standard input.
 
 0.  `--files-from=FILE`
@@ -2080,17 +2374,17 @@ your home directory (remove the '=' for that).
     tweaks the default behavior of rsync to make transferring just the
     specified files and directories easier:
 
-    - The `--relative` (`-R`) option is implied, which preserves the path
-      information that is specified for each item in the file (use
+    - The [`--relative`](#opt) (`-R`) option is implied, which preserves the
+      path information that is specified for each item in the file (use
       `--no-relative` or `--no-R` if you want to turn that off).
-    - The `--dirs` (`-d`) option is implied, which will create directories
-      specified in the list on the destination rather than noisily skipping
-      them (use `--no-dirs` or `--no-d` if you want to turn that off).
-    - The `--archive` (`-a`) option's behavior does not imply `--recursive`
-      (`-r`), so specify it explicitly, if you want it.
+    - The [`--dirs`](#opt) (`-d`) option is implied, which will create
+      directories specified in the list on the destination rather than noisily
+      skipping them (use `--no-dirs` or `--no-d` if you want to turn that off).
+    - The [`--archive`](#opt) (`-a`) option's behavior does not imply
+      [`--recursive`](#opt) (`-r`), so specify it explicitly, if you want it.
     - These side-effects change the default state of rsync, so the position of
       the `--files-from` option on the command-line has no bearing on how other
-      options are parsed (e.g. `-a` works the same before or after
+      options are parsed (e.g. [`-a`](#opt) works the same before or after
       `--files-from`, as does `--no-R` and all other options).
 
     The filenames that are read from the FILE are all relative to the source
@@ -2103,13 +2397,13 @@ your home directory (remove the '=' for that).
     directory will be created as /backup/bin on the remote host.  If it
     contains "bin/" (note the trailing slash), the immediate contents of the
     directory would also be sent (without needing to be explicitly mentioned in
-    the file -- this began in version 2.6.4).  In both cases, if the `-r`
-    option was enabled, that dir's entire hierarchy would also be transferred
-    (keep in mind that `-r` needs to be specified explicitly with
-    `--files-from`, since it is not implied by `-a`).  Also note that the
-    effect of the (enabled by default) `--relative` option is to duplicate only
-    the path info that is read from the file -- it does not force the
-    duplication of the source-spec path (/usr in this case).
+    the file -- this began in version 2.6.4).  In both cases, if the
+    [`-r`](#opt) option was enabled, that dir's entire hierarchy would also be
+    transferred (keep in mind that [`-r`](#opt) needs to be specified
+    explicitly with `--files-from`, since it is not implied by [`-a`](#opt).
+    Also note that the effect of the (enabled by default) [`-r`](#opt) option
+    is to duplicate only the path info that is read from the file -- it does
+    not force the duplication of the source-spec path (/usr in this case).
 
     In addition, the `--files-from` file can be read from the remote host
     instead of the local host if you specify a "host:" in front of the file
@@ -2122,9 +2416,9 @@ your home directory (remove the '=' for that).
     This would copy all the files specified in the /path/file-list file that
     was located on the remote "src" host.
 
-    If the `--iconv` and `--protect-args` options are specified and the
-    `--files-from` filenames are being sent from one host to another, the
-    filenames will be translated from the sending host's charset to the
+    If the [`--iconv`](#opt) and [`--secluded-args`](#opt) options are specified
+    and the `--files-from` filenames are being sent from one host to another,
+    the filenames will be translated from the sending host's charset to the
     receiving host's charset.
 
     NOTE: sorting the list of files in the `--files-from` input helps rsync to
@@ -2138,40 +2432,117 @@ your home directory (remove the '=' for that).
 
     This tells rsync that the rules/filenames it reads from a file are
     terminated by a null ('\\0') character, not a NL, CR, or CR+LF.  This
-    affects `--exclude-from`, `--include-from`, `--files-from`, and any merged
-    files specified in a `--filter` rule.  It does not affect `--cvs-exclude`
-    (since all names read from a .cvsignore file are split on whitespace).
+    affects [`--exclude-from`](#opt), [`--include-from`](#opt),
+    [`--files-from`](#opt), and any merged files specified in a
+    [`--filter`](#opt) rule.  It does not affect [`--cvs-exclude`](#opt) (since
+    all names read from a .cvsignore file are split on whitespace).
+
+0. `--old-args`
+
+    This option tells rsync to stop trying to protect the arg values on the
+    remote side from unintended word-splitting or other misinterpretation.
+    It also allows the client to treat an empty arg as a "." instead of
+    generating an error.
+
+    The default in a modern rsync is for "shell-active" characters (including
+    spaces) to be backslash-escaped in the args that are sent to the remote
+    shell.  The wildcard characters `*`, `?`, `[`, & `]` are not escaped in
+    filename args (allowing them to expand into multiple filenames) while being
+    protected in option args, such as [`--usermap`](#opt).
+
+    If you have a script that wants to use old-style arg splitting in its
+    filenames, specify this option once.  If the remote shell has a problem
+    with any backslash escapes at all, specify this option twice.
+
+    You may also control this setting via the [`RSYNC_OLD_ARGS`](#) environment
+    variable.  If it has the value "1", rsync will default to a single-option
+    setting.  If it has the value "2" (or more), rsync will default to a
+    repeated-option setting.  If it is "0", you'll get the default escaping
+    behavior.  The environment is always overridden by manually specified
+    positive or negative options (the negative is `--no-old-args`).
+
+    Note that this option also disables the extra safety check added in 3.2.5
+    that ensures that a remote sender isn't including extra top-level items in
+    the file-list that you didn't request.  This side-effect is necessary
+    because we can't know for sure what names to expect when the remote shell
+    is interpreting the args.
+
+    This option conflicts with the [`--secluded-args`](#opt) option.
+
+0.  `--secluded-args`, `-s`
+
+    This option sends all filenames and most options to the remote rsync via
+    the protocol (not the remote shell command line) which avoids letting the
+    remote shell modify them.  Wildcards are expanded on the remote host by
+    rsync instead of a shell.
+
+    This is similar to the default backslash-escaping of args that was added
+    in 3.2.4 (see [`--old-args`](#opt)) in that it prevents things like space
+    splitting and unwanted special-character side-effects. However, it has the
+    drawbacks of being incompatible with older rsync versions (prior to 3.0.0)
+    and of being refused by restricted shells that want to be able to inspect
+    all the option values for safety.
+
+    This option is useful for those times that you need the argument's
+    character set to be converted for the remote host, if the remote shell is
+    incompatible with the default backslash-escpaing method, or there is some
+    other reason that you want the majority of the options and arguments to
+    bypass the command-line of the remote shell.
+
+    If you combine this option with [`--iconv`](#opt), the args related to the
+    remote side will be translated from the local to the remote character-set.
+    The translation happens before wild-cards are expanded.  See also the
+    [`--files-from`](#opt) option.
+
+    You may also control this setting via the [`RSYNC_PROTECT_ARGS`](#)
+    environment variable.  If it has a non-zero value, this setting will be
+    enabled by default, otherwise it will be disabled by default.  Either state
+    is overridden by a manually specified positive or negative version of this
+    option (note that `--no-s` and `--no-secluded-args` are the negative
+    versions).  This environment variable is also superseded by a non-zero
+    [`RSYNC_OLD_ARGS`](#) export.
 
-0.  `--protect-args`, `-s`
+    This option conflicts with the [`--old-args`](#opt) option.
 
-    This option sends all filenames and most options to the remote rsync
-    without allowing the remote shell to interpret them.  This means that
-    spaces are not split in names, and any non-wildcard special characters are
-    not translated (such as `~`, `$`, `;`, `&`, etc.).  Wildcards are expanded
-    on the remote host by rsync (instead of the shell doing it).
+    This option used to be called `--protect-args` (before 3.2.6) and that
+    older name can still be used (though specifying it as `-s` is always the
+    easiest and most compatible choice).
 
-    If you use this option with `--iconv`, the args related to the remote side
-    will also be translated from the local to the remote character-set.  The
-    translation happens before wild-cards are expanded.  See also the
-    `--files-from` option.
+0.  `--trust-sender`
 
-    You may also control this option via the RSYNC_PROTECT_ARGS environment
-    variable.  If this variable has a non-zero value, this option will be
-    enabled by default, otherwise it will be disabled by default.  Either state
-    is overridden by a manually specified positive or negative version of this
-    option (note that `--no-s` and `--no-protect-args` are the negative
-    versions).  Since this option was first introduced in 3.0.0, you'll need to
-    make sure it's disabled if you ever need to interact with a remote rsync
-    that is older than that.
+    This option disables two extra validation checks that a local client
+    performs on the file list generated by a remote sender.  This option should
+    only be used if you trust the sender to not put something malicious in the
+    file list (something that could possibly be done via a modified rsync, a
+    modified shell, or some other similar manipulation).
+
+    Normally, the rsync client (as of version 3.2.5) runs two extra validation
+    checks when pulling files from a remote rsync:
+
+    - It verifies that additional arg items didn't get added at the top of the
+      transfer.
+    - It verifies that none of the items in the file list are names that should
+      have been excluded (if filter rules were specified).
+
+    Note that various options can turn off one or both of these checks if the
+    option interferes with the validation.  For instance:
+
+    - Using a per-directory filter file reads filter rules that only the server
+      knows about, so the filter checking is disabled.
+    - Using the [`--old-args`](#opt) option allows the sender to manipulate the
+      requested args, so the arg checking is disabled.
+    - Reading the files-from list from the server side means that the client
+      doesn't know the arg list, so the arg checking is disabled.
+    - Using [`--read-batch`](#opt) disables both checks since the batch file's
+      contents will have been verified when it was created.
 
-    Rsync can also be configured (at build time) to have this option enabled by
-    default (with is overridden by both the environment and the command-line).
-    Run `rsync --version` to check if this is the case, as it will display
-    "default protect-args" or "optional protect-args" depending on how it was
-    compiled.
+    This option may help an under-powered client server if the extra pattern
+    matching is slowing things down on a huge transfer.  It can also be used to
+    work around a currently-unknown bug in the verification logic for a transfer
+    from a trusted sender.
 
-    This option will eventually become a new default setting at some
-    as-yet-undetermined point in the future.
+    When using this option it is a good idea to specify a dedicated destination
+    directory, as discussed in the [MULTI-HOST SECURITY](#) section.
 
 0.  `--copy-as=USER[:GROUP]`
 
@@ -2189,14 +2560,14 @@ your home directory (remove the '=' for that).
     operation after the remote-shell or daemon connection is established.
 
     The option only affects one side of the transfer unless the transfer is
-    local, in which case it affects both sides.  Use the `--remote-option` to
-    affect the remote side, such as `-M--copy-as=joe`.  For a local transfer,
-    the lsh (or lsh.sh) support file provides a local-shell helper script that
-    can be used to allow a "localhost:" or "lh:" host-spec to be specified
-    without needing to setup any remote shells, allowing you to specify remote
-    options that affect the side of the transfer that is using the host-spec
-    (and using hostname "lh" avoids the overriding of the remote directory to
-    the user's home dir).
+    local, in which case it affects both sides.  Use the
+    [`--remote-option`](#opt) to affect the remote side, such as
+    `-M--copy-as=joe`.  For a local transfer, the lsh (or lsh.sh) support file
+    provides a local-shell helper script that can be used to allow a
+    "localhost:" or "lh:" host-spec to be specified without needing to setup
+    any remote shells, allowing you to specify remote options that affect the
+    side of the transfer that is using the host-spec (and using hostname "lh"
+    avoids the overriding of the remote directory to the user's home dir).
 
     For example, the following rsync writes the local files as user "joe":
 
@@ -2236,17 +2607,17 @@ your home directory (remove the '=' for that).
     new version on the disk at the same time.
 
     If you are using this option for reasons other than a shortage of disk
-    space, you may wish to combine it with the `--delay-updates` option, which
-    will ensure that all copied files get put into subdirectories in the
-    destination hierarchy, awaiting the end of the transfer.  If you don't have
-    enough room to duplicate all the arriving files on the destination
-    partition, another way to tell rsync that you aren't overly concerned about
-    disk space is to use the `--partial-dir` option with a relative path;
-    because this tells rsync that it is OK to stash off a copy of a single file
-    in a subdir in the destination hierarchy, rsync will use the partial-dir as
-    a staging area to bring over the copied file, and then rename it into place
-    from there. (Specifying a `--partial-dir` with an absolute path does not
-    have this side-effect.)
+    space, you may wish to combine it with the [`--delay-updates`](#opt)
+    option, which will ensure that all copied files get put into subdirectories
+    in the destination hierarchy, awaiting the end of the transfer.  If you
+    don't have enough room to duplicate all the arriving files on the
+    destination partition, another way to tell rsync that you aren't overly
+    concerned about disk space is to use the [`--partial-dir`](#opt) option
+    with a relative path; because this tells rsync that it is OK to stash off a
+    copy of a single file in a subdir in the destination hierarchy, rsync will
+    use the partial-dir as a staging area to bring over the copied file, and
+    then rename it into place from there. (Specifying a [`--partial-dir`](#opt)
+    with an absolute path does not have this side-effect.)
 
 0.  `--fuzzy`, `-y`
 
@@ -2257,12 +2628,12 @@ your home directory (remove the '=' for that).
     the fuzzy basis file to try to speed up the transfer.
 
     If the option is repeated, the fuzzy scan will also be done in any matching
-    alternate destination directories that are specified via `--compare-dest`,
-    `--copy-dest`, or `--link-dest`.
+    alternate destination directories that are specified via
+    [`--compare-dest`](#opt), [`--copy-dest`](#opt), or [`--link-dest`](#opt).
 
-    Note that the use of the `--delete` option might get rid of any potential
-    fuzzy-match files, so either use `--delete-after` or specify some filename
-    exclusions if you need to prevent this.
+    Note that the use of the [`--delete`](#opt) option might get rid of any
+    potential fuzzy-match files, so either use [`--delete-after`](#opt) or
+    specify some filename exclusions if you need to prevent this.
 
 0.  `--compare-dest=DIR`
 
@@ -2283,7 +2654,7 @@ your home directory (remove the '=' for that).
     transfer.
 
     If _DIR_ is a relative path, it is relative to the destination directory.
-    See also `--copy-dest` and `--link-dest`.
+    See also [`--copy-dest`](#opt) and [`--link-dest`](#opt).
 
     NOTE: beginning with version 3.1.0, rsync will remove a file from a
     non-empty destination hierarchy if an exact match is found in one of the
@@ -2292,7 +2663,7 @@ your home directory (remove the '=' for that).
 
 0.  `--copy-dest=DIR`
 
-    This option behaves like `--compare-dest`, but rsync will also copy
+    This option behaves like [`--compare-dest`](#opt), but rsync will also copy
     unchanged files found in _DIR_ to the destination directory using a local
     copy.  This is useful for doing transfers to a new destination while
     leaving existing files intact, and then doing a flash-cutover when all
@@ -2304,14 +2675,14 @@ your home directory (remove the '=' for that).
     try to speed up the transfer.
 
     If _DIR_ is a relative path, it is relative to the destination directory.
-    See also `--compare-dest` and `--link-dest`.
+    See also [`--compare-dest`](#opt) and [`--link-dest`](#opt).
 
 0.  `--link-dest=DIR`
 
-    This option behaves like `--copy-dest`, but unchanged files are hard linked
-    from _DIR_ to the destination directory.  The files must be identical in
-    all preserved attributes (e.g. permissions, possibly ownership) in order
-    for the files to be linked together.  An example:
+    This option behaves like [`--copy-dest`](#opt), but unchanged files are
+    hard linked from _DIR_ to the destination directory.  The files must be
+    identical in all preserved attributes (e.g. permissions, possibly
+    ownership) in order for the files to be linked together.  An example:
 
     >     rsync -av --link-dest=$PWD/prior_dir host:src_dir/ new_dir/
 
@@ -2335,18 +2706,19 @@ your home directory (remove the '=' for that).
     alternate-directory exact match would never be found (nor linked into the
     destination) when a destination file already exists.
 
-    Note that if you combine this option with `--ignore-times`, rsync will not
+    Note that if you combine this option with [`--ignore-times`](#opt), rsync will not
     link any files together because it only links identical files together as a
     substitute for transferring the file, never as an additional check after
     the file is updated.
 
     If _DIR_ is a relative path, it is relative to the destination directory.
-    See also `--compare-dest` and `--copy-dest`.
+    See also [`--compare-dest`](#opt) and [`--copy-dest`](#opt).
 
     Note that rsync versions prior to 2.6.1 had a bug that could prevent
-    `--link-dest` from working properly for a non-super-user when `-o` was
-    specified (or implied by `-a`).  You can work-around this bug by avoiding
-    the `-o` option when sending to an old rsync.
+    `--link-dest` from working properly for a non-super-user when
+    [`--owner`](#opt) (`-o`) was specified (or implied).  You can work-around
+    this bug by avoiding the `-o` option (or using `--no-o`) when sending to an
+    old rsync.
 
 0.  `--compress`, `-z`
 
@@ -2355,7 +2727,8 @@ your home directory (remove the '=' for that).
     something that is useful over a slow connection.
 
     Rsync supports multiple compression methods and will choose one for you
-    unless you force the choice using the `--compress-choice` (`--zc`) option.
+    unless you force the choice using the [`--compress-choice`](#opt) (`--zc`)
+    option.
 
     Run `rsync --version` to see the default compress list compiled into your
     version.
@@ -2367,10 +2740,10 @@ your home directory (remove the '=' for that).
     its list is assumed to be "zlib".
 
     The default order can be customized by setting the environment variable
-    RSYNC_COMPRESS_LIST to a space-separated list of acceptable compression
-    names.  If the string contains a "`&`" character, it is separated into the
-    "client string & server string", otherwise the same string applies to both.
-    If the string (or string portion) contains no
+    [`RSYNC_COMPRESS_LIST`](#) to a space-separated list of acceptable
+    compression names.  If the string contains a "`&`" character, it is
+    separated into the "client string & server string", otherwise the same
+    string applies to both.  If the string (or string portion) contains no
     non-whitespace characters, the default compress list is used.  Any unknown
     compression names are discarded from the list, but a list with only invalid
     names results in a failed negotiation.
@@ -2381,15 +2754,12 @@ your home directory (remove the '=' for that).
     ignore this weirdness unless the rsync server complains and tells you to
     specify `-zz`.
 
-    See also the `--skip-compress` option for the default list of file suffixes
-    that will be transferred with no (or minimal) compression.
-
 0.  `--compress-choice=STR`, `--zc=STR`
 
     This option can be used to override the automatic negotiation of the
-    compression algorithm that occurs when `--compress` is used.  The option
-    implies `--compress` unless "none" was specified, which instead implies
-    `--no-compress`.
+    compression algorithm that occurs when [`--compress`](#opt) is used.  The
+    option implies [`--compress`](#opt) unless "none" was specified, which
+    instead implies `--no-compress`.
 
     The compression options that you may be able to use are:
 
@@ -2414,22 +2784,23 @@ your home directory (remove the '=' for that).
 
 0.  `--compress-level=NUM`, `--zl=NUM`
 
-    Explicitly set the compression level to use (see `--compress`, `-z`)
-    instead of letting it default.  The `--compress` option is implied as long
-    as the level chosen is not a "don't compress" level for the compression
-    algorithm that is in effect (e.g. zlib compression treats level 0 as
-    "off").
+    Explicitly set the compression level to use (see [`--compress`](#opt),
+    `-z`) instead of letting it default.  The [`--compress`](#opt) option is
+    implied as long as the level chosen is not a "don't compress" level for the
+    compression algorithm that is in effect (e.g. zlib compression treats level
+    0 as "off").
 
     The level values vary depending on the checksum in effect.  Because rsync
     will negotiate a checksum choice by default (when the remote rsync is new
-    enough), it can be good to combine this option with a `--compress-choice`
-    (`--zc`) option unless you're sure of the choice in effect.  For example:
+    enough), it can be good to combine this option with a
+    [`--compress-choice`](#opt) (`--zc`) option unless you're sure of the
+    choice in effect.  For example:
 
     >     rsync -aiv --zc=zstd --zl=22 host:src/ dest/
 
     For zlib & zlibx compression the valid values are from 1 to 9 with 6 being
-    the default.  Specifying 0 turns compression off, and specifying -1 chooses
-    the default of 6.
+    the default.  Specifying `--zl=0` turns compression off, and specifying
+    `--zl=-1` chooses the default level of 6.
 
     For zstd compression the valid values are from -131072 to 22 with 3 being
     the default. Specifying 0 chooses the default of 3.
@@ -2442,20 +2813,21 @@ your home directory (remove the '=' for that).
     compression level no matter what algorithm was chosen.
 
     If you want to know the compression level that is in effect, specify
-    `--debug=nstr` to see the "negotiated string" results.  This will report
-    something like "`Client compress: zstd (level 3)`" (along with the checksum
-    choice in effect).
+    [`--debug=nstr`](#opt) to see the "negotiated string" results.  This will
+    report something like "`Client compress: zstd (level 3)`" (along with the
+    checksum choice in effect).
 
 0.  `--skip-compress=LIST`
 
+    **NOTE:** no compression method currently supports per-file compression
+    changes, so this option has no effect.
+
     Override the list of file suffixes that will be compressed as little as
     possible.  Rsync sets the compression level on a per-file basis based on
-    the file's suffix.  If the compression algorithm has an "off" level (such
-    as zlib/zlibx) then no compression occurs for those files.  Other
-    algorithms that support changing the streaming level on-the-fly will have
-    the level minimized to reduces the CPU usage as much as possible for a
-    matching file.  At this time, only zlib & zlibx compression support this
-    changing of levels on a per-file basis.
+    the file's suffix.  If the compression algorithm has an "off" level, then
+    no compression occurs for those files.  Other algorithms that support
+    changing the streaming level on-the-fly will have the level minimized to
+    reduces the CPU usage as much as possible for a matching file.
 
     The **LIST** should be one or more file suffixes (without the dot) separated
     by slashes (`/`).  You may specify an empty string to indicate that no files
@@ -2591,10 +2963,10 @@ your home directory (remove the '=' for that).
 
     If a user or group has no name on the source system or it has no match on
     the destination system, then the numeric ID from the source system is used
-    instead.  See also the comments on the "`use chroot`" setting in the
-    rsyncd.conf manpage for information on how the chroot setting affects
-    rsync's ability to look up the names of the users and groups and what you
-    can do about it.
+    instead.  See also the [`use chroot`](rsyncd.conf.5#use_chroot) setting
+    in the rsyncd.conf manpage for some comments on how the chroot setting
+    affects rsync's ability to look up the names of the users and groups and
+    what you can do about it.
 
 0.  `--usermap=STRING`, `--groupmap=STRING`
 
@@ -2627,36 +2999,39 @@ your home directory (remove the '=' for that).
 
     >     --usermap=:nobody --groupmap=*:nobody
 
-    When the `--numeric-ids` option is used, the sender does not send any
+    When the [`--numeric-ids`](#opt) option is used, the sender does not send any
     names, so all the IDs are treated as having an empty name.  This means that
     you will need to specify numeric **FROM** values if you want to map these
     nameless IDs to different values.
 
-    For the `--usermap` option to have any effect, the `-o` (`--owner`) option
-    must be used (or implied), and the receiver will need to be running as a
-    super-user (see also the `--fake-super` option).  For the `--groupmap`
-    option to have any effect, the `-g` (`--group`) option must be used (or
-    implied), and the receiver will need to have permissions to set that group.
+    For the `--usermap` option to work, the receiver will need to be running as
+    a super-user (see also the [`--super`](#opt) and [`--fake-super`](#opt)
+    options).  For the `--groupmap` option to work, the receiver will need to
+    have permissions to set that group.
 
-    The `--usermap` option implies the `--owner` option while the `--groupmap`
-    option implies the `--group` option.
+    Starting with rsync 3.2.4, the `--usermap` option implies the
+    [`--owner`](#opt) (`-o`) option while the `--groupmap` option implies the
+    [`--group`](#opt) (`-g`) option (since rsync needs to have those options
+    enabled for the mapping options to work).
 
-    If your shell complains about the wildcards, use `--protect-args` (`-s`).
+    An older rsync client may need to use [`-s`](#opt) to avoid a complaint
+    about wildcard characters, but a modern rsync handles this automatically.
 
 0.  `--chown=USER:GROUP`
 
     This option forces all files to be owned by USER with group GROUP.  This is
-    a simpler interface than using `--usermap` and `--groupmap` directly, but
-    it is implemented using those options internally, so you cannot mix them.
-    If either the USER or GROUP is empty, no mapping for the omitted user/group
-    will occur.  If GROUP is empty, the trailing colon may be omitted, but if
-    USER is empty, a leading colon must be supplied.
+    a simpler interface than using [`--usermap`](#opt) & [`--groupmap`](#opt)
+    directly, but it is implemented using those options internally so they
+    cannot be mixed.  If either the USER or GROUP is empty, no mapping for the
+    omitted user/group will occur.  If GROUP is empty, the trailing colon may
+    be omitted, but if USER is empty, a leading colon must be supplied.
 
     If you specify "`--chown=foo:bar`", this is exactly the same as specifying
-    "`--usermap=*:foo --groupmap=*:bar`", only easier (with the same implied
-    `--owner` and/or `--group` option).
+    "`--usermap=*:foo --groupmap=*:bar`", only easier (and with the same
+    implied [`--owner`](#opt) and/or [`--group`](#opt) options).
 
-    If your shell complains about the wildcards, use `--protect-args` (`-s`).
+    An older rsync client may need to use [`-s`](#opt) to avoid a complaint
+    about wildcard characters, but a modern rsync handles this automatically.
 
 0.  `--timeout=SECONDS`
 
@@ -2674,27 +3049,29 @@ your home directory (remove the '=' for that).
 
     By default rsync will bind to the wildcard address when connecting to an
     rsync daemon.  The `--address` option allows you to specify a specific IP
-    address (or hostname) to bind to.  See also this option in the `--daemon`
-    mode section.
+    address (or hostname) to bind to.
+
+    See also [the daemon version of the `--address` option](#dopt--address).
 
 0.  `--port=PORT`
 
     This specifies an alternate TCP port number to use rather than the default
     of 873.  This is only needed if you are using the double-colon (::) syntax
     to connect with an rsync daemon (since the URL syntax has a way to specify
-    the port as a part of the URL).  See also this option in the `--daemon`
-    mode section.
+    the port as a part of the URL).
+
+    See also [the daemon version of the `--port` option](#dopt--port).
 
 0.  `--sockopts=OPTIONS`
 
     This option can provide endless fun for people who like to tune their
     systems to the utmost degree.  You can set all sorts of socket options
-    which may make transfers faster (or slower!).  Read the man page for the
+    which may make transfers faster (or slower!).  Read the manpage for the
     `setsockopt()` system call for details on some of the options you may be
     able to set.  By default no special socket options are set.  This only
     affects direct socket connections to a remote rsync daemon.
 
-    This option also exists in the `--daemon` mode section.
+    See also [the daemon version of the `--sockopts` option](#dopt--sockopts).
 
 0.  `--blocking-io`
 
@@ -2716,10 +3093,10 @@ your home directory (remove the '=' for that).
 
     Requests a simple itemized list of the changes that are being made to each
     file, including attribute changes.  This is exactly the same as specifying
-    `--out-format='%i %n%L'`.  If you repeat the option, unchanged files will
-    also be output, but only if the receiving rsync is at least version 2.6.7
-    (you can use `-vv` with older versions of rsync, but that also turns on the
-    output of other verbose messages).
+    [`--out-format='%i %n%L'`](#opt).  If you repeat the option, unchanged
+    files will also be output, but only if the receiving rsync is at least
+    version 2.6.7 (you can use `-vv` with older versions of rsync, but that
+    also turns on the output of other verbose messages).
 
     The "%i" escape has a cryptic output that is 11 letters long.  The general
     format is like the string `YXcstpoguax`, where **Y** is replaced by the type
@@ -2734,7 +3111,7 @@ your home directory (remove the '=' for that).
     - A `c` means that a local change/creation is occurring for the item (such
       as the creation of a directory or the changing of a symlink, etc.).
     - A `h` means that the item is a hard link to another item (requires
-      `--hard-links`).
+      [`--hard-links`](#opt)).
     - A `.` means that the item is not being updated (though it might have
       attributes that are being modified).
     - A `*` means that the rest of the itemized-output area contains a message
@@ -2756,30 +3133,32 @@ your home directory (remove the '=' for that).
     The attribute that is associated with each letter is as follows:
 
     - A `c` means either that a regular file has a different checksum (requires
-      `--checksum`) or that a symlink, device, or special file has a changed
-      value.  Note that if you are sending files to an rsync prior to 3.0.1,
-      this change flag will be present only for checksum-differing regular
-      files.
+      [`--checksum`](#opt)) or that a symlink, device, or special file has a
+      changed value.  Note that if you are sending files to an rsync prior to
+      3.0.1, this change flag will be present only for checksum-differing
+      regular files.
     - A `s` means the size of a regular file is different and will be updated
       by the file transfer.
     - A `t` means the modification time is different and is being updated to
-      the sender's value (requires `--times`).  An alternate value of `T` means
-      that the modification time will be set to the transfer time, which
-      happens when a file/symlink/device is updated without `--times` and when
-      a symlink is changed and the receiver can't set its time. (Note: when
-      using an rsync 3.0.0 client, you might see the `s` flag combined with `t`
-      instead of the proper `T` flag for this time-setting failure.)
+      the sender's value (requires [`--times`](#opt)).  An alternate value of
+      `T` means that the modification time will be set to the transfer time,
+      which happens when a file/symlink/device is updated without
+      [`--times`](#opt) and when a symlink is changed and the receiver can't
+      set its time. (Note: when using an rsync 3.0.0 client, you might see the
+      `s` flag combined with `t` instead of the proper `T` flag for this
+      time-setting failure.)
     - A `p` means the permissions are different and are being updated to the
-      sender's value (requires `--perms`).
+      sender's value (requires [`--perms`](#opt)).
     - An `o` means the owner is different and is being updated to the sender's
-      value (requires `--owner` and super-user privileges).
+      value (requires [`--owner`](#opt) and super-user privileges).
     - A `g` means the group is different and is being updated to the sender's
-      value (requires `--group` and the authority to set the group).
-    - A `u`|`n`|`b` indicates the following information: `u`  means the access
-      (use) time is different and is being updated to the sender's value
-      (requires `--atimes`); `n` means the create time (newness) is different
-      and is being updated to the sender's value (requires `--crtimes`); `b`
-      means that both the access and create times are being updated.
+      value (requires [`--group`](#opt) and the authority to set the group).
+    - A `u`|`n`|`b` indicates the following information:
+      - `u`  means the access (use) time is different and is being updated to
+	the sender's value (requires [`--atimes`](#opt))
+      - `n` means the create time (newness) is different and is being updated
+	to the sender's value (requires [`--crtimes`](#opt))
+      - `b` means that both the access and create times are being updated
     - The `a` means that the ACL information is being changed.
     - The `x` means that the extended attribute information is being changed.
 
@@ -2793,26 +3172,27 @@ your home directory (remove the '=' for that).
     This allows you to specify exactly what the rsync client outputs to the
     user on a per-update basis.  The format is a text string containing
     embedded single-character escape sequences prefixed with a percent (%)
-    character.  A default format of "%n%L" is assumed if either `--info=name`
-    or `-v` is specified (this tells you just the name of the file and, if the
-    item is a link, where it points).  For a full list of the possible escape
-    characters, see the "`log format`" setting in the rsyncd.conf manpage.
-
-    Specifying the `--out-format` option implies the `--info=name` option,
-    which will mention each file, dir, etc. that gets updated in a significant
-    way (a transferred file, a recreated symlink/device, or a touched
-    directory).  In addition, if the itemize-changes escape (%i) is included in
-    the string (e.g. if the `--itemize-changes` option was used), the logging
-    of names increases to mention any item that is changed in any way (as long
-    as the receiving side is at least 2.6.4).  See the `--itemize-changes`
-    option for a description of the output of "%i".
+    character.  A default format of "%n%L" is assumed if either
+    [`--info=name`](#opt) or [`-v`](#opt) is specified (this tells you just the
+    name of the file and, if the item is a link, where it points).  For a full
+    list of the possible escape characters, see the [`log
+    format`](rsyncd.conf.5#log_format) setting in the rsyncd.conf manpage.
+
+    Specifying the `--out-format` option implies the [`--info=name`](#opt)
+    option, which will mention each file, dir, etc. that gets updated in a
+    significant way (a transferred file, a recreated symlink/device, or a
+    touched directory).  In addition, if the itemize-changes escape (%i) is
+    included in the string (e.g. if the [`--itemize-changes`](#opt) option was
+    used), the logging of names increases to mention any item that is changed
+    in any way (as long as the receiving side is at least 2.6.4).  See the
+    [`--itemize-changes`](#opt) option for a description of the output of "%i".
 
     Rsync will output the out-format string prior to a file's transfer unless
     one of the transfer-statistic escapes is requested, in which case the
     logging is done at the end of the file's transfer.  When this late logging
-    is in effect and `--progress` is also specified, rsync will also output the
-    name of the file being transferred prior to its progress information
-    (followed, of course, by the out-format output).
+    is in effect and [`--progress`](#opt) is also specified, rsync will also
+    output the name of the file being transferred prior to its progress
+    information (followed, of course, by the out-format output).
 
 0.  `--log-file=FILE`
 
@@ -2820,10 +3200,10 @@ your home directory (remove the '=' for that).
     similar to the logging that a daemon does, but can be requested for the
     client side and/or the server side of a non-daemon transfer.  If specified
     as a client option, transfer logging will be enabled with a default format
-    of "%i %n%L".  See the `--log-file-format` option if you wish to override
-    this.
+    of "%i %n%L".  See the [`--log-file-format`](#opt) option if you wish to
+    override this.
 
-    Here's a example command that requests the remote side to log what is
+    Here's an example command that requests the remote side to log what is
     happening:
 
     >     rsync -av --remote-option=--log-file=/tmp/rlog src/ dest/
@@ -2831,24 +3211,30 @@ your home directory (remove the '=' for that).
     This is very useful if you need to debug why a connection is closing
     unexpectedly.
 
+    See also [the daemon version of the `--log-file` option](#dopt--log-file).
+
 0.  `--log-file-format=FORMAT`
 
     This allows you to specify exactly what per-update logging is put into the
-    file specified by the `--log-file` option (which must also be specified for
-    this option to have any effect).  If you specify an empty string, updated
-    files will not be mentioned in the log file.  For a list of the possible
-    escape characters, see the "`log format`" setting in the rsyncd.conf manpage.
+    file specified by the [`--log-file`](#opt) option (which must also be
+    specified for this option to have any effect).  If you specify an empty
+    string, updated files will not be mentioned in the log file.  For a list of
+    the possible escape characters, see the [`log format`](rsyncd.conf.5#log_format)
+    setting in the rsyncd.conf manpage.
 
-    The default FORMAT used if `--log-file` is specified and this option is not
-    is '%i %n%L'.
+    The default FORMAT used if [`--log-file`](#opt) is specified and this
+    option is not is '%i %n%L'.
+
+    See also [the daemon version of the `--log-file-format`
+    option](#dopt--log-file-format).
 
 0.  `--stats`
 
     This tells rsync to print a verbose set of statistics on the file transfer,
     allowing you to tell how effective rsync's delta-transfer algorithm is for
-    your data.  This option is equivalent to `--info=stats2` if combined with 0
-    or 1 `-v` options, or `--info=stats3` if combined with 2 or more `-v`
-    options.
+    your data.  This option is equivalent to [`--info=stats2`](#opt) if
+    combined with 0 or 1 [`-v`](#opt) options, or [`--info=stats3`](#opt) if
+    combined with 2 or more [`-v`](#opt) options.
 
     The current statistics are as follows:
 
@@ -2909,12 +3295,14 @@ your home directory (remove the '=' for that).
 
 0.  `--human-readable`, `-h`
 
-    Output numbers in a more human-readable format.  There are 3 possible
-    levels: (1) output numbers with a separator between each set of 3 digits
-    (either a comma or a period, depending on if the decimal point is
-    represented by a period or a comma); (2) output numbers in units of 1000
-    (with a character suffix for larger units -- see below); (3) output
-    numbers in units of 1024.
+    Output numbers in a more human-readable format.  There are 3 possible levels:
+
+    1. output numbers with a separator between each set of 3 digits (either a
+       comma or a period, depending on if the decimal point is represented by a
+       period or a comma).
+    2. output numbers in units of 1000 (with a character suffix for larger
+       units -- see below).
+    3. output numbers in units of 1024.
 
     The default is human-readable level 1.  Each `-h` option increases the
     level by one.  You can take the level down to 0 (to output numbers as pure
@@ -2929,7 +3317,7 @@ your home directory (remove the '=' for that).
     support human-readable level 1, and they default to level 0.  Thus,
     specifying one or two `-h` options will behave in a comparable manner in
     old and new versions as long as you didn't specify a `--no-h` option prior
-    to one or more `-h` options.  See the `--list-only` option for one
+    to one or more `-h` options.  See the [`--list-only`](#opt) option for one
     difference.
 
 0.  `--partial`
@@ -2942,56 +3330,64 @@ your home directory (remove the '=' for that).
 
 0.  `--partial-dir=DIR`
 
-    A better way to keep partial files than the `--partial` option is to
-    specify a _DIR_ that will be used to hold the partial data (instead of
-    writing it out to the destination file).  On the next transfer, rsync will
-    use a file found in this dir as data to speed up the resumption of the
+    This option modifies the behavior of the [`--partial`](#opt) option while
+    also implying that it be enabled.  This enhanced partial-file method puts
+    any partially transferred files into the specified _DIR_ instead of writing
+    the partial file out to the destination file.  On the next transfer, rsync
+    will use a file found in this dir as data to speed up the resumption of the
     transfer and then delete it after it has served its purpose.
 
-    Note that if `--whole-file` is specified (or implied), any partial-dir file
-    that is found for a file that is being updated will simply be removed
-    (since rsync is sending files without using rsync's delta-transfer
-    algorithm).
+    Note that if [`--whole-file`](#opt) is specified (or implied), any
+    partial-dir files that are found for a file that is being updated will
+    simply be removed (since rsync is sending files without using rsync's
+    delta-transfer algorithm).
 
-    Rsync will create the _DIR_ if it is missing (just the last dir -- not the
-    whole path).  This makes it easy to use a relative path (such as
+    Rsync will create the _DIR_ if it is missing, but just the last dir -- not
+    the whole path.  This makes it easy to use a relative path (such as
     "`--partial-dir=.rsync-partial`") to have rsync create the
-    partial-directory in the destination file's directory when needed, and then
-    remove it again when the partial file is deleted.  Note that the directory
-    is only removed if it is a relative pathname, as it is expected that an
-    absolute path is to a directory that is reserved for partial-dir work.
+    partial-directory in the destination file's directory when it is needed,
+    and then remove it again when the partial file is deleted.  Note that this
+    directory removal is only done for a relative pathname, as it is expected
+    that an absolute path is to a directory that is reserved for partial-dir
+    work.
 
     If the partial-dir value is not an absolute path, rsync will add an exclude
     rule at the end of all your existing excludes.  This will prevent the
     sending of any partial-dir files that may exist on the sending side, and
     will also prevent the untimely deletion of partial-dir items on the
     receiving side.  An example: the above `--partial-dir` option would add the
-    equivalent of "`-f '-p .rsync-partial/'`" at the end of any other filter
-    rules.
+    equivalent of this "perishable" exclude at the end of any other filter
+    rules: `-f '-p .rsync-partial/'`
 
     If you are supplying your own exclude rules, you may need to add your own
-    exclude/hide/protect rule for the partial-dir because (1) the auto-added
-    rule may be ineffective at the end of your other rules, or (2) you may wish
-    to override rsync's exclude choice.  For instance, if you want to make
-    rsync clean-up any left-over partial-dirs that may be lying around, you
-    should specify `--delete-after` and add a "risk" filter rule, e.g.
-    `-f 'R .rsync-partial/'`. (Avoid using `--delete-before` or
-    `--delete-during` unless you don't need rsync to use any of the left-over
-    partial-dir data during the current run.)
+    exclude/hide/protect rule for the partial-dir because:
+
+    1. the auto-added rule may be ineffective at the end of your other rules, or
+    2. you may wish to override rsync's exclude choice.
+
+    For instance, if you want to make rsync clean-up any left-over partial-dirs
+    that may be lying around, you should specify [`--delete-after`](#opt) and
+    add a "risk" filter rule, e.g.  `-f 'R .rsync-partial/'`. Avoid using
+    [`--delete-before`](#opt) or [`--delete-during`](#opt) unless you don't
+    need rsync to use any of the left-over partial-dir data during the current
+    run.
 
     IMPORTANT: the `--partial-dir` should not be writable by other users or it
-    is a security risk.  E.g. AVOID "/tmp".
-
-    You can also set the partial-dir value the RSYNC_PARTIAL_DIR environment
-    variable.  Setting this in the environment does not force `--partial` to be
-    enabled, but rather it affects where partial files go when `--partial` is
-    specified.  For instance, instead of using `--partial-dir=.rsync-tmp` along
-    with `--progress`, you could set RSYNC_PARTIAL_DIR=.rsync-tmp in your
-    environment and then just use the `-P` option to turn on the use of the
-    .rsync-tmp dir for partial transfers.  The only times that the `--partial`
-    option does not look for this environment value are (1) when `--inplace`
-    was specified (since `--inplace` conflicts with `--partial-dir`), and (2)
-    when `--delay-updates` was specified (see below).
+    is a security risk!  E.g. AVOID "/tmp"!
+
+    You can also set the partial-dir value the [`RSYNC_PARTIAL_DIR`](#)
+    environment variable.  Setting this in the environment does not force
+    [`--partial`](#opt) to be enabled, but rather it affects where partial
+    files go when [`--partial`](#opt) is specified.  For instance, instead of
+    using `--partial-dir=.rsync-tmp` along with [`--progress`](#opt), you could
+    set [`RSYNC_PARTIAL_DIR=.rsync-tmp`](#) in your environment and then use
+    the [`-P`](#opt) option to turn on the use of the .rsync-tmp dir for
+    partial transfers.  The only times that the [`--partial`](#opt) option does
+    not look for this environment value are:
+
+    1. when [`--inplace`](#opt) was specified (since [`--inplace`](#opt)
+       conflicts with `--partial-dir`), and
+    2. when [`--delay-updates`](#opt) was specified (see below).
 
     When a modern rsync resumes the transfer of a file in the partial-dir, that
     partial file is now updated in-place instead of creating yet another
@@ -3000,10 +3396,10 @@ your home directory (remove the '=' for that).
     3.2.0.
 
     For the purposes of the daemon-config's "`refuse options`" setting,
-    `--partial-dir` does _not_ imply `--partial`.  This is so that a refusal of
-    the `--partial` option can be used to disallow the overwriting of
-    destination files with a partial transfer, while still allowing the safer
-    idiom provided by `--partial-dir`.
+    `--partial-dir` does _not_ imply [`--partial`](#opt).  This is so that a
+    refusal of the [`--partial`](#opt) option can be used to disallow the
+    overwriting of destination files with a partial transfer, while still
+    allowing the safer idiom provided by `--partial-dir`.
 
 0.  `--delay-updates`
 
@@ -3012,27 +3408,30 @@ your home directory (remove the '=' for that).
     renamed into place in rapid succession.  This attempts to make the updating
     of the files a little more atomic.  By default the files are placed into a
     directory named `.~tmp~` in each file's destination directory, but if
-    you've specified the `--partial-dir` option, that directory will be used
-    instead.  See the comments in the `--partial-dir` section for a discussion
-    of how this `.~tmp~` dir will be excluded from the transfer, and what you
-    can do if you want rsync to cleanup old `.~tmp~` dirs that might be lying
-    around.  Conflicts with `--inplace` and `--append`.
+    you've specified the [`--partial-dir`](#opt) option, that directory will be
+    used instead.  See the comments in the [`--partial-dir`](#opt) section for
+    a discussion of how this `.~tmp~` dir will be excluded from the transfer,
+    and what you can do if you want rsync to cleanup old `.~tmp~` dirs that
+    might be lying around.  Conflicts with [`--inplace`](#opt) and
+    [`--append`](#opt).
 
-    This option implies `--no-inc-recursive` since it needs the full file list
-    in memory in order to be able to iterate over it at the end.
+    This option implies [`--no-inc-recursive`](#opt) since it needs the full
+    file list in memory in order to be able to iterate over it at the end.
 
     This option uses more memory on the receiving side (one bit per file
     transferred) and also requires enough free disk space on the receiving side
     to hold an additional copy of all the updated files.  Note also that you
-    should not use an absolute path to `--partial-dir` unless (1) there is no
-    chance of any of the files in the transfer having the same name (since all
-    the updated files will be put into a single directory if the path is
-    absolute) and (2) there are no mount points in the hierarchy (since the
-    delayed updates will fail if they can't be renamed into place).
+    should not use an absolute path to [`--partial-dir`](#opt) unless:
+
+    1. there is no chance of any of the files in the transfer having the same
+       name (since all the updated files will be put into a single directory if
+       the path is absolute), and
+    2. there are no mount points in the hierarchy (since the delayed updates
+       will fail if they can't be renamed into place).
 
-    See also the "atomic-rsync" perl script in the "support" subdir for an
-    update algorithm that is even more atomic (it uses `--link-dest` and a
-    parallel hierarchy of files).
+    See also the "atomic-rsync" python script in the "support" subdir for an
+    update algorithm that is even more atomic (it uses [`--link-dest`](#opt)
+    and a parallel hierarchy of files).
 
 0.  `--prune-empty-dirs`, `-m`
 
@@ -3042,10 +3441,8 @@ your home directory (remove the '=' for that).
     directories when the sending rsync is recursively scanning a hierarchy of
     files using include/exclude/filter rules.
 
-    Note that the use of transfer rules, such as the `--min-size` option, does
-    not affect what goes into the file list, and thus does not leave
-    directories empty, even if none of the files in a directory match the
-    transfer rule.
+    This option can still leave empty directories on the receiving side if you
+    make use of [TRANSFER_RULES](#).
 
     Because the file-list is actually being pruned, this option also affects
     what directories get deleted when a delete is active.  However, keep in
@@ -3075,9 +3472,9 @@ your home directory (remove the '=' for that).
 
     This option tells rsync to print information showing the progress of the
     transfer.  This gives a bored user something to watch.  With a modern rsync
-    this is the same as specifying `--info=flist2,name,progress`, but any
-    user-supplied settings for those info flags takes precedence (e.g.
-    "`--info=flist0 --progress`").
+    this is the same as specifying [`--info=flist2,name,progress`](#opt), but
+    any user-supplied settings for those info flags takes precedence (e.g.
+    [`--info=flist0 --progress`](#opt)).
 
     While rsync is transferring a regular file, it updates a progress line that
     looks like this:
@@ -3120,16 +3517,17 @@ your home directory (remove the '=' for that).
 
 0.  `-P`
 
-    The `-P` option is equivalent to `--partial --progress`.  Its purpose is
-    to make it much easier to specify these two options for a long transfer
-    that may be interrupted.
+    The `-P` option is equivalent to "[`--partial`](#opt)
+    [`--progress`](#opt)".  Its purpose is to make it much easier to specify
+    these two options for a long transfer that may be interrupted.
 
-    There is also a `--info=progress2` option that outputs statistics based on
-    the whole transfer, rather than individual files.  Use this flag without
-    outputting a filename (e.g. avoid `-v` or specify `--info=name0`) if you
-    want to see how the transfer is doing without scrolling the screen with a
-    lot of names. (You don't need to specify the `--progress` option in order
-    to use `--info=progress2`.)
+    There is also a [`--info=progress2`](#opt) option that outputs statistics
+    based on the whole transfer, rather than individual files.  Use this flag
+    without outputting a filename (e.g. avoid `-v` or specify
+    [`--info=name0`](#opt)) if you want to see how the transfer is doing
+    without scrolling the screen with a lot of names. (You don't need to
+    specify the [`--progress`](#opt) option in order to use
+    [`--info=progress2`](#opt).)
 
     Finally, you can get an instant progress report by sending rsync a signal
     of either SIGINFO or SIGVTALRM.  On BSD systems, a SIGINFO is generated by
@@ -3138,8 +3536,8 @@ your home directory (remove the '=' for that).
     output a single progress report which is output when the current file
     transfer finishes (so it may take a little time if a big file is being
     handled when the signal arrives).  A filename is output (if needed)
-    followed by the `--info=progress2` format of progress info.  If you don't
-    know which of the 3 rsync processes is the client process, it's OK to
+    followed by the [`--info=progress2`](#opt) format of progress info.  If you
+    don't know which of the 3 rsync processes is the client process, it's OK to
     signal all of them (since the non-client processes ignore the signal).
 
     CAUTION: sending SIGVTALRM to an older rsync (pre-3.2.0) will kill it.
@@ -3172,40 +3570,48 @@ your home directory (remove the '=' for that).
 
     This option will cause the source files to be listed instead of
     transferred.  This option is inferred if there is a single source arg and
-    no destination specified, so its main uses are: (1) to turn a copy command
-    that includes a destination arg into a file-listing command, or (2) to be
-    able to specify more than one source arg (note: be sure to include the
-    destination).  Caution: keep in mind that a source arg with a wild-card is
-    expanded by the shell into multiple args, so it is never safe to try to
-    list such an arg without using this option. For example:
+    no destination specified, so its main uses are:
+
+    1. to turn a copy command that includes a destination arg into a
+       file-listing command, or
+    2. to be able to specify more than one source arg.  Note: be sure to
+       include the destination.
+
+    CAUTION: keep in mind that a source arg with a wild-card is expanded by the
+    shell into multiple args, so it is never safe to try to specify a single
+    wild-card arg to try to infer this option. A safe example is:
 
     >     rsync -av --list-only foo* dest/
 
-    Starting with rsync 3.1.0, the sizes output by `--list-only` are affected
-    by the `--human-readable` option.  By default they will contain digit
-    separators, but higher levels of readability will output the sizes with
-    unit suffixes.  Note also that the column width for the size output has
-    increased from 11 to 14 characters for all human-readable levels.  Use
-    `--no-h` if you want just digits in the sizes, and the old column width of
-    11 characters.
+    This option always uses an output format that looks similar to this:
+
+    >     drwxrwxr-x          4,096 2022/09/30 12:53:11 support
+    >     -rw-rw-r--             80 2005/01/11 10:37:37 support/Makefile
+
+    The only option that affects this output style is (as of 3.1.0) the
+    [`--human-readable`](#opt) (`-h`) option.  The default is to output sizes
+    as byte counts with digit separators (in a 14-character-width column).
+    Specifying at least one `-h` option makes the sizes output with unit
+    suffixes.  If you want old-style bytecount sizes without digit separators
+    (and an 11-character-width column) use `--no-h`.
 
     Compatibility note: when requesting a remote listing of files from an rsync
     that is version 2.6.3 or older, you may encounter an error if you ask for a
-    non-recursive listing.  This is because a file listing implies the `--dirs`
-    option w/o `--recursive`, and older rsyncs don't have that option.  To
-    avoid this problem, either specify the `--no-dirs` option (if you don't
-    need to expand a directory's content), or turn on recursion and exclude the
-    content of subdirectories: `-r --exclude='/*/*'`.
+    non-recursive listing.  This is because a file listing implies the
+    [`--dirs`](#opt) option w/o [`--recursive`](#opt), and older rsyncs don't
+    have that option.  To avoid this problem, either specify the `--no-dirs`
+    option (if you don't need to expand a directory's content), or turn on
+    recursion and exclude the content of subdirectories: `-r --exclude='/*/*'`.
 
 0.  `--bwlimit=RATE`
 
     This option allows you to specify the maximum transfer rate for the data
     sent over the socket, specified in units per second.  The RATE value can be
     suffixed with a string to indicate a size multiplier, and may be a
-    fractional value (e.g. "`--bwlimit=1.5m`").  If no suffix is specified, the
+    fractional value (e.g. `--bwlimit=1.5m`).  If no suffix is specified, the
     value will be assumed to be in units of 1024 bytes (as if "K" or "KiB" had
-    been appended).  See the `--max-size` option for a description of all the
-    available suffixes.  A value of 0 specifies no limit.
+    been appended).  See the [`--max-size`](#opt) option for a description of
+    all the available suffixes.  A value of 0 specifies no limit.
 
     For backward-compatibility reasons, the rate limit will be rounded to the
     nearest KiB unit, so no rate smaller than 1024 bytes per second is
@@ -3217,26 +3623,28 @@ your home directory (remove the '=' for that).
     rsync writes out a block of data and then sleeps to bring the average rate
     into compliance.
 
-    Due to the internal buffering of data, the `--progress` option may not be
-    an accurate reflection on how fast the data is being sent.  This is because
-    some files can show up as being rapidly sent when the data is quickly
-    buffered, while other can show up as very slow when the flushing of the
-    output buffer occurs.  This may be fixed in a future version.
+    Due to the internal buffering of data, the [`--progress`](#opt) option may
+    not be an accurate reflection on how fast the data is being sent.  This is
+    because some files can show up as being rapidly sent when the data is
+    quickly buffered, while other can show up as very slow when the flushing of
+    the output buffer occurs.  This may be fixed in a future version.
+
+    See also [the daemon version of the `--bwlimit` option](#dopt--bwlimit).
 
-0.  `--stop-after=MINS
+0.  `--stop-after=MINS`, (`--time-limit=MINS`)
 
     This option tells rsync to stop copying when the specified number of
     minutes has elapsed.
 
-    Rsync also accepts an earlier version of this option: `--time-limit=MINS`.
-
     For maximal flexibility, rsync does not communicate this option to the
     remote rsync since it is usually enough that one side of the connection
     quits as specified.  This allows the option's use even when only one side
     of the connection supports it.  You can tell the remote side about the time
-    limit using `--remote-option` (`-M`), should the need arise.
+    limit using [`--remote-option`](#opt) (`-M`), should the need arise.
+
+    The `--time-limit` version of this option is deprecated.
 
-0.  `--stop-at=y-m-dTh:m
+0.  `--stop-at=y-m-dTh:m`
 
     This option tells rsync to stop copying when the specified point in time
     has been reached. The date & time can be fully specified in a numeric
@@ -3259,9 +3667,9 @@ your home directory (remove the '=' for that).
     remote rsync since it is usually enough that one side of the connection
     quits as specified.  This allows the option's use even when only one side
     of the connection supports it.  You can tell the remote side about the time
-    limit using `--remote-option` (`-M`), should the need arise.  Do keep in
-    mind that the remote host may have a different default timezone than your
-    local host.
+    limit using [`--remote-option`](#opt) (`-M`), should the need arise.  Do
+    keep in mind that the remote host may have a different default timezone
+    than your local host.
 
 0.  `--fsync`
 
@@ -3272,20 +3680,20 @@ your home directory (remove the '=' for that).
 0.  `--write-batch=FILE`
 
     Record a file that can later be applied to another identical destination
-    with `--read-batch`.  See the "BATCH MODE" section for details, and also
-    the `--only-write-batch` option.
+    with [`--read-batch`](#opt).  See the "BATCH MODE" section for details, and
+    also the [`--only-write-batch`](#opt) option.
 
     This option overrides the negotiated checksum & compress lists and always
     negotiates a choice based on old-school md5/md4/zlib choices.  If you want
-    a more modern choice, use the `--checksum-choice` (`--cc`) and/or
-    `--compress-choice` (`--zc`) options.
+    a more modern choice, use the [`--checksum-choice`](#opt) (`--cc`) and/or
+    [`--compress-choice`](#opt) (`--zc`) options.
 
 0.  `--only-write-batch=FILE`
 
-    Works like `--write-batch`, except that no updates are made on the
+    Works like [`--write-batch`](#opt), except that no updates are made on the
     destination system when creating the batch.  This lets you transport the
     changes to the destination system via some other means and then apply the
-    changes via `--read-batch`.
+    changes via [`--read-batch`](#opt).
 
     Note that you can feel free to write the batch directly to some portable
     media: if this media fills to capacity before the end of the transfer, you
@@ -3302,18 +3710,18 @@ your home directory (remove the '=' for that).
 0.  `--read-batch=FILE`
 
     Apply all of the changes stored in FILE, a file previously generated by
-    `--write-batch`.  If _FILE_ is `-`, the batch data will be read from
-    standard input. See the "BATCH MODE" section for details.
+    [`--write-batch`](#opt).  If _FILE_ is `-`, the batch data will be read
+    from standard input. See the "BATCH MODE" section for details.
 
 0.  `--protocol=NUM`
 
     Force an older protocol version to be used.  This is useful for creating a
     batch file that is compatible with an older version of rsync.  For
-    instance, if rsync 2.6.4 is being used with the `--write-batch` option, but
-    rsync 2.6.3 is what will be used to run the `--read-batch` option, you
-    should use "--protocol=28" when creating the batch file to force the older
-    protocol version to be used in the batch file (assuming you can't upgrade
-    the rsync on the reading system).
+    instance, if rsync 2.6.4 is being used with the [`--write-batch`](#opt)
+    option, but rsync 2.6.3 is what will be used to run the
+    [`--read-batch`](#opt) option, you should use "--protocol=28" when creating
+    the batch file to force the older protocol version to be used in the batch
+    file (assuming you can't upgrade the rsync on the reading system).
 
 0.  `--iconv=CONVERT_SPEC`
 
@@ -3325,15 +3733,15 @@ your home directory (remove the '=' for that).
     This order ensures that the option will stay the same whether you're
     pushing or pulling files.  Finally, you can specify either `--no-iconv` or
     a CONVERT_SPEC of "-" to turn off any conversion.  The default setting of
-    this option is site-specific, and can also be affected via the RSYNC_ICONV
-    environment variable.
+    this option is site-specific, and can also be affected via the
+    [`RSYNC_ICONV`](#) environment variable.
 
     For a list of what charset names your local iconv library supports, you can
     run "`iconv --list`".
 
-    If you specify the `--protect-args` option (`-s`), rsync will translate the
-    filenames you specify on the command-line that are being sent to the remote
-    host.  See also the `--files-from` option.
+    If you specify the [`--secluded-args`](#opt) (`-s`) option, rsync will
+    translate the filenames you specify on the command-line that are being sent
+    to the remote host.  See also the [`--files-from`](#opt) option.
 
     Note that rsync does not do any conversion of names in filter files
     (including include/exclude files).  It is up to you to ensure that you're
@@ -3354,12 +3762,12 @@ your home directory (remove the '=' for that).
     socket when directly contacting an rsync daemon, as well as the forwarding
     of the `-4` or `-6` option to ssh when rsync can deduce that ssh is being
     used as the remote shell.  For other remote shells you'll need to specify
-    the "`--rsh SHELL -4`" option directly (or whatever ipv4/ipv6 hint options
+    the "`--rsh SHELL -4`" option directly (or whatever IPv4/IPv6 hint options
     it uses).
 
-    These options also exist in the `--daemon` mode section.
+    See also [the daemon version of these options](#dopt--ipv4).
 
-    If rsync was complied without support for IPv6, the `--ipv6` option will
+    If rsync was compiled without support for IPv6, the `--ipv6` option will
     have no effect.  The `rsync --version` output will contain "`no IPv6`" if
     is the case.
 
@@ -3374,7 +3782,7 @@ your home directory (remove the '=' for that).
     user wants a more random checksum seed.  Setting NUM to 0 causes rsync to
     use the default of **time**() for checksum seed.
 
-# DAEMON OPTIONS
+## DAEMON OPTIONS
 
 The options allowed when starting an rsync daemon are as follows:
 
@@ -3387,31 +3795,37 @@ The options allowed when starting an rsync daemon are as follows:
     If standard input is a socket then rsync will assume that it is being run
     via inetd, otherwise it will detach from the current terminal and become a
     background daemon.  The daemon will read the config file (rsyncd.conf) on
-    each connect made by a client and respond to requests accordingly.  See the
-    **rsyncd.conf**(5) man page for more details.
+    each connect made by a client and respond to requests accordingly.
+
+    See the [**rsyncd.conf**(5)](rsyncd.conf.5) manpage for more details.
 
 0.  `--address=ADDRESS`
 
     By default rsync will bind to the wildcard address when run as a daemon
     with the `--daemon` option.  The `--address` option allows you to specify a
     specific IP address (or hostname) to bind to.  This makes virtual hosting
-    possible in conjunction with the `--config` option.  See also the "address"
-    global option in the rsyncd.conf manpage.
+    possible in conjunction with the `--config` option.
+
+    See also the [address](rsyncd.conf.5#address) global option in the
+    rsyncd.conf manpage and the [client version of the `--address`
+    option](#opt--address).
 
 0.  `--bwlimit=RATE`
 
     This option allows you to specify the maximum transfer rate for the data
     the daemon sends over the socket.  The client can still specify a smaller
-    `--bwlimit` value, but no larger value will be allowed.  See the client
-    version of this option (above) for some extra details.
+    `--bwlimit` value, but no larger value will be allowed.
+
+    See the [client version of the `--bwlimit` option](#opt--bwlimit) for some
+    extra details.
 
 0.  `--config=FILE`
 
     This specifies an alternate config file than the default.  This is only
-    relevant when `--daemon` is specified.  The default is /etc/rsyncd.conf
-    unless the daemon is running over a remote shell program and the remote
-    user is not the super-user; in that case the default is rsyncd.conf in the
-    current directory (typically $HOME).
+    relevant when [`--daemon`](#dopt) is specified.  The default is
+    /etc/rsyncd.conf unless the daemon is running over a remote shell program
+    and the remote user is not the super-user; in that case the default is
+    rsyncd.conf in the current directory (typically $HOME).
 
 0.  `--dparam=OVERRIDE`, `-M`
 
@@ -3435,14 +3849,18 @@ The options allowed when starting an rsync daemon are as follows:
 0.  `--port=PORT`
 
     This specifies an alternate TCP port number for the daemon to listen on
-    rather than the default of 873.  See also the "port" global option in the
-    rsyncd.conf manpage.
+    rather than the default of 873.
+
+    See also [the client version of the `--port` option](#opt--port) and the
+    [port](rsyncd.conf.5#port) global setting in the rsyncd.conf manpage.
 
 0.  `--log-file=FILE`
 
     This option tells the rsync daemon to use the given log-file name instead
     of using the "`log file`" setting in the config file.
 
+    See also [the client version of the `--log-file` option](#opt--log-file).
+
 0.  `--log-file-format=FORMAT`
 
     This option tells the rsync daemon to use the given FORMAT string instead
@@ -3450,10 +3868,15 @@ The options allowed when starting an rsync daemon are as follows:
     "`transfer logging`" unless the string is empty, in which case transfer
     logging is turned off.
 
+    See also [the client version of the `--log-file-format`
+    option](#opt--log-file-format).
+
 0.  `--sockopts`
 
-    This overrides the `socket options` setting in the rsyncd.conf file and has
-    the same syntax.
+    This overrides the [`socket options`](rsyncd.conf.5#socket_options)
+    setting in the rsyncd.conf file and has the same syntax.
+
+    See also [the client version of the `--sockopts` option](#opt--sockopts).
 
 0.  `--verbose`, `-v`
 
@@ -3462,6 +3885,8 @@ The options allowed when starting an rsync daemon are as follows:
     will be controlled by the options that the client used and the
     "`max verbosity`" setting in the module's config section.
 
+    See also [the client version of the `--verbose` option](#opt--verbose).
+
 0.  `--ipv4`, `-4` or `--ipv6`, `-6`
 
     Tells rsync to prefer IPv4/IPv6 when creating the incoming sockets that the
@@ -3471,9 +3896,9 @@ The options allowed when starting an rsync daemon are as follows:
     using the port, try specifying `--ipv6` or `--ipv4` when starting the
     daemon).
 
-    These options also exist in the regular rsync options section.
+    See also [the client version of these options](#opt--ipv4).
 
-    If rsync was complied without support for IPv6, the `--ipv6` option will
+    If rsync was compiled without support for IPv6, the `--ipv6` option will
     have no effect.  The `rsync --version` output will contain "`no IPv6`" if
     is the case.
 
@@ -3482,21 +3907,148 @@ The options allowed when starting an rsync daemon are as follows:
     When specified after `--daemon`, print a short help page describing the
     options available for starting an rsync daemon.
 
-# FILTER RULES
+## FILTER RULES
 
-The filter rules allow for flexible selection of which files to transfer
-(include) and which files to skip (exclude).  The rules either directly specify
-include/exclude patterns or they specify a way to acquire more include/exclude
-patterns (e.g. to read them from a file).
+The filter rules allow for custom control of several aspects of how files are
+handled:
 
-As the list of files/directories to transfer is built, rsync checks each name
-to be transferred against the list of include/exclude patterns in turn, and the
-first matching pattern is acted on: if it is an exclude pattern, then that file
-is skipped; if it is an include pattern then that filename is not skipped; if
-no matching pattern is found, then the filename is not skipped.
+- Control which files the sending side puts into the file list that describes
+  the transfer hierarchy
+- Control which files the receiving side protects from deletion when the file
+  is not in the sender's file list
+- Control which extended attribute names are skipped when copying xattrs
+
+The rules are either directly specified via option arguments or they can be
+read in from one or more files.  The filter-rule files can even be a part of
+the hierarchy of files being copied, affecting different parts of the tree in
+different ways.
+
+### SIMPLE INCLUDE/EXCLUDE RULES
+
+We will first cover the basics of how include & exclude rules affect what files
+are transferred, ignoring any deletion side-effects.  Filter rules mainly
+affect the contents of directories that rsync is "recursing" into, but they can
+also affect a top-level item in the transfer that was specified as a argument.
+
+The default for any unmatched file/dir is for it to be included in the
+transfer, which puts the file/dir into the sender's file list.  The use of an
+exclude rule causes one or more matching files/dirs to be left out of the
+sender's file list.  An include rule can be used to limit the effect of an
+exclude rule that is matching too many files.
+
+The order of the rules is important because the first rule that matches is the
+one that takes effect.  Thus, if an early rule excludes a file, no include rule
+that comes after it can have any effect. This means that you must place any
+include overrides somewhere prior to the exclude that it is intended to limit.
 
-Rsync builds an ordered list of filter rules as specified on the command-line.
-Filter rules have the following syntax:
+When a directory is excluded, all its contents and sub-contents are also
+excluded.  The sender doesn't scan through any of it at all, which can save a
+lot of time when skipping large unneeded sub-trees.
+
+It is also important to understand that the include/exclude rules are applied
+to every file and directory that the sender is recursing into. Thus, if you
+want a particular deep file to be included, you have to make sure that none of
+the directories that must be traversed on the way down to that file are
+excluded or else the file will never be discovered to be included. As an
+example, if the directory "`a/path`" was given as a transfer argument and you
+want to ensure that the file "`a/path/down/deep/wanted.txt`" is a part of the
+transfer, then the sender must not exclude the directories "`a/path`",
+"`a/path/down`", or "`a/path/down/deep`" as it makes it way scanning through
+the file tree.
+
+When you are working on the rules, it can be helpful to ask rsync to tell you
+what is being excluded/included and why.  Specifying `--debug=FILTER` or (when
+pulling files) `-M--debug=FILTER` turns on level 1 of the FILTER debug
+information that will output a message any time that a file or directory is
+included or excluded and which rule it matched.  Beginning in 3.2.4 it will
+also warn if a filter rule has trailing whitespace, since an exclude of "foo "
+(with a trailing space) will not exclude a file named "foo".
+
+Exclude and include rules can specify wildcard [PATTERN MATCHING RULES](#)
+(similar to shell wildcards) that allow you to match things like a file suffix
+or a portion of a filename.
+
+A rule can be limited to only affecting a directory by putting a trailing slash
+onto the filename.
+
+### SIMPLE INCLUDE/EXCLUDE EXAMPLE
+
+With the following file tree created on the sending side:
+
+>     mkdir x/
+>     touch x/file.txt
+>     mkdir x/y/
+>     touch x/y/file.txt
+>     touch x/y/zzz.txt
+>     mkdir x/z/
+>     touch x/z/file.txt
+
+Then the following rsync command will transfer the file "`x/y/file.txt`" and
+the directories needed to hold it, resulting in the path "`/tmp/x/y/file.txt`"
+existing on the remote host:
+
+>     rsync -ai -f'+ x/' -f'+ x/y/' -f'+ x/y/file.txt' -f'- *' x host:/tmp/
+
+Aside: this copy could also have been accomplished using the [`-R`](#opt)
+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 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/
+
+This command would omit the zzz.txt file while copying "x" and everything else
+it contains:
+
+>     rsync -ai -f'- zzz.txt' x host:/tmp/
+
+### FILTER RULES WHEN DELETING
+
+By default the include & exclude filter rules affect both the sender
+(as it creates its file list)
+and the receiver (as it creates its file lists for calculating deletions).  If
+no delete option is in effect, the receiver skips creating the delete-related
+file lists.  This two-sided default can be manually overridden so that you are
+only specifying sender rules or receiver rules, as described in the [FILTER
+RULES IN DEPTH](#) section.
+
+When deleting, an exclude protects a file from being removed on the receiving
+side while an include overrides that protection (putting the file at risk of
+deletion). The default is for a file to be at risk -- its safety depends on it
+matching a corresponding file from the sender.
+
+An example of the two-sided exclude effect can be illustrated by the copying of
+a C development directory between 2 systems.  When doing a touch-up copy, you
+might want to skip copying the built executable and the `.o` files (sender
+hide) so that the receiving side can build their own and not lose any object
+files that are already correct (receiver protect).  For instance:
+
+>     rsync -ai --del -f'- *.o' -f'- cmd' src host:/dest/
+
+Note that using `-f'-p *.o'` is even better than `-f'- *.o'` if there is a
+chance that the directory structure may have changed.  The "p" modifier is
+discussed in [FILTER RULE MODIFIERS](#).
+
+One final note, if your shell doesn't mind unexpanded wildcards, you could
+simplify the typing of the filter options by using an underscore in place of
+the space and leaving off the quotes.  For instance, `-f -_*.o -f -_cmd` (and
+similar) could be used instead of the filter options above.
+
+### FILTER RULES IN DEPTH
+
+Rsync supports old-style include/exclude rules and new-style filter rules.  The
+older rules are specified using [`--include`](#opt) and [`--exclude`](#opt) as
+well as the [`--include-from`](#opt) and [`--exclude-from`](#opt). These are
+limited in behavior but they don't require a "-" or "+" prefix.  An old-style
+exclude rule is turned into a "`- name`" filter rule (with no modifiers) and an
+old-style include rule is turned into a "`+ name`" filter rule (with no
+modifiers).
+
+Rsync builds an ordered list of filter rules as specified on the command-line
+and/or read-in from files.  New style filter rules have the following syntax:
 
 >     RULE [PATTERN_OR_FILENAME]
 >     RULE,MODIFIERS [PATTERN_OR_FILENAME]
@@ -3504,174 +4056,147 @@ Filter rules have the following syntax:
 You have your choice of using either short or long RULE names, as described
 below.  If you use a short-named rule, the ',' separating the RULE from the
 MODIFIERS is optional.  The PATTERN or FILENAME that follows (when present)
-must come after either a single space or an underscore (\_).  Here are the
-available rule prefixes:
-
-0.  `exclude, '-'` specifies an exclude pattern.
-0.  `include, '+'` specifies an include pattern.
-0.  `merge, '.'` specifies a merge-file to read for more rules.
-0.  `dir-merge, ':'` specifies a per-directory merge-file.
+must come after either a single space or an underscore (\_). Any additional
+spaces and/or underscores are considered to be a part of the pattern name.
+Here are the available rule prefixes:
+
+0.  `exclude, '-'` specifies an exclude pattern that (by default) is both a
+    `hide` and a `protect`.
+0.  `include, '+'` specifies an include pattern that (by default) is both a
+    `show` and a `risk`.
+0.  `merge, '.'` specifies a merge-file on the client side to read for more
+    rules.
+0.  `dir-merge, ':'` specifies a per-directory merge-file.  Using this kind of
+    filter rule requires that you trust the sending side's filter checking, so
+    it has the side-effect mentioned under the [`--trust-sender`](#opt) option.
 0.  `hide, 'H'` specifies a pattern for hiding files from the transfer.
-0.  `show, 'S'` files that match the pattern are not hidden.
+    Equivalent to a sender-only exclude, so `-f'H foo'` could also be specified
+    as `-f'-s foo'`.
+0.  `show, 'S'` files that match the pattern are not hidden. Equivalent to a
+    sender-only include, so `-f'S foo'` could also be specified as `-f'+s
+    foo'`.
 0.  `protect, 'P'` specifies a pattern for protecting files from deletion.
-0.  `risk, 'R'` files that match the pattern are not protected.
+    Equivalent to a receiver-only exclude, so `-f'P foo'` could also be
+    specified as `-f'-r foo'`.
+0.  `risk, 'R'` files that match the pattern are not protected. Equivalent to a
+    receiver-only include, so `-f'R foo'` could also be specified as `-f'+r
+    foo'`.
 0.  `clear, '!'` clears the current include/exclude list (takes no arg)
 
-When rules are being read from a file, empty lines are ignored, as are
-whole-line comments that start with a '`#`' (filename rules that contain a hash
-are unaffected).
-
-[comment]: # (Remember that markdown strips spaces from start/end of ` ... ` sequences!)
-[comment]: # (Thus, the `x ` sequences below use a literal non-breakable space!)
-
-Note that the `--include` & `--exclude` command-line options do not allow the
-full range of rule parsing as described above -- they only allow the
-specification of include / exclude patterns plus a "`!`" token to clear the
-list (and the normal comment parsing when rules are read from a file).  If a
-pattern does not begin with "`- `" (dash, space) or "`+ `" (plus, space), then
-the rule will be interpreted as if "`+ `" (for an include option) or "`- `"
-(for an exclude option) were prefixed to the string.  A `--filter` option, on
-the other hand, must always contain either a short or long rule name at the
-start of the rule.
-
-Note also that the `--filter`, `--include`, and `--exclude` options take one
-rule/pattern each.  To add multiple ones, you can repeat the options on the
-command-line, use the merge-file syntax of the `--filter` option, or the
-`--include-from` / `--exclude-from` options.
-
-# INCLUDE/EXCLUDE PATTERN RULES
-
-You can include and exclude files by specifying patterns using the "+", "-",
-etc. filter rules (as introduced in the FILTER RULES section above).  The
-include/exclude rules each specify a pattern that is matched against the names
-of the files that are going to be transferred.  These patterns can take several
-forms:
-
-- if the pattern starts with a `/` then it is anchored to a particular spot in
-  the hierarchy of files, otherwise it is matched against the end of the
-  pathname.  This is similar to a leading `^` in regular expressions.  Thus
-  `/foo` would match a name of "foo" at either the "root of the transfer" (for
-  a global rule) or in the merge-file's directory (for a per-directory rule).
-  An unqualified `foo` would match a name of "foo" anywhere in the tree because
-  the algorithm is applied recursively from the top down; it behaves as if each
-  path component gets a turn at being the end of the filename.  Even the
-  unanchored "sub/foo" would match at any point in the hierarchy where a "foo"
-  was found within a directory named "sub".  See the section on ANCHORING
-  INCLUDE/EXCLUDE PATTERNS for a full discussion of how to specify a pattern
-  that matches at the root of the transfer.
-- if the pattern ends with a `/` then it will only match a directory, not a
-  regular file, symlink, or device.
-- rsync chooses between doing a simple string match and wildcard matching by
-  checking if the pattern contains one of these three wildcard characters:
-  '`*`', '`?`', and '`[`' .
-- a '`*`' matches any path component, but it stops at slashes.
-- use '`**`' to match anything, including slashes.
-- a '`?`' matches any character except a slash (`/`).
-- a '`[`' introduces a character class, such as `[a-z]` or `[[:alpha:]]`.
-- in a wildcard pattern, a backslash can be used to escape a wildcard
-  character, but it is matched literally when no wildcards are present.  This
-  means that there is an extra level of backslash removal when a pattern
-  contains wildcard characters compared to a pattern that has none.  e.g. if
-  you add a wildcard to "`foo\bar`" (which matches the backslash) you would
-  need to use "`foo\\bar*`" to avoid the "`\b`" becoming just "b".
-- if the pattern contains a `/` (not counting a trailing /) or a "`**`", then it
-  is matched against the full pathname, including any leading directories.  If
-  the pattern doesn't contain a `/` or a "`**`", then it is matched only against
-  the final component of the filename. (Remember that the algorithm is applied
-  recursively so "full filename" can actually be any portion of a path from the
-  starting directory on down.)
-- a trailing "`dir_name/***`" will match both the directory (as if "dir_name/"
+When rules are being read from a file (using merge or dir-merge), empty lines
+are ignored, as are whole-line comments that start with a '`#`' (filename rules
+that contain a hash character are unaffected).
+
+Note also that the [`--filter`](#opt), [`--include`](#opt), and
+[`--exclude`](#opt) options take one rule/pattern each.  To add multiple ones,
+you can repeat the options on the command-line, use the merge-file syntax of
+the [`--filter`](#opt) option, or the [`--include-from`](#opt) /
+[`--exclude-from`](#opt) options.
+
+### PATTERN MATCHING RULES
+
+Most of the rules mentioned above take an argument that specifies what the rule
+should match.  If rsync is recursing through a directory hierarchy, keep in
+mind that each pattern is matched against the name of every directory in the
+descent path as rsync finds the filenames to send.
+
+The matching rules for the pattern argument take several forms:
+
+- If a pattern contains a `/` (not counting a trailing slash) or a "`**`"
+  (which can match a slash), then the pattern is matched against the full
+  pathname, including any leading directories within the transfer.  If the
+  pattern doesn't contain a (non-trailing) `/` or a "`**`", then it is matched
+  only against the final component of the filename or pathname. For example,
+  `foo` means that the final path component must be "foo" while `foo/bar` would
+  match the last 2 elements of the path (as long as both elements are within
+  the transfer).
+- A pattern that ends with a `/` only matches a directory, not a regular file,
+  symlink, or device.
+- A pattern that starts with a `/` is anchored to the start of the transfer
+  path instead of the end.  For example, `/foo/**` or `/foo/bar/**` match only
+  leading elements in the path.  If the rule is read from a per-directory
+  filter file, the transfer path being matched will begin at the level of the
+  filter file instead of the top of the transfer.  See the section on
+  [ANCHORING INCLUDE/EXCLUDE PATTERNS](#) for a full discussion of how to
+  specify a pattern that matches at the root of the transfer.
+
+Rsync chooses between doing a simple string match and wildcard matching by
+checking if the pattern contains one of these three wildcard characters: '`*`',
+'`?`', and '`[`' :
+
+- a '`?`' matches any single character except a slash (`/`).
+- a '`*`' matches zero or more non-slash characters.
+- a '`**`' matches zero or more characters, including slashes.
+- a '`[`' introduces a character class, such as `[a-z]` or `[[:alpha:]]`, that
+  must match one character.
+- a trailing `***` in the pattern is a shorthand that allows you to match a
+  directory and all its contents using a single rule.  For example, specifying
+  "`dir_name/***`" will match both the "dir_name" directory (as if "`dir_name/`"
   had been specified) and everything in the directory (as if "`dir_name/**`"
-  had been specified).  This behavior was added in version 2.6.7.
-
-Note that, when using the `--recursive` (`-r`) option (which is implied by
-`-a`), every subdir component of every path is visited left to right, with each
-directory having a chance for exclusion before its content.  In this way
-include/exclude patterns are applied recursively to the pathname of each node
-in the filesystem's tree (those inside the transfer).  The exclude patterns
-short-circuit the directory traversal stage as rsync finds the files to send.
-
-For instance, to include "`/foo/bar/baz`", the directories "`/foo`" and "`/foo/bar`"
-must not be excluded.  Excluding one of those parent directories prevents the
-examination of its content, cutting off rsync's recursion into those paths and
-rendering the include for "`/foo/bar/baz`" ineffectual (since rsync can't match
-something it never sees in the cut-off section of the directory hierarchy).
-
-The concept path exclusion is particularly important when using a trailing '`*`'
-rule.  For instance, this won't work:
-
->     + /some/path/this-file-will-not-be-found
->     + /file-is-included
->     - *
-
-This fails because the parent directory "some" is excluded by the '`*`' rule, so
-rsync never visits any of the files in the "some" or "some/path" directories.
-One solution is to ask for all directories in the hierarchy to be included by
-using a single rule: "`+ */`" (put it somewhere before the "`- *`" rule), and
-perhaps use the `--prune-empty-dirs` option.  Another solution is to add
-specific include rules for all the parent dirs that need to be visited.  For
-instance, this set of rules works fine:
-
->     + /some/
->     + /some/path/
->     + /some/path/this-file-is-found
->     + /file-also-included
->     - *
+  had been specified).
+- a backslash can be used to escape a wildcard character, but it is only
+  interpreted as an escape character if at least one wildcard character is
+  present in the match pattern. For instance, the pattern "`foo\bar`" matches
+  that single backslash literally, while the pattern "`foo\bar*`" would need to
+  be changed to "`foo\\bar*`" to avoid the "`\b`" becoming just "b".
 
 Here are some examples of exclude/include matching:
 
-- "`- *.o`" would exclude all names matching `*.o`
-- "`- /foo`" would exclude a file (or directory) named foo in the transfer-root
-  directory
-- "`- foo/`" would exclude any directory named foo
-- "`- /foo/*/bar`" would exclude any file named bar which is at two levels
-  below a directory named foo in the transfer-root directory
-- "`- /foo/**/bar`" would exclude any file named bar two or more levels below a
-  directory named foo in the transfer-root directory
-- The combination of "`+ */`", "`+ *.c`", and "`- *`" would include all
-  directories and C source files but nothing else (see also the
-  `--prune-empty-dirs` option)
-- The combination of "`+ foo/`", "`+ foo/bar.c`", and "`- *`" would include
-  only the foo directory and foo/bar.c (the foo directory must be explicitly
-  included or it would be excluded by the "`*`")
-
-The following modifiers are accepted after a "`+`" or "`-`":
+- Option `-f'- *.o'` would exclude all filenames ending with `.o`
+- Option `-f'- /foo'` would exclude a file (or directory) named foo in the
+  transfer-root directory
+- Option `-f'- foo/'` would exclude any directory named foo
+- Option `-f'- foo/*/bar'` would exclude any file/dir named bar which is at two
+  levels below a directory named foo (if foo is in the transfer)
+- Option `-f'- /foo/**/bar'` would exclude any file/dir named bar that was two
+  or more levels below a top-level directory named foo (note that /foo/bar is
+  **not** excluded by this)
+- Options `-f'+ */' -f'+ *.c' -f'- *'` would include all directories and .c
+  source files but nothing else
+- Options `-f'+ foo/' -f'+ foo/bar.c' -f'- *'` would include only the foo
+  directory and foo/bar.c (the foo directory must be explicitly included or it
+  would be excluded by the "`- *`")
+
+### FILTER RULE MODIFIERS
+
+The following modifiers are accepted after an include (+) or exclude (-) rule:
 
 - A `/` specifies that the include/exclude rule should be matched against the
-  absolute pathname of the current item.  For example, "`-/ /etc/passwd`" would
-  exclude the passwd file any time the transfer was sending files from the
-  "/etc" directory, and "-/ subdir/foo" would always exclude "foo" when it is
-  in a dir named "subdir", even if "foo" is at the root of the current
+  absolute pathname of the current item.  For example, `-f'-/ /etc/passwd'`
+  would exclude the passwd file any time the transfer was sending files from
+  the "/etc" directory, and "-/ subdir/foo" would always exclude "foo" when it
+  is in a dir named "subdir", even if "foo" is at the root of the current
   transfer.
 - A `!` specifies that the include/exclude should take effect if the pattern
-  fails to match.  For instance, "`-! */`" would exclude all non-directories.
+  fails to match.  For instance, `-f'-! */'` would exclude all non-directories.
 - A `C` is used to indicate that all the global CVS-exclude rules should be
   inserted as excludes in place of the "-C".  No arg should follow.
 - An `s` is used to indicate that the rule applies to the sending side.  When a
-  rule affects the sending side, it prevents files from being transferred.  The
-  default is for a rule to affect both sides unless `--delete-excluded` was
-  specified, in which case default rules become sender-side only.  See also the
-  hide (H) and show (S) rules, which are an alternate way to specify
-  sending-side includes/excludes.
+  rule affects the sending side, it affects what files are put into the
+  sender's file list.  The default is for a rule to affect both sides unless
+  [`--delete-excluded`](#opt) was specified, in which case default rules become
+  sender-side only.  See also the hide (H) and show (S) rules, which are an
+  alternate way to specify sending-side includes/excludes.
 - An `r` is used to indicate that the rule applies to the receiving side.  When
   a rule affects the receiving side, it prevents files from being deleted.  See
   the `s` modifier for more info.  See also the protect (P) and risk (R) rules,
   which are an alternate way to specify receiver-side includes/excludes.
 - A `p` indicates that a rule is perishable, meaning that it is ignored in
-  directories that are being deleted.  For instance, the `-C` option's default
-  rules that exclude things like "CVS" and "`*.o`" are marked as perishable,
-  and will not prevent a directory that was removed on the source from being
-  deleted on the destination.
+  directories that are being deleted.  For instance, the
+  [`--cvs-exclude`](#opt) (`-C`) option's default rules that exclude things
+  like "CVS" and "`*.o`" are marked as perishable, and will not prevent a
+  directory that was removed on the source from being deleted on the
+  destination.
 - An `x` indicates that a rule affects xattr names in xattr copy/delete
   operations (and is thus ignored when matching file/dir names).  If no
   xattr-matching rules are specified, a default xattr filtering rule is used
-  (see the `--xattrs` option).
+  (see the [`--xattrs`](#opt) option).
 
-# MERGE-FILE FILTER RULES
+### MERGE-FILE FILTER RULES
 
 You can merge whole files into your filter rules by specifying either a merge
-(.) or a dir-merge (:) filter rule (as introduced in the FILTER RULES section
-above).
+(.) or a dir-merge (:) filter rule (as introduced in the [FILTER RULES](#)
+section above).
 
 There are two kinds of merged files -- single-instance ('.') and per-directory
 (':').  A single-instance merge file is read one time, and its rules are
@@ -3682,7 +4207,7 @@ list of inherited rules.  These per-directory rule files must be created on the
 sending side because it is the sending side that is being scanned for the
 available files to transfer.  These rule files may also need to be transferred
 to the receiving side if you want them to affect what files don't get deleted
-(see PER-DIRECTORY RULES AND DELETE below).
+(see [PER-DIRECTORY RULES AND DELETE](#) below).
 
 Some examples:
 
@@ -3753,7 +4278,7 @@ transfer).
 If a per-directory merge-file is specified with a path that is a parent
 directory of the first transfer directory, rsync will scan all the parent dirs
 from that starting point to the transfer directory for the indicated
-per-directory file.  For instance, here is a common filter (see `-F`):
+per-directory file.  For instance, here is a common filter (see [`-F`](#opt)):
 
 >     --filter=': /.rsync-filter'
 
@@ -3777,11 +4302,11 @@ the ".rsync-filter" files in each directory that is a part of the transfer.
 If you want to include the contents of a ".cvsignore" in your patterns, you
 should use the rule ":C", which creates a dir-merge of the .cvsignore file, but
 parsed in a CVS-compatible manner.  You can use this to affect where the
-`--cvs-exclude` (`-C`) option's inclusion of the per-directory .cvsignore file
-gets placed into your rules by putting the ":C" wherever you like in your
-filter rules.  Without this, rsync would add the dir-merge rule for the
-.cvsignore file at the end of all your other rules (giving it a lower priority
-than your command-line rules).  For example:
+[`--cvs-exclude`](#opt) (`-C`) option's inclusion of the per-directory
+.cvsignore file gets placed into your rules by putting the ":C" wherever you
+like in your filter rules.  Without this, rsync would add the dir-merge rule
+for the .cvsignore file at the end of all your other rules (giving it a lower
+priority than your command-line rules).  For example:
 
 > ```
 > cat < Target file: /dest/you/bar/baz
 > ```
 
-The easiest way to see what name you should filter is to just
-look at the output when using `--verbose` and put a / in front of the name
-(use the `--dry-run` option if you're not yet ready to copy any files).
+The easiest way to see what name you should filter is to just look at the
+output when using [`--verbose`](#opt) and put a / in front of the name (use the
+`--dry-run` option if you're not yet ready to copy any files).
 
-# PER-DIRECTORY RULES AND DELETE
+### PER-DIRECTORY RULES AND DELETE
 
 Without a delete option, per-directory rules are only relevant on the sending
 side, so you can feel free to exclude the merge files themselves without
@@ -3877,9 +4402,9 @@ for you, as seen in these two equivalent commands:
 However, if you want to do a delete on the receiving side AND you want some
 files to be excluded from being deleted, you'll need to be sure that the
 receiving side knows what files to exclude.  The easiest way is to include the
-per-directory merge files in the transfer and use `--delete-after`, because
-this ensures that the receiving side gets all the same exclude rules as the
-sending side before it tries to delete anything:
+per-directory merge files in the transfer and use [`--delete-after`](#opt),
+because this ensures that the receiving side gets all the same exclude rules as
+the sending side before it tries to delete anything:
 
 >     rsync -avF --delete-after host:src/dir /dest
 
@@ -3910,7 +4435,39 @@ one of these commands:
 > rsync -avFF --delete host:src/dir /dest
 > ```
 
-# BATCH MODE
+## TRANSFER RULES
+
+In addition to the [FILTER RULES](#) that affect the recursive file scans that
+generate the file list on the sending and (when deleting) receiving sides,
+there are transfer rules. These rules affect which files the generator decides
+need to be transferred without the side effects of an exclude filter rule.
+Transfer rules affect only files and never directories.
+
+Because a transfer rule does not affect what goes into the sender's (and
+receiver's) file list, it cannot have any effect on which files get deleted on
+the receiving side.  For example, if the file "foo" is present in the sender's
+list but its size is such that it is omitted due to a transfer rule, the
+receiving side does not request the file.  However, its presence in the file
+list means that a delete pass will not remove a matching file named "foo" on
+the receiving side.  On the other hand, a server-side exclude (hide) of the
+file "foo" leaves the file out of the server's file list, and absent a
+receiver-side exclude (protect) the receiver will remove a matching file named
+"foo" if deletions are requested.
+
+Given that the files are still in the sender's file list, the
+[`--prune-empty-dirs`](#opt) option will not judge a directory as being empty
+even if it contains only files that the transfer rules omitted.
+
+Similarly, a transfer rule does not have any extra effect on which files are
+deleted on the receiving side, so setting a maximum file size for the transfer
+does not prevent big files from being deleted.
+
+Examples of transfer rules include the default "quick check" algorithm (which
+compares size & modify time), the [`--update`](#opt) option, the
+[`--max-size`](#opt) option, the [`--ignore-non-existing`](#opt) option, and a
+few others.
+
+## BATCH MODE
 
 Batch mode can be used to apply the same set of updates to many identical
 systems.  Suppose one has a tree which is replicated on a number of hosts.  Now
@@ -3963,10 +4520,10 @@ flexibility you have in how you deal with batches:
   options when running the read-batch command on the remote host.
 - The second example reads the batch data via standard input so that the batch
   file doesn't need to be copied to the remote machine first.  This example
-  avoids the foo.sh script because it needed to use a modified `--read-batch`
-  option, but you could edit the script file if you wished to make use of it
-  (just be sure that no other option is trying to use standard input, such as
-  the "`--exclude-from=-`" option).
+  avoids the foo.sh script because it needed to use a modified
+  [`--read-batch`](#opt) option, but you could edit the script file if you
+  wished to make use of it (just be sure that no other option is trying to use
+  standard input, such as the [`--exclude-from=-`](#opt) option).
 
 Caveats:
 
@@ -3978,37 +4535,38 @@ already) or the file-update may be attempted and then, if the file fails to
 verify, the update discarded with an error.  This means that it should be safe
 to re-run a read-batch operation if the command got interrupted.  If you wish
 to force the batched-update to always be attempted regardless of the file's
-size and date, use the `-I` option (when reading the batch).  If an error
-occurs, the destination tree will probably be in a partially updated state.  In
-that case, rsync can be used in its regular (non-batch) mode of operation to
-fix up the destination tree.
+size and date, use the [`-I`](#opt) option (when reading the batch).  If an
+error occurs, the destination tree will probably be in a partially updated
+state.  In that case, rsync can be used in its regular (non-batch) mode of
+operation to fix up the destination tree.
 
 The rsync version used on all destinations must be at least as new as the one
 used to generate the batch file.  Rsync will die with an error if the protocol
 version in the batch file is too new for the batch-reading rsync to handle.
-See also the `--protocol` option for a way to have the creating rsync generate
-a batch file that an older rsync can understand.  (Note that batch files
-changed format in version 2.6.3, so mixing versions older than that with newer
-versions will not work.)
+See also the [`--protocol`](#opt) option for a way to have the creating rsync
+generate a batch file that an older rsync can understand.  (Note that batch
+files changed format in version 2.6.3, so mixing versions older than that with
+newer versions will not work.)
 
 When reading a batch file, rsync will force the value of certain options to
 match the data in the batch file if you didn't set them to the same as the
 batch-writing command.  Other options can (and should) be changed.  For
-instance `--write-batch` changes to `--read-batch`, `--files-from` is dropped,
-and the `--filter` / `--include` / `--exclude` options are not needed unless
-one of the `--delete` options is specified.
+instance [`--write-batch`](#opt) changes to [`--read-batch`](#opt),
+[`--files-from`](#opt) is dropped, and the [`--filter`](#opt) /
+[`--include`](#opt) / [`--exclude`](#opt) options are not needed unless one of
+the [`--delete`](#opt) options is specified.
 
 The code that creates the BATCH.sh file transforms any filter/include/exclude
 options into a single list that is appended as a "here" document to the shell
 script file.  An advanced user can use this to modify the exclude list if a
-change in what gets deleted by `--delete` is desired.  A normal user can ignore
-this detail and just use the shell script as an easy way to run the appropriate
-`--read-batch` command for the batched data.
+change in what gets deleted by [`--delete`](#opt) is desired.  A normal user
+can ignore this detail and just use the shell script as an easy way to run the
+appropriate [`--read-batch`](#opt) command for the batched data.
 
 The original batch mode in rsync was based on "rsync+", but the latest
 version uses a new implementation.
 
-# SYMBOLIC LINKS
+## SYMBOLIC LINKS
 
 Three basic behaviors are possible when rsync encounters a symbolic
 link in the source directory.
@@ -4016,40 +4574,53 @@ link in the source directory.
 By default, symbolic links are not transferred at all.  A message "skipping
 non-regular" file is emitted for any symlinks that exist.
 
-If `--links` is specified, then symlinks are recreated with the same target on
-the destination.  Note that `--archive` implies `--links`.
+If [`--links`](#opt) is specified, then symlinks are added to the transfer
+(instead of being noisily ignored), and the default handling is to recreate
+them with the same target on the destination.  Note that [`--archive`](#opt)
+implies [`--links`](#opt).
 
-If `--copy-links` is specified, then symlinks are "collapsed" by
+If [`--copy-links`](#opt) is specified, then symlinks are "collapsed" by
 copying their referent, rather than the symlink.
 
 Rsync can also distinguish "safe" and "unsafe" symbolic links.  An example
 where this might be used is a web site mirror that wishes to ensure that the
 rsync module that is copied does not include symbolic links to `/etc/passwd` in
-the public section of the site.  Using `--copy-unsafe-links` will cause any
-links to be copied as the file they point to on the destination.  Using
-`--safe-links` will cause unsafe links to be omitted altogether. (Note that you
-must specify `--links` for `--safe-links` to have any effect.)
+the public section of the site.  Using [`--copy-unsafe-links`](#opt) will cause
+any links to be copied as the file they point to on the destination.  Using
+[`--safe-links`](#opt) will cause unsafe links to be omitted by the receiver.
+(Note that you must specify or imply [`--links`](#opt) for
+[`--safe-links`](#opt) to have any effect.)
 
-Symbolic links are considered unsafe if they are absolute symlinks
-(start with `/`), empty, or if they contain enough ".."
-components to ascend from the directory being copied.
+Symbolic links are considered unsafe if they are absolute symlinks (start with
+`/`), empty, or if they contain enough ".." components to ascend from the top
+of the transfer.
 
 Here's a summary of how the symlink options are interpreted.  The list is in
 order of precedence, so if your combination of options isn't mentioned, use the
 first line that is a complete subset of your options:
 
-0.  `--copy-links` Turn all symlinks into normal files (leaving no symlinks for
-    any other options to affect).
-0.  `--links --copy-unsafe-links` Turn all unsafe symlinks into files and
-    duplicate all safe symlinks.
-0.  `--copy-unsafe-links` Turn all unsafe symlinks into files, noisily skip all
-    safe symlinks.
-0.  `--links --safe-links` Duplicate safe symlinks and skip unsafe ones.
-0.  `--links` Duplicate all symlinks.
+0.  `--copy-links` Turn all symlinks into normal files and directories
+    (leaving no symlinks in the transfer for any other options to affect).
+0.  `--copy-dirlinks` Turn just symlinks to directories into real
+    directories, leaving all other symlinks to be handled as described below.
+0.  `--links --copy-unsafe-links` Turn all unsafe symlinks
+    into files and create all safe symlinks.
+0.  `--copy-unsafe-links` Turn all unsafe symlinks into files, noisily
+    skip all safe symlinks.
+0.  `--links --safe-links` The receiver skips creating
+    unsafe symlinks found in the transfer and creates the safe ones.
+0.  `--links` Create all symlinks.
+
+For the effect of [`--munge-links`](#opt), see the discussion in that option's
+section.
+
+Note that the [`--keep-dirlinks`](#opt) option does not effect symlinks in the
+transfer but instead affects how rsync treats a symlink to a directory that
+already exists on the receiving side.  See that option's section for a warning.
 
-# DIAGNOSTICS
+## DIAGNOSTICS
 
-rsync occasionally produces error messages that may seem a little cryptic.  The
+Rsync occasionally produces error messages that may seem a little cryptic.  The
 one that seems to cause the most confusion is "protocol version mismatch -- is
 your shell clean?".
 
@@ -4070,108 +4641,166 @@ If you are having trouble debugging filter patterns, then try specifying the
 `-vv` option.  At this level of verbosity rsync will show why each individual
 file is included or excluded.
 
-# EXIT VALUES
-
-0.  **0** Success
-0.  **1** Syntax or usage error
-0.  **2** Protocol incompatibility
-0.  **3** Errors selecting input/output files, dirs
-0.  **4** Requested action not supported: an attempt was made to manipulate
-    64-bit files on a platform that cannot support them; or an option was
-    specified that is supported by the client and not by the server.
-0.  **5** Error starting client-server protocol
-0.  **6** Daemon unable to append to log-file
-0.  **10** Error in socket I/O
-0.  **11** Error in file I/O
-0.  **12** Error in rsync protocol data stream
-0.  **13** Errors with program diagnostics
-0.  **14** Error in IPC code
-0.  **20** Received SIGUSR1 or SIGINT
-0.  **21** Some error returned by **waitpid()**
-0.  **22** Error allocating core memory buffers
-0.  **23** Partial transfer due to error
-0.  **24** Partial transfer due to vanished source files
-0.  **25** The --max-delete limit stopped deletions
-0.  **30** Timeout in data send/receive
-0.  **35** Timeout waiting for daemon connection
-
-# ENVIRONMENT VARIABLES
+## EXIT VALUES
+
+- **0** - Success
+- **1** - Syntax or usage error
+- **2** - Protocol incompatibility
+- **3** - Errors selecting input/output files, dirs
+- **4** - Requested action not supported. Either:
+  - an attempt was made to manipulate 64-bit files on a platform that cannot support them
+  - an option was specified that is supported by the client and not by the server
+- **5** - Error starting client-server protocol
+- **6** - Daemon unable to append to log-file
+- **10** - Error in socket I/O
+- **11** - Error in file I/O
+- **12** - Error in rsync protocol data stream
+- **13** - Errors with program diagnostics
+- **14** - Error in IPC code
+- **20** - Received SIGUSR1 or SIGINT
+- **21** - Some error returned by **waitpid()**
+- **22** - Error allocating core memory buffers
+- **23** - Partial transfer due to error
+- **24** - Partial transfer due to vanished source files
+- **25** - The --max-delete limit stopped deletions
+- **30** - Timeout in data send/receive
+- **35** - Timeout waiting for daemon connection
+
+## ENVIRONMENT VARIABLES
 
 0.  `CVSIGNORE`
 
     The CVSIGNORE environment variable supplements any ignore patterns in
-    .cvsignore files.  See the `--cvs-exclude` option for more details.
+    .cvsignore files.  See the [`--cvs-exclude`](#opt) option for more details.
 
 0.  `RSYNC_ICONV`
 
-    Specify a default `--iconv` setting using this environment variable. (First
-    supported in 3.0.0.)
+    Specify a default [`--iconv`](#opt) setting using this environment
+    variable. First supported in 3.0.0.
+
+0.  `RSYNC_OLD_ARGS`
+
+    Specify a "1" if you want the [`--old-args`](#opt) option to be enabled by
+    default, a "2" (or more) if you want it to be enabled in the
+    repeated-option state, or a "0" to make sure that it is disabled by
+    default. When this environment variable is set to a non-zero value, it
+    supersedes the [`RSYNC_PROTECT_ARGS`](#) variable.
+
+    This variable is ignored if [`--old-args`](#opt), `--no-old-args`, or
+    [`--secluded-args`](#opt) is specified on the command line.
+
+    First supported in 3.2.4.
 
 0.  `RSYNC_PROTECT_ARGS`
 
-    Specify a non-zero numeric value if you want the `--protect-args` option to
-    be enabled by default, or a zero value to make sure that it is disabled by
-    default. (First supported in 3.1.0.)
+    Specify a non-zero numeric value if you want the [`--secluded-args`](#opt)
+    option to be enabled by default, or a zero value to make sure that it is
+    disabled by default.
+
+    This variable is ignored if [`--secluded-args`](#opt), `--no-secluded-args`,
+    or [`--old-args`](#opt) is specified on the command line.
+
+    First supported in 3.1.0.  Starting in 3.2.4, this variable is ignored if
+    [`RSYNC_OLD_ARGS`](#) is set to a non-zero value.
 
 0.  `RSYNC_RSH`
 
-    The RSYNC_RSH environment variable allows you to override the default shell
-    used as the transport for rsync.  Command line options are permitted after
-    the command name, just as in the `-e` option.
+    This environment variable allows you to override the default shell used as
+    the transport for rsync.  Command line options are permitted after the
+    command name, just as in the [`--rsh`](#opt) (`-e`) option.
 
 0.  `RSYNC_PROXY`
 
-    The RSYNC_PROXY environment variable allows you to redirect your rsync
-    client to use a web proxy when connecting to a rsync daemon.  You should
-    set RSYNC_PROXY to a hostname:port pair.
+    This environment variable allows you to redirect your rsync
+    client to use a web proxy when connecting to an rsync daemon.  You should
+    set `RSYNC_PROXY` to a hostname:port pair.
 
 0.  `RSYNC_PASSWORD`
 
-    Setting RSYNC_PASSWORD to the required password allows you to run
-    authenticated rsync connections to an rsync daemon without user
-    intervention.  Note that this does not supply a password to a remote shell
-    transport such as ssh; to learn how to do that, consult the remote shell's
-    documentation.
+    This environment variable allows you to set the password for an rsync
+    **daemon** connection, which avoids the password prompt.  Note that this
+    does **not** supply a password to a remote shell transport such as ssh
+    (consult its documentation for how to do that).
 
 0.  `USER` or `LOGNAME`
 
     The USER or LOGNAME environment variables are used to determine the default
     username sent to an rsync daemon.  If neither is set, the username defaults
-    to "nobody".
+    to "nobody".  If both are set, `USER` takes precedence.
+
+0. `RSYNC_PARTIAL_DIR`
+
+    This environment variable specifies the directory to use for a
+    [`--partial`](#opt) transfer without implying that partial transfers be
+    enabled.  See the [`--partial-dir`](#opt) option for full details.
+
+0. `RSYNC_COMPRESS_LIST`
+
+    This environment variable allows you to customize the negotiation of the
+    compression algorithm by specifying an alternate order or a reduced list of
+    names.  Use the command `rsync --version` to see the available compression
+    names.  See the [`--compress`](#opt) option for full details.
+
+0. `RSYNC_CHECKSUM_LIST`
+
+    This environment variable allows you to customize the negotiation of the
+    checksum algorithm by specifying an alternate order or a reduced list of
+    names.  Use the command `rsync --version` to see the available checksum
+    names.  See the [`--checksum-choice`](#opt) option for full details.
+
+0. `RSYNC_MAX_ALLOC`
+
+    This environment variable sets an allocation maximum as if you had used the
+    [`--max-alloc`](#opt) option.
+
+0. `RSYNC_PORT`
+
+    This environment variable is not read by rsync, but is instead set in
+    its sub-environment when rsync is running the remote shell in combination
+    with a daemon connection.  This allows a script such as
+    [`rsync-ssl`](rsync-ssl.1) to be able to know the port number that the user
+    specified on the command line.
 
 0.  `HOME`
 
-    The HOME environment variable is used to find the user's default .cvsignore
+    This environment variable is used to find the user's default .cvsignore
     file.
 
-# FILES
+0. `RSYNC_CONNECT_PROG`
 
-/etc/rsyncd.conf or rsyncd.conf
+    This environment variable is mainly used in debug setups to set the program
+    to use when making a daemon connection.  See [CONNECTING TO AN RSYNC
+    DAEMON](#) for full details.
 
-# SEE ALSO
+0. `RSYNC_SHELL`
 
-**rsync-ssl**(1), **rsyncd.conf**(5)
+    This environment variable is mainly used in debug setups to set the program
+    to use to run the program specified by [`RSYNC_CONNECT_PROG`](#).  See
+    [CONNECTING TO AN RSYNC DAEMON](#) for full details.
 
-# BUGS
+## FILES
 
-times are transferred as \*nix time_t values
+/etc/rsyncd.conf or rsyncd.conf
 
-When transferring to FAT filesystems rsync may re-sync
-unmodified files.
-See the comments on the `--modify-window` option.
+## SEE ALSO
 
-file permissions, devices, etc. are transferred as native numerical
-values
+[**rsync-ssl**(1)](rsync-ssl.1), [**rsyncd.conf**(5)](rsyncd.conf.5), [**rrsync**(1)](rrsync.1)
 
-see also the comments on the `--delete` option
+## BUGS
+
+- Times are transferred as \*nix time_t values.
+- When transferring to FAT filesystems rsync may re-sync unmodified files.  See
+  the comments on the [`--modify-window`](#opt) option.
+- File permissions, devices, etc. are transferred as native numerical values.
+- See also the comments on the [`--delete`](#opt) option.
 
 Please report bugs! See the web site at .
 
-# VERSION
+## VERSION
 
-This man page is current for version @VERSION@ of rsync.
+This manpage is current for version @VERSION@ of rsync.
 
-# INTERNAL OPTIONS
+## INTERNAL OPTIONS
 
 The options `--server` and `--sender` are used internally by rsync, and should
 never be typed by a user under normal circumstances.  Some awareness of these
@@ -4180,13 +4809,16 @@ that can only run an rsync command.  For instance, the support directory of the
 rsync distribution has an example script named rrsync (for restricted rsync)
 that can be used with a restricted ssh login.
 
-# CREDITS
+## CREDITS
+
+Rsync is distributed under the GNU General Public License.  See the file
+[COPYING](COPYING) for details.
 
-rsync is distributed under the GNU General Public License.  See the file
-COPYING for details.
+An rsync web site is available at .  The site
+includes an FAQ-O-Matic which may cover questions unanswered by this manual
+page.
 
-A web site is available at .  The site includes an
-FAQ-O-Matic which may cover questions unanswered by this manual page.
+The rsync github project is .
 
 We would be delighted to hear from you if you like this program.  Please
 contact the mailing-list at .
@@ -4194,7 +4826,7 @@ contact the mailing-list at .
 This program uses the excellent zlib compression library written by Jean-loup
 Gailly and Mark Adler.
 
-# THANKS
+## THANKS
 
 Special thanks go out to: John Van Essen, Matt McCutchen, Wesley W. Terpstra,
 David Dykstra, Jos Backus, Sebastian Krahmer, Martin Pool, and our
@@ -4203,11 +4835,10 @@ gone-but-not-forgotten compadre, J.W. Schultz.
 Thanks also to Richard Brent, Brendan Mackay, Bill Waite, Stephen Rothwell and
 David Bell.  I've probably missed some people, my apologies if I have.
 
-# AUTHOR
+## 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.
+Rsync was originally written by Andrew Tridgell and Paul Mackerras.  Many
+people from around the world have helped to maintain and improve it.
 
 Mailing lists for support and development are available at
 .
diff --git a/rsync.c b/rsync.c
index 60029c2d8..b130aba56 100644
--- a/rsync.c
+++ b/rsync.c
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 1996 Andrew Tridgell
  * Copyright (C) 1996 Paul Mackerras
- * Copyright (C) 2003-2020 Wayne Davison
+ * 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 as published by
@@ -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);
 }
 
@@ -642,7 +645,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
 #ifdef SUPPORT_ACLS
 	/* It's OK to call set_acl() now, even for a dir, as the generator
 	 * will enable owner-writability using chmod, if necessary.
-	 * 
+	 *
 	 * If set_acl() changes permission bits in the process of setting
 	 * an access ACL, it changes sxp->st.st_mode so we know whether we
 	 * need to chmod(). */
diff --git a/rsync.h b/rsync.h
index a6c0d1bb3..479ac4848 100644
--- a/rsync.h
+++ b/rsync.h
@@ -2,7 +2,7 @@
  * Copyright (C) 1996, 2000 Andrew Tridgell
  * Copyright (C) 1996 Paul Mackerras
  * Copyright (C) 2001, 2002 Martin Pool 
- * Copyright (C) 2003-2020 Wayne Davison
+ * 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 as published by
@@ -18,11 +18,6 @@
  * with this program; if not, visit the http://fsf.org website.
  */
 
-/* a non-zero CHAR_OFFSET makes the rolling sum stronger, but is
-   incompatible with older versions :-( */
-#define CHAR_OFFSET 0
-
-#ifndef AVX2_ASM /* do not include the rest of file for assembly */
 #define False 0
 #define True 1
 #define Unset (-1) /* Our BOOL values are always an int. */
@@ -43,6 +38,9 @@
 
 #define BACKUP_SUFFIX "~"
 
+/* a non-zero CHAR_OFFSET makes the rolling sum stronger, but is
+   incompatible with older versions :-( */
+#define CHAR_OFFSET 0
 
 /* These flags are only used during the flist transfer. */
 
@@ -94,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. */
 
@@ -112,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
@@ -340,6 +339,9 @@ enum delret {
 # endif
 # include 
 #endif
+#ifdef HAVE_BSD_STRING_H
+# include 
+#endif
 #ifdef HAVE_STRINGS_H
 # include 
 #endif
@@ -365,16 +367,10 @@ enum delret {
 #include 
 #endif
 
-#ifdef TIME_WITH_SYS_TIME
-#include 
-#include 
-#else
 #ifdef HAVE_SYS_TIME_H
 #include 
-#else
-#include 
-#endif
 #endif
+#include 
 
 #ifdef HAVE_FCNTL_H
 #include 
@@ -780,6 +776,11 @@ struct ht_int64_node {
 
 #if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
 #define USE_FLEXIBLE_ARRAY 1
+#define SIZE_T_FMT_MOD "z" /* printf supports %zd */
+#define SIZE_T_FMT_CAST size_t
+#else
+#define SIZE_T_FMT_MOD "l" /* printf supports %ld */
+#define SIZE_T_FMT_CAST long
 #endif
 
 union file_extras {
@@ -820,6 +821,7 @@ extern int uid_ndx;
 extern int gid_ndx;
 extern int acls_ndx;
 extern int xattrs_ndx;
+extern int file_sum_extra_cnt;
 
 #ifdef USE_FLEXIBLE_ARRAY
 #define FILE_STRUCT_LEN (sizeof (struct file_struct))
@@ -830,7 +832,7 @@ extern int xattrs_ndx;
 #define DEV_EXTRA_CNT 2
 #define DIRNODE_EXTRA_CNT 3
 #define EXTRA64_CNT ((sizeof (union file_extras64) + EXTRA_LEN - 1) / EXTRA_LEN)
-#define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
+#define SUM_EXTRA_CNT file_sum_extra_cnt
 
 #define REQ_EXTRA(f,ndx) ((union file_extras*)(f) - (ndx))
 #define OPT_EXTRA(f,bump) ((union file_extras*)(f) - file_extra_cnt - 1 - (bump))
@@ -957,12 +959,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 */
@@ -981,6 +983,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 */
@@ -1017,6 +1021,7 @@ typedef struct filter_struct {
 		int slash_cnt;
 		struct filter_list_struct *mergelist;
 	} u;
+	uchar elide;
 } filter_rule;
 
 typedef struct filter_list_struct {
@@ -1156,19 +1161,23 @@ typedef struct {
 #define NSTR_COMPRESS 1
 
 struct name_num_item {
-	int num;
-	const char *name, *main_name;
+	int num, flags;
+	const char *name;
+	struct name_num_item *main_nni;
 };
 
 struct name_num_obj {
 	const char *type;
-	const char *negotiated_name;
+	struct name_num_item *negotiated_nni;
 	uchar *saw;
 	int saw_len;
-	int negotiated_num;
-	struct name_num_item list[10]; /* we'll get a compile error/warning if this is ever too small */
+	struct name_num_item *list;
 };
 
+#ifdef EXTERNAL_ZLIB
+#define read_buf read_buf_
+#endif
+
 #ifndef __cplusplus
 #include "proto.h"
 #endif
@@ -1416,7 +1425,8 @@ extern short info_levels[], debug_levels[];
 #define INFO_MISC (INFO_FLIST+1)
 #define INFO_MOUNT (INFO_MISC+1)
 #define INFO_NAME (INFO_MOUNT+1)
-#define INFO_PROGRESS (INFO_NAME+1)
+#define INFO_NONREG (INFO_NAME+1)
+#define INFO_PROGRESS (INFO_NONREG+1)
 #define INFO_REMOVE (INFO_PROGRESS+1)
 #define INFO_SKIP (INFO_REMOVE+1)
 #define INFO_STATS (INFO_SKIP+1)
@@ -1471,4 +1481,9 @@ const char *get_panic_action(void);
     fprintf(stderr, "%s in %s at line %d\n", msg, __FILE__, __LINE__); \
     exit_cleanup(RERR_UNSUPPORTED); \
 } while (0)
-#endif  /* AVX2_ASM */
+
+#ifdef HAVE_MALLINFO2
+#define MEM_ALLOC_INFO mallinfo2
+#elif defined HAVE_MALLINFO
+#define MEM_ALLOC_INFO mallinfo
+#endif
diff --git a/rsyncd.conf.5.md b/rsyncd.conf.5.md
index 730ef71e1..2f257659d 100644
--- a/rsyncd.conf.5.md
+++ b/rsyncd.conf.5.md
@@ -1,12 +1,15 @@
-# NAME
+## NAME
 
 rsyncd.conf - configuration file for rsync in daemon mode
 
-# SYNOPSIS
+## SYNOPSIS
 
 rsyncd.conf
 
-# DESCRIPTION
+The online version of this manpage (that includes cross-linking of topics)
+is available at .
+
+## DESCRIPTION
 
 The rsyncd.conf file is the runtime configuration file for rsync when run as an
 rsync daemon.
@@ -14,7 +17,7 @@ rsync daemon.
 The rsyncd.conf file controls authentication, access, logging and available
 modules.
 
-# FILE FORMAT
+## FILE FORMAT
 
 The file consists of modules and parameters. A module begins with the name of
 the module in square brackets and continues until the next module begins.
@@ -40,10 +43,9 @@ The values following the equals sign in parameters are all either a string (no
 quotes needed) or a boolean, which may be given as yes/no, 0/1 or true/false.
 Case is not significant in boolean values, but is preserved in string values.
 
-# LAUNCHING THE RSYNC DAEMON
+## LAUNCHING THE RSYNC DAEMON
 
-The rsync daemon is launched by specifying the `--daemon` option to
-rsync.
+The rsync daemon is launched by specifying the `--daemon` option to rsync.
 
 The daemon must run with root privileges if you wish to use chroot, to bind to
 a port numbered under 1024 (as is the default 873), or to set file ownership.
@@ -69,36 +71,18 @@ reread its config file.
 Note that you should **not** send the rsync daemon a HUP signal to force it to
 reread the `rsyncd.conf` file. The file is re-read on each client connection.
 
-# GLOBAL PARAMETERS
+## GLOBAL PARAMETERS
 
 The first parameters in the file (before a [module] header) are the global
-parameters.  Rsync also allows for the use of a "[global]" module name to
-indicate the start of one or more global-parameter sections (the name must be
-lower case).
-
-You may also include any module parameters in the global part of the config
-file in which case the supplied value will override the default for that
-parameter.
-
-You may use references to environment variables in the values of parameters.
-String parameters will have %VAR% references expanded as late as possible (when
-the string is first used in the program), allowing for the use of variables
-that rsync sets at connection time, such as RSYNC_USER_NAME.  Non-string
-parameters (such as true/false settings) are expanded when read from the config
-file.  If a variable does not exist in the environment, or if a sequence of
-characters is not a valid reference (such as an un-paired percent sign), the
-raw characters are passed through unchanged.  This helps with backward
-compatibility and safety (e.g. expanding a non-existent %VAR% to an empty
-string in a path could result in a very unsafe path).  The safest way to insert
-a literal % into a value is to use %%.
+parameters:
 
 [comment]: # (An OL starting at 0 is converted into a DL by the parser.)
 
 0.  `motd file`
 
-    This parameter allows you to specify a "message of the day" to display to
-    clients on each connect. This usually contains site information and any
-    legal notices. The default is no motd file.  This can be overridden by the
+    This parameter allows you to specify a "message of the day" (MOTD) to display
+    to clients on each connect. This usually contains site information and any
+    legal notices. The default is no MOTD file.  This can be overridden by the
     `--dparam=motdfile=FILE` command-line option when starting the daemon.
 
 0.  `pid file`
@@ -126,7 +110,7 @@ a literal % into a value is to use %%.
 
     This parameter can provide endless fun for people who like to tune their
     systems to the utmost degree. You can set all sorts of socket options which
-    may make transfers faster (or slower!). Read the man page for the
+    may make transfers faster (or slower!). Read the manpage for the
     **setsockopt()** system call for details on some of the options you may be
     able to set. By default no special socket options are set.  These settings
     can also be specified via the `--sockopts` command-line option.
@@ -136,7 +120,23 @@ a literal % into a value is to use %%.
     You can override the default backlog value when the daemon listens for
     connections.  It defaults to 5.
 
-# MODULE PARAMETERS
+You may also include any [MODULE PARAMETERS](#) in the global part of the
+config file, in which case the supplied value will override the default for
+that parameter.
+
+You may use references to environment variables in the values of parameters.
+String parameters will have %VAR% references expanded as late as possible (when
+the string is first used in the program), allowing for the use of variables
+that rsync sets at connection time, such as RSYNC_USER_NAME.  Non-string
+parameters (such as true/false settings) are expanded when read from the config
+file.  If a variable does not exist in the environment, or if a sequence of
+characters is not a valid reference (such as an un-paired percent sign), the
+raw characters are passed through unchanged.  This helps with backward
+compatibility and safety (e.g. expanding a non-existent %VAR% to an empty
+string in a path could result in a very unsafe path).  The safest way to insert
+a literal % into a value is to use %%.
+
+## MODULE PARAMETERS
 
 After the global parameters you should define a number of modules, each module
 exports a directory tree as a symbolic name. Modules are exported by specifying
@@ -144,11 +144,17 @@ a module name in square brackets [module] followed by the parameters for that
 module.  The module name cannot contain a slash or a closing square bracket.
 If the name contains whitespace, each internal sequence of whitespace will be
 changed into a single space, while leading or trailing whitespace will be
-discarded.  Also, the name cannot be "global" as that exact name indicates that
-global parameters follow (see above).
+discarded.
 
-As with GLOBAL PARAMETERS, you may use references to environment variables in
-the values of parameters.  See the GLOBAL PARAMETERS section for more details.
+There is also a special module name of "[global]" that does not define a module
+but instead switches back to the global settings context where default
+parameters can be specified.  Because each defined module gets its full set of
+parameters as a combination of the default values that are set at that position
+in the config file plus its own parameter list, the use of a "[global]" section
+can help to maintain shared config values for multiple modules.
+
+As with [GLOBAL PARAMETERS](#), you may use references to environment variables
+in the values of parameters.  See that section for details.
 
 0.  `comment`
 
@@ -162,6 +168,16 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     available in this module.  You must specify this parameter for each module
     in `rsyncd.conf`.
 
+    If the value contains a "/./" element then the path will be divided at that
+    point into a chroot dir and an inner-chroot subdir.  If [`use chroot`](#)
+    is set to false, though, the extraneous dot dir is just cleaned out of the
+    path.  An example of this idiom is:
+
+    >     path = /var/rsync/./module1
+
+    This will (when chrooting) chroot to "/var/rsync" and set the inside-chroot
+    path to "/module1".
+
     You may base the path's value off of an environment variable by surrounding
     the variable name with percent signs.  You can even reference a variable
     that is set by rsync when the user connects.  For example, this would use
@@ -177,7 +193,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
 
 0.  `use chroot`
 
-    If "use chroot" is true, the rsync daemon will chroot to the "path" before
+    If "use chroot" is true, the rsync daemon will chroot to the "[path](#)" before
     starting the file transfer with the client.  This has the advantage of
     extra protection against possible implementation security holes, but it has
     the disadvantages of requiring super-user privileges, of not being able to
@@ -185,30 +201,48 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     path, and of complicating the preservation of users and groups by name (see
     below).
 
-    As an additional safety feature, you can specify a dot-dir in the module's
-    "path" to indicate the point where the chroot should occur.  This allows
-    rsync to run in a chroot with a non-"/" path for the top of the transfer
-    hierarchy.  Doing this guards against unintended library loading (since
-    those absolute paths will not be inside the transfer hierarchy unless you
-    have used an unwise pathname), and lets you setup libraries for the chroot
-    that are outside of the transfer.  For example, specifying
-    "/var/rsync/./module1" will chroot to the "/var/rsync" directory and set
-    the inside-chroot path to "/module1".  If you had omitted the dot-dir, the
-    chroot would have used the whole path, and the inside-chroot path would
-    have been "/".
-
-    When both "use chroot" and "daemon chroot" are false, OR the inside-chroot
-    path of "use chroot" is not "/", rsync will: (1) munge symlinks by default
-    for security reasons (see "munge symlinks" for a way to turn this off, but
-    only if you trust your users), (2) substitute leading slashes in absolute
-    paths with the module's path (so that options such as `--backup-dir`,
-    `--compare-dest`, etc. interpret an absolute path as rooted in the module's
-    "path" dir), and (3) trim ".." path elements from args if rsync believes
-    they would escape the module hierarchy.  The default for "use chroot" is
-    true, and is the safer choice (especially if the module is not read-only).
-
-    When this parameter is enabled *and* the "name converter" parameter is
-    *not* set, the "numeric ids" parameter will default to being enabled
+    If `use chroot` is not set, it defaults to trying to enable a chroot but
+    allows the daemon to continue (after logging a warning) if it fails. The
+    one exception to this is when a module's [`path`](#) has a "/./" chroot
+    divider in it -- this causes an unset value to be treated as true for that
+    module.
+
+    Prior to rsync 3.2.7, the default value was "true".  The new "unset"
+    default makes it easier to setup an rsync daemon as a non-root user or to
+    run a daemon on a system where chroot fails.  Explicitly setting the value
+    to "true" in rsyncd.conf will always require the chroot to succeed.
+
+    It is also possible to specify a dot-dir in the module's "[path](#)" to
+    indicate that you want to chdir to the earlier part of the path and then
+    serve files from inside the latter part of the path (with sanitizing and
+    default symlink munging).  This can be useful if you need some library dirs
+    inside the chroot (typically for uid & gid lookups) but don't want to put
+    the lib dir into the top of the served path (even though they can be hidden
+    with an [`exclude`](#) directive).  However, a better choice for a modern
+    rsync setup is to use a [`name converter`](#)" and try to avoid inner lib
+    dirs altogether.  See also the [`daemon chroot`](#) parameter, which causes
+    rsync to chroot into its own chroot area before doing any path-related
+    chrooting.
+
+    If the daemon is serving the "/" dir (either directly or due to being
+    chrooted to the module's path), rsync does not do any path sanitizing or
+    (default) munging.
+
+    When it has to limit access to a particular subdir (either due to chroot
+    being disabled or having an inside-chroot path set), rsync will munge
+    symlinks (by default) and sanitize paths.  Those that dislike munged
+    symlinks (and really, really trust their users to not break out of the
+    subdir) can disable the symlink munging via the "[munge symlinks](#)"
+    parameter.
+
+    When rsync is sanitizing paths, it trims ".." path elements from args that
+    it believes would escape the module hierarchy. It also substitutes leading
+    slashes in absolute paths with the module's path (so that options such as
+    `--backup-dir` & `--compare-dest` interpret an absolute path as rooted in
+    the module's "[path](#)" dir).
+
+    When a chroot is in effect *and* the "[name converter](#)" parameter is
+    *not* set, the "[numeric ids](#)" parameter will default to being enabled
     (disabling name lookups).  This means that if you manually setup
     name-lookup libraries in your chroot (instead of using a name converter)
     that you need to explicitly set `numeric ids = false` for rsync to do name
@@ -217,16 +251,16 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     If you copy library resources into the module's chroot area, you should
     protect them through your OS's normal user/group or ACL settings (to
     prevent the rsync module's user from being able to change them), and then
-    hide them from the user's view via "exclude" (see how in the discussion of
+    hide them from the user's view via "[exclude](#)" (see how in the discussion of
     that parameter).  However, it's easier and safer to setup a name converter.
 
 0.  `daemon chroot`
 
     This parameter specifies a path to which the daemon will chroot before
-    beginning communication with clients. Module paths (and any "use chroot"
+    beginning communication with clients. Module paths (and any "[use chroot](#)"
     settings) will then be related to this one. This lets you choose if you
     want the whole daemon to be chrooted (with this setting), just the
-    transfers to be chrooted (with "use chroot"), or both.  Keep in mind that
+    transfers to be chrooted (with "[use chroot](#)"), or both.  Keep in mind that
     the "daemon chroot" area may need various OS/lib/etc files installed to
     allow the daemon to function.  By default the daemon runs without any
     chrooting.
@@ -284,11 +318,11 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     transfer behave as if the client had passed the `--numeric-ids`
     command-line option.  By default, this parameter is enabled for chroot
     modules and disabled for non-chroot modules.  Also keep in mind that
-    uid/gid preservation requires the module to be running as root (see "uid")
-    or for "fake super" to be configured.
+    uid/gid preservation requires the module to be running as root (see "[uid](#)")
+    or for "[fake super](#)" to be configured.
 
     A chroot-enabled module should not have this parameter set to false unless
-    you're using a "name converter" program *or* you've taken steps to ensure
+    you're using a "[name converter](#)" program *or* you've taken steps to ensure
     that the module has the necessary resources it needs to translate names and
     that it is not possible for a user to change those resources.
 
@@ -298,12 +332,12 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     (non-daemon-affecting) `--munge-links` command-line option (using a method
     described below).  This should help protect your files from user trickery
     when your daemon module is writable.  The default is disabled when
-    "use chroot" is on with an inside-chroot path of "/", OR if "daemon chroot"
+    "[use chroot](#)" is on with an inside-chroot path of "/", OR if "[daemon chroot](#)"
     is on, otherwise it is enabled.
 
     If you disable this parameter on a daemon that is not read-only, there are
     tricks that a user can play with uploaded symlinks to access
-    daemon-excluded items (if your module has any), and, if "use chroot" is
+    daemon-excluded items (if your module has any), and, if "[use chroot](#)" is
     off, rsync can even be tricked into showing or changing data that is
     outside the module's path (as access-permissions allow).
 
@@ -324,7 +358,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     the source code named "munge-symlinks" that can be used to add or remove
     this prefix from your symlinks.
 
-    When this parameter is disabled on a writable module and "use chroot" is
+    When this parameter is disabled on a writable module and "[use chroot](#)" is
     off (or the inside-chroot path is not "/"), incoming symlinks will be
     modified to drop a leading slash and to remove ".." path elements that
     rsync believes will allow a symlink to escape the module's hierarchy.
@@ -340,10 +374,10 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     conversion in a chroot module without extra files in the chroot area, and
     also ensures that name-translation is done in a consistent manner.  If the
     "charset" parameter is not set, the `--iconv` option is refused, just as if
-    "iconv" had been specified via "refuse options".
+    "iconv" had been specified via "[refuse options](#)".
 
     If you wish to force users to always use `--iconv` for a particular module,
-    add "no-iconv" to the "refuse options" parameter.  Keep in mind that this
+    add "no-iconv" to the "[refuse options](#)" parameter.  Keep in mind that this
     will restrict access to your module to very new rsync clients.
 
 0.  `max connections`
@@ -352,7 +386,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     connections you will allow.  Any clients connecting when the maximum has
     been reached will receive a message telling them to try later.  The default
     is 0, which means no limit.  A negative value disables the module.  See
-    also the "lock file" parameter.
+    also the "[lock file](#)" parameter.
 
 0.  `log file`
 
@@ -381,7 +415,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     facility name which is defined on your system. Common names are auth,
     authpriv, cron, daemon, ftp, kern, lpr, mail, news, security, syslog, user,
     uucp, local0, local1, local2, local3, local4, local5, local6 and local7.
-    The default is daemon.  This setting has no effect if the "log file"
+    The default is daemon.  This setting has no effect if the "[log file](#)"
     setting is a non-empty string (either set in the per-modules settings, or
     inherited from the global settings).
 
@@ -389,7 +423,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
 
     This parameter allows you to specify the syslog tag to use when logging
     messages from the rsync daemon. The default is "rsyncd".  This setting has
-    no effect if the "log file" setting is a non-empty string (either set in
+    no effect if the "[log file](#)" setting is a non-empty string (either set in
     the per-modules settings, or inherited from the global settings).
 
     For example, if you wanted each authenticated user's name to be included in
@@ -414,7 +448,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
 
 0.  `lock file`
 
-    This parameter specifies the file to use to support the "max connections"
+    This parameter specifies the file to use to support the "[max connections](#)"
     parameter. The rsync daemon uses record locking on this file to ensure that
     the max connections limit is not exceeded for the modules sharing the lock
     file.  The default is `/var/run/rsyncd.lock`.
@@ -426,7 +460,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     "read only" is false then uploads will be possible if file permissions on
     the daemon side allow them. The default is for all modules to be read only.
 
-    Note that "auth users" can override this setting on a per-user basis.
+    Note that "[auth users](#)" can override this setting on a per-user basis.
 
 0.  `write only`
 
@@ -460,8 +494,8 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     This parameter determines whether this module is listed when the client
     asks for a listing of available modules.  In addition, if this is false,
     the daemon will pretend the module does not exist when a client denied by
-    "hosts allow" or "hosts deny" attempts to access it.  Realize that if
-    "reverse lookup" is disabled globally but enabled for the module, the
+    "[hosts allow](#)" or "[hosts deny](#)" attempts to access it.  Realize that if
+    "[reverse lookup](#)" is disabled globally but enabled for the module, the
     resulting reverse lookup to a potentially client-controlled DNS server may
     still reveal to the client that it hit an existing module.  The default is
     for modules to be listable.
@@ -470,10 +504,10 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
 
     This parameter specifies the user name or user ID that file transfers to
     and from that module should take place as when the daemon was run as root.
-    In combination with the "gid" parameter this determines what file
+    In combination with the "[gid](#)" parameter this determines what file
     permissions are available. The default when run by a super-user is to
     switch to the system's "nobody" user.  The default for a non-super-user is
-    to not try to change the user.  See also the "gid" parameter.
+    to not try to change the user.  See also the "[gid](#)" parameter.
 
     The RSYNC_USER_NAME environment variable may be used to request that rsync
     run as the authorizing user.  For example, if you want a rsync to run as
@@ -489,7 +523,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     accessing the module.  The first one will be the default group, and any
     extra ones be set as supplemental groups.  You may also specify a "`*`" as
     the first gid in the list, which will be replaced by all the normal groups
-    for the transfer's user (see "uid").  The default when run by a super-user
+    for the transfer's user (see "[uid](#)").  The default when run by a super-user
     is to switch to your OS's "nobody" (or perhaps "nogroup") group with no
     other supplementary groups.  The default for a non-super-user is to not
     change any group attributes (and indeed, your OS may not allow a
@@ -505,13 +539,13 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
 
     This parameter specifies a uid under which the daemon will run. The daemon
     usually runs as user root, and when this is left unset the user is left
-    unchanged. See also the "uid" parameter.
+    unchanged. See also the "[uid](#)" parameter.
 
 0.  `daemon gid`
 
     This parameter specifies a gid under which the daemon will run. The daemon
     usually runs as group root, and when this is left unset, the group is left
-    unchanged. See also the "gid" parameter.
+    unchanged. See also the "[gid](#)" parameter.
 
 0.  `fake super`
 
@@ -532,8 +566,8 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     or tampering with private administrative files, such as files you may add
     to support uid/gid name translations.
 
-    The daemon filter chain is built from the "filter", "include from",
-    "include", "exclude from", and "exclude" parameters, in that order of
+    The daemon filter chain is built from the "filter", "[include from](#)",
+    "[include](#)", "[exclude from](#)", and "[exclude](#)" parameters, in that order of
     priority.  Anchored patterns are anchored at the root of the module.  To
     prevent access to an entire subtree, for example, "`/secret`", you **must**
     exclude everything in the subtree; the easiest way to do this is with a
@@ -560,8 +594,8 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
 
 0.  `include`
 
-    Use an "include" to override the effects of the "exclude" parameter.  Only
-    one "include" parameter can apply to a given module.  See the "filter"
+    Use an "include" to override the effects of the "[exclude](#)" parameter.  Only
+    one "include" parameter can apply to a given module.  See the "[filter](#)"
     parameter for a description of how excluded files affect the daemon.
 
 0.  `exclude from`
@@ -569,14 +603,14 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     This parameter specifies the name of a file on the daemon that contains
     daemon exclude patterns, one per line.  Only one "exclude from" parameter
     can apply to a given module; if you have multiple exclude-from files, you
-    can specify them as a merge file in the "filter" parameter.  See the
-    "filter" parameter for a description of how excluded files affect the
+    can specify them as a merge file in the "[filter](#)" parameter.  See the
+    "[filter](#)" parameter for a description of how excluded files affect the
     daemon.
 
 0.  `include from`
 
-    Analogue of "exclude from" for a file of daemon include patterns.  Only one
-    "include from" parameter can apply to a given module.  See the "filter"
+    Analogue of "[exclude from](#)" for a file of daemon include patterns.  Only one
+    "include from" parameter can apply to a given module.  See the "[filter](#)"
     parameter for a description of how excluded files affect the daemon.
 
 0.  `incoming chmod`
@@ -611,7 +645,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     to supply a username and password to connect to the module. A challenge
     response authentication protocol is used for this exchange. The plain text
     usernames and passwords are stored in the file specified by the
-    "secrets file" parameter. The default is for all users to be able to
+    "[secrets file](#)" parameter. The default is for all users to be able to
     connect without a password (this is called "anonymous rsync").
 
     In addition to username matching, you can specify groupname matching via a
@@ -623,7 +657,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     Finally, options may be specified after a colon (:).  The options allow you
     to "deny" a user or a group, set the access to "ro" (read-only), or set the
     access to "rw" (read/write).  Setting an auth-rule-specific ro/rw setting
-    overrides the module's "read only" setting.
+    overrides the module's "[read only](#)" setting.
 
     Be sure to put the rules in the order you want them to be matched, because
     the checking stops at the first matching user or group, and that is the
@@ -661,7 +695,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
 
     This parameter specifies the name of a file that contains the
     username:password and/or @groupname:password pairs used for authenticating
-    this module. This file is only consulted if the "auth users" parameter is
+    this module. This file is only consulted if the "[auth users](#)" parameter is
     specified.  The file is line-based and contains one name:password pair per
     line.  Any line has a hash (#) as the very first character on the line is
     considered a comment and is skipped.  The passwords can contain any
@@ -675,14 +709,14 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     "@groupname:password" line for the group that triggered the authentication.
 
     It is up to you what kind of password entries you want to include, either
-    users, groups, or both.  The use of group rules in "auth users" does not
+    users, groups, or both.  The use of group rules in "[auth users](#)" does not
     require that you specify a group password if you do not want to use shared
     passwords.
 
     There is no default for the "secrets file" parameter, you must choose a
     name (such as `/etc/rsyncd.secrets`).  The file must normally not be
-    readable by "other"; see "strict modes".  If the file is not found or is
-    rejected, no logins for a "user auth" module will be possible.
+    readable by "other"; see "[strict modes](#)".  If the file is not found or is
+    rejected, no logins for an "[auth users](#)" module will be possible.
 
 0.  `strict modes`
 
@@ -714,11 +748,11 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
       addresses which match the masked IP address will be allowed in.
     - a hostname pattern using wildcards. If the hostname of the connecting IP
       (as determined by a reverse lookup) matches the wildcarded name (using
-      the same rules as normal unix filename matching), the client is allowed
-      in.  This only works if "reverse lookup" is enabled (the default).
+      the same rules as normal Unix filename matching), the client is allowed
+      in.  This only works if "[reverse lookup](#)" is enabled (the default).
     - a hostname. A plain hostname is matched against the reverse DNS of the
-      connecting IP (if "reverse lookup" is enabled), and/or the IP of the
-      given hostname is matched against the connecting IP (if "forward lookup"
+      connecting IP (if "[reverse lookup](#)" is enabled), and/or the IP of the
+      given hostname is matched against the connecting IP (if "[forward lookup](#)"
       is enabled, as it is by default).  Any match will be allowed in.
     - an '@' followed by a netgroup name, which will match if the reverse DNS
       of the connecting IP is in the specified netgroup.
@@ -730,11 +764,11 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     >     fe80::%link1/64
     >     fe80::%link1/ffff:ffff:ffff:ffff::
 
-    You can also combine "hosts allow" with "hosts deny" as a way to add
+    You can also combine "hosts allow" with "[hosts deny](#)" as a way to add
     exceptions to your deny list.  When both parameters are specified, the
     "hosts allow" parameter is checked first and a match results in the client
     being able to connect.  A non-allowed host is then matched against the
-    "hosts deny" list to see if it should be rejected.  A host that does not
+    "[hosts deny](#)" list to see if it should be rejected.  A host that does not
     match either list is allowed to connect.
 
     The default is no "hosts allow" parameter, which means all hosts can
@@ -745,7 +779,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     This parameter allows you to specify a list of comma- and/or
     whitespace-separated patterns that are matched against a connecting clients
     hostname and IP address. If the pattern matches then the connection is
-    rejected. See the "hosts allow" parameter for more information.
+    rejected. See the "[hosts allow](#)" parameter for more information.
 
     The default is no "hosts deny" parameter, which means all hosts can
     connect.
@@ -753,8 +787,8 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
 0.  `reverse lookup`
 
     Controls whether the daemon performs a reverse lookup on the client's IP
-    address to determine its hostname, which is used for "hosts allow" &
-    "hosts deny" checks and the "%h" log escape.  This is enabled by default,
+    address to determine its hostname, which is used for "[hosts allow](#)" &
+    "[hosts deny](#)" checks and the "%h" log escape.  This is enabled by default,
     but you may wish to disable it to save time if you know the lookup will not
     return a useful result, in which case the daemon will use the name
     "UNDETERMINED" instead.
@@ -794,7 +828,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     logs the transfer at the end, so if a transfer is aborted, no mention will
     be made in the log file.
 
-    If you want to customize the log lines, see the "log format" parameter.
+    If you want to customize the log lines, see the "[log format](#)" parameter.
 
 0.  `log format`
 
@@ -811,7 +845,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     (e.g. "`%''l %'b %f`").
 
     The default log format is "`%o %h [%a] %m (%u) %f %l`", and a "`%t [%p] `"
-    is always prefixed when using the "log file" parameter.  (A perl script
+    is always prefixed when using the "[log file](#)" parameter.  (A perl script
     that will summarize this default log format is included in the rsync source
     code distribution in the "support" subdirectory: rsyncstats.)
 
@@ -892,7 +926,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     >     refuse options = * !a !v !compress*
 
     Don't worry that the "`*`" will refuse certain vital options such as
-    `--dry-run`, `--server`, `--no-iconv`, `--protect-args`, etc. These
+    `--dry-run`, `--server`, `--no-iconv`, `--seclude-args`, etc. These
     important options are not matched by wild-card, so they must be overridden
     by their exact name.  For instance, if you're forcing iconv transfers you
     could use something like this:
@@ -922,17 +956,19 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
 
     >     refuse options = * !a !delete* delete-after
 
-    A note on refusing "compress" -- it is better to set the "dont compress"
-    daemon parameter to "`*`" because that disables compression silently
+    A note on refusing "compress": it may be better to set the "[dont compress](#)"
+    daemon parameter to "`*`" and ensure that `RSYNC_COMPRESS_LIST=zlib` is set
+    in the environment of the daemon in order to disable compression silently
     instead of returning an error that forces the client to remove the `-z`
     option.
 
-    If you are un-refusing the compress option, you probably want to match
-    "`!compress*`" so that you also accept the `--compress-level` option.
+    If you are un-refusing the compress option, you may want to match
+    "`!compress*`" if you also want to allow the `--compress-level` option.
 
-    Note that the "write-devices" option is refused by default, but can be
-    explicitly accepted with "`!write-devices`".  The options "log-file" and
-    "log-file-format" are forcibly refused and cannot be accepted.
+    Note that the "copy-devices" & "write-devices" options are refused by
+    default, but they can be explicitly accepted with "`!copy-devices`" and/or
+    "`!write-devices`".  The options "log-file" and "log-file-format" are
+    forcibly refused and cannot be accepted.
 
     Here are all the options that are not matched by wild-cards:
 
@@ -942,18 +978,22 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
       receiver.  While rsync passes the older alias `--log-format` for
       compatibility reasons, this options should not be confused with
       `--log-file-format`.
-    - `--sender`: Use "write only" parameter instead of refusing this.
+    - `--sender`: Use "[write only](#)" parameter instead of refusing this.
     - `--dry-run`, `-n`: Who would want to disable this?
-    - `--protect-args`, `-s`: This actually makes transfers safer.
+    - `--seclude-args`, `-s`: Is the oldest arg-protection method.
     - `--from0`, `-0`: Makes it easier to accept/refuse `--files-from` without
       affecting this helpful modifier.
-    - `--iconv`: This is auto-disabled based on "charset" parameter.
+    - `--iconv`: This is auto-disabled based on "[charset](#)" parameter.
     - `--no-iconv`: Most transfers use this option.
     - `--checksum-seed`: Is a fairly rare, safe option.
     - `--write-devices`: Is non-wild but also auto-disabled.
 
 0.  `dont compress`
 
+    **NOTE:** This parameter currently has no effect except in one instance: if
+    it is set to "`*`" then it minimizes or disables compression for all files
+    (for those that don't want to refuse the `--compress` option completely).
+
     This parameter allows you to select filenames based on wildcard patterns
     that should not be compressed when pulling files from the daemon (no
     analogous parameter exists to govern the pushing of files to a daemon).
@@ -964,14 +1004,14 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     The "dont compress" parameter takes a space-separated list of
     case-insensitive wildcard patterns. Any source filename matching one of the
     patterns will be compressed as little as possible during the transfer.  If
-    the compression algorithm has an "off" level (such as zlib/zlibx) then no
-    compression occurs for those files.  Other algorithms have the level
-    minimized to reduces the CPU usage as much as possible.
+    the compression algorithm has an "off" level, then no compression occurs
+    for those files.  If an algorithms has the ability to change the level in
+    mid-stream, it will be minimized to reduce the CPU usage as much as
+    possible.
 
     See the `--skip-compress` parameter in the **rsync**(1) manpage for the
-    list of file suffixes that are not compressed by default.  Specifying a
-    value for the "dont compress" parameter changes the default when the daemon
-    is the sender.
+    list of file suffixes that are skipped by default if this parameter is not
+    set.
 
 0.  `early exec`, `pre-xfer exec`, `post-xfer exec`
 
@@ -983,7 +1023,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     _not_ displayed if the script returns success.  The other programs cannot
     send any text to the user.  All output except for the `pre-xfer exec`
     stdout goes to the corresponding daemon's stdout/stderr, which is typically
-    discarded.  See the `--no-detatch` option for a way to see the daemon's
+    discarded.  See the `--no-detach` option for a way to see the daemon's
     output, which can assist with debugging.
 
     Note that the `early exec` command runs before any part of the transfer
@@ -1033,7 +1073,7 @@ the values of parameters.  See the GLOBAL PARAMETERS section for more details.
     **system()** call's default shell), and use RSYNC_NO_XFER_EXEC to disable
     both options completely.
 
-# CONFIG DIRECTIVES
+## CONFIG DIRECTIVES
 
 There are currently two config directives available that allow a config file to
 incorporate the contents of other files:  `&include` and `&merge`.  Both allow
@@ -1088,7 +1128,7 @@ This would merge any `/etc/rsyncd.d/*.inc` files (for global values that should
 stay in effect), and then include any `/etc/rsyncd.d/*.conf` files (defining
 modules without any global-value cross-talk).
 
-# AUTHENTICATION STRENGTH
+## AUTHENTICATION STRENGTH
 
 The authentication protocol used in rsync is a 128 bit MD4 based challenge
 response system. This is fairly weak protection, though (with at least one
@@ -1103,18 +1143,18 @@ authentication is provided. Use ssh as the transport if you want encryption.
 You can also make use of SSL/TLS encryption if you put rsync behind an
 SSL proxy.
 
-# SSL/TLS Daemon Setup
+## SSL/TLS Daemon Setup
 
 When setting up an rsync daemon for access via SSL/TLS, you will need to
-configure a proxy (such as haproxy or nginx) as the front-end that handles the
-encryption.
+configure a TCP proxy (such as haproxy or nginx) as the front-end that handles
+the encryption.
 
 - You should limit the access to the backend-rsyncd port to only allow the
   proxy to connect.  If it is on the same host as the proxy, then configuring
   it to only listen on localhost is a good idea.
-- You should consider turning on the `proxy protocol` parameter if your proxy
-  supports sending that information.  The examples below assume that this is
-  enabled.
+- You should consider turning on the `proxy protocol` rsync-daemon parameter if
+  your proxy supports sending that information.  The examples below assume that
+  this is enabled.
 
 An example haproxy setup is as follows:
 
@@ -1141,14 +1181,17 @@ An example nginx proxy setup is as follows:
 >        ssl_certificate_key /etc/letsencrypt/example.com/privkey.pem;
 >
 >        proxy_pass localhost:873;
->        proxy_protocol on; # Requires "proxy protocol = true"
+>        proxy_protocol on; # Requires rsyncd.conf "proxy protocol = true"
 >        proxy_timeout 1m;
 >        proxy_connect_timeout 5s;
 >    }
 > }
 > ```
 
-# EXAMPLES
+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
 `/home/ftp` would be:
@@ -1197,46 +1240,40 @@ The /etc/rsyncd.secrets file would look something like this:
 >     tridge:mypass
 >     susan:herpass
 
-# FILES
+## FILES
 
 /etc/rsyncd.conf or rsyncd.conf
 
-# SEE ALSO
+## SEE ALSO
 
-**rsync**(1), **rsync-ssl**(1)
+[**rsync**(1)](rsync.1), [**rsync-ssl**(1)](rsync-ssl.1)
 
-# BUGS
+## BUGS
 
 Please report bugs! The rsync bug tracking system is online at
 .
 
-# VERSION
-
-This man page is current for version @VERSION@ of rsync.
-
-# CREDITS
-
-rsync is distributed under the GNU General Public License.  See the file
-COPYING for details.
+## VERSION
 
-The primary ftp site for rsync is 
+This manpage is current for version @VERSION@ of rsync.
 
-A web site is available at .
+## CREDITS
 
-We would be delighted to hear from you if you like this program.
+Rsync is distributed under the GNU General Public License.  See the file
+[COPYING](COPYING) for details.
 
-This program uses the zlib compression library written by Jean-loup Gailly and
-Mark Adler.
+An rsync web site is available at  and its github
+project is .
 
-# THANKS
+## THANKS
 
 Thanks to Warren Stanley for his original idea and patch for the rsync daemon.
 Thanks to Karsten Thygesen for his many suggestions and documentation!
 
-# AUTHOR
+## AUTHOR
 
-rsync was written by Andrew Tridgell and Paul Mackerras.  Many people have
-later contributed to it.
+Rsync was originally written by Andrew Tridgell and Paul Mackerras.  Many
+people from around the world have helped to maintain and improve it.
 
 Mailing lists for support and development are available at
 .
diff --git a/runtests.sh b/runtests.sh
index 38f814d22..0c463bec7 100755
--- a/runtests.sh
+++ b/runtests.sh
@@ -1,7 +1,7 @@
 #! /bin/sh
 
 # Copyright (C) 2001, 2002 by Martin Pool 
-# Copyright (C) 2003-2020 Wayne Davison
+# 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
@@ -155,7 +155,7 @@ if test x"$TOOLDIR" = x; then
     TOOLDIR=`pwd`
 fi
 srcdir=`dirname $0`
-if test x"$srcdir" = x -o x"$srcdir" = x.; then
+if test x"$srcdir" = x || test x"$srcdir" = x.; then
     srcdir="$TOOLDIR"
 fi
 if test x"$rsync_bin" = x; then
@@ -238,7 +238,7 @@ failed=0
 # 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="$TOOLDIR"/testtmp
+scratchbase="${scratchbase:-$TOOLDIR}"/testtmp
 echo "    scratchbase=$scratchbase"
 [ -d "$scratchbase" ] || mkdir "$scratchbase"
 
@@ -288,7 +288,7 @@ for testscript in $suitedir/$whichtests; do
     result=$?
     set -e
 
-    if [ "x$always_log" = xyes -o \( $result != 0 -a $result != 77 -a $result != 78 \) ]
+    if [ "x$always_log" = xyes ] || ( [ $result != 0 ] && [ $result != 77 ] && [ $result != 78 ] )
     then
 	echo "----- $testbase log follows"
 	cat "$scratchdir/test.log"
@@ -336,7 +336,7 @@ 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 -a "$expect_skipped" != IGNORE ]; then
+if [ "$full_run" = yes ] && [ "$expect_skipped" != IGNORE ]; then
     skipped_list=`echo "$skipped_list" | sed 's/^,//'`
     echo "----- skipped results:"
     echo "      expected: $expect_skipped"
@@ -353,7 +353,7 @@ echo '------------------------------------------------------------'
 # because -e is set.
 
 result=`expr $failed + $missing || true`
-if [ "$result" = 0 -a "$skipped_list" != "$expect_skipped" ]; then
+if [ "$result" = 0 ] && [ "$skipped_list" != "$expect_skipped" ]; then
     result=1
 fi
 echo "overall result is $result"
diff --git a/sender.c b/sender.c
index 83603b995..a4d46c39e 100644
--- a/sender.c
+++ b/sender.c
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 1996 Andrew Tridgell
  * Copyright (C) 1996 Paul Mackerras
- * Copyright (C) 2003-2020 Wayne Davison
+ * 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 as published by
@@ -25,11 +25,13 @@
 extern int do_xfers;
 extern int am_server;
 extern int am_daemon;
+extern int local_server;
 extern int inc_recurse;
 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;
@@ -37,6 +39,7 @@ extern int io_error;
 extern int flist_eof;
 extern int whole_file;
 extern int allowed_lull;
+extern int copy_devices;
 extern int preserve_xattrs;
 extern int protocol_version;
 extern int remove_source_files;
@@ -50,6 +53,7 @@ extern int file_old_total;
 extern BOOL want_progress_now;
 extern struct stats stats;
 extern struct file_list *cur_flist, *first_flist, *dir_flist;
+extern char num_dev_ino_buf[4 + 8 + 8];
 
 BOOL extra_flist_sending_enabled;
 
@@ -91,10 +95,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;
@@ -143,6 +148,13 @@ void successful_send(int ndx)
 		goto failed;
 	}
 
+	if (local_server
+	 && (int64)st.st_dev == IVAL64(num_dev_ino_buf, 4)
+	 && (int64)st.st_ino == IVAL64(num_dev_ino_buf, 4 + 8)) {
+		rprintf(FERROR_XFER, "ERROR: Skipping sender remove of destination file: %s\n", fname);
+		return;
+	}
+
 	if (st.st_size != F_LENGTH(file) || st.st_mtime != file->modtime
 #ifdef ST_MTIME_NSEC
 	 || (NSEC_BUMP(file) && (uint32)st.ST_MTIME_NSEC != F_MOD_NSEC(file))
@@ -338,7 +350,7 @@ void send_files(int f_in, int f_out)
 			exit_cleanup(RERR_PROTOCOL);
 		}
 
-		fd = do_open(fname, O_RDONLY, 0);
+		fd = do_open_checklinks(fname);
 		if (fd == -1) {
 			if (errno == ENOENT) {
 				enum logcode c = am_daemon && protocol_version < 28 ? FERROR : FWARNING;
@@ -366,6 +378,15 @@ void send_files(int f_in, int f_out)
 			exit_cleanup(RERR_FILEIO);
 		}
 
+		if (IS_DEVICE(st.st_mode)) {
+			if (!copy_devices) {
+				rprintf(FERROR, "attempt to copy device contents without --copy-devices\n");
+				exit_cleanup(RERR_PROTOCOL);
+			}
+			if (st.st_size == 0)
+				st.st_size = get_device_size(fd, fname);
+		}
+
 		if (append_mode > 0 && st.st_size < F_LENGTH(file)) {
 			rprintf(FWARNING, "skipped diminished file: %s\n",
 				full_fname(fname));
diff --git a/simd-checksum-avx2.S b/simd-checksum-avx2.S
index dc8d145b7..549cc3ef9 100644
--- a/simd-checksum-avx2.S
+++ b/simd-checksum-avx2.S
@@ -1,15 +1,21 @@
+#include "config.h"
+
+#ifdef USE_ROLL_ASM /* { */
+
+#define CHAR_OFFSET 0 /* Keep this the same as rsync.h, which isn't likely to change. */
+
 #ifdef __APPLE__
-#define get_checksum1_avx2  _get_checksum1_avx2
+#define get_checksum1_avx2_asm  _get_checksum1_avx2_asm
 #endif
 
 .intel_syntax noprefix
 .text
 
 	.p2align 5
-	.globl get_checksum1_avx2
+	.globl get_checksum1_avx2_asm
 
 # rdi=*buf, esi=len, edx=i, rcx= *ps1, r8= *ps2
-get_checksum1_avx2:
+get_checksum1_avx2_asm:
 	vmovd	xmm6,[rcx] # load *ps1
 	lea	eax, [rsi-128] # at least 128 bytes to process?
 	cmp	edx, eax
@@ -167,3 +173,5 @@ get_checksum1_avx2:
 	.byte 3
 	.byte 2
 	.byte 1
+
+#endif /* } USE_ROLL_ASM */
diff --git a/simd-checksum-x86_64.cpp b/simd-checksum-x86_64.cpp
index ebeeac2dc..d649091ea 100644
--- a/simd-checksum-x86_64.cpp
+++ b/simd-checksum-x86_64.cpp
@@ -51,12 +51,12 @@
  * GCC 4.x are not supported to ease configure.ac logic.
  */
 
-#ifdef __x86_64__
-#ifdef __cplusplus
+#ifdef __x86_64__ /* { */
+#ifdef __cplusplus /* { */
 
 #include "rsync.h"
 
-#ifdef HAVE_SIMD
+#ifdef USE_ROLL_SIMD /* { */
 
 #include 
 
@@ -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
@@ -85,6 +85,9 @@ typedef long long __m256i_u __attribute__((__vector_size__(32), __may_alias__, _
 #define SSE2_HADDS_EPI16(a, b) _mm_adds_epi16(SSE2_INTERLEAVE_EVEN_EPI16(a, b), SSE2_INTERLEAVE_ODD_EPI16(a, b))
 #define SSE2_MADDUBS_EPI16(a, b) _mm_adds_epi16(SSE2_MULU_EVEN_EPI8(a, b), SSE2_MULU_ODD_EPI8(a, b))
 
+#ifndef USE_ROLL_ASM
+__attribute__ ((target("default"))) MVSTATIC int32 get_checksum1_avx2_64(schar* buf, int32 len, int32 i, uint32* ps1, uint32* ps2) { return i; }
+#endif
 __attribute__ ((target("default"))) MVSTATIC int32 get_checksum1_ssse3_32(schar* buf, int32 len, int32 i, uint32* ps1, uint32* ps2) { return i; }
 __attribute__ ((target("default"))) MVSTATIC int32 get_checksum1_sse2_32(schar* buf, int32 len, int32 i, uint32* ps1, uint32* ps2) { return i; }
 
@@ -245,7 +248,7 @@ __attribute__ ((target("sse2"))) MVSTATIC int32 get_checksum1_sse2_32(schar* buf
 
             // (4*buf[i] + 3*buf[i+1]), (2*buf[i+2], buf[i+3]), ... 2*[int16*8]
             __m128i mul_const = _mm_set1_epi32(4 + (3 << 8) + (2 << 16) + (1 << 24));
-            __m128i mul_add16_1 = SSE2_MADDUBS_EPI16(mul_const, in8_1);
+            __m128i mul_add16_1 = SSE2_MADDUBS_EPI16(mul_const, in8_1);
             __m128i mul_add16_2 = SSE2_MADDUBS_EPI16(mul_const, in8_2);
 
             // s2 += 32*s1
@@ -310,7 +313,127 @@ __attribute__ ((target("sse2"))) MVSTATIC int32 get_checksum1_sse2_32(schar* buf
     return i;
 }
 
-extern "C" __attribute__ ((target("avx2"))) int32 get_checksum1_avx2(schar* buf, int32 len, int32 i, uint32* ps1, uint32* ps2);
+#ifdef USE_ROLL_ASM /* { */
+
+extern "C" __attribute__ ((target("avx2"))) int32 get_checksum1_avx2_asm(schar* buf, int32 len, int32 i, uint32* ps1, uint32* ps2);
+
+#else /* } { */
+
+/*
+  AVX2 loop per 64 bytes:
+    int16 t1[16];
+    int16 t2[16];
+    for (int j = 0; j < 16; j++) {
+      t1[j] = buf[j*4 + i] + buf[j*4 + i+1] + buf[j*4 + i+2] + buf[j*4 + i+3];
+      t2[j] = 4*buf[j*4 + i] + 3*buf[j*4 + i+1] + 2*buf[j*4 + i+2] + buf[j*4 + i+3];
+    }
+    s2 += 64*s1 + (uint32)(
+              60*t1[0] + 56*t1[1] + 52*t1[2] + 48*t1[3] + 44*t1[4] + 40*t1[5] + 36*t1[6] + 32*t1[7] + 28*t1[8] + 24*t1[9] + 20*t1[10] + 16*t1[11] + 12*t1[12] + 8*t1[13] + 4*t1[14] +
+              t2[0] + t2[1] + t2[2] + t2[3] + t2[4] + t2[5] + t2[6] + t2[7] + t2[8] + t2[9] + t2[10] + t2[11] + t2[12] + t2[13] + t2[14] + t2[15]
+          ) + 2080*CHAR_OFFSET;
+    s1 += (uint32)(t1[0] + t1[1] + t1[2] + t1[3] + t1[4] + t1[5] + t1[6] + t1[7] + t1[8] + t1[9] + t1[10] + t1[11] + t1[12] + t1[13] + t1[14] + t1[15]) +
+          64*CHAR_OFFSET;
+ */
+
+__attribute__ ((target("avx2"))) MVSTATIC int32 get_checksum1_avx2_64(schar* buf, int32 len, int32 i, uint32* ps1, uint32* ps2)
+{
+    if (len > 64) {
+
+        uint32 x[4] = {0};
+        __m128i ss1 = _mm_cvtsi32_si128(*ps1);
+        __m128i ss2 = _mm_cvtsi32_si128(*ps2);
+
+        const char mul_t1_buf[16] = {60, 56, 52, 48, 44, 40, 36, 32, 28, 24, 20, 16, 12, 8, 4, 0};
+	__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
+
+        for (; i < (len-64); i+=64) {
+            // Load ... 4*[int8*16]
+            __m256i in8_1, in8_2;
+	    __m128i in8_1_low, in8_2_low, in8_1_high, in8_2_high;
+	    in8_1_low = _mm_loadu_si128((__m128i_u*)&buf[i]);
+	    in8_2_low = _mm_loadu_si128((__m128i_u*)&buf[i+16]);
+	    in8_1_high = _mm_loadu_si128((__m128i_u*)&buf[i+32]);
+	    in8_2_high = _mm_loadu_si128((__m128i_u*)&buf[i+48]);
+	    in8_1 = _mm256_inserti128_si256(_mm256_castsi128_si256(in8_1_low), in8_1_high,1);
+	    in8_2 = _mm256_inserti128_si256(_mm256_castsi128_si256(in8_2_low), in8_2_high,1);
+
+            // (1*buf[i] + 1*buf[i+1]), (1*buf[i+2], 1*buf[i+3]), ... 2*[int16*8]
+            // Fastest, even though multiply by 1
+            __m256i add16_1 = _mm256_maddubs_epi16(mul_one, in8_1);
+            __m256i add16_2 = _mm256_maddubs_epi16(mul_one, in8_2);
+
+            // (4*buf[i] + 3*buf[i+1]), (2*buf[i+2], buf[i+3]), ... 2*[int16*8]
+            __m256i mul_add16_1 = _mm256_maddubs_epi16(mul_const, in8_1);
+            __m256i mul_add16_2 = _mm256_maddubs_epi16(mul_const, in8_2);
+
+            // s2 += 64*s1
+            ss2 = _mm_add_epi32(ss2, _mm_slli_epi32(ss1, 6));
+
+            // [sum(t1[0]..t1[7]), X, X, X] [int32*4]; faster than multiple _mm_hadds_epi16
+            __m256i sum_add32 = _mm256_add_epi16(add16_1, add16_2);
+            sum_add32 = _mm256_add_epi16(sum_add32, _mm256_srli_epi32(sum_add32, 16));
+            sum_add32 = _mm256_add_epi16(sum_add32, _mm256_srli_si256(sum_add32, 4));
+            sum_add32 = _mm256_add_epi16(sum_add32, _mm256_srli_si256(sum_add32, 8));
+
+            // [sum(t2[0]..t2[7]), X, X, X] [int32*4]; faster than multiple _mm_hadds_epi16
+            __m256i sum_mul_add32 = _mm256_add_epi16(mul_add16_1, mul_add16_2);
+            sum_mul_add32 = _mm256_add_epi16(sum_mul_add32, _mm256_srli_epi32(sum_mul_add32, 16));
+            sum_mul_add32 = _mm256_add_epi16(sum_mul_add32, _mm256_srli_si256(sum_mul_add32, 4));
+            sum_mul_add32 = _mm256_add_epi16(sum_mul_add32, _mm256_srli_si256(sum_mul_add32, 8));
+
+            // s1 += t1[0] + t1[1] + t1[2] + t1[3] + t1[4] + t1[5] + t1[6] + t1[7]
+	    __m128i sum_add32_hi = _mm256_extracti128_si256(sum_add32, 0x1);
+            ss1 = _mm_add_epi32(ss1, _mm256_castsi256_si128(sum_add32));
+            ss1 = _mm_add_epi32(ss1, sum_add32_hi);
+
+            // s2 += t2[0] + t2[1] + t2[2] + t2[3] + t2[4] + t2[5] + t2[6] + t2[7]
+	    __m128i sum_mul_add32_hi = _mm256_extracti128_si256(sum_mul_add32, 0x1);
+            ss2 = _mm_add_epi32(ss2, _mm256_castsi256_si128(sum_mul_add32));
+            ss2 = _mm_add_epi32(ss2, sum_mul_add32_hi);
+
+            // [t1[0] + t1[1], t1[2] + t1[3] ...] [int16*8]
+            // We could've combined this with generating sum_add32 above and
+            // save an instruction but benchmarking shows that as being slower
+            __m256i add16 = _mm256_hadds_epi16(add16_1, add16_2);
+
+            // [t1[0], t1[1], ...] -> [t1[0]*28 + t1[1]*24, ...] [int32*4]
+            __m256i mul32 = _mm256_madd_epi16(add16, mul_t1);
+
+            // [sum(mul32), X, X, X] [int32*4]; faster than multiple _mm_hadd_epi32
+            mul32 = _mm256_add_epi32(mul32, _mm256_srli_si256(mul32, 4));
+            mul32 = _mm256_add_epi32(mul32, _mm256_srli_si256(mul32, 8));
+	    // prefetch 2 cacheline ahead
+            _mm_prefetch(&buf[i + 160], _MM_HINT_T0);
+
+            // s2 += 28*t1[0] + 24*t1[1] + 20*t1[2] + 16*t1[3] + 12*t1[4] + 8*t1[5] + 4*t1[6]
+	    __m128i mul32_hi = _mm256_extracti128_si256(mul32, 0x1);
+            ss2 = _mm_add_epi32(ss2, _mm256_castsi256_si128(mul32));
+            ss2 = _mm_add_epi32(ss2, mul32_hi);
+
+#if CHAR_OFFSET != 0
+            // s1 += 32*CHAR_OFFSET
+            __m128i char_offset_multiplier = _mm_set1_epi32(32 * CHAR_OFFSET);
+            ss1 = _mm_add_epi32(ss1, char_offset_multiplier);
+
+            // s2 += 528*CHAR_OFFSET
+            char_offset_multiplier = _mm_set1_epi32(528 * CHAR_OFFSET);
+            ss2 = _mm_add_epi32(ss2, char_offset_multiplier);
+#endif
+        }
+
+        _mm_store_si128((__m128i_u*)x, ss1);
+        *ps1 = x[0];
+        _mm_store_si128((__m128i_u*)x, ss2);
+        *ps2 = x[0];
+    }
+    return i;
+}
+
+#endif /* } !USE_ROLL_ASM */
 
 static int32 get_checksum1_default_1(schar* buf, int32 len, int32 i, uint32* ps1, uint32* ps2)
 {
@@ -338,7 +461,11 @@ static inline uint32 get_checksum1_cpp(char *buf1, int32 len)
     uint32 s2 = 0;
 
     // multiples of 64 bytes using AVX2 (if available)
-    i = get_checksum1_avx2((schar*)buf1, len, i, &s1, &s2);
+#ifdef USE_ROLL_ASM
+    i = get_checksum1_avx2_asm((schar*)buf1, len, i, &s1, &s2);
+#else
+    i = get_checksum1_avx2_64((schar*)buf1, len, i, &s1, &s2);
+#endif
 
     // multiples of 32 bytes using SSSE3 (if available)
     i = get_checksum1_ssse3_32((schar*)buf1, len, i, &s1, &s2);
@@ -407,7 +534,11 @@ int main() {
     benchmark("Raw-C", get_checksum1_default_1, (schar*)buf, BLOCK_LEN);
     benchmark("SSE2", get_checksum1_sse2_32, (schar*)buf, BLOCK_LEN);
     benchmark("SSSE3", get_checksum1_ssse3_32, (schar*)buf, BLOCK_LEN);
-    benchmark("AVX2", get_checksum1_avx2, (schar*)buf, BLOCK_LEN);
+#ifdef USE_ROLL_ASM
+    benchmark("AVX2-ASM", get_checksum1_avx2_asm, (schar*)buf, BLOCK_LEN);
+#else
+    benchmark("AVX2", get_checksum1_avx2_64, (schar*)buf, BLOCK_LEN);
+#endif
 
     free(buf);
     return 0;
@@ -417,6 +548,6 @@ int main() {
 #pragma clang optimize on
 #endif /* BENCHMARK_SIMD_CHECKSUM1 */
 
-#endif /* HAVE_SIMD */
-#endif /* __cplusplus */
-#endif /* __x86_64__ */
+#endif /* } USE_ROLL_SIMD */
+#endif /* } __cplusplus */
+#endif /* } __x86_64__ */
diff --git a/support/atomic-rsync b/support/atomic-rsync
index 0346fb49a..1964090da 100755
--- a/support/atomic-rsync
+++ b/support/atomic-rsync
@@ -1,92 +1,94 @@
-#!/usr/bin/env perl
-#
+#!/usr/bin/env python3
 # This script lets you update a hierarchy of files in an atomic way by
 # first creating a new hierarchy using rsync's --link-dest option, and
 # then swapping the hierarchy into place.  **See the usage message for
 # more details and some important caveats!**
 
-use strict;
-use warnings;
-use Cwd 'abs_path';
-
-my $RSYNC_PROG = '/usr/bin/rsync';
-my $RM_PROG = '/bin/rm';
-
-my $dest_dir = $ARGV[-1];
-&usage if !defined $dest_dir || $dest_dir =~ /(^-|^$)/ || grep(/^--help/, @ARGV);
-$dest_dir =~ s{(?<=.)/+$} {};
-
-if (!-d $dest_dir) {
-    die "$dest_dir is not a directory.\nUse --help for help.\n";
-}
-
-if (@_ = grep(/^--[a-z]+-dest\b/, @ARGV)) {
-    $_ = join(' or ', @_);
-    die "You cannot use the $_ option with atomic-rsync.\nUse --help for help.\n";
-}
-
-my $symlink_content = readlink $dest_dir; # undef when a real dir
-
-my $dest_arg = $dest_dir;
-# This gives us the real destination dir, with all symlinks dereferenced.
-$dest_dir = abs_path($dest_dir);
-if ($dest_dir eq '/') {
-    die qq|You must not use "/" as the destination directory.\nUse --help for help.\n|;
-}
-
-my($old_dir, $new_dir);
-if (defined $symlink_content && $dest_dir =~ /-([12])$/) {
-    my $num = 3 - $1;
-    $old_dir = undef;
-    ($new_dir = $dest_dir) =~ s/-[12]$/-$num/;
-    $symlink_content =~ s/-[12]$/-$num/;
-} else {
-    $old_dir = "$dest_dir~old~";
-    $new_dir = "$dest_dir~new~";
-}
-
-$ARGV[-1] = "$new_dir/";
-
-system($RM_PROG, '-rf', $old_dir) if defined $old_dir && -d $old_dir;
-system($RM_PROG, '-rf', $new_dir) if -d $new_dir;
-
-if (system($RSYNC_PROG, "--link-dest=$dest_dir", @ARGV)) {
-    if ($? == -1) {
-	print "failed to execute $RSYNC_PROG: $!\n";
-    } elsif ($? & 127) {
-	printf "child died with signal %d, %s coredump\n",
-	    ($? & 127),  ($? & 128) ? 'with' : 'without';
-    } else {
-	printf "child exited with value %d\n", $? >> 8;
-    }
-    exit 1;
-}
-
-if (!defined $old_dir) {
-    atomic_symlink($symlink_content, $dest_arg);
-    exit;
-}
-
-rename($dest_dir, $old_dir) or die "Unable to rename $dest_dir to $old_dir: $!";
-rename($new_dir, $dest_dir) or die "Unable to rename $new_dir to $dest_dir: $!";
-
-exit;
-
-sub atomic_symlink
-{
-    my($target, $link) = @_;
-    my $newlink = "$link~new~";
-
-    unlink($newlink); # Just in case
-    symlink($target, $newlink) or die "Unable to symlink $newlink -> $target: $!\n";
-    rename($newlink, $link) or die "Unable to rename $newlink to $link: $!\n";
-}
-
-
-sub usage
-{
-    die <) {
-    chomp;
-    s#^\./##;
-
-    my $entries = "$_/Entries";
-    s/CVS$/.cvsinclude/;
-    my $filter = $_;
-
-    open(ENTRIES, $entries) or die "Unable to open $entries: $!\n";
-    my @includes;
-    while () {
-	push(@includes, $1) if m#/(.+?)/#;
-    }
-    close ENTRIES;
-    if (@includes) {
-	open(FILTER, ">$filter") or die "Unable to write $filter: $!\n";
-	print FILTER map "+ /$_\n", @includes;
-	close FILTER;
-	print "Updated $filter\n";
-    } elsif (-f $filter) {
-	unlink($filter);
-	print "Removed $filter\n";
-    }
-}
-close FIND;
+# CVS gets an added or removed file. Maybe just run it before every copy.
+
+import os, argparse
+
+INC_NAME = '.cvsinclude'
+
+def main():
+    if args.dir:
+        os.chdir(args.dir)
+
+    cvs_includes = set()
+    for root, dirs, files in os.walk('.'):
+        if INC_NAME in files:
+            cvs_includes.add((root + '/' + INC_NAME)[2:])
+        if root.endswith('/CVS') and 'Entries' in files:
+            entries = root[2:] + '/Entries'
+            includes = [ ]
+            with open(entries) as fh:
+                for line in fh:
+                    if line.startswith(('/', 'D/')):
+                        includes.append(line.split('/', 2)[1])
+            if includes:
+                inc = root[2:-3] + INC_NAME
+                cvs_includes.discard(inc)
+                try:
+                    with open(inc) as fh:
+                        old_txt = fh.read()
+                except OSError:
+                    old_txt = ''
+                txt = ''.join(f"+ /{x}\n" for x in includes)
+                if txt == old_txt:
+                    print("Unchanged", inc)
+                else:
+                    print("Updating", inc)
+                    with open(inc, 'w') as fh:
+                        fh.write(txt)
+        dirs.sort()
+
+    for inc in sorted(cvs_includes):
+        print("Removing", inc)
+        os.unlink(inc)
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description=f"Transform CVS/Entries into {INC_NAME} files.", add_help=False)
+    parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
+    parser.add_argument("dir", nargs='?', help="The top CVS dir. Defaults to the current directory.")
+    args = parser.parse_args()
+    main()
+
+# vim: sw=4 et
diff --git a/support/files-to-excludes b/support/files-to-excludes
index a28955cb6..a47d7f8a2 100755
--- a/support/files-to-excludes
+++ b/support/files-to-excludes
@@ -1,27 +1,37 @@
-#!/usr/bin/env perl
-# This script takes an input of filenames and outputs a set of
-# include/exclude directives that can be used by rsync to copy
-# just the indicated files using an --exclude-from=FILE option.
-use strict;
+#!/usr/bin/env python3
+# This script takes an input of filenames and outputs a set of include/exclude
+# directives that can be used by rsync to copy just the indicated files using
+# an --exclude-from=FILE or -f'. FILE' option. To be able to delete files on
+# the receiving side, either use --delete-excluded or change the exclude (-)
+# rules to hide filter rules (H) that only affect the sending side.
 
-my %hash;
+import os, fileinput, argparse
 
-while (<>) {
-    chomp;
-    s#^/+##;
-    my $path = '/';
-    while (m#([^/]+/)/*#g) {
-	$path .= $1;
-	print "+ $path\n" unless $hash{$path}++;
-    }
-    if (m#([^/]+)$#) {
-	print "+ $path$1\n";
-    } else {
-	delete $hash{$path};
-    }
-}
+def main():
+    paths = set()
+    for line in fileinput.input(args.files):
+        dirs = line.strip().lstrip('/').split('/')
+        if not dirs:
+            continue
+        for j in range(1, len(dirs)):
+            if dirs[j] == '':
+                continue
+            path = '/' + '/'.join(dirs[:j]) + '/'
+            if path not in paths:
+                print('+', path)
+                paths.add(path)
+        print('+', '/' + '/'.join(dirs))
 
-foreach (sort keys %hash) {
-    print "- $_*\n";
-}
-print "- /*\n";
+    for path in sorted(paths):
+        print('-', path + '*')
+    print('-', '/*')
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description="Transform a list of files into a set of include/exclude rules.", add_help=False)
+    parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
+    parser.add_argument("files", metavar="FILE", default='-', nargs='*', help="The file(s) that hold the pathnames to translate. Defaults to stdin.")
+    args = parser.parse_args()
+    main()
+
+# vim: sw=4 et
diff --git a/support/git-set-file-times b/support/git-set-file-times
index 24b3fde54..601248b90 100755
--- a/support/git-set-file-times
+++ b/support/git-set-file-times
@@ -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$')
 
@@ -38,7 +38,7 @@ def main():
                 print_line(fn, mtime, mtime)
             ls.discard(fn)
 
-    cmd = git + 'log -r --name-only --no-color --pretty=raw --no-renames -z'.split()
+    cmd = git + 'log -r --name-only --format=%x00commit%x20%H%n%x00commit_time%x20%ct%n --no-renames -z'.split()
     if args.tree:
         cmd.append(args.tree)
     cmd += ['--'] + args.files
@@ -46,7 +46,7 @@ def main():
     proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, encoding='utf-8')
     for line in proc.stdout:
         line = line.strip()
-        m = re.match(r'^committer .*? (\d+) [-+]\d+$', line)
+        m = re.match(r'^\0commit_time (\d+)$', line)
         if m:
             commit_time = int(m[1])
         elif NULL_COMMIT_RE.search(line):
@@ -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)
 
diff --git a/support/idmap b/support/idmap
new file mode 100755
index 000000000..b212aaf6c
--- /dev/null
+++ b/support/idmap
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+# This helper script makes it easy to use a passwd or group file to map values
+# in a LOCAL transfer.  For instance, if you mount a backup that does not have
+# the same passwd setup as the local machine, you can do a copy to/from the
+# backup area as follows and get the differing ID values mapped just like a
+# remote transfer to/from the backed-up machine would do:
+#
+# rsync -av --usermap=`idmap --to /mnt/backup/etc/passwd` \
+#           --groupmap=`idmap --to /mnt/backup/etc/group` \
+#           /some/src/ /mnt/backup/some/dest/
+#
+# rsync -av --usermap=`idmap --from /mnt/backup/etc/passwd` \
+#           --groupmap=`idmap --from /mnt/backup/etc/group` \
+#           /mnt/backup/some/src/ /some/dest/
+
+import re, fileinput, argparse
+
+NAME_ID_RE = re.compile(r'^(\w+):[^:]+:(\d+)')
+
+def main():
+    maps = [ ]
+    for line in fileinput.input(args.files):
+        m = NAME_ID_RE.match(line)
+        if not m:
+            continue
+        if args.to:
+            pair = (m[1], m[2])
+        else:
+            pair = (m[2], m[1])
+        maps.append(':'.join(pair))
+    print(','.join(maps))
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description="Output usermap or groupmap args for rsync.", add_help=False)
+    action = parser.add_argument_group()
+    action = parser.add_mutually_exclusive_group(required=True)
+    action.add_argument("--from", action="store_true", help="Output the map for use on the sending side.")
+    action.add_argument("--to", action="store_true", help="Output the map for use on the receiving side.")
+    parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
+    parser.add_argument("files", metavar="FILE", default='-', nargs='*', help="The file(s) that hold the name & id pairs. Defaults to stdin.")
+    args = parser.parse_args()
+    main()
+
+# vim: sw=4 et
diff --git a/support/install_deps_ubuntu.sh b/support/install_deps_ubuntu.sh
new file mode 100755
index 000000000..ac49055bb
--- /dev/null
+++ b/support/install_deps_ubuntu.sh
@@ -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
diff --git a/support/json-rsync-version b/support/json-rsync-version
new file mode 100755
index 000000000..b01e1e495
--- /dev/null
+++ b/support/json-rsync-version
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+
+import sys, argparse, subprocess, json
+
+TWEAK_NAME = {
+        'asm': 'asm_roll',
+        'ASM': 'asm_roll',
+        'hardlink_special': 'hardlink_specials',
+        'protect_args': 'secluded_args',
+        'protected_args': 'secluded_args',
+        'SIMD': 'SIMD_roll',
+    }
+
+MOVE_OPTIM = set('asm_roll SIMD_roll'.split())
+
+def main():
+    if not args.rsync or args.rsync == '-':
+        ver_out = sys.stdin.read().strip()
+    else:
+        ver_out = subprocess.check_output([args.rsync, '--version', '--version'], encoding='utf-8').strip()
+    if ver_out.startswith('{'):
+        print(ver_out)
+        return
+    info = { }
+    misplaced_optims = { }
+    for line in ver_out.splitlines():
+        if line.startswith('rsync '):
+            prog, vstr, ver, pstr, vstr2, proto = line.split()
+            info['program'] = prog
+            if ver.startswith('v'):
+                ver = ver[1:]
+            info[vstr] = ver
+            if '.' not in proto:
+                proto += '.0'
+            else:
+                proto = proto.replace('.PR', '.')
+            info[pstr] = proto
+        elif line.startswith('Copyright '):
+            info['copyright'] = line[10:]
+        elif line.startswith('Web site: '):
+            info['url'] = line[10:]
+        elif line.startswith('  '):
+            if not saw_comma and ',' in line:
+                saw_comma = True
+                info[sect_name] = { }
+            if saw_comma:
+                for x in line.strip(' ,').split(', '):
+                    if ' ' in x:
+                        val, var = x.split(' ', 1)
+                        if val == 'no':
+                            val = False
+                        elif val.endswith('-bit'):
+                            var = var[:-1] + '_bits'
+                            val = int(val.split('-')[0])
+                    else:
+                        var = x
+                        val = True
+                    var = var.replace(' ', '_').replace('-', '_')
+                    if var in TWEAK_NAME:
+                        var = TWEAK_NAME[var]
+                    if sect_name[0] != 'o' and var in MOVE_OPTIM:
+                        misplaced_optims[var] = val
+                    else:
+                        info[sect_name][var] = val
+            else:
+                info[sect_name] += [ x for x in line.split() if not x.startswith('(') ]
+        elif line == '':
+            break
+        else:
+            sect_name = line.strip(' :').replace(' ', '_').lower()
+            info[sect_name] = [ ]
+            saw_comma = False
+    for chk in 'capabilities optimizations'.split():
+        if chk not in info:
+            info[chk] = { }
+    if misplaced_optims:
+        info['optimizations'].update(misplaced_optims)
+    for chk in 'checksum_list compress_list daemon_auth_list'.split():
+        if chk not in info:
+            info[chk] = [ ]
+    info['license'] = 'GPLv3' if ver[0] == '3' else 'GPLv2'
+    info['caveat'] = 'rsync comes with ABSOLUTELY NO WARRANTY'
+    print(json.dumps(info))
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description="Output rsync's version data in JSON format, even if the rsync doesn't support a native json-output method.", add_help=False)
+    parser.add_argument('rsync', nargs='?', help="Specify an rsync command to run. Otherwise stdin is consumed.")
+    parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
+    args = parser.parse_args()
+    main()
+
+# vim: sw=4 et
diff --git a/support/lsh b/support/lsh
index be29310c7..7b3c0656d 100755
--- a/support/lsh
+++ b/support/lsh
@@ -15,6 +15,8 @@ GetOptions(
     'b|c|D|e|F|i|L|m|O|o|p|R|S|w=s' => sub { }, # Ignore
     'no-cd' => \( my $no_chdir ),
     'sudo' => \( my $use_sudo ),
+    'rrsync=s' => \( my $rrsync_dir ),
+    'rropts=s' => \( my $rrsync_opts ),
 ) or &usage;
 &usage unless @ARGV > 1;
 
@@ -67,22 +69,40 @@ unless ($no_chdir) {
     chdir $home_dir or die "Unable to chdir to $home_dir: $!\n";
 }
 
-push @cmd, '/bin/sh', '-c', "@ARGV";
+if ($rrsync_dir) {
+    $ENV{SSH_ORIGINAL_COMMAND} = join(' ', @ARGV);
+    push @cmd, 'rrsync';
+    if ($rrsync_opts) {
+	foreach my $opt (split(/[ ,]+/, $rrsync_opts)) {
+	    $opt = "-$opt" unless $opt =~ /^-/;
+	    push @cmd, $opt;
+	}
+    }
+    push @cmd, $rrsync_dir;
+} else {
+    push @cmd, '/bin/sh', '-c', "@ARGV";
+}
 exec @cmd;
 die "Failed to exec: $!\n";
 
 sub usage
 {
     die <) {
-    push @_, "$2:$1" if /^(\w+):[^:]+:(\d+)/;
-}
-print join(',', @_), "\n";
diff --git a/support/mapto b/support/mapto
deleted file mode 100755
index 958875237..000000000
--- a/support/mapto
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env perl
-# This helper script makes it easy to use a passwd or group file to map
-# values in a LOCAL transfer.  For instance, if you mount a backup that
-# does not have the same passwd setup as the local machine, you can do
-# a copy TO the backup area as follows and get the differing ID values
-# mapped just like a remote transfer TO the backed-up machine would do:
-#
-# rsync -av --usermap=`mapto /mnt/backup/etc/passwd` \
-#           --groupmap=`mapto /mnt/backup/etc/group` \
-#           /some/src/ /mnt/backup/some/dest/
-
-while (<>) {
-    push @_, "$1:$2" if /^(\w+):[^:]+:(\d+)/;
-}
-print join(',', @_), "\n";
diff --git a/support/mnt-excl b/support/mnt-excl
index ed7b49ba5..bc8b5bcd8 100755
--- a/support/mnt-excl
+++ b/support/mnt-excl
@@ -1,4 +1,4 @@
-#!/usr/bin/env perl
+#!/usr/bin/env python3
 # This script takes a command-line arg of a source directory
 # that will be passed to rsync, and generates a set of excludes
 # that will exclude all mount points from the list.  This is
@@ -27,23 +27,33 @@
 # awk '{print $2}' /proc/mounts | grep -v '^/$' | \
 #   rsync -avf 'merge,/- -' /dir host:/dest/
 
-use strict;
-use warnings;
-use Cwd 'abs_path';
+import os, argparse
 
-my $file = '/proc/mounts';
-my $dir = shift || '/';
-my $trailing_slash = $dir =~ m{./$} ? '/' : '';
-$dir = abs_path($dir) . $trailing_slash;
-$dir =~ s{([^/]*)$}{};
-my $trailing = $1;
-$trailing = '' if $trailing eq '.' || !-d "$dir$trailing";
-$trailing .= '/' if $trailing ne '';
+MNT_FILE = '/proc/mounts';
 
-open(IN, $file) or die "Unable to open $file: $!\n";
-while () {
-    $_ = (split)[1];
-    next unless s{^\Q$dir$trailing\E}{}o && $_ ne '';
-    print "- /$trailing$_\n";
-}
-close IN;
+def main():
+    trailing_slash = '/' if args.path.endswith(('/', '/.')) and args.path != '/' else ''
+    args.path = os.path.realpath(args.path) + trailing_slash
+    parent_dir = os.path.dirname(args.path)
+    trailing = os.path.basename(args.path)
+    if not os.path.isdir(args.path):
+        trailing = ''
+    elif trailing != '':
+        trailing += '/'
+    want_path = os.path.join(parent_dir, trailing)
+    wp_len = len(want_path)
+
+    with open(MNT_FILE) as fh:
+        for line in fh:
+            mnt_path = line.split()[1]
+            if mnt_path.startswith(want_path) and mnt_path != want_path:
+                print(f"- /{trailing}{mnt_path[wp_len:]}")
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description="Output mount points as rsync excludes.", add_help=False)
+    parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
+    parser.add_argument('path', metavar='PATH', nargs='?', default='/', help="Limit output to those within the PATH hierarchy.")
+    args = parser.parse_args()
+    main()
+
+# vim: sw=4 et
diff --git a/support/munge-symlinks b/support/munge-symlinks
index 3e5f3ad2a..e7a547416 100755
--- a/support/munge-symlinks
+++ b/support/munge-symlinks
@@ -1,60 +1,71 @@
-#!/usr/bin/env perl
+#!/usr/bin/env python3
 # This script will either prefix all symlink values with the string
 # "/rsyncd-munged/" or remove that prefix.
 
-use strict;
-use Getopt::Long;
-
-my $SYMLINK_PREFIX = '/rsyncd-munged/';
-
-my $munge_opt;
-
-&GetOptions(
-    'munge' => sub { $munge_opt = 1 },
-    'unmunge' => sub { $munge_opt = 0 },
-    'all' => \( my $all_opt ),
-    'help|h' => \( my $help_opt ),
-) or &usage;
-
-&usage if $help_opt || !defined $munge_opt;
-
-my $munged_re = $all_opt ? qr/^($SYMLINK_PREFIX)+(?=.)/ : qr/^$SYMLINK_PREFIX(?=.)/;
-
-push(@ARGV, '.') unless @ARGV;
-
-open(PIPE, '-|', 'find', @ARGV, '-type', 'l') or die $!;
-
-while () {
-    chomp;
-    my $lnk = readlink($_) or next;
-    if ($munge_opt) {
-	next if !$all_opt && $lnk =~ /$munged_re/;
-	$lnk =~ s/^/$SYMLINK_PREFIX/;
-    } else {
-	next unless $lnk =~ s/$munged_re//;
-    }
-    if (!unlink($_)) {
-	warn "Unable to unlink symlink: $_ ($!)\n";
-    } elsif (!symlink($lnk, $_)) {
-	warn "Unable to recreate symlink: $_ -> $lnk ($!)\n";
-    } else {
-	print "$_ -> $lnk\n";
-    }
-}
-
-close PIPE;
-exit;
-
-sub usage
-{
-    die <', lnk + ':', str(e), file=sys.stderr)
+        return
+    print(fn, '->', lnk)
+
+
+if __name__ == '__main__':
+    our_desc = """\
+Adds or removes the %s prefix to/from the start of each symlink's value.
+When given the name of a directory, affects all the symlinks in that directory hierarchy.
+""" % SYMLINK_PREFIX
+    epilog = 'See the "munge symlinks" option in the rsyncd.conf manpage for more details.'
+    parser = argparse.ArgumentParser(description=our_desc, epilog=epilog, add_help=False)
+    uniq_group = parser.add_mutually_exclusive_group()
+    uniq_group.add_argument('--munge', action='store_true', help="Add the prefix to symlinks (the default).")
+    uniq_group.add_argument('--unmunge', action='store_true', help="Remove the prefix from symlinks.")
+    parser.add_argument('--all', action='store_true', help="Always adds the prefix when munging (even if already munged) or removes multiple instances of the prefix when unmunging.")
+    parser.add_argument('--help', '-h', action='help', help="Output this help message and exit.")
+    parser.add_argument('names', metavar='NAME', nargs='+', help="One or more directories and/or symlinks to process.")
+    args = parser.parse_args()
+    main()
+
+# vim: sw=4 et
diff --git a/support/rrsync b/support/rrsync
old mode 100644
new mode 100755
index 438e3a24a..e8b0cc0d2
--- a/support/rrsync
+++ b/support/rrsync
@@ -1,257 +1,387 @@
-#!/usr/bin/env perl
-# Name: /usr/local/bin/rrsync (should also have a symlink in /usr/bin)
-# Purpose: Restricts rsync to subdirectory declared in .ssh/authorized_keys
-# Author: Joe Smith  30-Sep-2004
-# Modified by: Wayne Davison 
-use strict;
-
-use Socket;
-use Cwd 'abs_path';
-use File::Glob ':glob';
-
-# You may configure these values to your liking.  See also the section
-# of options if you want to disable any options that rsync accepts.
-use constant RSYNC => '/usr/bin/rsync';
-use constant LOGFILE => 'rrsync.log';
-
-my $Usage = < 30-Sep-2004
+# Python version by: Wayne Davison 
+
+# You may configure these 2 values to your liking.  See also the section of
+# short & long options if you want to disable any options that rsync accepts.
+RSYNC = '/usr/bin/rsync'
+LOGFILE = 'rrsync.log' # NOTE: the file must exist for a line to be appended!
+
+# The following options are mainly the options that a client rsync can send
+# to the server, and usually just in the one option format that the stock
+# rsync produces. However, there are some additional convenience options
+# added as well, and thus a few options are present in both the short and
+# long lists (such as --group, --owner, and --perms).
 
-our $subdir = shift;
-die "$0: No subdirectory specified\n$Usage" unless defined $subdir;
-$subdir = abs_path($subdir);
-die "$0: Restricted directory does not exist!\n" if $subdir ne '/' && !-d $subdir;
-
-# The client uses "rsync -av -e ssh src/ server:dir/", and sshd on the server
-# executes this program when .ssh/authorized_keys has 'command="..."'.
-# For example:
-# command="rrsync logs/client" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAzGhEeNlPr...
-# command="rrsync -ro results" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAmkHG1WCjC...
-#
-# Format of the environment variables set by sshd:
-# SSH_ORIGINAL_COMMAND=rsync --server          -vlogDtpr --partial . ARG # push
-# SSH_ORIGINAL_COMMAND=rsync --server --sender -vlogDtpr --partial . ARGS # pull
-# SSH_CONNECTION=client_addr client_port server_port
-
-my $command = $ENV{SSH_ORIGINAL_COMMAND};
-die "$0: Not invoked via sshd\n$Usage"	unless defined $command;
-die "$0: SSH_ORIGINAL_COMMAND='$command' is not rsync\n" unless $command =~ s/^rsync\s+//;
-die "$0: --server option is not first\n" unless $command =~ /^--server\s/;
-our $am_sender = $command =~ /^--server\s+--sender\s/; # Restrictive on purpose!
-die "$0 sending to read-only server not allowed\n" if $only eq 'r' && !$am_sender;
-die "$0 reading from write-only server not allowed\n" if $only eq 'w' && $am_sender;
-
-### START of options data produced by the cull_options script. ###
-
-# These options are the only options that rsync might send to the server,
-# and only in the option format that the stock rsync produces.
+# NOTE when disabling: check for both a short & long version of the option!
+
+### START of options data produced by the cull-options script. ###
 
 # To disable a short-named option, add its letter to this string:
-our $short_disabled = 's';
+short_disabled = 's'
+
+# These are also disabled when the restricted dir is not "/":
+short_disabled_subdir = 'KLk'
 
-our $short_no_arg = 'ACDEHIJKLNORSUWXbcdgklmnopqrstuvxyz'; # DO NOT REMOVE ANY
-our $short_with_num = '@B'; # DO NOT REMOVE ANY
+# These are all possible short options that we will accept (when not disabled above):
+short_no_arg = 'ACDEHIJKLNORSUWXbcdgklmnopqrstuvxyz' # DO NOT REMOVE ANY
+short_with_num = '@B' # DO NOT REMOVE ANY
 
 # To disable a long-named option, change its value to a -1.  The values mean:
 # 0 = the option has no arg; 1 = the arg doesn't need any checking; 2 = only
 # check the arg when receiving; and 3 = always check the arg.
-our %long_opt = (
-  'append' => 0,
-  'backup-dir' => 2,
-  'block-size' => 1,
-  'bwlimit' => 1,
-  'checksum-choice' => 1,
-  'checksum-seed' => 1,
-  'compare-dest' => 2,
-  'compress-choice' => 1,
-  'compress-level' => 1,
-  'copy-dest' => 2,
-  'copy-unsafe-links' => 0,
-  'daemon' => -1,
-  'debug' => 1,
-  'delay-updates' => 0,
-  'delete' => 0,
-  'delete-after' => 0,
-  'delete-before' => 0,
-  'delete-delay' => 0,
-  'delete-during' => 0,
-  'delete-excluded' => 0,
-  'delete-missing-args' => 0,
-  'existing' => 0,
-  'fake-super' => 0,
-  'files-from' => 3,
-  'force' => 0,
-  'from0' => 0,
-  'fsync' => 2,
-  'fuzzy' => 0,
-  'group' => 0,
-  'groupmap' => 1,
-  'hard-links' => 0,
-  'iconv' => 1,
-  'ignore-errors' => 0,
-  'ignore-existing' => 0,
-  'ignore-missing-args' => 0,
-  'ignore-times' => 0,
-  'info' => 1,
-  'inplace' => 0,
-  'link-dest' => 2,
-  'links' => 0,
-  'list-only' => 0,
-  'log-file' => $only eq 'r' ? -1 : 3,
-  'log-format' => 1,
-  'max-alloc' => 1,
-  'max-delete' => 1,
-  'max-size' => 1,
-  'min-size' => 1,
-  'mkpath' => 0,
-  'modify-window' => 1,
-  'msgs2stderr' => 0,
-  'new-compress' => 0,
-  'no-W' => 0,
-  'no-implied-dirs' => 0,
-  'no-msgs2stderr' => 0,
-  'no-r' => 0,
-  'no-relative' => 0,
-  'no-specials' => 0,
-  'numeric-ids' => 0,
-  'old-compress' => 0,
-  'one-file-system' => 0,
-  'only-write-batch' => 1,
-  'open-noatime' => 0,
-  'owner' => 0,
-  'partial' => 0,
-  'partial-dir' => 2,
-  'perms' => 0,
-  'preallocate' => 0,
-  'recursive' => 0,
-  'remove-sent-files' => $only eq 'r' ? -1 : 0,
-  'remove-source-files' => $only eq 'r' ? -1 : 0,
-  'safe-links' => 0,
-  'sender' => $only eq 'w' ? -1 : 0,
-  'server' => 0,
-  'size-only' => 0,
-  'skip-compress' => 1,
-  'specials' => 0,
-  'stats' => 0,
-  'suffix' => 1,
-  'super' => 0,
-  'temp-dir' => 2,
-  'timeout' => 1,
-  'times' => 0,
-  'use-qsort' => 0,
-  'usermap' => 1,
-  'write-devices' => -1,
-);
-
-### END of options data produced by the cull_options script. ###
-
-if ($short_disabled ne '') {
-    $short_no_arg =~ s/[$short_disabled]//go;
-    $short_with_num =~ s/[$short_disabled]//go;
-}
-$short_no_arg = "[$short_no_arg]" if length($short_no_arg) > 1;
-$short_with_num = "[$short_with_num]" if length($short_with_num) > 1;
-
-my $write_log = -f LOGFILE && open(LOG, '>>', LOGFILE);
-
-chdir($subdir) or die "$0: Unable to chdir to restricted dir: $!\n";
-
-my(@opts, @args);
-my $in_options = 1;
-my $last_opt = '';
-my $check_type;
-while ($command =~ /((?:[^\s\\]+|\\.[^\s\\]*)+)/g) {
-  $_ = $1;
-  if ($check_type) {
-    push(@opts, check_arg($last_opt, $_, $check_type));
-    $check_type = 0;
-  } elsif ($in_options) {
-    push(@opts, $_);
-    if ($_ eq '.') {
-      $in_options = 0;
-    } else {
-      die "$0: invalid option: '-'\n" if $_ eq '-';
-      next if /^-$short_no_arg*(e\d*\.\w*)?$/o || /^-$short_with_num\d+$/o;
-
-      my($opt,$arg) = /^--([^=]+)(?:=(.*))?$/;
-      my $disabled;
-      if (defined $opt) {
-	my $ct = $long_opt{$opt};
-	last unless defined $ct;
-	next if $ct == 0;
-	if ($ct > 0) {
-	  if (!defined $arg) {
-	    $check_type = $ct;
-	    $last_opt = $opt;
-	    next;
-	  }
-	  $arg = check_arg($opt, $arg, $ct);
-	  $opts[-1] =~ s/=.*/=$arg/;
-	  next;
-	}
-	$disabled = 1;
-	$opt = "--$opt";
-      } elsif ($short_disabled ne '') {
-	$disabled = /^-$short_no_arg*([$short_disabled])/o;
-	$opt = "-$1";
-      }
-
-      last unless $disabled; # Generate generic failure
-      die "$0: option $opt has been disabled on this server.\n";
-    }
-  } else {
-    if ($subdir ne '/') {
-      # Validate args to ensure they don't try to leave our restricted dir.
-      s{//+}{/}g;
-      s{^/}{};
-      s{^$}{.};
-    }
-    push(@args, bsd_glob($_, GLOB_LIMIT|GLOB_NOCHECK|GLOB_BRACE|GLOB_QUOTE));
-  }
+long_opts = {
+  'append': 0,
+  'backup-dir': 2,
+  'block-size': 1,
+  'bwlimit': 1,
+  'checksum-choice': 1,
+  'checksum-seed': 1,
+  'compare-dest': 2,
+  'compress-choice': 1,
+  'compress-level': 1,
+  'copy-dest': 2,
+  'copy-devices': -1,
+  'copy-unsafe-links': 0,
+  'daemon': -1,
+  'debug': 1,
+  'delay-updates': 0,
+  'delete': 0,
+  'delete-after': 0,
+  'delete-before': 0,
+  'delete-delay': 0,
+  'delete-during': 0,
+  'delete-excluded': 0,
+  'delete-missing-args': 0,
+  'existing': 0,
+  'fake-super': 0,
+  'files-from': 3,
+  'force': 0,
+  'from0': 0,
+  'fsync': 0,
+  'fuzzy': 0,
+  'group': 0,
+  'groupmap': 1,
+  'hard-links': 0,
+  'iconv': 1,
+  'ignore-errors': 0,
+  'ignore-existing': 0,
+  'ignore-missing-args': 0,
+  'ignore-times': 0,
+  'info': 1,
+  'inplace': 0,
+  'link-dest': 2,
+  'links': 0,
+  'list-only': 0,
+  'log-file': 3,
+  'log-format': 1,
+  'max-alloc': 1,
+  'max-delete': 1,
+  'max-size': 1,
+  'min-size': 1,
+  'mkpath': 0,
+  'modify-window': 1,
+  'msgs2stderr': 0,
+  'munge-links': 0,
+  'new-compress': 0,
+  'no-W': 0,
+  'no-implied-dirs': 0,
+  'no-msgs2stderr': 0,
+  'no-munge-links': -1,
+  'no-r': 0,
+  'no-relative': 0,
+  'no-specials': 0,
+  'numeric-ids': 0,
+  'old-compress': 0,
+  'one-file-system': 0,
+  'only-write-batch': 1,
+  'open-noatime': 0,
+  'owner': 0,
+  'partial': 0,
+  'partial-dir': 2,
+  'perms': 0,
+  'preallocate': 0,
+  'recursive': 0,
+  'remove-sent-files': 0,
+  'remove-source-files': 0,
+  'safe-links': 0,
+  'sender': 0,
+  'server': 0,
+  'size-only': 0,
+  'skip-compress': 1,
+  'specials': 0,
+  'stats': 0,
+  'stderr': 1,
+  'suffix': 1,
+  'super': 0,
+  'temp-dir': 2,
+  'timeout': 1,
+  'times': 0,
+  'use-qsort': 0,
+  'usermap': 1,
+  'write-devices': -1,
 }
-die "$0: invalid rsync-command syntax or options\n" if $in_options;
 
-if ($subdir ne '/') {
-    die "$0: do not use .. in any path!\n" if grep m{(^|/)\.\.(/|$)}, @args;
-}
+### END of options data produced by the cull-options script. ###
 
-@args = ( '.' ) if !@args;
+import os, sys, re, argparse, glob, socket, time, subprocess
+from argparse import RawTextHelpFormatter
 
-if ($write_log) {
-  my ($mm,$hh) = (localtime)[1,2];
-  my $host = $ENV{SSH_CONNECTION} || 'unknown';
-  $host =~ s/ .*//; # Keep only the client's IP addr
-  $host =~ s/^::ffff://;
-  $host = gethostbyaddr(inet_aton($host),AF_INET) || $host;
-  printf LOG "%02d:%02d %-13s [%s]\n", $hh, $mm, $host, "@opts @args";
-  close LOG;
-}
+try:
+    from braceexpand import braceexpand
+except:
+    braceexpand = lambda x: [ DE_BACKSLASH_RE.sub(r'\1', x) ]
 
-# Note: This assumes that the rsync protocol will not be maliciously hijacked.
-exec(RSYNC, @opts, '--', @args) or die "exec(rsync @opts -- @args) failed: $? $!";
-
-sub check_arg
-{
-  my($opt, $arg, $type) = @_;
-  $arg =~ s/\\(.)/$1/g;
-  if ($subdir ne '/' && ($type == 3 || ($type == 2 && !$am_sender))) {
-    $arg =~ s{//}{/}g;
-    die "Do not use .. in --$opt; anchor the path at the root of your restricted dir.\n"
-      if $arg =~ m{(^|/)\.\.(/|$)};
-    $arg =~ s{^/}{$subdir/};
-  }
-  $arg;
-}
+HAS_DOT_DOT_RE = re.compile(r'(^|/)\.\.(/|$)')
+LONG_OPT_RE = re.compile(r'^--([^=]+)(?:=(.*))?$')
+DE_BACKSLASH_RE = re.compile(r'\\(.)')
+
+def main():
+    if not os.path.isdir(args.dir):
+        die("Restricted directory does not exist!")
+
+    # The format of the environment variables set by sshd:
+    #   SSH_ORIGINAL_COMMAND:
+    #     rsync --server          -vlogDtpre.iLsfxCIvu --etc . ARG  # push
+    #     rsync --server --sender -vlogDtpre.iLsfxCIvu --etc . ARGS # pull
+    #   SSH_CONNECTION (client_ip client_port server_ip server_port):
+    #     192.168.1.100 64106 192.168.1.2 22
+
+    command = os.environ.get('SSH_ORIGINAL_COMMAND', None)
+    if not command:
+        die("Not invoked via sshd")
+    if command == 'true':
+        # Allow checking connectivity with "ssh  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")
+    if command[1:2] != ['--server']:
+        die("--server option is not the first arg")
+    command = '' if len(command) < 3 else command[2]
+
+    global am_sender
+    am_sender = command.startswith("--sender ") # Restrictive on purpose!
+    if args.ro and not am_sender:
+        die("sending to read-only server is not allowed")
+    if args.wo and am_sender:
+        die("reading from write-only server is not allowed")
+
+    if args.wo or not am_sender:
+        long_opts['sender'] = -1
+    if args.no_del:
+        for opt in long_opts:
+            if opt.startswith(('remove', 'delete')):
+                long_opts[opt] = -1
+    if args.ro:
+        long_opts['log-file'] = -1
+
+    if args.dir != '/':
+        global short_disabled
+        short_disabled += short_disabled_subdir
+
+    short_no_arg_re = short_no_arg
+    short_with_num_re = short_with_num
+    if short_disabled:
+        for ltr in short_disabled:
+            short_no_arg_re = short_no_arg_re.replace(ltr, '')
+            short_with_num_re = short_with_num_re.replace(ltr, '')
+        short_disabled_re = re.compile(r'^-[%s]*([%s])' % (short_no_arg_re, short_disabled))
+    short_no_arg_re = re.compile(r'^-(?=.)[%s]*(e\d*\.\w*)?$' % short_no_arg_re)
+    short_with_num_re = re.compile(r'^-[%s]\d+$' % short_with_num_re)
+
+    log_fh = open(LOGFILE, 'a') if os.path.isfile(LOGFILE) else None
+
+    try:
+        os.chdir(args.dir)
+    except OSError as e:
+        die('unable to chdir to restricted dir:', str(e))
+
+    rsync_opts = [ '--server' ]
+    rsync_args = [ ]
+    saw_the_dot_arg = False
+    last_opt = check_type = None
+
+    for arg in re.findall(r'(?:[^\s\\]+|\\.[^\s\\]*)+', command):
+        if check_type:
+            rsync_opts.append(validated_arg(last_opt, arg, check_type))
+            check_type = None
+        elif saw_the_dot_arg:
+            # NOTE: an arg that starts with a '-' is safe due to our use of "--" in the cmd tuple.
+            try:
+                b_e = braceexpand(arg) # Also removes backslashes
+            except: # Handle errors such as unbalanced braces by just de-backslashing the arg:
+                b_e = [ DE_BACKSLASH_RE.sub(r'\1', arg) ]
+            for xarg in b_e:
+                rsync_args += validated_arg('arg', xarg, wild=True)
+        else: # parsing the option args
+            if arg == '.':
+                saw_the_dot_arg = True
+                continue
+            rsync_opts.append(arg)
+            if short_no_arg_re.match(arg) or short_with_num_re.match(arg):
+                continue
+            disabled = False
+            m = LONG_OPT_RE.match(arg)
+            if m:
+                opt = m.group(1)
+                opt_arg = m.group(2)
+                ct = long_opts.get(opt, None)
+                if ct is None:
+                    break # Generate generic failure due to unfinished arg parsing
+                if ct == 0:
+                    continue
+                opt = '--' + opt
+                if ct > 0:
+                    if opt_arg is not None:
+                        rsync_opts[-1] = opt + '=' + validated_arg(opt, opt_arg, ct)
+                    else:
+                        check_type = ct
+                        last_opt = opt
+                    continue
+                disabled = True
+            elif short_disabled:
+                m = short_disabled_re.match(arg)
+                if m:
+                    disabled = True
+                    opt = '-' + m.group(1)
+
+            if disabled:
+                die("option", opt, "has been disabled on this server.")
+            break # Generate a generic failure
+
+    if not saw_the_dot_arg:
+        die("invalid rsync-command syntax or options")
+
+    if args.munge:
+        rsync_opts.append('--munge-links')
+    
+    if args.no_overwrite:
+      rsync_opts.append('--ignore-existing')
+
+    if not rsync_args:
+        rsync_args = [ '.' ]
+
+    cmd = (RSYNC, *rsync_opts, '--', '.', *rsync_args)
+
+    if log_fh:
+        now = time.localtime()
+        host = os.environ.get('SSH_CONNECTION', 'unknown').split()[0] # Drop everything after the IP addr
+        if host.startswith('::ffff:'):
+            host = host[7:]
+        try:
+            host = socket.gethostbyaddr(socket.inet_aton(host))
+        except:
+            pass
+        log_fh.write("%02d:%02d:%02d %-16s %s\n" % (now.tm_hour, now.tm_min, now.tm_sec, host, str(cmd)))
+        log_fh.close()
+
+    # NOTE: This assumes that the rsync protocol will not be maliciously hijacked.
+    if args.no_lock:
+        os.execlp(RSYNC, *cmd)
+        die("execlp(", RSYNC, *cmd, ')  failed')
+    child = subprocess.run(cmd)
+    if child.returncode != 0:
+        sys.exit(child.returncode)
+
+
+def validated_arg(opt, arg, typ=3, wild=False):
+    if opt != 'arg': # arg values already have their backslashes removed.
+        arg = DE_BACKSLASH_RE.sub(r'\1', arg)
+
+    orig_arg = arg
+    if arg.startswith('./'):
+        arg = arg[1:]
+    arg = arg.replace('//', '/')
+    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)")
+        if arg.startswith('/'):
+            arg = args.dir + arg
+
+    if wild:
+        got = glob.glob(arg)
+        if not got:
+            got = [ arg ]
+    else:
+        got = [ arg ]
+
+    ret = [ ]
+    for arg in got:
+        if args.dir != '/' and arg != '.' and (typ == 3 or (typ == 2 and not am_sender)):
+            arg_has_trailing_slash = arg.endswith('/')
+            if arg_has_trailing_slash:
+                arg = arg[:-1]
+            else:
+                arg_has_trailing_slash_dot = arg.endswith('/.')
+                if arg_has_trailing_slash_dot:
+                    arg = arg[:-2]
+            real_arg = os.path.realpath(arg)
+            if arg != real_arg and not real_arg.startswith(args.dir_slash):
+                die('unsafe arg:', orig_arg, [arg, real_arg])
+            if arg_has_trailing_slash:
+                arg += '/'
+            elif arg_has_trailing_slash_dot:
+                arg += '/.'
+            if opt == 'arg' and arg.startswith(args.dir_slash):
+                arg = arg[args.dir_slash_len:]
+                if arg == '':
+                    arg = '.'
+        ret.append(arg)
+
+    return ret if wild else ret[0]
+
+
+def lock_or_die(dirname):
+    import fcntl
+    global lock_handle
+    lock_handle = os.open(dirname, os.O_RDONLY)
+    try:
+        fcntl.flock(lock_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
+    except:
+        die('Another instance of rrsync is already accessing this directory.')
+
+
+def die(*msg):
+    print(sys.argv[0], 'error:', *msg, file=sys.stderr)
+    if sys.stdin.isatty():
+        arg_parser.print_help(sys.stderr)
+    sys.exit(1)
+
+
+# This class displays the --help to the user on argparse error IFF they're running it interactively.
+class OurArgParser(argparse.ArgumentParser):
+    def error(self, msg):
+        die(msg)
+
+
+if __name__ == '__main__':
+    our_desc = """Use "man rrsync" to learn how to restrict ssh users to using a restricted rsync command."""
+    arg_parser = OurArgParser(description=our_desc, add_help=False)
+    only_group = arg_parser.add_mutually_exclusive_group()
+    only_group.add_argument('-ro', action='store_true', help="Allow only reading from the DIR. Implies -no-del and -no-lock.")
+    only_group.add_argument('-wo', action='store_true', help="Allow only writing to the DIR.")
+    arg_parser.add_argument('-munge', action='store_true', help="Enable rsync's --munge-links on the server side.")
+    arg_parser.add_argument('-no-del', action='store_true', help="Disable rsync's --delete* and --remove* options.")
+    arg_parser.add_argument('-no-lock', action='store_true', help="Avoid the single-run (per-user) lock check.")
+    arg_parser.add_argument('-no-overwrite', action='store_true', help="Prevent overwriting existing files by enforcing --ignore-existing")
+    arg_parser.add_argument('-help', '-h', action='help', help="Output this help message and exit.")
+    arg_parser.add_argument('dir', metavar='DIR', help="The restricted directory to use.")
+    args = arg_parser.parse_args()
+    args.dir = os.path.realpath(args.dir)
+    args.dir_slash = args.dir + '/'
+    args.dir_slash_len = len(args.dir_slash)
+    if args.ro:
+        args.no_del = True
+    elif not args.no_lock:
+        lock_or_die(args.dir)
+    main()
+
+# vim: sw=4 et
diff --git a/support/rrsync.1.md b/support/rrsync.1.md
new file mode 100644
index 000000000..09b2f2822
--- /dev/null
+++ b/support/rrsync.1.md
@@ -0,0 +1,171 @@
+## NAME
+
+rrsync - a script to setup restricted rsync users via ssh logins
+
+## SYNOPSIS
+
+```
+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
+relative to the user's home directory or an absolute path.
+
+The online version of this manpage (that includes cross-linking of topics)
+is available at .
+
+## DESCRIPTION
+
+A user's ssh login can be restricted to only allow the running of an rsync
+transfer in one of two easy ways:
+
+* forcing the running of the rrsync script
+* forcing the running of an rsync daemon-over-ssh command.
+
+Both of these setups use a feature of ssh that allows a command to be forced to
+run instead of an interactive shell.  However, if the user's home shell is bash,
+please see [BASH SECURITY ISSUE](#) for a potential issue.
+
+To use the rrsync script, edit the user's `~/.ssh/authorized_keys` file and add
+a prefix like one of the following (followed by a space) in front of each
+ssh-key line that should be restricted:
+
+> ```
+> command="rrsync DIR"
+> command="rrsync -ro DIR"
+> command="rrsync -munge -no-del DIR"
+> ```
+
+Then, ensure that the rrsync script has your desired option restrictions. You
+may want to copy the script to a local bin dir with a unique name if you want
+to have multiple configurations. One or more rrsync options can be specified
+prior to the _DIR_ if you want to further restrict the transfer.
+
+To use an rsync daemon setup, edit the user's `~/.ssh/authorized_keys` file and
+add a prefix like one of the following (followed by a space) in front of each
+ssh-key line that should be restricted:
+
+> ```
+> command="rsync --server --daemon ."
+> command="rsync --server --daemon --config=/PATH/TO/rsyncd.conf ."
+> ```
+
+Then, ensure that the rsyncd.conf file is created with one or more module names
+with the appropriate path and option restrictions.  If rsync's
+[`--config`](rsync.1#dopt) option is omitted, it defaults to `~/rsyncd.conf`.
+See the [**rsyncd.conf**(5)](rsyncd.conf.5) manpage for details of how to
+configure an rsync daemon.
+
+When using rrsync, there can be just one restricted dir per authorized key.  A
+daemon setup, on the other hand, allows multiple module names inside the config
+file, each one with its own path setting.
+
+The remainder of this manpage is dedicated to using the rrsync script.
+
+## OPTIONS
+
+0.  `-ro`
+
+    Allow only reading from the DIR. Implies [`-no-del`](#opt) and
+    [`-no-lock`](#opt).
+
+0.  `-wo`
+
+    Allow only writing to the DIR.
+
+0.  `-munge`
+
+    Enable rsync's [`--munge-links`](rsync.1#opt) on the server side.
+
+0.  `-no-del`
+
+    Disable rsync's `--delete*` and `--remove*` options.
+
+0.  `-no-lock`
+
+    Avoid the single-run (per-user) lock check.  Useful with [`-munge`](#opt).
+
+0.  `-no-overwrite`
+
+    Enforce `--ignore-existing` on the server. Prevents overwriting existing
+    files when the server is the receiver.
+
+0.  `-help`, `-h`
+
+    Output this help message and exit.
+
+## SECURITY RESTRICTIONS
+
+The rrsync script validates the path arguments it is sent to try to restrict
+them to staying within the specified DIR.
+
+The rrsync script rejects rsync's [`--copy-links`](rsync.1#opt) option (by
+default) so that a copy cannot dereference a symlink within the DIR to get to a
+file outside the DIR.
+
+The rrsync script rejects rsync's [`--protect-args`](rsync.1#opt) (`-s`) option
+because it would allow options to be sent to the server-side that the script
+cannot check.  If you want to support `--protect-args`, use a daemon-over-ssh
+setup.
+
+The rrsync script accepts just a subset of rsync's options that the real rsync
+uses when running the server command.  A few extra convenience options are also
+included to help it to interact with BackupPC and accept some convenient user
+overrides.
+
+The script (or a copy of it) can be manually edited if you want it to customize
+the option handling.
+
+## BASH SECURITY ISSUE
+
+If your users have bash set as their home shell, bash may try to be overly
+helpful and ensure that the user's login bashrc files are run prior to
+executing the forced command.  This can be a problem if the user can somehow
+update their home bashrc files, perhaps via the restricted copy, a shared home
+directory, or something similar.
+
+One simple way to avoid the issue is to switch the user to a simpler shell,
+such as dash.  When choosing the new home shell, make sure that you're not
+choosing bash in disguise, as it is unclear if it avoids the security issue.
+
+Another potential fix is to ensure that the user's home directory is not a
+shared mount and that they have no means of copying files outside of their
+restricted directories.  This may require you to force the enabling of symlink
+munging on the server side.
+
+A future version of openssh may have a change to the handling of forced
+commands that allows it to avoid using the user's home shell.
+
+## EXAMPLES
+
+The `~/.ssh/authorized_keys` file might have lines in it like this:
+
+> ```
+> command="rrsync client/logs" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAzG...
+> command="rrsync -ro results" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAmk...
+> ```
+
+## FILES
+
+~/.ssh/authorized_keys
+
+## SEE ALSO
+
+[**rsync**(1)](rsync.1), [**rsyncd.conf**(5)](rsyncd.conf.5)
+
+## VERSION
+
+This manpage is current for version @VERSION@ of rsync.
+
+## CREDITS
+
+rsync is distributed under the GNU General Public License.  See the file
+[COPYING](COPYING) for details.
+
+An rsync web site is available at  and its github
+project is .
+
+## AUTHOR
+
+The original rrsync perl script was written by Joe Smith.  Many people have
+later contributed to it.  The python version was created by Wayne Davison.
diff --git a/support/rsync-no-vanished b/support/rsync-no-vanished
index 0f0bb22f5..b31a5d210 100755
--- a/support/rsync-no-vanished
+++ b/support/rsync-no-vanished
@@ -1,12 +1,21 @@
 #!/usr/bin/env bash
 
+REAL_RSYNC=/usr/bin/rsync
 IGNOREEXIT=24
 IGNOREOUT='^(file has vanished: |rsync warning: some files vanished before they could be transferred)'
 
+# If someone installs this as "rsync", make sure we don't affect a server run.
+for arg in "${@}"; do
+    if [[ "$arg" == --server ]]; then
+	exec $REAL_RSYNC "${@}"
+	exit $? # Not reached
+    fi
+done
+
 set -o pipefail
 
-rsync "${@}" 2>&1 | (grep -E -v "$IGNOREOUT" || true)
-ret=$?
+# This filters stderr without merging it with stdout:
+{ $REAL_RSYNC "${@}" 2>&1 1>&3 3>&- | grep -E -v "$IGNOREOUT"; ret=${PIPESTATUS[0]}; } 3>&1 1>&2
 
 if [[ $ret == $IGNOREEXIT ]]; then
     ret=0
diff --git a/support/rsync-slash-strip b/support/rsync-slash-strip
index 2869e45cb..b57e61c53 100755
--- a/support/rsync-slash-strip
+++ b/support/rsync-slash-strip
@@ -6,12 +6,19 @@
 #
 # To use this, name it something like "rs", put it somewhere in your path, and
 # then use "rs" in place of "rsync" when you are typing your copy commands.
+
+REAL_RSYNC=/usr/bin/rsync
+
 args=()
 for arg in "${@}"; do
+    if [[ "$arg" == --server ]]; then
+	exec $REAL_RSYNC "${@}"
+	exit $? # Not reached
+    fi
     if [[ "$arg" == / ]]; then
 	args=("${args[@]}" /)
     else
 	args=("${args[@]}" "${arg%/}")
     fi
 done
-exec /usr/bin/rsync "${args[@]}"
+exec $REAL_RSYNC "${args[@]}"
diff --git a/syscall.c b/syscall.c
index 56948a832..34a9bba08 100644
--- a/syscall.c
+++ b/syscall.c
@@ -4,7 +4,7 @@
  *
  * Copyright (C) 1998 Andrew Tridgell
  * Copyright (C) 2002 Martin Pool
- * Copyright (C) 2003-2020 Wayne Davison
+ * 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 as published by
@@ -33,6 +33,8 @@
 #include 
 #endif
 
+#include "ifuncs.h"
+
 extern int dry_run;
 extern int am_root;
 extern int am_sender;
@@ -43,6 +45,8 @@ extern int preallocate_files;
 extern int preserve_perms;
 extern int preserve_executability;
 extern int open_noatime;
+extern int copy_links;
+extern int copy_unsafe_links;
 
 #ifndef S_BLKSIZE
 # if defined hpux || defined __hpux__ || defined __hpux
@@ -247,7 +251,7 @@ int do_chmod(const char *path, mode_t mode)
 		else if (errno != ENOTSUP)
 			break;
 #endif
-
+		/* FALLTHROUGH */
 	default:
 		if (S_ISLNK(mode)) {
 # if defined HAVE_SETATTRLIST
@@ -257,7 +261,10 @@ int do_chmod(const char *path, mode_t mode)
 			memset(&attrList, 0, sizeof attrList);
 			attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
 			attrList.commonattr = ATTR_CMN_ACCESSMASK;
-			code = setattrlist(path, &attrList, &m, sizeof m, FSOPT_NOFOLLOW);
+			if ((code = setattrlist(path, &attrList, &m, sizeof m, FSOPT_NOFOLLOW)) == 0)
+				break;
+			if (errno == ENOTSUP)
+				code = 1;
 # else
 			code = 1;
 # endif
@@ -385,11 +392,6 @@ int do_fstat(int fd, STRUCT_STAT *st)
 OFF_T do_lseek(int fd, OFF_T offset, int whence)
 {
 #ifdef HAVE_LSEEK64
-#if !SIZEOF_OFF64_T
-	OFF_T lseek64();
-#else
-	off64_t lseek64();
-#endif
 	return lseek64(fd, offset, whence);
 #else
 	return lseek(fd, offset, whence);
@@ -709,3 +711,100 @@ int do_open_nofollow(const char *pathname, int flags)
 
 	return fd;
 }
+
+/*
+  open a file relative to a base directory. The basedir can be NULL,
+  in which case the current working directory is used. The relpath
+  must be a relative path, and the relpath must not contain any
+  elements in the path which follow symlinks (ie. like O_NOFOLLOW, but
+  applies to all path components, not just the last component)
+
+  The relpath must also not contain any ../ elements in the path
+*/
+int secure_relative_open(const char *basedir, const char *relpath, int flags, mode_t mode)
+{
+	if (!relpath || relpath[0] == '/') {
+		// must be a relative path
+		errno = EINVAL;
+		return -1;
+	}
+	if (strncmp(relpath, "../", 3) == 0 || strstr(relpath, "/../")) {
+		// no ../ elements allowed in the relpath
+		errno = EINVAL;
+		return -1;
+	}
+
+#if !defined(O_NOFOLLOW) || !defined(O_DIRECTORY) || !defined(AT_FDCWD)
+	// really old system, all we can do is live with the risks
+	if (!basedir) {
+		return open(relpath, flags, mode);
+	}
+	char fullpath[MAXPATHLEN];
+	pathjoin(fullpath, sizeof fullpath, basedir, relpath);
+	return open(fullpath, flags, mode);
+#else
+	int dirfd = AT_FDCWD;
+	if (basedir != NULL) {
+		dirfd = openat(AT_FDCWD, basedir, O_RDONLY | O_DIRECTORY);
+		if (dirfd == -1) {
+			return -1;
+		}
+	}
+	int retfd = -1;
+
+	char *path_copy = my_strdup(relpath, __FILE__, __LINE__);
+	if (!path_copy) {
+		return -1;
+	}
+	
+	for (const char *part = strtok(path_copy, "/");
+	     part != NULL;
+	     part = strtok(NULL, "/"))
+	{
+		int next_fd = openat(dirfd, part, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+		if (next_fd == -1 && errno == ENOTDIR) {
+			if (strtok(NULL, "/") != NULL) {
+				// this is not the last component of the path
+				errno = ELOOP;
+				goto cleanup;
+			}
+			// this could be the last component of the path, try as a file
+			retfd = openat(dirfd, part, flags | O_NOFOLLOW, mode);
+			goto cleanup;
+		}
+		if (next_fd == -1) {
+			goto cleanup;
+		}
+		if (dirfd != AT_FDCWD) close(dirfd);
+		dirfd = next_fd;
+	}
+
+	// the path must be a directory
+	errno = EINVAL;
+
+cleanup:
+	free(path_copy);
+	if (dirfd != AT_FDCWD) {
+		close(dirfd);
+	}
+	return retfd;
+#endif // O_NOFOLLOW, O_DIRECTORY
+}
+
+/*
+  varient of do_open/do_open_nofollow which does do_open() if the
+  copy_links or copy_unsafe_links options are set and does
+  do_open_nofollow() otherwise
+
+  This is used to prevent a race condition where an attacker could be
+  switching a file between being a symlink and being a normal file
+
+  The open is always done with O_RDONLY flags
+ */
+int do_open_checklinks(const char *pathname)
+{
+	if (copy_links || copy_unsafe_links) {
+		return do_open(pathname, O_RDONLY, 0);
+	}
+	return do_open_nofollow(pathname, O_RDONLY);
+}
diff --git a/t_stub.c b/t_stub.c
index 12f7b622c..eee927299 100644
--- a/t_stub.c
+++ b/t_stub.c
@@ -3,7 +3,7 @@
  * functions, so that module test harnesses can run standalone.
  *
  * Copyright (C) 2001, 2002 Martin Pool 
- * Copyright (C) 2003-2020 Wayne Davison
+ * 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 as published by
@@ -28,8 +28,7 @@ int preallocate_files = 0;
 int protect_args = 0;
 int module_id = -1;
 int relative_paths = 0;
-int module_dirlen = 0;
-int preserve_mtimes = 0;
+unsigned int module_dirlen = 0;
 int preserve_xattrs = 0;
 int preserve_perms = 0;
 int preserve_executability = 0;
diff --git a/t_unsafe.c b/t_unsafe.c
index 010cac50d..e10619a2a 100644
--- a/t_unsafe.c
+++ b/t_unsafe.c
@@ -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
diff --git a/testsuite/00-hello.test b/testsuite/00-hello.test
index ed72c0bdc..ebd068365 100644
--- a/testsuite/00-hello.test
+++ b/testsuite/00-hello.test
@@ -1,9 +1,11 @@
 #!/bin/sh
 
-test_fail() {
-    echo "$@" >&2
-    exit 1
-}
+# Test some foundational things.
+
+. "$suitedir/rsync.fns"
+
+RSYNC_RSH="$scratchdir/src/support/lsh.sh"
+export RSYNC_RSH
 
 echo $0 running
 
@@ -12,3 +14,48 @@ $RSYNC --version || test_fail '--version output failed'
 $RSYNC --info=help || test_fail '--info=help output failed'
 
 $RSYNC --debug=help || test_fail '--debug=help output failed'
+
+weird_name="A weird)name"
+
+mkdir "$fromdir"
+mkdir "$fromdir/$weird_name"
+
+append_line() {
+    echo "$1"
+    echo "$1" >>"$fromdir/$weird_name/file"
+}
+
+append_line test1
+checkit "$RSYNC -ai '$fromdir/' '$todir/'" "$fromdir" "$todir"
+
+copy_weird() {
+    checkit "$RSYNC $1 --rsync-path='$RSYNC' '$2$fromdir/$weird_name/' '$3$todir/$weird_name'" "$fromdir" "$todir"
+}
+
+append_line test2
+copy_weird '-ai' 'lh:' ''
+
+append_line test3
+copy_weird '-ai' '' 'lh:'
+
+append_line test4
+copy_weird '-ais' 'lh:' ''
+
+append_line test5
+copy_weird '-ais' '' 'lh:'
+
+echo test6
+
+touch "$fromdir/one" "$fromdir/two"
+(cd "$fromdir" && $RSYNC -ai --old-args --rsync-path="$RSYNC" lh:'one two' "$todir/")
+if [ ! -f "$todir/one" ] || [ ! -f "$todir/two" ]; then
+    test_fail "old-args copy of 'one two' failed"
+fi
+
+echo test7
+
+rm "$todir/one" "$todir/two"
+(cd "$fromdir" && RSYNC_OLD_ARGS=1 $RSYNC -ai --rsync-path="$RSYNC" lh:'one two' "$todir/")
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/acls-default.test b/testsuite/acls-default.test
index a0a482ced..d8fba7fee 100644
--- a/testsuite/acls-default.test
+++ b/testsuite/acls-default.test
@@ -7,7 +7,7 @@
 
 . $suitedir/rsync.fns
 
-$RSYNC --version | grep "[, ] ACLs" >/dev/null || test_skipped "Rsync is configured without ACL support"
+$RSYNC -VV | grep '"ACLs": true' >/dev/null || test_skipped "Rsync is configured without ACL support"
 
 case "$setfacl_nodef" in
 true) test_skipped "I don't know how to use your setfacl command" ;;
diff --git a/testsuite/acls.test b/testsuite/acls.test
index 23449018a..693da6677 100644
--- a/testsuite/acls.test
+++ b/testsuite/acls.test
@@ -7,7 +7,7 @@
 
 . $suitedir/rsync.fns
 
-$RSYNC --version | grep "[, ] ACLs" >/dev/null || test_skipped "Rsync is configured without ACL support"
+$RSYNC -VV | grep '"ACLs": true' >/dev/null || test_skipped "Rsync is configured without ACL support"
 
 makepath "$fromdir/foo"
 echo something >"$fromdir/file1"
diff --git a/testsuite/alt-dest.test b/testsuite/alt-dest.test
new file mode 100644
index 000000000..d2fb5a1bb
--- /dev/null
+++ b/testsuite/alt-dest.test
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+# Copyright (C) 2004-2022 Wayne Davison
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test rsync handling of --compare-dest and similar options.
+
+. "$suitedir/rsync.fns"
+
+alt1dir="$tmpdir/alt1"
+alt2dir="$tmpdir/alt2"
+alt3dir="$tmpdir/alt3"
+
+SSH="$scratchdir/src/support/lsh.sh"
+
+# Build some files/dirs/links to copy
+
+hands_setup
+
+# Setup the alt and chk dirs
+$RSYNC -av --include=text --include='*/' --exclude='*' "$fromdir/" "$alt1dir/"
+$RSYNC -av --include=etc-ltr-list --include='*/' --exclude='*' "$fromdir/" "$alt2dir/"
+
+# Create a side dir where there is a candidate destfile of the same name as a sourcefile
+echo "This is a test file" >"$fromdir/likely"
+
+mkdir "$alt3dir"
+echo "This is a test file" >"$alt3dir/likely"
+
+sleep 1
+touch "$fromdir/dir/text" "$fromdir/likely"
+
+$RSYNC -av --exclude=/text --exclude=etc-ltr-list "$fromdir/" "$chkdir/"
+
+# Let's do it!
+checkit "$RSYNC -avv --no-whole-file \
+    --compare-dest='$alt1dir' --compare-dest='$alt2dir' \
+    '$fromdir/' '$todir/'" "$chkdir" "$todir"
+
+rm -rf "$todir"
+checkit "$RSYNC -avv --no-whole-file \
+    --copy-dest='$alt1dir' --copy-dest='$alt2dir' \
+    '$fromdir/' '$todir/'" "$fromdir" "$todir"
+
+# Test that copy_file() works correctly with tmpfiles
+for maybe_inplace in '' --inplace; do
+    rm -rf "$todir"
+    checkit "$RSYNC -av $maybe_inplace --copy-dest='$alt3dir' \
+	'$fromdir/' '$todir/'" "$fromdir" "$todir"
+
+    for srchost in '' 'localhost:'; do
+	if [ -z "$srchost" ]; then
+	    desthost='localhost:'
+	else
+	    desthost=''
+	fi
+
+	rm -rf "$todir"
+	checkit "$RSYNC -ave '$SSH' --rsync-path='$RSYNC' $maybe_inplace \
+	    --copy-dest='$alt3dir' '$srchost$fromdir/' '$desthost$todir/'" \
+	    "$fromdir" "$todir"
+    done
+done
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/atimes.test b/testsuite/atimes.test
index 3bdb1d463..4d46eb057 100644
--- a/testsuite/atimes.test
+++ b/testsuite/atimes.test
@@ -4,7 +4,7 @@
 
 . "$suitedir/rsync.fns"
 
-$RSYNC --version | grep "[, ] atimes" >/dev/null || test_skipped "Rsync is configured without atimes support"
+$RSYNC -VV | grep '"atimes": true' >/dev/null || test_skipped "Rsync is configured without atimes support"
 
 mkdir "$fromdir"
 
diff --git a/testsuite/backup.test b/testsuite/backup.test
index 925c9fe0b..4de38674d 100644
--- a/testsuite/backup.test
+++ b/testsuite/backup.test
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-# Copyright (C) 2004-2020 Wayne Davison
+# Copyright (C) 2004-2022 Wayne Davison
 
 # This program is distributable under the terms of the GNU GPL (see
 # COPYING).
diff --git a/testsuite/chmod-temp-dir.test b/testsuite/chmod-temp-dir.test
index e5541e4c9..362d9d993 100644
--- a/testsuite/chmod-temp-dir.test
+++ b/testsuite/chmod-temp-dir.test
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-# Copyright (C) 2004-2020 Wayne Davison
+# Copyright (C) 2004-2022 Wayne Davison
 
 # This program is distributable under the terms of the GNU GPL (see
 # COPYING).
@@ -17,7 +17,7 @@ sdev=`$TOOLDIR/getfsdev $scratchdir`
 tdev=$sdev
 
 for tmpdir2 in "${RSYNC_TEST_TMP:-/override-tmp-not-specified}" /run/shm /var/tmp /tmp; do
-    [ -d "$tmpdir2" -a -w "$tmpdir2" ] || continue
+    [ -d "$tmpdir2" ] && [ -w "$tmpdir2" ] || continue
     tdev=`$TOOLDIR/getfsdev "$tmpdir2"`
     [ x$sdev != x$tdev ] && break
 done
diff --git a/testsuite/chmod.test b/testsuite/chmod.test
index 63258df7c..1646a9c81 100644
--- a/testsuite/chmod.test
+++ b/testsuite/chmod.test
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-# Copyright (C) 2004-2020 Wayne Davison
+# Copyright (C) 2004-2022 Wayne Davison
 
 # This program is distributable under the terms of the GNU GPL (see
 # COPYING).
diff --git a/testsuite/chown.test b/testsuite/chown.test
index 5dadb836c..b53413e1e 100644
--- a/testsuite/chown.test
+++ b/testsuite/chown.test
@@ -15,7 +15,7 @@
 
 case $0 in
 *fake*)
-    $RSYNC --version | grep "[, ] xattrs" >/dev/null || test_skipped "Rsync needs xattrs for fake device tests"
+    $RSYNC -VV | grep '"xattrs": true' >/dev/null || test_skipped "Rsync needs xattrs for fake device tests"
     RSYNC="$RSYNC --fake-super"
     TLS_ARGS="$TLS_ARGS --fake-super"
     case "$HOST_OS" in
diff --git a/testsuite/compare-dest.test b/testsuite/compare-dest.test
deleted file mode 100644
index 3c6348576..000000000
--- a/testsuite/compare-dest.test
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/sh
-
-# Copyright (C) 2004-2020 Wayne Davison
-
-# This program is distributable under the terms of the GNU GPL (see
-# COPYING).
-
-# Test rsync handling of the --compare-dest option.
-
-. "$suitedir/rsync.fns"
-
-alt1dir="$tmpdir/alt1"
-alt2dir="$tmpdir/alt2"
-
-# Build some files/dirs/links to copy
-
-hands_setup
-
-# Setup the alt and chk dirs
-$RSYNC -av --include=text --include='*/' --exclude='*' "$fromdir/" "$alt1dir/"
-$RSYNC -av --include=etc-ltr-list --include='*/' --exclude='*' "$fromdir/" "$alt2dir/"
-
-sleep 1
-touch "$fromdir/dir/text"
-
-$RSYNC -av --exclude=/text --exclude=etc-ltr-list "$fromdir/" "$chkdir/"
-
-# Let's do it!
-checkit "$RSYNC -avv --no-whole-file \
-    --compare-dest='$alt1dir' --compare-dest='$alt2dir' \
-    '$fromdir/' '$todir/'" "$chkdir" "$todir"
-checkit "$RSYNC -avv --no-whole-file \
-    --copy-dest='$alt1dir' --copy-dest='$alt2dir' \
-    '$fromdir/' '$todir/'" "$fromdir" "$todir"
-
-# The script would have aborted on error, so getting here means we've won.
-exit 0
diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
index 51bfe301c..456f0a5f1 100644
--- a/testsuite/crtimes.test
+++ b/testsuite/crtimes.test
@@ -4,13 +4,13 @@
 
 . "$suitedir/rsync.fns"
 
-$RSYNC --version | grep "[, ] crtimes" >/dev/null || test_skipped "Rsync is configured without crtimes support"
+$RSYNC -VV | grep '"crtimes": true' >/dev/null || test_skipped "Rsync is configured without crtimes support"
 
 # Setting an older time via touch sets the create time to the mtime.
 # Setting it to a newer time affects just the mtime.
 
 mkdir "$fromdir"
-echo hiho "$fromdir/foo"
+echo hiho >"$fromdir/foo"
 
 touch -t 200101011111.11 "$fromdir"
 touch -t 200202022222.22 "$fromdir"
diff --git a/testsuite/daemon.test b/testsuite/daemon.test
index 80d2baf65..60aa334b7 100644
--- a/testsuite/daemon.test
+++ b/testsuite/daemon.test
@@ -81,7 +81,7 @@ drwxr-xr-x         DIR ####/##/## ##:##:## foo
 EOT
 diff $diffopt "$chkfile" "$outfile" || test_fail "test 3 failed"
 
-if $RSYNC --version | grep "[, ] atimes" >/dev/null; then
+if $RSYNC -VV | grep '"atimes": true' >/dev/null; then
     checkdiff "$RSYNC -rU localhost::test-from/f*" \
 	"sed -e '$FILE_REPL' -e '$DIR_REPL' -e '$LS_REPL'" </dev/null || test_skipped "Rsync needs xattrs for fake device tests"
+    $RSYNC -VV | grep '"xattrs": true' >/dev/null || test_skipped "Rsync needs xattrs for fake device tests"
     RSYNC="$RSYNC --fake-super"
     TLS_ARGS="$TLS_ARGS --fake-super"
     case "$HOST_OS" in
@@ -94,7 +94,7 @@ esac
 
 # TODO: Need to test whether hardlinks are possible on this OS/filesystem
 
-$RSYNC --version | grep "[, ] hardlink-special" >/dev/null && CAN_HLINK_SPECIAL=yes || CAN_HLINK_SPECIAL=no
+$RSYNC -VV | grep '"hardlink_specials": true' >/dev/null && CAN_HLINK_SPECIAL=yes || CAN_HLINK_SPECIAL=no
 
 mkdir "$fromdir"
 mkdir "$todir"
diff --git a/testsuite/exclude-lsh.test b/testsuite/exclude-lsh.test
new file mode 120000
index 000000000..84bc98ac2
--- /dev/null
+++ b/testsuite/exclude-lsh.test
@@ -0,0 +1 @@
+exclude.test
\ No newline at end of file
diff --git a/testsuite/exclude.test b/testsuite/exclude.test
index bd21c51cc..56b68b8ca 100644
--- a/testsuite/exclude.test
+++ b/testsuite/exclude.test
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-# Copyright (C) 2003-2020 Wayne Davison
+# Copyright (C) 2003-2022 Wayne Davison
 
 # This program is distributable under the terms of the GNU GPL (see
 # COPYING).
@@ -15,6 +15,19 @@
 CVSIGNORE='*.junk'
 export CVSIGNORE
 
+case $0 in
+*-lsh.*)
+    RSYNC_RSH="$scratchdir/src/support/lsh.sh"
+    export RSYNC_RSH
+    rpath=" --rsync-path='$RSYNC'"
+    host='lh:'
+    ;;
+*)
+    rpath=''
+    host=''
+    ;;
+esac
+
 # Build some files/dirs/links to copy
 
 makepath "$fromdir/foo/down/to/you"
@@ -106,8 +119,8 @@ home-cvs-exclude
 EOF
 
 # Start with a check of --prune-empty-dirs:
-$RSYNC -av -f -_foo/too/ -f -_foo/down/ -f -_foo/and/ -f -_new/ "$fromdir/" "$chkdir/"
-checkit "$RSYNC -av --prune-empty-dirs '$fromdir/' '$todir/'" "$chkdir" "$todir"
+$RSYNC -av --rsync-path="$RSYNC" -f -_foo/too/ -f -_foo/down/ -f -_foo/and/ -f -_new/ "$host$fromdir/" "$chkdir/"
+checkit "$RSYNC -av$rpath --prune-empty-dirs '$host$fromdir/' '$todir/'" "$chkdir" "$todir"
 rm -rf "$todir"
 
 # Add a directory symlink.
@@ -120,7 +133,7 @@ touch "$scratchdir/up1/same-newness" "$scratchdir/up2/same-newness"
 touch "$scratchdir/up1/extra-src" "$scratchdir/up2/extra-dest"
 
 # Create chkdir with what we expect to be excluded.
-checkit "$RSYNC -avv '$fromdir/' '$chkdir/'" "$fromdir" "$chkdir"
+checkit "$RSYNC -avv$rpath '$host$fromdir/' '$chkdir/'" "$fromdir" "$chkdir"
 sleep 1 # Ensures that the rm commands will tweak the directory times.
 rm -r "$chkdir"/foo/down
 rm -r "$chkdir"/mid/for/foo/and
@@ -135,12 +148,12 @@ touch "$scratchdir/up1/src-newness" "$scratchdir/up2/dst-newness"
 
 # Un-tweak the directory times in our first (weak) exclude test (though
 # it's a good test of the --existing option).
-$RSYNC -av --existing --include='*/' --exclude='*' "$fromdir/" "$chkdir/"
+$RSYNC -av --rsync-path="$RSYNC" --existing --include='*/' --exclude='*' "$host$fromdir/" "$chkdir/"
 
 # Now, test if rsync excludes the same files.
 
-checkit "$RSYNC -avv --exclude-from='$excl' \
-    --delete-during '$fromdir/' '$todir/'" "$chkdir" "$todir"
+checkit "$RSYNC -avv$rpath --exclude-from='$excl' \
+    --delete-during '$host$fromdir/' '$todir/'" "$chkdir" "$todir"
 
 # Modify the chk dir by removing cvs-ignored files and then tweaking the dir times.
 
@@ -150,13 +163,15 @@ rm "$chkdir"/bar/down/to/foo/*.junk
 rm "$chkdir"/bar/down/to/home-cvs-exclude
 rm "$chkdir"/mid/one-in-one-out
 
-$RSYNC -av --existing --filter='exclude,! */' "$fromdir/" "$chkdir/"
+$RSYNC -av --rsync-path="$RSYNC" --existing --filter='exclude,! */' "$host$fromdir/" "$chkdir/"
 
 # Now, test if rsync excludes the same files, this time with --cvs-exclude
 # and --delete-excluded.
 
-checkit "$RSYNC -avvC --filter='merge $excl' --delete-excluded \
-    --delete-during '$fromdir/' '$todir/'" "$chkdir" "$todir"
+# The -C option gets applied in a different order when pushing & pulling, so we instead
+# add the 2 --cvs-exclude filter rules (":C" & "-C") via -f to keep the order the same.
+checkit "$RSYNC -avv$rpath --filter='merge $excl' -f:C -f-C --delete-excluded \
+    --delete-during '$host$fromdir/' '$todir/'" "$chkdir" "$todir"
 
 # Modify the chk dir for our merge-exclude test and then tweak the dir times.
 
@@ -165,19 +180,19 @@ rm "$chkdir"/bar/down/to/bar/baz/*.deep
 cp_touch "$fromdir"/bar/down/to/foo/*.junk "$chkdir"/bar/down/to/foo
 cp_touch "$fromdir"/bar/down/to/foo/to "$chkdir"/bar/down/to/foo
 
-$RSYNC -av --existing -f 'show .filt*' -f 'hide,! */' --del "$fromdir/" "$todir/"
+$RSYNC -av --rsync-path="$RSYNC" --existing -f 'show .filt*' -f 'hide,! */' --del "$host$fromdir/" "$todir/"
 
 echo retained >"$todir"/bar/down/to/bar/baz/nodel.deep
 cp_touch "$todir"/bar/down/to/bar/baz/nodel.deep "$chkdir"/bar/down/to/bar/baz
 
-$RSYNC -av --existing --filter='-! */' "$fromdir/" "$chkdir/"
+$RSYNC -av --rsync-path="$RSYNC" --existing --filter='-! */' "$host$fromdir/" "$chkdir/"
 
 # Now, test if rsync excludes the same files, this time with a merge-exclude
 # file.
 
 checkit "sed '/!/d' '$excl' |
-    $RSYNC -avv -f dir-merge_.filt -f merge_- \
-    --delete-during '$fromdir/' '$todir/'" "$chkdir" "$todir"
+    $RSYNC -avv$rpath -f dir-merge_.filt -f merge_- \
+    --delete-during '$host$fromdir/' '$todir/'" "$chkdir" "$todir"
 
 # Remove the files that will be deleted.
 
@@ -188,14 +203,14 @@ rm "$chkdir"/bar/down/to/foo/.filt2
 rm "$chkdir"/bar/down/to/bar/.filt2
 rm "$chkdir"/mid/.filt
 
-$RSYNC -av --protocol=28 --existing --include='*/' --exclude='*' "$fromdir/" "$chkdir/"
+$RSYNC -av --rsync-path="$RSYNC" --existing --include='*/' --exclude='*' "$host$fromdir/" "$chkdir/"
 
 # Now, try the prior command with --delete-before and some side-specific
 # rules.
 
 checkit "sed '/!/d' '$excl' |
-    $RSYNC -avv -f :s_.filt -f .s_- -f P_nodel.deep \
-    --delete-before '$fromdir/' '$todir/'" "$chkdir" "$todir"
+    $RSYNC -avv$rpath -f :s_.filt -f .s_- -f P_nodel.deep \
+    --delete-before '$host$fromdir/' '$todir/'" "$chkdir" "$todir"
 
 # Next, we'll test some rule-restricted filter files.
 
@@ -206,26 +221,26 @@ cat >"$fromdir/bar/down/to/foo/.excl" <f$all_plus extra-src
diff --git a/testsuite/fuzzy.test b/testsuite/fuzzy.test
index 2415173de..101ffd3c1 100644
--- a/testsuite/fuzzy.test
+++ b/testsuite/fuzzy.test
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-# Copyright (C) 2005-2020 Wayne Davison
+# Copyright (C) 2005-2022 Wayne Davison
 
 # This program is distributable under the terms of the GNU GPL (see
 # COPYING).
diff --git a/testsuite/hardlinks.test b/testsuite/hardlinks.test
index af2ea4088..68fd2701c 100644
--- a/testsuite/hardlinks.test
+++ b/testsuite/hardlinks.test
@@ -77,5 +77,11 @@ 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"
+checkit "$RSYNC -aH '$fromdir/sym' '$todir'" "$fromdir" "$todir"
+
 # The script would have aborted on error, so getting here means we've won.
 exit 0
diff --git a/testsuite/itemize.test b/testsuite/itemize.test
index 923272389..c1c57c59a 100644
--- a/testsuite/itemize.test
+++ b/testsuite/itemize.test
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-# Copyright (C) 2005-2020 Wayne Davison
+# Copyright (C) 2005-2022 Wayne Davison
 
 # This program is distributable under the terms of the GNU GPL (see
 # COPYING).
@@ -25,7 +25,7 @@ ln "$fromdir/foo/config1" "$fromdir/foo/extra"
 rm -f "$to2dir"
 
 # Check if rsync is set to hard-link symlinks.
-if $RSYNC --version | grep "[, ] hardlink-symlinks" >/dev/null; then
+if $RSYNC -VV | grep '"hardlink_symlinks": true' >/dev/null; then
     L=hL
     sym_dots="$allspace"
     L_sym_dots=".L$allspace"
@@ -45,7 +45,7 @@ case "$RSYNC" in
     T=.T
     ;;
 *)
-    if $RSYNC --version | grep "[, ] symtimes" >/dev/null; then
+    if $RSYNC -VV | grep '"symtimes": true' >/dev/null; then
 	T=.t
     else
 	T=.T
diff --git a/testsuite/merge.test b/testsuite/merge.test
index 5f9a6da66..17050a1d8 100644
--- a/testsuite/merge.test
+++ b/testsuite/merge.test
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-# Copyright (C) 2004-2020 Wayne Davison
+# Copyright (C) 2004-2022 Wayne Davison
 
 # This program is distributable under the terms of the GNU GPL (see
 # COPYING).
diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
index 1e2b399f0..2ab97b69c 100644
--- a/testsuite/rsync.fns
+++ b/testsuite/rsync.fns
@@ -65,7 +65,7 @@ set_cp_destdir() {
 # even if the copy rounded microsecond times on the destination file.
 cp_touch() {
     cp_p "${@}"
-    if test $# -gt 2 -o -d "$2"; then
+    if test $# -gt 2 || test -d "$2"; then
 	set_cp_destdir "${@}" # sets destdir var
 	while test $# -gt 1; do
 	    destname="$destdir/`basename $1`"
diff --git a/testsuite/safe-links.test b/testsuite/safe-links.test
new file mode 100644
index 000000000..6e95a4b93
--- /dev/null
+++ b/testsuite/safe-links.test
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+. "$suitedir/rsync.fns"
+
+test_symlink() {
+	is_a_link "$1" || test_fail "File $1 is not a symlink"
+}
+
+test_regular() {
+	if [ ! -f "$1" ]; then
+		test_fail "File $1 is not regular file or not exists"
+	fi
+}
+
+test_notexist() {
+        if [ -e "$1" ]; then
+                test_fail "File $1 exists"
+	fi
+        if [ -h "$1" ]; then
+                test_fail "File $1 exists as a symlink"
+	fi
+}
+
+cd "$tmpdir"
+
+mkdir from
+
+mkdir "from/safe"
+mkdir "from/unsafe"
+
+mkdir "from/safe/files"
+mkdir "from/safe/links"
+
+touch "from/safe/files/file1"
+touch "from/safe/files/file2"
+touch "from/unsafe/unsafefile"
+
+ln -s ../files/file1 "from/safe/links/"
+ln -s ../files/file2 "from/safe/links/"
+ln -s ../../unsafe/unsafefile "from/safe/links/"
+ln -s a/a/a/../../../unsafe2 "from/safe/links/"
+
+#echo "LISTING FROM"
+#ls -lR from
+
+echo "rsync with relative path and just -a"
+$RSYNC -avv --safe-links from/safe/ to
+
+#echo "LISTING TO"
+#ls -lR to
+
+test_symlink to/links/file1
+test_symlink to/links/file2
+test_notexist to/links/unsafefile
+test_notexist to/links/unsafe2
diff --git a/testsuite/unsafe-byname.test b/testsuite/unsafe-byname.test
index 75e720145..d2e318ef4 100644
--- a/testsuite/unsafe-byname.test
+++ b/testsuite/unsafe-byname.test
@@ -40,7 +40,7 @@ test_unsafe ..//../dest 		from/dir			unsafe
 test_unsafe ..				from/file			safe
 test_unsafe ../..			from/file			unsafe
 test_unsafe ..//..			from//file			unsafe
-test_unsafe dir/..			from				safe
+test_unsafe dir/..			from				unsafe
 test_unsafe dir/../..			from				unsafe
 test_unsafe dir/..//..			from				unsafe
 
diff --git a/testsuite/wildmatch.test b/testsuite/wildmatch.test
index fa225d90f..cfe758412 100644
--- a/testsuite/wildmatch.test
+++ b/testsuite/wildmatch.test
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-# Copyright (C) 2003-2020 Wayne Davison
+# Copyright (C) 2003-2022 Wayne Davison
 
 # This program is distributable under the terms of the GNU GPL (see
 # COPYING).
diff --git a/testsuite/xattrs.test b/testsuite/xattrs.test
index 455abef1b..d94d5f950 100644
--- a/testsuite/xattrs.test
+++ b/testsuite/xattrs.test
@@ -8,7 +8,7 @@
 . $suitedir/rsync.fns
 lnkdir="$tmpdir/lnk"
 
-$RSYNC --version | grep "[, ] xattrs" >/dev/null || test_skipped "Rsync is configured without xattr support"
+$RSYNC -VV | grep '"xattrs": true' >/dev/null || test_skipped "Rsync is configured without xattr support"
 
 case "$HOST_OS" in
 darwin*)
diff --git a/tls.c b/tls.c
index 966986745..858f8f10c 100644
--- a/tls.c
+++ b/tls.c
@@ -2,7 +2,7 @@
  * Trivial ls for comparing two directories after running an rsync.
  *
  * Copyright (C) 2001, 2002 Martin Pool 
- * Copyright (C) 2003-2020 Wayne Davison
+ * 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 as published by
@@ -49,6 +49,9 @@ int list_only = 0;
 int link_times = 0;
 int link_owner = 0;
 int nsec_times = 0;
+int safe_symlinks = 0;
+int copy_links = 0;
+int copy_unsafe_links = 0;
 
 #ifdef SUPPORT_XATTRS
 
diff --git a/token.c b/token.c
index 3a6d069ed..c108b3af5 100644
--- a/token.c
+++ b/token.c
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 1996 Andrew Tridgell
  * Copyright (C) 1996 Paul Mackerras
- * Copyright (C) 2003-2020 Wayne Davison
+ * 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 as published by
@@ -39,7 +39,6 @@ extern char *skip_compress;
 #define Z_INSERT_ONLY Z_SYNC_FLUSH
 #endif
 
-static int compression_level; /* The compression level for the current file. */
 static int skip_compression_level; /* The least possible compressing for handling skip-compress files. */
 static int per_file_default_level; /* The default level that each new file gets prior to checking its suffix. */
 
@@ -224,9 +223,11 @@ static void init_set_compression(void)
 /* determine the compression level based on a wildcard filename list */
 void set_compression(const char *fname)
 {
+#if 0 /* No compression algorithms currently allow mid-stream changing of the level. */
 	const struct suffix_tree *node;
 	const char *s;
 	char ltr;
+#endif
 
 	if (!do_compression)
 		return;
@@ -234,6 +235,7 @@ void set_compression(const char *fname)
 	if (!match_list)
 		init_set_compression();
 
+#if 0
 	compression_level = per_file_default_level;
 
 	if (!*match_list && !suftree)
@@ -270,6 +272,9 @@ void set_compression(const char *fname)
 		if (!(node = node->child))
 			return;
 	}
+#else
+	(void)fname;
+#endif
 }
 
 /* non-compressing recv token */
@@ -361,7 +366,7 @@ send_deflated_token(int f, int32 token, struct map_struct *buf, OFF_T offset, in
 			tx_strm.next_in = NULL;
 			tx_strm.zalloc = NULL;
 			tx_strm.zfree = NULL;
-			if (deflateInit2(&tx_strm, compression_level,
+			if (deflateInit2(&tx_strm, per_file_default_level,
 					 Z_DEFLATED, -15, 8,
 					 Z_DEFAULT_STRATEGY) != Z_OK) {
 				rprintf(FERROR, "compression init failed\n");
diff --git a/trimslash.c b/trimslash.c
index 1ec928cac..f2774cd73 100644
--- a/trimslash.c
+++ b/trimslash.c
@@ -26,6 +26,8 @@ int am_root = 0;
 int am_sender = 1;
 int read_only = 1;
 int list_only = 0;
+int copy_links = 0;
+int copy_unsafe_links = 0;
 
 int
 main(int argc, char **argv)
diff --git a/uidlist.c b/uidlist.c
index 6100b5035..99a34679a 100644
--- a/uidlist.c
+++ b/uidlist.c
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 1996 Andrew Tridgell
  * Copyright (C) 1996 Paul Mackerras
- * Copyright (C) 2004-2020 Wayne Davison
+ * Copyright (C) 2004-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 as published by
@@ -210,7 +210,7 @@ static int is_in_group(gid_t gid)
 			ngroups = getgroups(ngroups, gidset);
 		/* The default gid might not be in the list on some systems. */
 		for (n = 0; n < ngroups; n++) {
-			if (gidset[n] == our_gid)
+			if ((gid_t)gidset[n] == our_gid)
 				break;
 		}
 		if (n == ngroups)
@@ -229,7 +229,7 @@ static int is_in_group(gid_t gid)
 
 	last_in = gid;
 	for (n = 0; n < ngroups; n++) {
-		if (gidset[n] == gid)
+		if ((gid_t)gidset[n] == gid)
 			return last_out = 1;
 	}
 	return last_out = 0;
diff --git a/usage.c b/usage.c
index db13535f4..f346385f4 100644
--- a/usage.c
+++ b/usage.c
@@ -1,7 +1,7 @@
 /*
  * Some usage & version related functions.
  *
- * Copyright (C) 2002-2020 Wayne Davison
+ * Copyright (C) 2002-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 as published by
@@ -22,9 +22,9 @@
 #include "latest-year.h"
 #include "git-version.h"
 #include "default-cvsignore.h"
+#include "itypes.h"
 
-extern struct name_num_obj valid_checksums;
-extern struct name_num_obj valid_compressions;
+extern struct name_num_obj valid_checksums, valid_compressions, valid_auth_checksums;
 
 static char *istring(const char *fmt, int val)
 {
@@ -37,7 +37,8 @@ static char *istring(const char *fmt, int val)
 static void print_info_flags(enum logcode f)
 {
 	STRUCT_STAT *dumstat;
-	char line_buf[75];
+	BOOL as_json = f == FNONE ? 1 : 0; /* We use 1 == first attribute, 2 == need closing array */
+	char line_buf[75], item_buf[32];
 	int line_len, j;
 	char *info_flags[] = {
 
@@ -110,12 +111,12 @@ static void print_info_flags(enum logcode f)
 #endif
 			"xattrs",
 
-#ifdef RSYNC_USE_PROTECTED_ARGS
+#ifdef RSYNC_USE_SECLUDED_ARGS
 		"default "
 #else
 		"optional "
 #endif
-			"protect-args",
+			"secluded-args",
 
 #ifndef ICONV_OPTION
 		"no "
@@ -139,64 +140,162 @@ static void print_info_flags(enum logcode f)
 
 	"*Optimizations",
 
-#ifndef HAVE_SIMD
+#ifndef USE_ROLL_SIMD
 		"no "
 #endif
-			"SIMD",
+			"SIMD-roll",
 
-#ifndef HAVE_ASM
+#ifndef USE_ROLL_ASM
 		"no "
 #endif
-			"asm",
+			"asm-roll",
 
 #ifndef USE_OPENSSL
 		"no "
 #endif
 			"openssl-crypto",
 
+#ifndef USE_MD5_ASM
+		"no "
+#endif
+			"asm-MD5",
+
 		NULL
 	};
 
 	for (line_len = 0, j = 0; ; j++) {
 		char *str = info_flags[j], *next_nfo = str ? info_flags[j+1] : NULL;
-		int str_len = str && *str != '*' ? strlen(str) : 1000;
 		int need_comma = next_nfo && *next_nfo != '*' ? 1 : 0;
-		if (line_len && line_len + 1 + str_len + need_comma >= (int)sizeof line_buf) {
-			rprintf(f, "   %s\n", line_buf);
+		int item_len;
+		if (!str || *str == '*')
+			item_len = 1000;
+		else if (as_json) {
+			char *space = strchr(str, ' ');
+			int is_no = space && strncmp(str, "no ", 3) == 0;
+			int is_bits = space && isDigit(str);
+			char *quot = space && !is_no && !is_bits ? "\"" : "";
+			char *item = space ? space + 1 : str;
+			char *val = !space ? "true" : is_no ? "false" : str;
+			int val_len = !space ? 4 : is_no ? 5 : space - str;
+			if (is_bits && (space = strchr(val, '-')) != NULL)
+			    val_len = space - str;
+			item_len = snprintf(item_buf, sizeof item_buf,
+					   " \"%s%s\": %s%.*s%s%s", item, is_bits ? "bits" : "",
+					   quot, val_len, val, quot, need_comma ? "," : "");
+			if (is_bits)
+				item_buf[strlen(item)+2-1] = '_'; /* Turn the 's' into a '_' */
+			for (space = item; (space = strpbrk(space, " -")) != NULL; space++)
+				item_buf[space - item + 2] = '_';
+		} else
+			item_len = snprintf(item_buf, sizeof item_buf, " %s%s", str, need_comma ? "," : "");
+		if (line_len && line_len + item_len >= (int)sizeof line_buf) {
+			if (as_json)
+				printf("   %s\n", line_buf);
+			else
+				rprintf(f, "   %s\n", line_buf);
 			line_len = 0;
 		}
 		if (!str)
 			break;
 		if (*str == '*') {
-			rprintf(f, "%s:\n", str+1);
-			continue;
+			if (as_json) {
+				if (as_json == 2)
+					printf("  }");
+				else
+					as_json = 2;
+				printf(",\n  \"%c%s\": {\n", toLower(str+1), str+2);
+			} else
+				rprintf(f, "%s:\n", str+1);
+		} else {
+			strlcpy(line_buf + line_len, item_buf, sizeof line_buf - line_len);
+			line_len += item_len;
 		}
-		line_len += snprintf(line_buf+line_len, sizeof line_buf - line_len, " %s%s", str, need_comma ? "," : "");
 	}
+	if (as_json == 2)
+		printf("  }");
 }
 
-void print_rsync_version(enum logcode f)
+static void output_nno_list(enum logcode f, const char *name, struct name_num_obj *nno)
 {
-	char tmpbuf[256], *subprotocol = "";
+	char namebuf[64], tmpbuf[256];
+	char *tok, *next_tok, *comma = ",";
+	char *cp;
+
+	/* Using '(' ensures that we get a trailing "none" but also includes aliases. */
+	get_default_nno_list(nno, tmpbuf, sizeof tmpbuf - 1, '(');
+	if (f != FNONE) {
+		rprintf(f, "%s:\n", name);
+		rprintf(f, "    %s\n", tmpbuf);
+		return;
+	}
+
+	strlcpy(namebuf, name, sizeof namebuf);
+	for (cp = namebuf; *cp; cp++) {
+		if (*cp == ' ')
+			*cp = '_';
+		else if (isUpper(cp))
+			*cp = toLower(cp);
+	}
+
+	printf(",\n  \"%s\": [\n   ", namebuf);
+
+	for (tok = strtok(tmpbuf, " "); tok; tok = next_tok) {
+		next_tok = strtok(NULL, " ");
+		if (*tok != '(') /* Ignore the alises in the JSON output */
+			printf(" \"%s\"%s", tok, comma + (next_tok ? 0 : 1));
+	}
 
+	printf("\n  ]");
+}
+
+/* A request of f == FNONE wants json on stdout. */
+void print_rsync_version(enum logcode f)
+{
+	char copyright[] = "(C) 1996-" LATEST_YEAR " by Andrew Tridgell, Wayne Davison, and others.";
+	char url[] = "https://rsync.samba.org/";
+	BOOL first_line = 1;
+
+#define json_line(name, value) \
+	do { \
+		printf("%c\n  \"%s\": \"%s\"", first_line ? '{' : ',', name, value); \
+		first_line = 0; \
+	} while (0)
+
+	if (f == FNONE) {
+		char verbuf[32];
+		json_line("program", RSYNC_NAME);
+		json_line("version", rsync_version());
+		(void)snprintf(verbuf, sizeof verbuf, "%d.%d", PROTOCOL_VERSION, SUBPROTOCOL_VERSION);
+		json_line("protocol", verbuf);
+		json_line("copyright", copyright);
+		json_line("url", url);
+	} else {
 #if SUBPROTOCOL_VERSION != 0
-	subprotocol = istring(".PR%d", SUBPROTOCOL_VERSION);
+		char *subprotocol = istring(".PR%d", SUBPROTOCOL_VERSION);
+#else
+		char *subprotocol = "";
 #endif
-	rprintf(f, "%s  version %s  protocol version %d%s\n",
-		RSYNC_NAME, rsync_version(), PROTOCOL_VERSION, subprotocol);
-
-	rprintf(f, "Copyright (C) 1996-" LATEST_YEAR " by Andrew Tridgell, Wayne Davison, and others.\n");
-	rprintf(f, "Web site: https://rsync.samba.org/\n");
+		rprintf(f, "%s  version %s  protocol version %d%s\n",
+			RSYNC_NAME, rsync_version(), PROTOCOL_VERSION, subprotocol);
+		rprintf(f, "Copyright %s\n", copyright);
+		rprintf(f, "Web site: %s\n", url);
+	}
 
 	print_info_flags(f);
 
-	rprintf(f, "Checksum list:\n");
-	get_default_nno_list(&valid_checksums, tmpbuf, sizeof tmpbuf, '(');
-	rprintf(f, "    %s\n", tmpbuf);
+	init_checksum_choices();
 
-	rprintf(f, "Compress list:\n");
-	get_default_nno_list(&valid_compressions, tmpbuf, sizeof tmpbuf, '(');
-	rprintf(f, "    %s\n", tmpbuf);
+	output_nno_list(f, "Checksum list", &valid_checksums);
+	output_nno_list(f, "Compress list", &valid_compressions);
+	output_nno_list(f, "Daemon auth list", &valid_auth_checksums);
+
+	if (f == FNONE) {
+		json_line("license", "GPLv3");
+		json_line("caveat", "rsync comes with ABSOLUTELY NO WARRANTY");
+		printf("\n}\n");
+		fflush(stdout);
+		return;
+	}
 
 #ifdef MAINTAINER_MODE
 	rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
@@ -240,7 +339,7 @@ void usage(enum logcode F)
 #include "help-rsync.h"
   rprintf(F,"\n");
   rprintf(F,"Use \"rsync --daemon --help\" to see the daemon-mode command-line options.\n");
-  rprintf(F,"Please see the rsync(1) and rsyncd.conf(5) man pages for full documentation.\n");
+  rprintf(F,"Please see the rsync(1) and rsyncd.conf(5) manpages for full documentation.\n");
   rprintf(F,"See https://rsync.samba.org/ for updates, bug reports, and answers\n");
 }
 
@@ -253,16 +352,18 @@ void daemon_usage(enum logcode F)
 #include "help-rsyncd.h"
   rprintf(F,"\n");
   rprintf(F,"If you were not trying to invoke rsync as a daemon, avoid using any of the\n");
-  rprintf(F,"daemon-specific rsync options.  See also the rsyncd.conf(5) man page.\n");
+  rprintf(F,"daemon-specific rsync options.  See also the rsyncd.conf(5) manpage.\n");
 }
 
 const char *rsync_version(void)
 {
+	char *ver;
 #ifdef RSYNC_GITVER
-	return RSYNC_GITVER;
+	ver = RSYNC_GITVER;
 #else
-	return RSYNC_VERSION;
+	ver = RSYNC_VERSION;
 #endif
+	return *ver == 'v' ? ver+1 : ver;
 }
 
 const char *default_cvsignore(void)
diff --git a/util1.c b/util1.c
index 3f337d077..d84bc4140 100644
--- a/util1.c
+++ b/util1.c
@@ -4,7 +4,7 @@
  * Copyright (C) 1996-2000 Andrew Tridgell
  * Copyright (C) 1996 Paul Mackerras
  * Copyright (C) 2001, 2002 Martin Pool 
- * Copyright (C) 2003-2020 Wayne Davison
+ * 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 as published by
@@ -31,7 +31,6 @@ extern int do_fsync;
 extern int protect_args;
 extern int modify_window;
 extern int relative_paths;
-extern int preserve_mtimes;
 extern int preserve_xattrs;
 extern int omit_link_times;
 extern int preallocate_files;
@@ -320,44 +319,65 @@ static int safe_read(int desc, char *ptr, size_t len)
 	return n_chars;
 }
 
-/* Copy a file.  If ofd < 0, copy_file unlinks and opens the "dest" file.
- * Otherwise, it just writes to and closes the provided file descriptor.
+/* Remove existing file @dest and reopen, creating a new file with @mode */
+static int unlink_and_reopen(const char *dest, mode_t mode)
+{
+	int ofd;
+
+	if (robust_unlink(dest) && errno != ENOENT) {
+		int save_errno = errno;
+		rsyserr(FERROR_XFER, errno, "unlink %s", full_fname(dest));
+		errno = save_errno;
+		return -1;
+	}
+
+#ifdef SUPPORT_XATTRS
+	if (preserve_xattrs)
+		mode |= S_IWUSR;
+#endif
+	mode &= INITACCESSPERMS;
+	if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
+		int save_errno = errno;
+		rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(dest));
+		errno = save_errno;
+		return -1;
+	}
+	return ofd;
+}
+
+/* Copy contents of file @source to file @dest with mode @mode.
+ *
+ * If @tmpfilefd is < 0, copy_file unlinks @dest and then opens a new
+ * file with name @dest.
+ *
+ * Otherwise, copy_file writes to and closes the provided file
+ * descriptor.
+ *
  * In either case, if --xattrs are being preserved, the dest file will
  * have its xattrs set from the source file.
  *
  * This is used in conjunction with the --temp-dir, --backup, and
  * --copy-dest options. */
-int copy_file(const char *source, const char *dest, int ofd, mode_t mode)
+int copy_file(const char *source, const char *dest, int tmpfilefd, mode_t mode)
 {
-	int ifd;
+	int ifd, ofd;
 	char buf[1024 * 8];
 	int len;   /* Number of bytes read into `buf'. */
 	OFF_T prealloc_len = 0, offset = 0;
 
-	if ((ifd = do_open(source, O_RDONLY, 0)) < 0) {
+	if ((ifd = do_open_nofollow(source, O_RDONLY)) < 0) {
 		int save_errno = errno;
 		rsyserr(FERROR_XFER, errno, "open %s", full_fname(source));
 		errno = save_errno;
 		return -1;
 	}
 
-	if (ofd < 0) {
-		if (robust_unlink(dest) && errno != ENOENT) {
-			int save_errno = errno;
-			rsyserr(FERROR_XFER, errno, "unlink %s", full_fname(dest));
-			close(ifd);
-			errno = save_errno;
-			return -1;
-		}
-
-#ifdef SUPPORT_XATTRS
-		if (preserve_xattrs)
-			mode |= S_IWUSR;
-#endif
-		mode &= INITACCESSPERMS;
-		if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
+	if (tmpfilefd >= 0) {
+		ofd = tmpfilefd;
+	} else {
+		ofd = unlink_and_reopen(dest, mode);
+		if (ofd < 0) {
 			int save_errno = errno;
-			rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(dest));
 			close(ifd);
 			errno = save_errno;
 			return -1;
@@ -1298,7 +1318,14 @@ int handle_partial_dir(const char *fname, int create)
  *
  * "src" is the top source directory currently applicable at the level
  * of the referenced symlink.  This is usually the symlink's full path
- * (including its name), as referenced from the root of the transfer. */
+ * (including its name), as referenced from the root of the transfer.
+ *
+ * NOTE: this also rejects dest names with a .. component in other
+ * than the first component of the name ie. it rejects names such as
+ * a/b/../x/y. This needs to be done as the leading subpaths 'a' or
+ * 'b' could later be replaced with symlinks such as a link to '.'
+ * resulting in the link being transferred now becoming unsafe
+ */
 int unsafe_symlink(const char *dest, const char *src)
 {
 	const char *name, *slash;
@@ -1308,6 +1335,23 @@ int unsafe_symlink(const char *dest, const char *src)
 	if (!dest || !*dest || *dest == '/')
 		return 1;
 
+	// reject destinations with /../ in the name other than at the start of the name
+	const char *dest2 = dest;
+	while (strncmp(dest2, "../", 3) == 0) {
+	    dest2 += 3;
+	    while (*dest2 == '/') {
+		// allow for ..//..///../foo
+		dest2++;
+	    }
+	}
+	if (strstr(dest2, "/../"))
+	    return 1;
+
+	// reject if the destination ends in /..
+	const size_t dlen = strlen(dest);
+	if (dlen > 3 && strcmp(&dest[dlen-3], "/..") == 0)
+	    return 1;
+
 	/* find out what our safety margin is */
 	for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) {
 		/* ".." segment starts the count over.  "." segment is ignored. */
@@ -1467,12 +1511,19 @@ const char *find_filename_suffix(const char *fn, int fn_len, int *len_ptr)
 
 #define UNIT (1 << 16)
 
-uint32 fuzzy_distance(const char *s1, unsigned len1, const char *s2, unsigned len2)
+uint32 fuzzy_distance(const char *s1, unsigned len1, const char *s2, unsigned len2, uint32 upperlimit)
 {
 	uint32 a[MAXPATHLEN], diag, above, left, diag_inc, above_inc, left_inc;
 	int32 cost;
 	unsigned i1, i2;
 
+	/* Check to see if the Levenshtein distance must be greater than the
+	 * upper limit defined by the previously found lowest distance using
+	 * the heuristic that the Levenshtein distance is greater than the
+	 * difference in length of the two strings */
+	if ((len1 > len2 ? len1 - len2 : len2 - len1) * UNIT > upperlimit)
+		return 0xFFFFU * UNIT + 1;
+
 	if (!len1 || !len2) {
 		if (!len1) {
 			s1 = s2;
diff --git a/util2.c b/util2.c
index a8609a5d5..b59bff0a0 100644
--- a/util2.c
+++ b/util2.c
@@ -4,7 +4,7 @@
  * Copyright (C) 1996-2000 Andrew Tridgell
  * Copyright (C) 1996 Paul Mackerras
  * Copyright (C) 2001, 2002 Martin Pool 
- * Copyright (C) 2003-2020 Wayne Davison
+ * Copyright (C) 2003-2024 Wayne Davison
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -72,7 +72,7 @@ int msleep(int t)
 
 void *my_alloc(void *ptr, size_t num, size_t size, const char *file, int line)
 {
-	if (max_alloc && num >= max_alloc/size) {
+	if (num >= max_alloc/size) {
 		if (!file)
 			return NULL;
 		rprintf(FERROR, "[%s] exceeded --max-alloc=%s setting (file=%s, line=%d)\n",
diff --git a/version.h b/version.h
index da21e0fdc..7f18570de 100644
--- a/version.h
+++ b/version.h
@@ -1 +1,2 @@
-#define RSYNC_VERSION "3.2.4dev"
+#define RSYNC_VERSION "3.4.2dev"
+#define MAINTAINER_TZ_OFFSET -7.0
diff --git a/xattrs.c b/xattrs.c
index 3c5491920..26e50a6f9 100644
--- a/xattrs.c
+++ b/xattrs.c
@@ -3,7 +3,7 @@
  * Written by Jay Fenlason, vaguely based on the ACLs patch.
  *
  * Copyright (C) 2004 Red Hat, Inc.
- * Copyright (C) 2006-2020 Wayne Davison
+ * Copyright (C) 2006-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 as published by
@@ -39,9 +39,13 @@ extern int preserve_specials;
 extern int checksum_seed;
 extern int saw_xattr_filter;
 
+extern struct name_num_item *xattr_sum_nni;
+extern int xattr_sum_len;
+
 #define RSYNC_XAL_INITIAL 5
 #define RSYNC_XAL_LIST_INITIAL 100
 
+#define MAX_XATTR_DIGEST_LEN MD5_DIGEST_LEN
 #define MAX_FULL_DATUM 32
 
 #define HAS_PREFIX(str, prfx) (*(str) == *(prfx) && strncmp(str, prfx, sizeof (prfx) - 1) == 0)
@@ -269,8 +273,8 @@ static int rsync_xal_get(const char *fname, item_list *xalp)
 
 		if (datum_len > MAX_FULL_DATUM) {
 			/* For large datums, we store a flag and a checksum. */
-			name_offset = 1 + MAX_DIGEST_LEN;
-			sum_init(-1, checksum_seed);
+			name_offset = 1 + MAX_XATTR_DIGEST_LEN;
+			sum_init(xattr_sum_nni, checksum_seed);
 			sum_update(ptr, datum_len);
 			free(ptr);
 
@@ -377,20 +381,14 @@ static int64 xattr_lookup_hash(const item_list *xalp)
 {
 	const rsync_xa *rxas = xalp->items;
 	size_t i;
-	int64 key = hashlittle(&xalp->count, sizeof xalp->count);
+	int64 key = hashlittle2(&xalp->count, sizeof xalp->count);
 
 	for (i = 0; i < xalp->count; i++) {
-		key += hashlittle(rxas[i].name, rxas[i].name_len);
+		key += hashlittle2(rxas[i].name, rxas[i].name_len);
 		if (rxas[i].datum_len > MAX_FULL_DATUM)
-			key += hashlittle(rxas[i].datum, MAX_DIGEST_LEN);
+			key += hashlittle2(rxas[i].datum, xattr_sum_len);
 		else
-			key += hashlittle(rxas[i].datum, rxas[i].datum_len);
-	}
-
-	if (key == 0) {
-		/* This is very unlikely, but we should never
-		 * return 0 as hashtable_find() doesn't like it. */
-		return 1;
+			key += hashlittle2(rxas[i].datum, rxas[i].datum_len);
 	}
 
 	return key;
@@ -435,7 +433,7 @@ static int find_matching_xattr(const item_list *xalp)
 			if (rxas1[j].datum_len > MAX_FULL_DATUM) {
 				if (memcmp(rxas1[j].datum + 1,
 					   rxas2[j].datum + 1,
-					   MAX_DIGEST_LEN) != 0)
+					   xattr_sum_len) != 0)
 					break;
 			} else {
 				if (memcmp(rxas1[j].datum, rxas2[j].datum,
@@ -471,8 +469,6 @@ static int rsync_xal_store(item_list *xalp)
 
 	if (rsync_xal_h == NULL)
 		rsync_xal_h = hashtable_create(512, HT_KEY64);
-	if (rsync_xal_h == NULL)
-		out_of_memory("rsync_xal_h hashtable_create()");
 
 	new_ref = new0(rsync_xa_list_ref);
 	new_ref->ndx = ndx;
@@ -535,7 +531,7 @@ int send_xattr(int f, stat_x *sxp)
 #endif
 			write_buf(f, name, name_len);
 			if (rxa->datum_len > MAX_FULL_DATUM)
-				write_buf(f, rxa->datum + 1, MAX_DIGEST_LEN);
+				write_buf(f, rxa->datum + 1, xattr_sum_len);
 			else
 				write_bigbuf(f, rxa->datum, rxa->datum_len);
 		}
@@ -588,7 +584,7 @@ int xattr_diff(struct file_struct *file, stat_x *sxp, int find_all)
 		else if (snd_rxa->datum_len > MAX_FULL_DATUM) {
 			same = cmp == 0 && snd_rxa->datum_len == rec_rxa->datum_len
 			    && memcmp(snd_rxa->datum + 1, rec_rxa->datum + 1,
-				      MAX_DIGEST_LEN) == 0;
+				      xattr_sum_len) == 0;
 			/* Flag unrequested items that we need. */
 			if (!same && find_all && snd_rxa->datum[0] == XSTATE_ABBREV)
 				snd_rxa->datum[0] = XSTATE_TODO;
@@ -797,7 +793,7 @@ void receive_xattr(int f, struct file_struct *file)
 		rsync_xa *rxa;
 		size_t name_len = read_varint(f);
 		size_t datum_len = read_varint(f);
-		size_t dget_len = datum_len > MAX_FULL_DATUM ? 1 + MAX_DIGEST_LEN : datum_len;
+		size_t dget_len = datum_len > MAX_FULL_DATUM ? 1 + (size_t)xattr_sum_len : datum_len;
 		size_t extra_len = MIGHT_NEED_RPRE ? RPRE_LEN : 0;
 		if (SIZE_MAX - dget_len < extra_len || SIZE_MAX - dget_len - extra_len < name_len)
 			overflow_exit("receive_xattr");
@@ -812,7 +808,7 @@ void receive_xattr(int f, struct file_struct *file)
 			read_buf(f, ptr, dget_len);
 		else {
 			*ptr = XSTATE_ABBREV;
-			read_buf(f, ptr + 1, MAX_DIGEST_LEN);
+			read_buf(f, ptr + 1, xattr_sum_len);
 		}
 
 		if (saw_xattr_filter) {
@@ -943,7 +939,7 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
 	rsync_xa *rxas = xalp->items;
 	ssize_t list_len;
 	size_t i, len;
-	char *name, *ptr, sum[MAX_DIGEST_LEN];
+	char *name, *ptr, sum[MAX_XATTR_DIGEST_LEN];
 #ifdef HAVE_LINUX_XATTRS
 	int user_only = am_root <= 0;
 #endif
@@ -958,7 +954,6 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
 		name = rxas[i].name;
 
 		if (XATTR_ABBREV(rxas[i])) {
-			int sum_len;
 			/* See if the fnamecmp version is identical. */
 			len = name_len = rxas[i].name_len;
 			if ((ptr = get_xattr_data(fnamecmp, name, &len, 1)) == NULL) {
@@ -975,10 +970,10 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
 				goto still_abbrev;
 			}
 
-			sum_init(-1, checksum_seed);
+			sum_init(xattr_sum_nni, checksum_seed);
 			sum_update(ptr, len);
-			sum_len = sum_end(sum);
-			if (memcmp(sum, rxas[i].datum + 1, sum_len) != 0) {
+			sum_end(sum);
+			if (memcmp(sum, rxas[i].datum + 1, xattr_sum_len) != 0) {
 				free(ptr);
 				goto still_abbrev;
 			}
diff --git a/zlib/deflate.c b/zlib/deflate.c
index 529c5e8d9..2cbc4fc2f 100644
--- a/zlib/deflate.c
+++ b/zlib/deflate.c
@@ -227,11 +227,6 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
     int wrap = 1;
     static const char my_version[] = ZLIB_VERSION;
 
-    ushf *overlay;
-    /* We overlay pending_buf and d_buf+l_buf. This works since the average
-     * output size for (length,distance) codes is <= 24 bits.
-     */
-
     if (version == Z_NULL || version[0] != my_version[0] ||
         stream_size != sizeof(z_stream)) {
         return Z_VERSION_ERROR;
@@ -300,9 +295,47 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
 
     s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */
 
-    overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
-    s->pending_buf = (uchf *) overlay;
-    s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L);
+    /* We overlay pending_buf and sym_buf. This works since the average size
+     * for length/distance pairs over any compressed block is assured to be 31
+     * bits or less.
+     *
+     * Analysis: The longest fixed codes are a length code of 8 bits plus 5
+     * extra bits, for lengths 131 to 257. The longest fixed distance codes are
+     * 5 bits plus 13 extra bits, for distances 16385 to 32768. The longest
+     * possible fixed-codes length/distance pair is then 31 bits total.
+     *
+     * sym_buf starts one-fourth of the way into pending_buf. So there are
+     * three bytes in sym_buf for every four bytes in pending_buf. Each symbol
+     * in sym_buf is three bytes -- two for the distance and one for the
+     * literal/length. As each symbol is consumed, the pointer to the next
+     * sym_buf value to read moves forward three bytes. From that symbol, up to
+     * 31 bits are written to pending_buf. The closest the written pending_buf
+     * bits gets to the next sym_buf symbol to read is just before the last
+     * code is written. At that time, 31*(n-2) bits have been written, just
+     * after 24*(n-2) bits have been consumed from sym_buf. sym_buf starts at
+     * 8*n bits into pending_buf. (Note that the symbol buffer fills when n-1
+     * symbols are written.) The closest the writing gets to what is unread is
+     * then n+14 bits. Here n is lit_bufsize, which is 16384 by default, and
+     * can range from 128 to 32768.
+     *
+     * Therefore, at a minimum, there are 142 bits of space between what is
+     * written and what is read in the overlain buffers, so the symbols cannot
+     * be overwritten by the compressed data. That space is actually 139 bits,
+     * due to the three-bit fixed-code block header.
+     *
+     * That covers the case where either Z_FIXED is specified, forcing fixed
+     * codes, or when the use of fixed codes is chosen, because that choice
+     * results in a smaller compressed block than dynamic codes. That latter
+     * condition then assures that the above analysis also covers all dynamic
+     * blocks. A dynamic-code block will only be chosen to be emitted if it has
+     * fewer bits than a fixed-code block would for the same set of symbols.
+     * Therefore its average symbol length is assured to be less than 31. So
+     * the compressed data for a dynamic block also cannot overwrite the
+     * symbols from which it is being constructed.
+     */
+
+    s->pending_buf = (uchf *) ZALLOC(strm, s->lit_bufsize, 4);
+    s->pending_buf_size = (ulg)s->lit_bufsize * 4;
 
     if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL ||
         s->pending_buf == Z_NULL) {
@@ -311,8 +344,12 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
         deflateEnd (strm);
         return Z_MEM_ERROR;
     }
-    s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
-    s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+    s->sym_buf = s->pending_buf + s->lit_bufsize;
+    s->sym_end = (s->lit_bufsize - 1) * 3;
+    /* We avoid equality with lit_bufsize*3 because of wraparound at 64K
+     * on 16 bit machines and because stored blocks are restricted to
+     * 64K-1 bytes.
+     */
 
     s->level = level;
     s->strategy = strategy;
@@ -473,7 +510,7 @@ int ZEXPORT deflatePrime (strm, bits, value)
 
     if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
     s = strm->state;
-    if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3))
+    if (s->sym_buf < s->pending_out + ((Buf_size + 7) >> 3))
         return Z_BUF_ERROR;
     do {
         put = Buf_size - s->bi_valid;
@@ -1022,7 +1059,6 @@ int ZEXPORT deflateCopy (dest, source)
 #else
     deflate_state *ds;
     deflate_state *ss;
-    ushf *overlay;
 
 
     if (source == Z_NULL || dest == Z_NULL || source->state == Z_NULL) {
@@ -1042,8 +1078,7 @@ int ZEXPORT deflateCopy (dest, source)
     ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte));
     ds->prev   = (Posf *)  ZALLOC(dest, ds->w_size, sizeof(Pos));
     ds->head   = (Posf *)  ZALLOC(dest, ds->hash_size, sizeof(Pos));
-    overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2);
-    ds->pending_buf = (uchf *) overlay;
+    ds->pending_buf = (uchf *) ZALLOC(dest, ds->lit_bufsize, 4);
 
     if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL ||
         ds->pending_buf == Z_NULL) {
@@ -1057,8 +1092,7 @@ int ZEXPORT deflateCopy (dest, source)
     zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size);
 
     ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf);
-    ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush);
-    ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize;
+    ds->sym_buf = ds->pending_buf + ds->lit_bufsize;
 
     ds->l_desc.dyn_tree = ds->dyn_ltree;
     ds->d_desc.dyn_tree = ds->dyn_dtree;
@@ -1737,7 +1771,7 @@ local block_state deflate_fast(s, flush)
         FLUSH_BLOCK(s, 1);
         return finish_done;
     }
-    if (s->last_lit)
+    if (s->sym_next)
         FLUSH_BLOCK(s, 0);
     return block_done;
 }
@@ -1878,7 +1912,7 @@ local block_state deflate_slow(s, flush)
         FLUSH_BLOCK(s, 1);
         return finish_done;
     }
-    if (s->last_lit)
+    if (s->sym_next)
         FLUSH_BLOCK(s, 0);
     return block_done;
 }
@@ -1953,7 +1987,7 @@ local block_state deflate_rle(s, flush)
         FLUSH_BLOCK(s, 1);
         return finish_done;
     }
-    if (s->last_lit)
+    if (s->sym_next)
         FLUSH_BLOCK(s, 0);
     return block_done;
 }
@@ -1992,7 +2026,7 @@ local block_state deflate_huff(s, flush)
         FLUSH_BLOCK(s, 1);
         return finish_done;
     }
-    if (s->last_lit)
+    if (s->sym_next)
         FLUSH_BLOCK(s, 0);
     return block_done;
 }
diff --git a/zlib/deflate.h b/zlib/deflate.h
index ce0299edd..a300a28d2 100644
--- a/zlib/deflate.h
+++ b/zlib/deflate.h
@@ -214,7 +214,7 @@ typedef struct internal_state {
     /* Depth of each subtree used as tie breaker for trees of equal frequency
      */
 
-    uchf *l_buf;          /* buffer for literals or lengths */
+    uchf *sym_buf;        /* buffer for distances and literals/lengths */
 
     uInt  lit_bufsize;
     /* Size of match buffer for literals/lengths.  There are 4 reasons for
@@ -236,13 +236,8 @@ typedef struct internal_state {
      *   - I can't count above 4
      */
 
-    uInt last_lit;      /* running index in l_buf */
-
-    ushf *d_buf;
-    /* Buffer for distances. To simplify the code, d_buf and l_buf have
-     * the same number of elements. To use different lengths, an extra flag
-     * array would be necessary.
-     */
+    uInt sym_next;      /* running index in sym_buf */
+    uInt sym_end;       /* symbol table full when sym_next reaches this */
 
     ulg opt_len;        /* bit length of current block with optimal trees */
     ulg static_len;     /* bit length of current block with static trees */
@@ -322,20 +317,22 @@ void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf,
 
 # define _tr_tally_lit(s, c, flush) \
   { uch cc = (c); \
-    s->d_buf[s->last_lit] = 0; \
-    s->l_buf[s->last_lit++] = cc; \
+    s->sym_buf[s->sym_next++] = 0; \
+    s->sym_buf[s->sym_next++] = 0; \
+    s->sym_buf[s->sym_next++] = cc; \
     s->dyn_ltree[cc].Freq++; \
-    flush = (s->last_lit == s->lit_bufsize-1); \
+    flush = (s->sym_next == s->sym_end); \
    }
 # define _tr_tally_dist(s, distance, length, flush) \
   { uch len = (length); \
     ush dist = (distance); \
-    s->d_buf[s->last_lit] = dist; \
-    s->l_buf[s->last_lit++] = len; \
+    s->sym_buf[s->sym_next++] = dist; \
+    s->sym_buf[s->sym_next++] = dist >> 8; \
+    s->sym_buf[s->sym_next++] = len; \
     dist--; \
     s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \
     s->dyn_dtree[d_code(dist)].Freq++; \
-    flush = (s->last_lit == s->lit_bufsize-1); \
+    flush = (s->sym_next == s->sym_end); \
   }
 #else
 # define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c)
diff --git a/zlib/inflate.c b/zlib/inflate.c
index e43abd9e0..e9840b679 100644
--- a/zlib/inflate.c
+++ b/zlib/inflate.c
@@ -740,8 +740,9 @@ int flush;
                 if (copy > have) copy = have;
                 if (copy) {
                     if (state->head != Z_NULL &&
-                        state->head->extra != Z_NULL) {
-                        len = state->head->extra_len - state->length;
+                        state->head->extra != Z_NULL &&
+                        (len = state->head->extra_len - state->length) <
+                            state->head->extra_max) {
                         zmemcpy(state->head->extra + len, next,
                                 len + copy > state->head->extra_max ?
                                 state->head->extra_max - len : copy);
diff --git a/zlib/trees.c b/zlib/trees.c
index 1fd7759ef..9c667702c 100644
--- a/zlib/trees.c
+++ b/zlib/trees.c
@@ -418,7 +418,7 @@ local void init_block(s)
 
     s->dyn_ltree[END_BLOCK].Freq = 1;
     s->opt_len = s->static_len = 0L;
-    s->last_lit = s->matches = 0;
+    s->sym_next = s->matches = 0;
 }
 
 #define SMALLEST 1
@@ -943,7 +943,7 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
 
         Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
                 opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
-                s->last_lit));
+                s->sym_next / 3));
 
         if (static_lenb <= opt_lenb) opt_lenb = static_lenb;
 
@@ -1012,8 +1012,9 @@ int ZLIB_INTERNAL _tr_tally (s, dist, lc)
     unsigned dist;  /* distance of matched string */
     unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
 {
-    s->d_buf[s->last_lit] = (ush)dist;
-    s->l_buf[s->last_lit++] = (uch)lc;
+    s->sym_buf[s->sym_next++] = dist;
+    s->sym_buf[s->sym_next++] = dist >> 8;
+    s->sym_buf[s->sym_next++] = lc;
     if (dist == 0) {
         /* lc is the unmatched char */
         s->dyn_ltree[lc].Freq++;
@@ -1028,30 +1029,7 @@ int ZLIB_INTERNAL _tr_tally (s, dist, lc)
         s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++;
         s->dyn_dtree[d_code(dist)].Freq++;
     }
-
-#ifdef TRUNCATE_BLOCK
-    /* Try to guess if it is profitable to stop the current block here */
-    if ((s->last_lit & 0x1fff) == 0 && s->level > 2) {
-        /* Compute an upper bound for the compressed length */
-        ulg out_length = (ulg)s->last_lit*8L;
-        ulg in_length = (ulg)((long)s->strstart - s->block_start);
-        int dcode;
-        for (dcode = 0; dcode < D_CODES; dcode++) {
-            out_length += (ulg)s->dyn_dtree[dcode].Freq *
-                (5L+extra_dbits[dcode]);
-        }
-        out_length >>= 3;
-        Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
-               s->last_lit, in_length, out_length,
-               100L - out_length*100L/in_length));
-        if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1;
-    }
-#endif
-    return (s->last_lit == s->lit_bufsize-1);
-    /* We avoid equality with lit_bufsize because of wraparound at 64K
-     * on 16 bit machines and because stored blocks are restricted to
-     * 64K-1 bytes.
-     */
+    return (s->sym_next == s->sym_end);
 }
 
 /* ===========================================================================
@@ -1064,13 +1042,14 @@ local void compress_block(s, ltree, dtree)
 {
     unsigned dist;      /* distance of matched string */
     int lc;             /* match length or unmatched char (if dist == 0) */
-    unsigned lx = 0;    /* running index in l_buf */
+    unsigned sx = 0;    /* running index in sym_buf */
     unsigned code;      /* the code to send */
     int extra;          /* number of extra bits to send */
 
-    if (s->last_lit != 0) do {
-        dist = s->d_buf[lx];
-        lc = s->l_buf[lx++];
+    if (s->sym_next != 0) do {
+        dist = s->sym_buf[sx++] & 0xff;
+        dist += (unsigned)(s->sym_buf[sx++] & 0xff) << 8;
+        lc = s->sym_buf[sx++];
         if (dist == 0) {
             send_code(s, lc, ltree); /* send a literal byte */
             Tracecv(isgraph(lc), (stderr," '%c' ", lc));
@@ -1095,11 +1074,10 @@ local void compress_block(s, ltree, dtree)
             }
         } /* literal or match pair ? */
 
-        /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
-        Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
-               "pendingBuf overflow");
+        /* Check that the overlay between pending_buf and sym_buf is ok: */
+        Assert(s->pending < s->lit_bufsize + sx, "pendingBuf overflow");
 
-    } while (lx < s->last_lit);
+    } while (sx < s->sym_next);
 
     send_code(s, END_BLOCK, ltree);
 }