summaryrefslogtreecommitdiffstats
path: root/testsuite
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 16:14:31 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 16:14:31 +0000
commit2d5707c7479eacb3b1ad98e01b53f56a88f8fb78 (patch)
treed9c334e83692851c02e3e1b8e65570c97bc82481 /testsuite
parentInitial commit. (diff)
downloadrsync-2d5707c7479eacb3b1ad98e01b53f56a88f8fb78.tar.xz
rsync-2d5707c7479eacb3b1ad98e01b53f56a88f8fb78.zip
Adding upstream version 3.2.7.upstream/3.2.7
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testsuite')
-rw-r--r--testsuite/00-hello.test61
-rw-r--r--testsuite/README.testsuite28
-rw-r--r--testsuite/acls-default.test66
-rw-r--r--testsuite/acls.test62
-rw-r--r--testsuite/alt-dest.test68
-rw-r--r--testsuite/atimes.test19
-rw-r--r--testsuite/backup.test63
-rw-r--r--testsuite/batch-mode.test51
-rw-r--r--testsuite/chgrp.test29
-rw-r--r--testsuite/chmod-option.test71
-rw-r--r--testsuite/chmod-temp-dir.test41
-rw-r--r--testsuite/chmod.test30
-rw-r--r--testsuite/chown.test86
-rw-r--r--testsuite/crtimes.test26
-rw-r--r--testsuite/daemon-gzip-download.test37
-rw-r--r--testsuite/daemon-gzip-upload.test31
-rw-r--r--testsuite/daemon.test90
-rw-r--r--testsuite/delay-updates.test21
-rw-r--r--testsuite/delete.test57
-rw-r--r--testsuite/devices.test171
-rw-r--r--testsuite/dir-sgid.test48
-rw-r--r--testsuite/duplicates.test44
l---------testsuite/exclude-lsh.test1
-rw-r--r--testsuite/exclude.test252
-rw-r--r--testsuite/executability.test47
-rw-r--r--testsuite/files-from.test45
-rw-r--r--testsuite/fuzzy.test24
-rw-r--r--testsuite/hands.test38
-rw-r--r--testsuite/hardlinks.test81
-rw-r--r--testsuite/itemize.test246
-rw-r--r--testsuite/longdir.test26
-rw-r--r--testsuite/merge.test57
-rw-r--r--testsuite/missing.test34
-rw-r--r--testsuite/mkpath.test47
-rw-r--r--testsuite/protected-regular.test31
-rw-r--r--testsuite/relative.test60
-rw-r--r--testsuite/rsync.fns498
-rw-r--r--testsuite/ssh-basic.test34
-rw-r--r--testsuite/symlink-ignore.test34
-rw-r--r--testsuite/trimslash.test26
-rw-r--r--testsuite/unsafe-byname.test58
-rw-r--r--testsuite/unsafe-links.test65
-rw-r--r--testsuite/wildmatch.test23
-rw-r--r--testsuite/xattrs.test239
44 files changed, 3166 insertions, 0 deletions
diff --git a/testsuite/00-hello.test b/testsuite/00-hello.test
new file mode 100644
index 0000000..ebd0683
--- /dev/null
+++ b/testsuite/00-hello.test
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+# Test some foundational things.
+
+. "$suitedir/rsync.fns"
+
+RSYNC_RSH="$scratchdir/src/support/lsh.sh"
+export RSYNC_RSH
+
+echo $0 running
+
+$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/README.testsuite b/testsuite/README.testsuite
new file mode 100644
index 0000000..782cb1c
--- /dev/null
+++ b/testsuite/README.testsuite
@@ -0,0 +1,28 @@
+automatic testsuite for rsync -*- text -*-
+
+We're trying to develop some more substantial tests to prevent rsync
+regressions. Ideally, all code changes or bug reports would come with
+an appropriate test suite.
+
+You can run these tests by typing "make check" in the build directory.
+The tests will run using the rsync binary in the build directory, so
+you do not need to do "make install" first. Indeed, you probably
+should not install rsync before running the tests.
+
+If you instead type "make installcheck" then the suite will test the
+rsync binary from its installed location (e.g. /usr/local/bin/rsync).
+You can use this to test a distribution build, or perhaps to run a new
+test suite against an old version of rsync. Note that in accordance
+with the GNU Standards, installcheck does not look for rsync on the
+path.
+
+If the tests pass, you should see a report to that effect. Some tests
+require being root or some other precondition, and so will normally not
+be checked -- look at the test scripts for more information.
+
+If the tests fail, you will see rather more output. The scratch
+directory will remain in the build directory. It would be useful if
+you could include the log messages when reporting a failure.
+
+These tests also run automatically on the build farm, and you can see
+the results on http://build.samba.org/.
diff --git a/testsuite/acls-default.test b/testsuite/acls-default.test
new file mode 100644
index 0000000..d8fba7f
--- /dev/null
+++ b/testsuite/acls-default.test
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that rsync obeys default ACLs. -- Matt McCutchen
+
+. $suitedir/rsync.fns
+
+$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" ;;
+*-k*) opts='-dm u::7,g::5,o:5' ;;
+*) opts='-m d:u::7,d:g::5,d:o:5' ;;
+esac
+setfacl $opts "$scratchdir" || test_skipped "Your filesystem has ACLs disabled"
+
+# Call as: testit <dirname> <default-acl> <file-expected> <program-expected>
+testit() {
+ todir="$scratchdir/$1"
+ mkdir "$todir"
+ $setfacl_nodef "$todir"
+ if [ -n "$2" ]; then
+ case "$setfacl_nodef" in
+ *-k*) opts="-dm $2" ;;
+ *) opts="-m `echo $2 | sed 's/\([ugom]:\)/d:\1/g'`"
+ esac
+ setfacl $opts "$todir"
+ fi
+ # Make sure we obey ACLs when creating a directory to hold multiple transferred files,
+ # even though the directory itself is outside the transfer
+ $RSYNC -rvv "$scratchdir/dir" "$scratchdir/file" "$scratchdir/program" "$todir/to/"
+ check_perms "$todir/to" $4 "Target $1"
+ check_perms "$todir/to/dir" $4 "Target $1"
+ check_perms "$todir/to/file" $3 "Target $1"
+ check_perms "$todir/to/program" $4 "Target $1"
+ # Make sure get_local_name doesn't mess us up when transferring only one file
+ $RSYNC -rvv "$scratchdir/file" "$todir/to/anotherfile"
+ check_perms "$todir/to/anotherfile" $3 "Target $1"
+ # Make sure we obey default ACLs when not transferring a regular file
+ $RSYNC -rvv "$scratchdir/dir/" "$todir/to/anotherdir/"
+ check_perms "$todir/to/anotherdir" $4 "Target $1"
+}
+
+mkdir "$scratchdir/dir"
+echo "File!" >"$scratchdir/file"
+echo "#!/bin/sh" >"$scratchdir/program"
+chmod 777 "$scratchdir/dir"
+chmod 666 "$scratchdir/file"
+chmod 777 "$scratchdir/program"
+
+# Test some target directories
+umask 0077
+testit da777 u::7,g::7,o:7 rw-rw-rw- rwxrwxrwx
+testit da775 u::7,g::7,o:5 rw-rw-r-- rwxrwxr-x
+testit da750 u::7,g::5,o:0 rw-r----- rwxr-x---
+testit da750mask u::7,u:0:7,g::7,m:5,o:0 rw-r----- rwxr-x---
+testit noda1 '' rw------- rwx------
+umask 0000
+testit noda2 '' rw-rw-rw- rwxrwxrwx
+umask 0022
+testit noda3 '' rw-r--r-- rwxr-xr-x
+
+# Hooray
+exit 0
diff --git a/testsuite/acls.test b/testsuite/acls.test
new file mode 100644
index 0000000..693da66
--- /dev/null
+++ b/testsuite/acls.test
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that rsync handles basic ACL preservation.
+
+. $suitedir/rsync.fns
+
+$RSYNC -VV | grep '"ACLs": true' >/dev/null || test_skipped "Rsync is configured without ACL support"
+
+makepath "$fromdir/foo"
+echo something >"$fromdir/file1"
+echo else >"$fromdir/file2"
+
+files='foo file1 file2'
+
+case "$setfacl_nodef" in
+true)
+ if ! chmod --help 2>&1 | grep -F +a >/dev/null; then
+ test_skipped "I don't know how to use setfacl or chmod for ACLs"
+ fi
+ chmod +a "root allow read,write,execute" "$fromdir/foo" || test_skipped "Your filesystem has ACLs disabled"
+ chmod +a "root allow read,execute" "$fromdir/file1"
+ chmod +a "admin allow read" "$fromdir/file1"
+ chmod +a "daemon allow read,write" "$fromdir/file1"
+ chmod +a "root allow read,execute" "$fromdir/file2"
+
+ see_acls() {
+ ls -le "${@}"
+ }
+ ;;
+*)
+ setfacl -m u:0:7 "$fromdir/foo" || test_skipped "Your filesystem has ACLs disabled"
+ setfacl -m g:1:5 "$fromdir/foo"
+ setfacl -m g:2:1 "$fromdir/foo"
+ setfacl -m g:0:7 "$fromdir/foo"
+ setfacl -m u:2:1 "$fromdir/foo"
+ setfacl -m u:1:5 "$fromdir/foo"
+
+ setfacl -m u:0:5 "$fromdir/file1"
+ setfacl -m g:0:4 "$fromdir/file1"
+ setfacl -m u:1:6 "$fromdir/file1"
+
+ setfacl -m u:0:5 "$fromdir/file2"
+
+ see_acls() {
+ getfacl "${@}"
+ }
+ ;;
+esac
+
+cd "$fromdir"
+$RSYNC -avvA $files "$todir/"
+
+see_acls $files >"$scratchdir/acls.txt"
+
+cd "$todir"
+see_acls $files | diff $diffopt "$scratchdir/acls.txt" -
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/alt-dest.test b/testsuite/alt-dest.test
new file mode 100644
index 0000000..d2fb5a1
--- /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
new file mode 100644
index 0000000..4d46eb0
--- /dev/null
+++ b/testsuite/atimes.test
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# Test rsync copying atimes
+
+. "$suitedir/rsync.fns"
+
+$RSYNC -VV | grep '"atimes": true' >/dev/null || test_skipped "Rsync is configured without atimes support"
+
+mkdir "$fromdir"
+
+touch "$fromdir/foo"
+touch -a -t 200102031717.42 "$fromdir/foo"
+
+TLS_ARGS=--atimes
+
+checkit "$RSYNC -rtUgvvv \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/backup.test b/testsuite/backup.test
new file mode 100644
index 0000000..4de3867
--- /dev/null
+++ b/testsuite/backup.test
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+# Copyright (C) 2004-2022 Wayne Davison
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that the --backup option works right.
+
+. "$suitedir/rsync.fns"
+
+bakdir="$tmpdir/bak"
+
+makepath "$fromdir/deep" "$bakdir/dname"
+name1="$fromdir/deep/name1"
+name2="$fromdir/deep/name2"
+
+cat "$srcdir"/[gr]*.[ch] > "$name1"
+cat "$srcdir"/[et]*.[ch] > "$name2"
+
+checkit "$RSYNC -ai --info=backup '$fromdir/' '$todir/'" "$fromdir" "$todir"
+
+checkit "$RSYNC -ai --info=backup '$fromdir/' '$chkdir/'" "$fromdir" "$chkdir"
+cat "$srcdir"/[fgpr]*.[ch] > "$name1"
+cat "$srcdir"/[etw]*.[ch] > "$name2"
+
+checktee "$RSYNC -ai --info=backup --no-whole-file --backup '$fromdir/' '$todir/'"
+for fn in deep/name1 deep/name2; do
+ grep "backed up $fn to $fn~" "$outfile" >/dev/null || test_fail "no backup message output for $fn"
+ diff $diffopt "$fromdir/$fn" "$todir/$fn" || test_fail "copy of $fn failed"
+ diff $diffopt "$chkdir/$fn" "$todir/$fn~" || test_fail "backup of $fn to $fn~ failed"
+ mv "$todir/$fn~" "$todir/$fn"
+done
+
+echo deleted-file >"$todir/dname"
+cp_touch "$todir/dname" "$chkdir"
+
+checkit "$RSYNC -ai --info=backup --no-whole-file --delete-delay \
+ --backup --backup-dir='$bakdir' '$fromdir/' '$todir/'" "$fromdir" "$todir" \
+ | tee "$outfile"
+
+for fn in deep/name1 deep/name2; do
+ grep "backed up $fn to .*/$fn$" "$outfile" >/dev/null || test_fail "no backup message output for $fn"
+done
+diff -r $diffopt "$chkdir" "$bakdir" || test_fail "backup dir contents are bogus"
+rm "$bakdir/dname"
+
+checkit "$RSYNC -ai --info=backup --del '$fromdir/' '$chkdir/'" "$fromdir" "$chkdir"
+cat "$srcdir"/[efgr]*.[ch] > "$name1"
+cat "$srcdir"/[ew]*.[ch] > "$name2"
+
+checkit "$RSYNC -ai --info=backup --inplace --no-whole-file --backup --backup-dir='$bakdir' '$fromdir/' '$todir/'" "$fromdir" "$todir" \
+ | tee "$outfile"
+
+for fn in deep/name1 deep/name2; do
+ grep "backed up $fn to .*/$fn$" "$outfile" >/dev/null || test_fail "no backup message output for $fn"
+done
+diff -r $diffopt "$chkdir" "$bakdir" || test_fail "backup dir contents are bogus"
+
+checkit "$RSYNC -ai --info=backup --inplace --no-whole-file '$fromdir/' '$bakdir/'" "$fromdir" "$bakdir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/batch-mode.test b/testsuite/batch-mode.test
new file mode 100644
index 0000000..cf4e94d
--- /dev/null
+++ b/testsuite/batch-mode.test
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+# Copyright (C) 2004 by Chris Shoemaker <c.shoemaker@cox.net>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test rsync's --write-batch and --read-batch options
+
+. "$suitedir/rsync.fns"
+
+hands_setup
+
+cd "$tmpdir"
+
+# Build chkdir for the daemon tests using a normal rsync and an --exclude.
+$RSYNC -av --exclude=foobar.baz "$fromdir/" "$chkdir/"
+
+$RSYNC -av --only-write-batch=BATCH --exclude=foobar.baz "$fromdir/" "$todir/missing/"
+test -d "$todir/missing" && test_fail "--only-write-batch should not have created destination dir"
+
+runtest "--read-batch (only)" 'checkit "$RSYNC -av --read-batch=BATCH \"$todir\"" "$chkdir" "$todir"'
+
+rm -rf "$todir" BATCH*
+runtest "local --write-batch" 'checkit "$RSYNC -av --write-batch=BATCH \"$fromdir/\" \"$todir\"" "$fromdir" "$todir"'
+
+rm -rf "$todir"
+runtest "--read-batch" 'checkit "$RSYNC -av --read-batch=BATCH \"$todir\"" "$fromdir" "$todir"'
+
+build_rsyncd_conf
+
+RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon"
+export RSYNC_CONNECT_PROG
+
+rm -rf "$todir"
+runtest "daemon sender --write-batch" 'checkit "$RSYNC -av --write-batch=BATCH rsync://localhost/test-from/ \"$todir\"" "$chkdir" "$todir"'
+
+rm -rf "$todir"
+runtest "--read-batch from daemon" 'checkit "$RSYNC -av --read-batch=BATCH \"$todir\"" "$chkdir" "$todir"'
+
+rm -rf "$todir"
+runtest "BATCH.sh use of --read-batch" 'checkit "./BATCH.sh" "$chkdir" "$todir"'
+
+runtest "do-nothing re-run of batch" 'checkit "./BATCH.sh" "$chkdir" "$todir"'
+
+rm -rf "$todir"
+mkdir "$todir" || test_fail "failed to restore empty destination directory"
+runtest "daemon recv --write-batch" 'checkit "\"$ignore23\" $RSYNC -av --write-batch=BATCH \"$fromdir/\" rsync://localhost/test-to" "$chkdir" "$todir"'
+
+# The script would have aborted on error, so getting here means we pass.
+exit 0
diff --git a/testsuite/chgrp.test b/testsuite/chgrp.test
new file mode 100644
index 0000000..467d402
--- /dev/null
+++ b/testsuite/chgrp.test
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+# Copyright (C) 2002 by Martin Pool <mbp@samba.org>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that rsync with -gr will preserve groups when the user running
+# the test is a member of them. Hopefully they're in at least one
+# test.
+
+. "$suitedir/rsync.fns"
+
+# Build some hardlinks
+
+mygrps="`rsync_getgroups`" || test_fail "Can't get groups"
+mkdir "$fromdir"
+
+for g in $mygrps; do
+ name="$fromdir/foo-$g"
+ date > "$name"
+ chgrp "$g" "$name" || test_fail "Can't chgrp"
+done
+sleep 2
+
+checkit "$RSYNC -rtgpvvv '$fromdir/' '$todir/'" "$fromdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/chmod-option.test b/testsuite/chmod-option.test
new file mode 100644
index 0000000..ddf764c
--- /dev/null
+++ b/testsuite/chmod-option.test
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+# Copyright (C) 2002 by Martin Pool <mbp@samba.org>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that the --chmod option functions correctly.
+
+. $suitedir/rsync.fns
+
+# Build some files
+
+fromdir="$scratchdir/from"
+todir="$scratchdir/to"
+checkdir="$scratchdir/check"
+
+mkdir "$fromdir"
+name1="$fromdir/name1"
+name2="$fromdir/name2"
+dir1="$fromdir/dir1"
+dir2="$fromdir/dir2"
+echo "This is the file" > "$name1"
+echo "This is the other file" > "$name2"
+mkdir "$dir1" "$dir2"
+
+chmod 4700 "$name1" || test_skipped "Can't chmod"
+chmod 700 "$dir1"
+chmod 770 "$dir2"
+
+# Copy the files we've created over to another directory
+checkit "$RSYNC -avv '$fromdir/' '$checkdir/'" "$fromdir" "$checkdir"
+
+# And then manually make the changes which should occur
+umask 002
+chmod ug-s,a+rX "$checkdir"/*
+chmod +w "$checkdir" "$checkdir"/dir*
+
+checkit "$RSYNC -avv --chmod ug-s,a+rX,D+w '$fromdir/' '$todir/'" "$checkdir" "$todir"
+
+rm -r "$fromdir" "$checkdir" "$todir"
+makepath "$todir" "$fromdir/foo"
+touch "$fromdir/bar"
+
+checkit "$RSYNC -avv '$fromdir/' '$checkdir/'" "$fromdir" "$checkdir"
+chmod o+x "$fromdir"/bar
+
+checkit "$RSYNC -avv --chmod=Fo-x '$fromdir/' '$todir/'" "$checkdir" "$todir"
+
+# Tickle a bug in rsync 2.6.8: if you push a new directory with --perms off to
+# a daemon with an incoming chmod, the daemon pretends the directory is a file
+# for the purposes of the second application of the incoming chmod.
+
+build_rsyncd_conf
+cat >>"$scratchdir/test-rsyncd.conf" <<EOF
+[test-incoming-chmod]
+ path = $todir
+ read only = no
+ incoming chmod = Fo-x
+EOF
+
+RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon"
+export RSYNC_CONNECT_PROG
+
+rm -r "$todir"
+makepath "$todir"
+
+checkit "$RSYNC -avv --no-perms '$fromdir/' localhost::test-incoming-chmod/" "$checkdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/chmod-temp-dir.test b/testsuite/chmod-temp-dir.test
new file mode 100644
index 0000000..362d9d9
--- /dev/null
+++ b/testsuite/chmod-temp-dir.test
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+# Copyright (C) 2004-2022 Wayne Davison
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that various read-only and set[ug]id permissions work properly,
+# even when using a --temp-dir option (which we try to point at a
+# different filesystem than the destination dir).
+
+. "$suitedir/rsync.fns"
+
+hands_setup
+
+sdev=`$TOOLDIR/getfsdev $scratchdir`
+tdev=$sdev
+
+for tmpdir2 in "${RSYNC_TEST_TMP:-/override-tmp-not-specified}" /run/shm /var/tmp /tmp; do
+ [ -d "$tmpdir2" ] && [ -w "$tmpdir2" ] || continue
+ tdev=`$TOOLDIR/getfsdev "$tmpdir2"`
+ [ x$sdev != x$tdev ] && break
+done
+
+[ x$sdev = x$tdev ] && test_skipped "Can't find a tmp dir on a different file system"
+
+chmod 440 "$fromdir/text"
+chmod 500 "$fromdir/dir/text"
+e="$fromdir/dir/subdir/foobar.baz"
+chmod 6450 "$e" || chmod 2450 "$e" || chmod 1450 "$e" || chmod 450 "$e"
+e="$fromdir/dir/subdir/subsubdir/etc-ltr-list"
+chmod 2670 "$e" || chmod 1670 "$e" || chmod 670 "$e"
+
+# First a normal copy.
+runtest "normal copy" 'checkit "$RSYNC -avv --temp-dir=\"$tmpdir2\" \"$fromdir/\" \"$todir\"" "$fromdir" "$todir"'
+
+# Then we update all the files.
+runtest "update copy" 'checkit "$RSYNC -avvI --no-whole-file --temp-dir=\"$tmpdir2\" \"$fromdir/\" \"$todir\"" "$fromdir" "$todir"'
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/chmod.test b/testsuite/chmod.test
new file mode 100644
index 0000000..1646a9c
--- /dev/null
+++ b/testsuite/chmod.test
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+# Copyright (C) 2004-2022 Wayne Davison
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that various read-only and set[ug]id permissions work properly,
+# even when using a --temp-dir option (which we try to point at a
+# different filesystem than the destination dir).
+
+. "$suitedir/rsync.fns"
+
+hands_setup
+
+chmod 440 "$fromdir/text"
+chmod 500 "$fromdir/dir/text"
+e="$fromdir/dir/subdir/foobar.baz"
+chmod 6450 "$e" || chmod 2450 "$e" || chmod 1450 "$e" || chmod 450 "$e"
+e="$fromdir/dir/subdir/subsubdir/etc-ltr-list"
+chmod 2670 "$e" || chmod 1670 "$e" || chmod 670 "$e"
+
+# First a normal copy.
+runtest "normal copy" 'checkit "$RSYNC -avv \"$fromdir/\" \"$todir\"" "$fromdir" "$todir"'
+
+# Then we update all the files.
+runtest "update copy" 'checkit "$RSYNC -avvI --no-whole-file \"$fromdir/\" \"$todir\"" "$fromdir" "$todir"'
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/chown.test b/testsuite/chown.test
new file mode 100644
index 0000000..b53413e
--- /dev/null
+++ b/testsuite/chown.test
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+# Copyright (C) 2002 by Martin Pool <mbp@samba.org>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that when rsync is running as root and has -a it correctly sets
+# the ownership of the destination.
+
+# We don't know what users will be present on this system, so we just
+# use random numeric uids and gids.
+
+. "$suitedir/rsync.fns"
+
+case $0 in
+*fake*)
+ $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
+ darwin*)
+ chown() {
+ own=$1
+ shift
+ xattr -s 'rsync.%stat' "100644 0,0 $own" "${@}"
+ }
+ ;;
+ solaris*)
+ chown() {
+ own=$1
+ shift
+ for fn in "${@}"; do
+ runat "$fn" "$SHELL_PATH" <<EOF
+echo "100644 0,0 $own" > rsync.%stat
+EOF
+ done
+ }
+ ;;
+ freebsd*)
+ chown() {
+ own=$1
+ shift
+ setextattr -h user "rsync.%stat" "100644 0,0 $own" "${@}"
+ }
+ ;;
+ *)
+ chown() {
+ own=$1
+ shift
+ setfattr -n 'user.rsync.%stat' -v "100644 0,0 $own" "${@}"
+ }
+ ;;
+ esac
+ ;;
+*)
+ RSYNC="$RSYNC --super"
+ my_uid=`get_testuid`
+ root_uid=`get_rootuid`
+ if test x"$my_uid" = x; then
+ : # If "id" failed, try to continue...
+ elif test x"$my_uid" != x"$root_uid"; then
+ if [ -e "$FAKEROOT_PATH" ]; then
+ echo "Let's try re-running the script under fakeroot..."
+ exec "$FAKEROOT_PATH" "$SHELL_PATH" "$0"
+ fi
+ fi
+ ;;
+esac
+
+# Build some hardlinks
+
+mkdir "$fromdir"
+name1="$fromdir/name1"
+name2="$fromdir/name2"
+echo "This is the file" > "$name1"
+echo "This is the other file" > "$name2"
+
+chown 5000:5002 "$name1" || test_skipped "Can't chown (probably need root)"
+chown 5001:5003 "$name2" || test_skipped "Can't chown (probably need root)"
+
+cd "$fromdir/.."
+checkit "$RSYNC -aHvv from/ to/" "$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
new file mode 100644
index 0000000..456f0a5
--- /dev/null
+++ b/testsuite/crtimes.test
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# Test rsync copying create times
+
+. "$suitedir/rsync.fns"
+
+$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"
+
+touch -t 200101011111.11 "$fromdir"
+touch -t 200202022222.22 "$fromdir"
+
+touch -t 200111111111.11 "$fromdir/foo"
+touch -t 200212122222.22 "$fromdir/foo"
+
+TLS_ARGS=--crtimes
+
+checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/daemon-gzip-download.test b/testsuite/daemon-gzip-download.test
new file mode 100644
index 0000000..57dd820
--- /dev/null
+++ b/testsuite/daemon-gzip-download.test
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+# Copyright (C) 2001, 2002 by Martin Pool <mbp@samba.org>
+
+# 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# This test tries to download a tree over a compressed connection from
+# the server. This ought to exercise (exorcise?) a bug in 2.5.3.
+
+. "$suitedir/rsync.fns"
+
+build_rsyncd_conf
+
+RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon"
+export RSYNC_CONNECT_PROG
+
+hands_setup
+
+# Build chkdir with a normal rsync and an --exclude.
+$RSYNC -av --exclude=foobar.baz "$fromdir/" "$chkdir/"
+
+checkit "$RSYNC -avvvvzz localhost::test-from/ '$todir/'" "$chkdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/daemon-gzip-upload.test b/testsuite/daemon-gzip-upload.test
new file mode 100644
index 0000000..b2110ea
--- /dev/null
+++ b/testsuite/daemon-gzip-upload.test
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# Copyright (C) 2001, 2002 by Martin Pool <mbp@samba.org>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING)
+
+# We don't really want to start the server listening, because that
+# might interfere with the security or operation of the test machine.
+# Instead we use the fake-connect feature to dynamically assign a pair
+# of ports.
+
+# This test tries to upload a file over a compressed connection to the
+# server. This ought to exercise (exorcise?) a bug in 2.5.3.
+
+. "$suitedir/rsync.fns"
+
+build_rsyncd_conf
+
+RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon"
+export RSYNC_CONNECT_PROG
+
+hands_setup
+
+# Build chkdir with a normal rsync and an --exclude.
+$RSYNC -av --exclude=foobar.baz "$fromdir/" "$chkdir/"
+
+checkit "'$ignore23' $RSYNC -avvvvzz '$fromdir/' localhost::test-to/" "$chkdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/daemon.test b/testsuite/daemon.test
new file mode 100644
index 0000000..60aa334
--- /dev/null
+++ b/testsuite/daemon.test
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+# Copyright (C) 2001 by Martin Pool <mbp@samba.org>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING)
+
+# We don't really want to start the server listening, because that
+# might interfere with the security or operation of the test machine.
+# Instead we use the fake-connect feature to dynamically assign a pair
+# of ports.
+
+# Having started the server we try some basic operations against it:
+
+# getting a list of module
+# listing files in a module
+# retrieving a module
+# uploading to a module
+# checking the log file
+# password authentication
+
+. "$suitedir/rsync.fns"
+
+SSH="src/support/lsh.sh --no-cd"
+FILE_REPL='s/^\([^d][^ ]*\) *\(..........[0-9]\) /\1 \2 /'
+DIR_REPL='s/^\(d[^ ]*\) *[0-9][.,0-9]* /\1 DIR /'
+LS_REPL='s;[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] ;####/##/## ##:##:## ;g'
+
+build_rsyncd_conf
+
+makepath "$fromdir/foo" "$fromdir/bar/baz"
+makepath "$todir"
+echo one >"$fromdir/foo/one"
+echo two >"$fromdir/bar/two"
+echo three >"$fromdir/bar/baz/three"
+
+cd "$scratchdir"
+
+ln -s test-rsyncd.conf rsyncd.conf
+
+my_uid=`get_testuid`
+root_uid=`get_rootuid`
+confopt=''
+if test x"$my_uid" = x"$root_uid"; then
+ # Root needs to specify the config file, or it uses /etc/rsyncd.conf.
+ echo "Forcing --config=$conf"
+ confopt=" --config=$conf"
+fi
+
+# These have a space-padded 15-char name, then a tab, then a comment.
+sed 's/NOCOMMENT//' <<EOT >"$chkfile"
+test-from r/o
+test-to r/w
+test-scratch NOCOMMENT
+EOT
+
+checkdiff2 "$RSYNC -ve '$SSH' --rsync-path='$RSYNC$confopt' localhost::"
+echo '===='
+
+RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon"
+export RSYNC_CONNECT_PROG
+
+checkdiff2 "$RSYNC -v localhost::"
+echo '===='
+
+checkdiff "$RSYNC -r localhost::test-hidden" \
+ "sed -e '$FILE_REPL' -e '$DIR_REPL' -e '$LS_REPL'" <<EOT
+drwxr-xr-x DIR ####/##/## ##:##:## .
+drwxr-xr-x DIR ####/##/## ##:##:## bar
+-rw-r--r-- 4 ####/##/## ##:##:## bar/two
+drwxr-xr-x DIR ####/##/## ##:##:## bar/baz
+-rw-r--r-- 6 ####/##/## ##:##:## bar/baz/three
+drwxr-xr-x DIR ####/##/## ##:##:## foo
+-rw-r--r-- 4 ####/##/## ##:##:## foo/one
+EOT
+
+checkdiff "$RSYNC -r localhost::test-from/f*" \
+ "sed -e '$FILE_REPL' -e '$DIR_REPL' -e '$LS_REPL'" <<EOT
+drwxr-xr-x DIR ####/##/## ##:##:## foo
+-rw-r--r-- 4 ####/##/## ##:##:## foo/one
+EOT
+diff $diffopt "$chkfile" "$outfile" || test_fail "test 3 failed"
+
+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'" <<EOT
+drwxr-xr-x DIR ####/##/## ##:##:## foo
+-rw-r--r-- 4 ####/##/## ##:##:## ####/##/## ##:##:## foo/one
+EOT
+fi
diff --git a/testsuite/delay-updates.test b/testsuite/delay-updates.test
new file mode 100644
index 0000000..3b6226b
--- /dev/null
+++ b/testsuite/delay-updates.test
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# Test rsync --delay-updates
+
+. "$suitedir/rsync.fns"
+
+mkdir "$fromdir"
+
+echo 1 > "$fromdir/foo"
+
+checkit "$RSYNC -aiv --delay-updates \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
+
+mkdir "$todir/.~tmp~"
+echo 2 > "$todir/.~tmp~/foo"
+touch -r .. "$todir/.~tmp~/foo" "$todir/foo"
+echo 3 > "$fromdir/foo"
+
+checkit "$RSYNC -aiv --delay-updates \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/delete.test b/testsuite/delete.test
new file mode 100644
index 0000000..2a9df7c
--- /dev/null
+++ b/testsuite/delete.test
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+# Copyright (C) 2005-2022 Wayne Davison
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test rsync handling of various delete directives.
+
+. "$suitedir/rsync.fns"
+
+hands_setup
+
+makepath "$chkdir" "$todir/extradir" "$todir/emptydir/subdir"
+
+echo extra >"$todir"/remove1
+echo extra >"$todir"/remove2
+echo extra >"$todir"/extradir/remove3
+echo extra >"$todir"/emptydir/subdir/remove4
+
+# Create two chk dirs, one with a copy of the source files, and one with
+# what we expect to be left behind by the copy using --remove-source-files.
+# Also, make sure that --dry-run --del doesn't output anything extraneous.
+$RSYNC -av "$fromdir/" "$chkdir/copy/" >"$tmpdir/copy.out" 2>&1
+cat "$tmpdir/copy.out"
+grep -E -v '^(created directory|sent|total size) ' "$tmpdir/copy.out" >"$tmpdir/copy.new"
+mv "$tmpdir/copy.new" "$tmpdir/copy.out"
+
+$RSYNC -avn --del "$fromdir/" "$chkdir/copy2/" >"$tmpdir/copy2.out" 2>&1 || true
+cat "$tmpdir/copy2.out"
+grep -E -v '^(created directory|sent|total size) ' "$tmpdir/copy2.out" >"$tmpdir/copy2.new"
+mv "$tmpdir/copy2.new" "$tmpdir/copy2.out"
+
+diff $diffopt "$tmpdir/copy.out" "$tmpdir/copy2.out"
+
+$RSYNC -av -f 'exclude,! */' "$fromdir/" "$chkdir/empty/"
+
+checkit "$RSYNC -avv --del --remove-source-files '$fromdir/' '$todir/'" "$chkdir/copy" "$todir"
+
+diff -r "$chkdir/empty" "$fromdir"
+
+# Make sure that "P" but not "-" per-dir merge-file filters take effect with
+# --delete-excluded.
+cat >"$todir/filters" <<EOF
+P foo
+- bar
+EOF
+touch "$todir/foo" "$todir/bar" "$todir/baz"
+
+$RSYNC -r --exclude=baz --filter=': filters' --delete-excluded "$fromdir/" "$todir/"
+
+test -f "$todir/foo" || test_fail "rsync should NOT have deleted $todir/foo"
+test -f "$todir/bar" && test_fail "rsync SHOULD have deleted $todir/bar"
+test -f "$todir/baz" && test_fail "rsync SHOULD have deleted $todir/baz"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/devices.test b/testsuite/devices.test
new file mode 100644
index 0000000..ad5f936
--- /dev/null
+++ b/testsuite/devices.test
@@ -0,0 +1,171 @@
+#!/bin/sh
+
+# Copyright (C) 2002 by Martin Pool <mbp@samba.org>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test rsync handling of devices. This can only run if you're root.
+
+. "$suitedir/rsync.fns"
+
+# Build some hardlinks
+
+case $0 in
+*fake*)
+ $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
+ darwin*)
+ mknod() {
+ fn="$1"
+ case "$2" in
+ p) mode=10644 ;;
+ c) mode=20644 ;;
+ b) mode=60644 ;;
+ esac
+ maj="${3:-0}"
+ min="${4:-0}"
+ touch "$fn"
+ xattr -s 'rsync.%stat' "$mode $maj,$min 0:0" "$fn"
+ }
+ ;;
+ solaris*)
+ mknod() {
+ fn="$1"
+ case "$2" in
+ p) mode=10644 ;;
+ c) mode=20644 ;;
+ b) mode=60644 ;;
+ esac
+ maj="${3:-0}"
+ min="${4:-0}"
+ touch "$fn"
+ runat "$fn" "$SHELL_PATH" <<EOF
+echo "$mode $maj,$min 0:0" > rsync.%stat
+EOF
+ }
+ ;;
+ freebsd*)
+ mknod() {
+ fn="$1"
+ case "$2" in
+ p) mode=10644 ;;
+ c) mode=20644 ;;
+ b) mode=60644 ;;
+ esac
+ maj="${3:-0}"
+ min="${4:-0}"
+ touch "$fn"
+ setextattr -h user "rsync.%stat" "$mode $maj,$min 0:0" "$fn"
+ }
+ ;;
+ *)
+ mknod() {
+ fn="$1"
+ case "$2" in
+ p) mode=10644 ;;
+ c) mode=20644 ;;
+ b) mode=60644 ;;
+ esac
+ maj="${3:-0}"
+ min="${4:-0}"
+ touch "$fn"
+ setfattr -n 'user.rsync.%stat' -v "$mode $maj,$min 0:0" "$fn"
+ }
+ ;;
+ esac
+ ;;
+*)
+ my_uid=`get_testuid`
+ root_uid=`get_rootuid`
+ if test x"$my_uid" = x; then
+ : # If "id" failed, try to continue...
+ elif test x"$my_uid" != x"$root_uid"; then
+ if [ -e "$FAKEROOT_PATH" ]; then
+ echo "Let's try re-running the script under fakeroot..."
+ exec "$FAKEROOT_PATH" "$SHELL_PATH" $RUNSHFLAGS "$0"
+ fi
+ test_skipped "Rsync needs root/fakeroot for device tests"
+ fi
+ ;;
+esac
+
+# TODO: Need to test whether hardlinks are possible on this OS/filesystem
+
+$RSYNC -VV | grep '"hardlink_specials": true' >/dev/null && CAN_HLINK_SPECIAL=yes || CAN_HLINK_SPECIAL=no
+
+mkdir "$fromdir"
+mkdir "$todir"
+mknod "$fromdir/char" c 41 67 || test_skipped "Can't create char device node"
+mknod "$fromdir/char2" c 42 68 || test_skipped "Can't create char device node"
+mknod "$fromdir/char3" c 42 69 || test_skipped "Can't create char device node"
+mknod "$fromdir/block" b 42 69 || test_skipped "Can't create block device node"
+mknod "$fromdir/block2" b 42 73 || test_skipped "Can't create block device node"
+mknod "$fromdir/block3" b 105 73 || test_skipped "Can't create block device node"
+if test "$CAN_HLINK_SPECIAL" = yes; then
+ ln "$fromdir/block3" "$fromdir/block3.5"
+else
+ echo "Skipping hard-linked device test..."
+fi
+mkfifo "$fromdir/fifo" || mknod "$fromdir/fifo" p || test_skipped "Can't run mkfifo"
+# Work around time rounding/truncating issue by touching both files.
+touch -r "$fromdir/block" "$fromdir/block" "$fromdir/block2"
+
+checkdiff "$RSYNC -ai '$fromdir/block' '$todir/block2'" <<EOT
+cD$all_plus block
+EOT
+
+checkdiff "$RSYNC -ai '$fromdir/block2' '$todir/block'" <<EOT
+cD$all_plus block2
+EOT
+
+sleep 1
+
+checkdiff "$RSYNC -Di '$fromdir/block3' '$todir/block'" <<EOT
+cDc.T.$dots block3
+EOT
+
+cat >"$chkfile" <<EOT
+.d..t.$dots ./
+cDc.t.$dots block
+cDc...$dots block2
+cD$all_plus block3
+hD$all_plus block3.5 => block3
+cD$all_plus char
+cD$all_plus char2
+cD$all_plus char3
+cS$all_plus fifo
+EOT
+if test "$CAN_HLINK_SPECIAL" = no; then
+ grep -v block3.5 <"$chkfile" >"$chkfile.new"
+ mv "$chkfile.new" "$chkfile"
+fi
+
+checkdiff2 "$RSYNC -aiHvv '$fromdir/' '$todir/'" v_filt
+
+echo "check how the directory listings compare with diff:"
+echo ""
+( cd "$fromdir" && rsync_ls_lR . ) > "$tmpdir/ls-from"
+( cd "$todir" && rsync_ls_lR . ) > "$tmpdir/ls-to"
+diff $diffopt "$tmpdir/ls-from" "$tmpdir/ls-to"
+
+if test "$CAN_HLINK_SPECIAL" = yes; then
+ set -x
+ checkdiff "$RSYNC -aii --link-dest='$todir' '$fromdir/' '$chkdir/'" <<EOT
+created directory $chkdir
+cd$allspace ./
+hD$allspace block
+hD$allspace block2
+hD$allspace block3
+hD$allspace block3.5
+hD$allspace char
+hD$allspace char2
+hD$allspace char3
+hS$allspace fifo
+EOT
+fi
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/dir-sgid.test b/testsuite/dir-sgid.test
new file mode 100644
index 0000000..d6b9a3c
--- /dev/null
+++ b/testsuite/dir-sgid.test
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that rsync obeys directory setgid. -- Matt McCutchen
+
+. $suitedir/rsync.fns
+
+umask 077
+
+# Call as: testit <dirname> <dirperms> <file-expected> <program-expected> <dir-expected>
+testit() {
+ todir="$scratchdir/$1"
+ mkdir "$todir"
+ chmod $2 "$todir"
+ # Make sure we obey directory setgid when creating a directory to hold multiple transferred files,
+ # even though the directory itself is outside the transfer
+ $RSYNC -rvv "$scratchdir/dir" "$scratchdir/file" "$scratchdir/program" "$todir/to/"
+ check_perms "$todir/to" $5 "Target $1"
+ check_perms "$todir/to/dir" $5 "Target $1"
+ check_perms "$todir/to/file" $3 "Target $1"
+ check_perms "$todir/to/program" $4 "Target $1"
+}
+
+mkdir "$scratchdir/dir"
+# Cygwin has a persistent default dir ACL that ruins this test.
+case `getfacl "$scratchdir/dir" 2>/dev/null || true` in
+*default:user::*) test_skipped "The default ACL mode interferes with this test" ;;
+esac
+
+echo "File!" >"$scratchdir/file"
+echo "#!/bin/sh" >"$scratchdir/program"
+
+chmod u=rwx,g=rw,g+s,o=r "$scratchdir/dir" || test_skipped "Can't chmod"
+chmod 664 "$scratchdir/file"
+chmod 775 "$scratchdir/program"
+
+[ -g "$scratchdir/dir" ] || test_skipped "The directory setgid bit vanished!"
+mkdir "$scratchdir/dir/blah"
+[ -g "$scratchdir/dir/blah" ] || test_skipped "Your filesystem doesn't use directory setgid; maybe it's BSD."
+
+# Test some target directories
+testit setgid-off 700 rw------- rwx------ rwx------
+testit setgid-on u=rwx,g=rw,g+s,o-rwx rw------- rwx------ rwx--S---
+
+# Hooray
+exit 0
diff --git a/testsuite/duplicates.test b/testsuite/duplicates.test
new file mode 100644
index 0000000..3317e72
--- /dev/null
+++ b/testsuite/duplicates.test
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# Copyright (C) 2002 by Martin Pool <mbp@samba.org>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test rsync handling of duplicate filenames.
+
+# It's quite possible that the user might specify the same source file
+# more than once on the command line, perhaps through shell variables
+# or wildcard expansions. It might cause problems for rsync if the
+# same name occurred more than once in the file list, because we might
+# be trying to update the first copy and generate checksums for the
+# second copy at the same time. See clean_flist() for the implementation.
+
+# We don't need to worry about hardlinks or symlinks. Because we
+# always rename-and-replace the new copy, they can't affect us.
+
+# This test is not great, because it is a timing-dependent bug.
+
+. "$suitedir/rsync.fns"
+
+# Build some hardlinks
+
+mkdir "$fromdir"
+name1="$fromdir/name1"
+name2="$fromdir/name2"
+echo "This is the file" > "$name1"
+ln -s "$name1" "$name2" || test_fail "can't create symlink"
+
+checkit "$RSYNC -avv '$fromdir/' '$fromdir/' '$fromdir/' '$fromdir/' '$fromdir/' '$fromdir/' '$fromdir/' '$fromdir/' '$fromdir/' '$fromdir/' '$todir/'" "$fromdir" "$todir" \
+ | tee "$outfile"
+
+# Make sure each file was only copied once...
+if [ `grep -c '^name1$' "$outfile"` != 1 ]; then
+ test_fail "name1 was not copied exactly once"
+fi
+if [ `grep -c '^name2 -> ' "$outfile"` != 1 ]; then
+ test_fail "name2 was not copied exactly once"
+fi
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/exclude-lsh.test b/testsuite/exclude-lsh.test
new file mode 120000
index 0000000..84bc98a
--- /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
new file mode 100644
index 0000000..56b68b8
--- /dev/null
+++ b/testsuite/exclude.test
@@ -0,0 +1,252 @@
+#!/bin/sh
+
+# Copyright (C) 2003-2022 Wayne Davison
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test rsync handling of exclude/include directives.
+
+# Test some of the more obscure wildcard handling of exclude/include
+# processing.
+
+. "$suitedir/rsync.fns"
+
+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"
+makepath "$fromdir/foo/sub"
+makepath "$fromdir/bar/down/to/foo/too"
+makepath "$fromdir/bar/down/to/bar/baz"
+makepath "$fromdir/mid/for/foo/and/that/is/who"
+makepath "$fromdir/new/keep/this"
+makepath "$fromdir/new/lose/this"
+cat >"$fromdir/.filt" <<EOF
+exclude down
+: .filt-temp
+clear
+- .filt
+- *.bak
+- *.old
+EOF
+echo filtered-1 >"$fromdir/foo/file1"
+echo removed >"$fromdir/foo/file2"
+echo cvsout >"$fromdir/foo/file2.old"
+cat >"$fromdir/foo/.filt" <<EOF
+include .filt
+- /file1
+EOF
+echo not-filtered-1 >"$fromdir/foo/sub/file1"
+cat >"$fromdir/bar/.filt" <<EOF
+- home-cvs-exclude
+dir-merge .filt2
++ to
+EOF
+echo cvsout >"$fromdir/bar/down/to/home-cvs-exclude"
+cat >"$fromdir/bar/down/to/.filt2" <<EOF
+- .filt2
+EOF
+cat >"$fromdir/bar/down/to/foo/.filt2" <<EOF
++ *.junk
+EOF
+echo keeper >"$fromdir/bar/down/to/foo/file1"
+echo cvsout >"$fromdir/bar/down/to/foo/file1.bak"
+echo gone >"$fromdir/bar/down/to/foo/file3"
+echo lost >"$fromdir/bar/down/to/foo/file4"
+echo weird >"$fromdir/bar/down/to/foo/+ file3"
+echo cvsout-but-filtin >"$fromdir/bar/down/to/foo/file4.junk"
+echo smashed >"$fromdir/bar/down/to/foo/to"
+cat >"$fromdir/bar/down/to/bar/.filt2" <<EOF
+- *.deep
+EOF
+echo filtout >"$fromdir/bar/down/to/bar/baz/file5.deep"
+# This one should be ineffectual
+cat >"$fromdir/mid/.filt2" <<EOF
+- extra
+EOF
+echo cvsout >"$fromdir/mid/one-in-one-out"
+echo one-in-one-out >"$fromdir/mid/.cvsignore"
+echo cvsin >"$fromdir/mid/one-for-all"
+cat >"$fromdir/mid/.filt" <<EOF
+:C
+EOF
+echo cvsin >"$fromdir/mid/for/one-in-one-out"
+echo expunged >"$fromdir/mid/for/foo/extra"
+echo retained >"$fromdir/mid/for/foo/keep"
+
+# Setup our test exclude/include files.
+
+excl="$scratchdir/exclude-from"
+cat >"$excl" <<EOF
+!
+# If the second line of these two lines does anything, it's a bug.
++ **/bar
+- /bar
+# This should match against the whole path, not just the name.
++ foo**too
+# These should float at the end of the path.
++ foo/s?b/
+- foo/*/
+# Test how /** differs from /***
+- new/keep/**
+- new/lose/***
+# Test some normal excludes. Competing lines are paired.
++ t[o]/
+- to
++ file4
+- file[2-9]
+- /mid/for/foo/extra
+EOF
+
+cat >"$scratchdir/.cvsignore" <<EOF
+home-cvs-exclude
+EOF
+
+# Start with a check of --prune-empty-dirs:
+$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.
+ln -s too "$fromdir/bar/down/to/foo/sym"
+
+# Start to prep an --update test dir
+mkdir "$scratchdir/up1" "$scratchdir/up2"
+touch "$scratchdir/up1/dst-newness" "$scratchdir/up2/src-newness"
+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$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
+rm -r "$chkdir"/new/keep/this
+rm -r "$chkdir"/new/lose
+rm "$chkdir"/foo/file[235-9]
+rm "$chkdir"/bar/down/to/foo/to "$chkdir"/bar/down/to/foo/file[235-9]
+rm "$chkdir"/mid/for/foo/extra
+
+# Finish prep for the --update test (run last)
+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 --rsync-path="$RSYNC" --existing --include='*/' --exclude='*' "$host$fromdir/" "$chkdir/"
+
+# Now, test if rsync excludes the same files.
+
+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.
+
+rm "$chkdir"/foo/*.old
+rm "$chkdir"/bar/down/to/foo/*.bak
+rm "$chkdir"/bar/down/to/foo/*.junk
+rm "$chkdir"/bar/down/to/home-cvs-exclude
+rm "$chkdir"/mid/one-in-one-out
+
+$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.
+
+# 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.
+
+rm "$chkdir"/foo/file1
+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 --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 --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$rpath -f dir-merge_.filt -f merge_- \
+ --delete-during '$host$fromdir/' '$todir/'" "$chkdir" "$todir"
+
+# Remove the files that will be deleted.
+
+rm "$chkdir"/.filt
+rm "$chkdir"/bar/.filt
+rm "$chkdir"/bar/down/to/.filt2
+rm "$chkdir"/bar/down/to/foo/.filt2
+rm "$chkdir"/bar/down/to/bar/.filt2
+rm "$chkdir"/mid/.filt
+
+$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$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.
+
+cat >"$fromdir/bar/down/.excl" <<EOF
+file3
+EOF
+cat >"$fromdir/bar/down/to/foo/.excl" <<EOF
++ file3
+*.bak
+EOF
+$RSYNC -av --rsync-path="$RSYNC" --del "$host$fromdir/" "$chkdir/"
+rm "$chkdir/bar/down/to/foo/file1.bak"
+rm "$chkdir/bar/down/to/foo/file3"
+rm "$chkdir/bar/down/to/foo/+ file3"
+$RSYNC -av --rsync-path="$RSYNC" --existing --filter='-! */' "$host$fromdir/" "$chkdir/"
+$RSYNC -av --rsync-path="$RSYNC" --delete-excluded --exclude='*' "$host$fromdir/" "$todir/"
+
+checkit "$RSYNC -avv$rpath -f dir-merge,-_.excl \
+ '$host$fromdir/' '$todir/'" "$chkdir" "$todir"
+
+relative_opts='--relative --chmod=Du+w --copy-unsafe-links'
+$RSYNC -av --rsync-path="$RSYNC" $relative_opts "$host$fromdir/foo" "$chkdir/"
+rm -rf "$chkdir$fromdir/foo/down"
+$RSYNC -av $relative_opts --existing --filter='-! */' "$fromdir/foo" "$chkdir/"
+
+checkit "$RSYNC -avv$rpath $relative_opts --exclude='$fromdir/foo/down' \
+ '$host$fromdir/foo' '$todir'" "$chkdir$fromdir/foo" "$todir$fromdir/foo"
+
+# Now we'll test the --update option.
+checkdiff "$RSYNC -aiiO$rpath --update --info=skip '$host$scratchdir/up1/' '$scratchdir/up2/'" \
+ "grep -v '^\.d$allspace'" <<EOT
+dst-newness is newer
+>f$all_plus extra-src
+.f$allspace same-newness
+>f..t.$dots src-newness
+EOT
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/executability.test b/testsuite/executability.test
new file mode 100644
index 0000000..8f09d8f
--- /dev/null
+++ b/testsuite/executability.test
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test the --executability or -E option. -- Matt McCutchen
+
+. $suitedir/rsync.fns
+
+# Put some files in the From directory
+mkdir "$fromdir"
+cat <<EOF >"$fromdir/1"
+#!/bin/sh
+echo 'Program One!'
+EOF
+cat <<EOF >"$fromdir/2"
+#!/bin/sh
+echo 'Program Two!'
+EOF
+
+chmod 1700 "$fromdir/1" || test_skipped "Can't chmod"
+chmod 600 "$fromdir/2"
+
+$RSYNC -rvv "$fromdir/" "$todir/"
+
+check_perms "$todir/1" rwx------ 1
+check_perms "$todir/2" rw------- 1
+
+# Mix up the permissions a bit
+chmod 600 "$fromdir/1"
+chmod 601 "$fromdir/2"
+chmod 604 "$todir/2"
+
+$RSYNC -rvv "$fromdir/" "$todir/"
+
+# No -E, so nothing should have changed
+check_perms "$todir/1" rwx------ 2
+check_perms "$todir/2" rw----r-- 2
+
+$RSYNC -rvvE "$fromdir/" "$todir/"
+
+# Now things should have happened!
+check_perms "$todir/1" rw------- 3
+check_perms "$todir/2" rwx---r-x 3
+
+# Hooray
+exit 0
diff --git a/testsuite/files-from.test b/testsuite/files-from.test
new file mode 100644
index 0000000..207eab5
--- /dev/null
+++ b/testsuite/files-from.test
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# Copyright (C) 2008-2020 Wayne Davison
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that --files-from=FILE works right.
+
+. "$suitedir/rsync.fns"
+
+SSH="$scratchdir/src/support/lsh.sh"
+
+hands_setup
+
+# This list of files skips the contents of "subsubdir" but includes
+# the contents of "subsubdir2" due to its trailing slash.
+cat >"$scratchdir/filelist" <<EOT
+from/./
+from/./dir/subdir
+from/./dir/subdir/subsubdir
+from/./dir/subdir/subsubdir2/
+from/./dir/subdir/foobar.baz
+EOT
+
+# Create a chkdir without the content that we expect to be omitted.
+$RSYNC -a --exclude=dir/text --exclude='subsubdir/**' "$fromdir/" "$chkdir/"
+
+checkit "$RSYNC -av --files-from='$scratchdir/filelist' '$scratchdir' '$todir/'" "$chkdir" "$todir"
+
+for filehost in '' 'localhost:'; do
+ for srchost in '' 'localhost:'; do
+ if [ -z "$srchost" ]; then
+ desthost='localhost:'
+ else
+ desthost=''
+ fi
+
+ rm -rf "$todir"
+ checkit "$RSYNC -avse '$SSH' --rsync-path='$RSYNC' --files-from='$filehost$scratchdir/filelist' '$srchost$scratchdir' '$desthost$todir/'" "$chkdir" "$todir"
+ done
+done
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/fuzzy.test b/testsuite/fuzzy.test
new file mode 100644
index 0000000..101ffd3
--- /dev/null
+++ b/testsuite/fuzzy.test
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# Copyright (C) 2005-2022 Wayne Davison
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test rsync handling of the --fuzzy option.
+
+. "$suitedir/rsync.fns"
+
+mkdir "$fromdir"
+mkdir "$todir"
+
+cp_p "$srcdir"/rsync.c "$fromdir"/rsync.c
+cp_touch "$fromdir"/rsync.c "$todir"/rsync2.c
+sleep 1
+
+# Let's do it!
+checkit "$RSYNC -avvi --no-whole-file --fuzzy --delete-delay \
+ '$fromdir/' '$todir/'" "$fromdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/hands.test b/testsuite/hands.test
new file mode 100644
index 0000000..8e265b7
--- /dev/null
+++ b/testsuite/hands.test
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# Copyright (C) 1998, 1999 by Philip Hands <phil@hands.com>
+# Copyright (C) 2001, 2002 by Martin Pool <mbp@samba.org>
+#
+# This program is distributable under the terms of the GNU GPL (see COPYING)
+
+. "$suitedir/rsync.fns"
+
+hands_setup
+
+DEBUG_OPTS="--debug=all0,deltasum0"
+
+# Main script starts here
+
+runtest "basic operation" 'checkit "$RSYNC -av \"$fromdir/\" \"$todir\"" "$fromdir/" "$todir"'
+
+ln "$fromdir/filelist" "$fromdir/dir"
+runtest "hard links" 'checkit "$RSYNC -avH --bwlimit=0 $DEBUG_OPTS \"$fromdir/\" \"$todir\"" "$fromdir/" "$todir"'
+
+rm "$todir/text"
+runtest "one file" 'checkit "$RSYNC -avH $DEBUG_OPTS \"$fromdir/\" \"$todir\"" "$fromdir/" "$todir"'
+
+echo "extra line" >> "$todir/text"
+runtest "extra data" 'checkit "$RSYNC -avH $DEBUG_OPTS --no-whole-file \"$fromdir/\" \"$todir\"" "$fromdir/" "$todir"'
+
+cp "$fromdir/text" "$todir/ThisShouldGo"
+runtest " --delete" 'checkit "$RSYNC --delete -avH $DEBUG_OPTS \"$fromdir/\" \"$todir\"" "$fromdir/" "$todir"'
+
+cd "$tmpdir"
+rm -rf to from/*dir
+
+# Do the real copy, touch up the parent-dir's time, and then check the copy.
+$RSYNC -av from/* to/
+checkit "$RSYNC -av --exclude='*' from/ to/" "$fromdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/hardlinks.test b/testsuite/hardlinks.test
new file mode 100644
index 0000000..af2ea40
--- /dev/null
+++ b/testsuite/hardlinks.test
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+# Copyright (C) 2002 by Martin Pool <mbp@samba.org>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test rsync handling of hardlinks. By default, rsync does not detect
+# hard links and they get sent as separate files. If you specify -H,
+# then hard links are detected and linked together on the receiver.
+
+. "$suitedir/rsync.fns"
+
+SSH="$scratchdir/src/support/lsh.sh"
+
+# Build some hardlinks
+
+fromdir="$scratchdir/from"
+todir="$scratchdir/to"
+
+# TODO: Need to test whether hardlinks are possible on this OS/filesystem
+
+mkdir "$fromdir"
+name1="$fromdir/name1"
+name2="$fromdir/name2"
+name3="$fromdir/name3"
+name4="$fromdir/name4"
+echo "This is the file" > "$name1"
+ln "$name1" "$name2" || test_skipped "Can't create hardlink"
+ln "$name2" "$name3" || test_fail "Can't create hardlink"
+cp "$name2" "$name4" || test_fail "Can't copy file"
+cat $srcdir/*.c >"$fromdir/text"
+
+checkit "$RSYNC -aHivv --debug=HLINK5 '$fromdir/' '$todir/'" "$fromdir" "$todir"
+
+echo "extra extra" >>"$todir/name1"
+
+checkit "$RSYNC -aHivv --debug=HLINK5 --no-whole-file '$fromdir/' '$todir/'" "$fromdir" "$todir"
+
+# Add a new link in a new subdirectory to test that we don't try to link
+# the files before the directory gets created. We also create a bunch of
+# extra files to ensure that an incremental-recursion transfer works across
+# distant files.
+makepath "$fromdir/subdir/down/deep"
+
+files=''
+for x in a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9; do
+ for y in a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9; do
+ files="$files $x$y"
+ done
+done
+(cd "$fromdir/subdir"; touch $files)
+
+ln "$name1" "$fromdir/subdir/down/deep/new-file"
+rm "$todir/text"
+
+checkit "$RSYNC -aHivve '$SSH' --debug=HLINK5 --rsync-path='$RSYNC' '$fromdir/' localhost:'$todir/'" "$fromdir" "$todir"
+
+# Do some duplicate copies using --link-dest and --copy-dest to test that
+# we hard-link all locally-inherited items.
+checkit "$RSYNC -aHivv --debug=HLINK5 --link-dest='$todir' '$fromdir/' '$chkdir/'" "$todir" "$chkdir"
+
+rm -rf "$chkdir"
+checkit "$RSYNC -aHivv --debug=HLINK5 --copy-dest='$todir' '$fromdir/' '$chkdir/'" "$fromdir" "$chkdir"
+
+# Create a hard link that has only one part in the hierarchy.
+echo "This is another file" >"$fromdir/solo"
+ln "$fromdir/solo" "$chkdir/solo" || test_fail "Can't create hardlink"
+
+# Make sure that the checksum data doesn't slide due to an HLINK_BUMP() change.
+checktee "$RSYNC -aHivc --debug=HLINK5 '$fromdir/' '$chkdir/'"
+grep solo "$outfile" && test_fail "Erroneous copy of solo file occurred!"
+
+# Make sure there's nothing wrong with sending a single file with -H
+# enabled (this has broken twice so far, so we need this test).
+rm -rf "$todir"
+$RSYNC -aHivv --debug=HLINK5 "$name1" "$todir/"
+diff $diffopt "$name1" "$todir" || test_fail "solo copy of name1 failed"
+
+# 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
new file mode 100644
index 0000000..c1c57c5
--- /dev/null
+++ b/testsuite/itemize.test
@@ -0,0 +1,246 @@
+#!/bin/sh
+
+# Copyright (C) 2005-2022 Wayne Davison
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test the output of various copy commands to ensure itemized output
+# and double-verbose output is correct.
+
+. "$suitedir/rsync.fns"
+
+to2dir="$tmpdir/to2"
+
+makepath "$fromdir/foo"
+makepath "$fromdir/bar/baz"
+cp_p "$srcdir/configure.ac" "$fromdir/foo/config1"
+cp_p "$srcdir/config.sub" "$fromdir/foo/config2"
+cp_p "$srcdir/rsync.h" "$fromdir/bar/baz/rsync"
+chmod 600 "$fromdir"/foo/config? "$fromdir/bar/baz/rsync"
+umask 0
+ln -s ../bar/baz/rsync "$fromdir/foo/sym"
+umask 022
+ln "$fromdir/foo/config1" "$fromdir/foo/extra"
+rm -f "$to2dir"
+
+# Check if rsync is set to hard-link symlinks.
+if $RSYNC -VV | grep '"hardlink_symlinks": true' >/dev/null; then
+ L=hL
+ sym_dots="$allspace"
+ L_sym_dots=".L$allspace"
+ is_uptodate='is uptodate'
+ touch "$chkfile.extra"
+else
+ L=cL
+ sym_dots="c.t.$dots"
+ L_sym_dots="cL$sym_dots"
+ is_uptodate='-> ../bar/baz/rsync'
+ echo "cL$sym_dots foo/sym $is_uptodate" >"$chkfile.extra"
+fi
+
+# Check if rsync can preserve time on symlinks
+case "$RSYNC" in
+*protocol=2*)
+ T=.T
+ ;;
+*)
+ if $RSYNC -VV | grep '"symtimes": true' >/dev/null; then
+ T=.t
+ else
+ T=.T
+ fi
+ ;;
+esac
+
+checkdiff "$RSYNC -iplr '$fromdir/' '$todir/'" <<EOT
+created directory $todir
+cd$all_plus ./
+cd$all_plus bar/
+cd$all_plus bar/baz/
+>f$all_plus bar/baz/rsync
+cd$all_plus foo/
+>f$all_plus foo/config1
+>f$all_plus foo/config2
+>f$all_plus foo/extra
+cL$all_plus foo/sym -> ../bar/baz/rsync
+EOT
+
+# Ensure there are no accidental directory-time problems.
+$RSYNC -a -f '-! */' "$fromdir/" "$todir"
+
+cp_p "$srcdir/configure.ac" "$fromdir/foo/config2"
+chmod 601 "$fromdir/foo/config2"
+checkdiff "$RSYNC -iplrH '$fromdir/' '$todir/'" <<EOT
+>f..T.$dots bar/baz/rsync
+>f..T.$dots foo/config1
+>f.sTp$dots foo/config2
+hf..T.$dots foo/extra => foo/config1
+EOT
+
+$RSYNC -a -f '-! */' "$fromdir/" "$todir"
+cp_p "$srcdir/config.sub" "$fromdir/foo/config2"
+sleep 1 # For directory mod below to ensure time difference
+rm "$todir/foo/sym"
+umask 0
+ln -s ../bar/baz "$todir/foo/sym"
+umask 022
+chmod 600 "$fromdir/foo/config2"
+chmod 777 "$todir/bar/baz/rsync"
+
+checkdiff "$RSYNC -iplrtc '$fromdir/' '$todir/'" <<EOT
+.f..tp$dots bar/baz/rsync
+.d..t.$dots foo/
+.f..t.$dots foo/config1
+>fcstp$dots foo/config2
+cLc$T.$dots foo/sym -> ../bar/baz/rsync
+EOT
+
+cp_p "$srcdir/configure.ac" "$fromdir/foo/config2"
+chmod 600 "$fromdir/foo/config2"
+# Lack of -t is for unchanged hard-link stress-test!
+checkdiff "$RSYNC -vvplrH '$fromdir/' '$todir/'" \
+ v_filt <<EOT
+bar/baz/rsync is uptodate
+foo/config1 is uptodate
+foo/extra is uptodate
+foo/sym is uptodate
+foo/config2
+EOT
+
+chmod 747 "$todir/bar/baz/rsync"
+$RSYNC -a -f '-! */' "$fromdir/" "$todir"
+checkdiff "$RSYNC -ivvplrtH '$fromdir/' '$todir/'" \
+ v_filt <<EOT
+.d$allspace ./
+.d$allspace bar/
+.d$allspace bar/baz/
+.f...p$dots bar/baz/rsync
+.d$allspace foo/
+.f$allspace foo/config1
+>f..t.$dots foo/config2
+hf$allspace foo/extra
+.L$allspace foo/sym -> ../bar/baz/rsync
+EOT
+
+chmod 757 "$todir/foo/config1"
+touch "$todir/foo/config2"
+checkdiff "$RSYNC -vplrtH '$fromdir/' '$todir/'" \
+ v_filt <<EOT
+foo/config2
+EOT
+
+chmod 757 "$todir/foo/config1"
+touch "$todir/foo/config2"
+checkdiff "$RSYNC -iplrtH '$fromdir/' '$todir/'" <<EOT
+.f...p$dots foo/config1
+>f..t.$dots foo/config2
+EOT
+
+checkdiff "$RSYNC -ivvplrtH --copy-dest=../to '$fromdir/' '$to2dir/'" \
+ v_filt <<EOT
+cd$allspace ./
+cd$allspace bar/
+cd$allspace bar/baz/
+cf$allspace bar/baz/rsync
+cd$allspace foo/
+cf$allspace foo/config1
+cf$allspace foo/config2
+hf$allspace foo/extra => foo/config1
+cL$sym_dots foo/sym -> ../bar/baz/rsync
+EOT
+
+rm -rf "$to2dir"
+cat - "$chkfile.extra" <<EOT >"$chkfile"
+created directory $to2dir
+hf$allspace foo/extra => foo/config1
+EOT
+checkdiff2 "$RSYNC -iplrtH --copy-dest=../to '$fromdir/' '$to2dir/'"
+
+rm -rf "$to2dir"
+checkdiff "$RSYNC -vvplrtH --copy-dest='$todir' '$fromdir/' '$to2dir/'" \
+ v_filt <<EOT
+./ is uptodate
+bar/ is uptodate
+bar/baz/ is uptodate
+bar/baz/rsync is uptodate
+foo/ is uptodate
+foo/config1 is uptodate
+foo/config2 is uptodate
+foo/sym $is_uptodate
+foo/extra => foo/config1
+EOT
+
+rm -rf "$to2dir"
+checkdiff "$RSYNC -ivvplrtH --link-dest='$todir' '$fromdir/' '$to2dir/'" \
+ v_filt <<EOT
+cd$allspace ./
+cd$allspace bar/
+cd$allspace bar/baz/
+hf$allspace bar/baz/rsync
+cd$allspace foo/
+hf$allspace foo/config1
+hf$allspace foo/config2
+hf$allspace foo/extra => foo/config1
+$L$sym_dots foo/sym -> ../bar/baz/rsync
+EOT
+
+rm -rf "$to2dir"
+cat - "$chkfile.extra" <<EOT >"$chkfile"
+created directory $to2dir
+EOT
+checkdiff2 "$RSYNC -iplrtH --dry-run --link-dest=../to '$fromdir/' '$to2dir/'"
+
+rm -rf "$to2dir"
+checkdiff2 "$RSYNC -iplrtH --link-dest=../to '$fromdir/' '$to2dir/'"
+
+rm -rf "$to2dir"
+checkdiff "$RSYNC -vvplrtH --link-dest='$todir' '$fromdir/' '$to2dir/'" \
+ v_filt <<EOT
+./ is uptodate
+bar/ is uptodate
+bar/baz/ is uptodate
+bar/baz/rsync is uptodate
+foo/ is uptodate
+foo/config1 is uptodate
+foo/config2 is uptodate
+foo/extra is uptodate
+foo/sym $is_uptodate
+EOT
+
+rm -rf "$to2dir"
+checkdiff "$RSYNC -ivvplrtH --compare-dest='$todir' '$fromdir/' '$to2dir/'" \
+ v_filt <<EOT
+cd$allspace ./
+cd$allspace bar/
+cd$allspace bar/baz/
+.f$allspace bar/baz/rsync
+cd$allspace foo/
+.f$allspace foo/config1
+.f$allspace foo/config2
+.f$allspace foo/extra
+$L_sym_dots foo/sym -> ../bar/baz/rsync
+EOT
+
+rm -rf "$to2dir"
+cat - "$chkfile.extra" <<EOT >"$chkfile"
+created directory $to2dir
+EOT
+checkdiff2 "$RSYNC -iplrtH --compare-dest='$todir' '$fromdir/' '$to2dir/'"
+
+rm -rf "$to2dir"
+checkdiff "$RSYNC -vvplrtH --compare-dest='$todir' '$fromdir/' '$to2dir/'" \
+ v_filt <<EOT
+./ is uptodate
+bar/ is uptodate
+bar/baz/ is uptodate
+bar/baz/rsync is uptodate
+foo/ is uptodate
+foo/config1 is uptodate
+foo/config2 is uptodate
+foo/extra is uptodate
+foo/sym $is_uptodate
+EOT
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/longdir.test b/testsuite/longdir.test
new file mode 100644
index 0000000..8d66bb5
--- /dev/null
+++ b/testsuite/longdir.test
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# Copyright (C) 1998,1999 Philip Hands <phil@hands.com>
+# Copyright (C) 2001 by Martin Pool <mbp@samba.org>
+#
+# This program is distributable under the terms of the GNU GPL (see COPYING)
+
+. "$suitedir/rsync.fns"
+
+hands_setup
+
+longname=This-is-a-directory-with-a-stupidly-long-name-created-in-an-attempt-to-provoke-an-error-found-in-2.0.11-that-should-hopefully-never-appear-again-if-this-test-does-its-job
+longdir="$fromdir/$longname/$longname/$longname"
+
+makepath "$longdir" || test_skipped "unable to create long directory"
+touch "$longdir/1" || test_skipped "unable to create files in long directory"
+date > "$longdir/1"
+if [ -r /etc ]; then
+ ls -la /etc >"$longdir/2"
+else
+ ls -la / >"$longdir/2"
+fi
+checkit "$RSYNC --delete -avH '$fromdir/' '$todir'" "$fromdir/" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/merge.test b/testsuite/merge.test
new file mode 100644
index 0000000..17050a1
--- /dev/null
+++ b/testsuite/merge.test
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+# Copyright (C) 2004-2022 Wayne Davison
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Make sure we can merge files from multiple directories into one.
+
+. "$suitedir/rsync.fns"
+
+# Build some files/dirs/links to copy
+
+# Use local dirnames to better exercise the arg-parsing code.
+cd "$tmpdir"
+
+mkdir from1 from2 from3 deep
+mkdir from2/sub1 from3/sub1
+mkdir from3/sub2 from1/dir-and-not-dir
+mkdir chk chk/sub1 chk/sub2 chk/dir-and-not-dir
+echo "one" >from1/one
+cp_touch from1/one from2/one
+cp_touch from1/one from3/one
+echo "two" >from1/two
+echo "three" >from2/three
+echo "four" >from3/four
+echo "five" >from1/five
+echo "six" >from3/six
+echo "sub1" >from2/sub1/uno
+cp_touch from2/sub1/uno from3/sub1/uno
+echo "sub2" >from3/sub1/dos
+echo "sub3" >from2/sub1/tres
+echo "subby" >from3/sub2/subby
+echo "extra" >from1/dir-and-not-dir/inside
+echo "not-dir" >from3/dir-and-not-dir
+echo "arg-test" >deep/arg-test
+echo "shallow" >shallow
+
+cp_touch from1/one from1/two from2/three from3/four from1/five from3/six chk
+cp_touch deep/arg-test shallow chk
+cp_touch from1/dir-and-not-dir/inside chk/dir-and-not-dir
+cp_touch from2/sub1/uno from3/sub1/dos from2/sub1/tres chk/sub1
+cp_touch from3/sub2/subby chk/sub2
+
+# Make sure that time has moved on.
+sleep 1
+
+# Get rid of any directory-time differences
+$RSYNC -av --existing -f 'exclude,! */' from1/ from2/
+$RSYNC -av --existing -f 'exclude,! */' from2/ from3/
+$RSYNC -av --existing -f 'exclude,! */' from1/ chk/
+$RSYNC -av --existing -f 'exclude,! */' from3/ chk/
+
+checkit "$RSYNC -avv deep/arg-test shallow from1/ from2/ from3/ to/" "$chkdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/missing.test b/testsuite/missing.test
new file mode 100644
index 0000000..2fbf461
--- /dev/null
+++ b/testsuite/missing.test
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test three bugs fixed by my redoing of the missing_below logic.
+
+. $suitedir/rsync.fns
+
+makepath "$fromdir/subdir" "$todir"
+echo data >"$fromdir/subdir/file"
+echo data >"$todir/other"
+
+# Test 1: Too much "not creating new..." output on a dry run
+$RSYNC -n -r --ignore-non-existing -vv "$fromdir/" "$todir/" | tee "$scratchdir/out"
+if grep 'not creating new.*subdir/file' "$scratchdir/out" >/dev/null; then
+ test_fail 'test 1 failed'
+fi
+
+case "$RSYNC" in
+*protocol=29*) # FIXME can we get past the new flist sanity check in protocol 29?
+ echo "Skipped test 2 for protocol 29."
+ ;;
+*)
+ # Test 2: Attempt to make a fuzzy dirlist for a dir not created on a dry run
+ $RSYNC -n -r -R --no-implied-dirs -y "$fromdir/./subdir/file" "$todir/" \
+ || test_fail 'test 2 failed'
+ ;;
+esac
+
+# Test 3: --delete-after pass skipped when last dir is dry-missing
+$RSYNC -n -r --delete-after -i "$fromdir/" "$todir/" | tee "$scratchdir/out"
+grep '^\*deleting * other' "$scratchdir/out" >/dev/null \
+ || test_fail 'test 3 failed'
diff --git a/testsuite/mkpath.test b/testsuite/mkpath.test
new file mode 100644
index 0000000..8046345
--- /dev/null
+++ b/testsuite/mkpath.test
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+. "$suitedir/rsync.fns"
+
+makepath "$fromdir"
+makepath "$todir"
+
+cp_p "$srcdir/rsync.h" "$fromdir/text"
+cp_p "$srcdir/configure.ac" "$fromdir/extra"
+
+cd "$tmpdir"
+
+deep_dir=to/foo/bar/baz/down/deep
+
+# Check that we can create several levels of dest dir
+$RSYNC -aiv --mkpath from/text $deep_dir/new
+test -f $deep_dir/new || test_fail "'new' file not found in $deep_dir dir"
+rm -rf to/foo
+
+$RSYNC -aiv --mkpath from/text $deep_dir/
+test -f $deep_dir/text || test_fail "'text' file not found in $deep_dir dir"
+rm $deep_dir/text
+
+# Make sure we can handle an existing path
+mkdir $deep_dir/new
+$RSYNC -aiv --mkpath from/text $deep_dir/new
+test -f $deep_dir/new/text || test_fail "'text' file not found in $deep_dir/new dir"
+
+# ... and an existing path when an alternate dest filename is specified
+$RSYNC -aiv --mkpath from/text $deep_dir/new/text2
+test -f $deep_dir/new/text2 || test_fail "'text2' file not found in $deep_dir/new dir"
+rm -rf to/foo
+
+# Try the tests again with multiple source args
+$RSYNC -aiv --mkpath from/ $deep_dir
+test -f $deep_dir/extra || test_fail "'extra' file not found in $deep_dir dir"
+rm -rf to/foo
+
+$RSYNC -aiv --mkpath from/ $deep_dir/
+test -f $deep_dir/text || test_fail "'text' file not found in $deep_dir dir"
+
+# Make sure that we can handle no path
+$RSYNC -aiv --mkpath from/text to_text
+test -f to_text || test_fail "'to_text' file not found in current dir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/protected-regular.test b/testsuite/protected-regular.test
new file mode 100644
index 0000000..40416b0
--- /dev/null
+++ b/testsuite/protected-regular.test
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# Copyright (C) 2021 by Achim Leitner <aleitner@lis-engineering.de>
+# This program is distributable under the terms of the GNU GPL (see COPYING)
+#
+# Modern linux systems have the protected_regular feature set to 1 or 2
+# See https://www.kernel.org/doc/Documentation/sysctl/fs.txt
+# Make sure we can still write these files in --inplace mode
+
+. "$suitedir/rsync.fns"
+
+test -f /proc/sys/fs/protected_regular || test_skipped "Can't find protected_regular setting (only available on Linux)"
+pr_lvl=`cat /proc/sys/fs/protected_regular 2>/dev/null` || test_skipped "Can't check if fs.protected_regular is enabled (probably need root)"
+test "$pr_lvl" != 0 || test_skipped "fs.protected_regular is not enabled"
+
+workdir="$tmpdir/files"
+mkdir "$workdir"
+chmod 1777 "$workdir"
+
+echo "Source" > "$workdir/src"
+echo "" > "$workdir/dst"
+chown 5001 "$workdir/dst" || test_skipped "Can't chown (probably need root)"
+
+# Output is only shown in case of an error
+echo "Contents of $workdir:"
+ls -al "$workdir"
+
+$RSYNC --inplace "$workdir/src" "$workdir/dst" || test_fail
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/relative.test b/testsuite/relative.test
new file mode 100644
index 0000000..5546291
--- /dev/null
+++ b/testsuite/relative.test
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+# Copyright (C) 2005-2020 Wayne Davison
+#
+# This program is distributable under the terms of the GNU GPL (see COPYING)
+
+. "$suitedir/rsync.fns"
+
+deepstr='down/3/deep'
+deepdir="$fromdir/$deepstr"
+extradir="$fromdir/extra"
+makepath "$deepdir" "$extradir/$deepstr" "$chkdir"
+
+fromdir="$deepdir"
+hands_setup
+fromdir="$tmpdir/from"
+
+extrafile="$extradir/./$deepstr/extra.added.value"
+echo wowza >"$extrafile"
+
+$RSYNC -av --existing --include='*/' --exclude='*' "$fromdir/" "$extradir/"
+
+cd "$fromdir"
+
+# Main script starts here
+
+$RSYNC -ai --include=/down/ --exclude='/*' "$fromdir/" "$chkdir/"
+
+sleep 1
+runtest "basic relative" 'checkit "$RSYNC -avR ./$deepstr \"$todir\"" "$chkdir" "$todir"'
+
+ln $deepstr/filelist $deepstr/dir
+ln ../chk/$deepstr/filelist ../chk/$deepstr/dir
+# Work around time rounding/truncating issue by touching both dirs.
+touch -r $deepstr/dir $deepstr/dir ../chk/$deepstr/dir
+runtest "hard links" 'checkit "$RSYNC -avHR ./$deepstr/ \"$todir\"" "$chkdir" "$todir"'
+
+cp "$deepdir/text" "$todir/$deepstr/ThisShouldGo"
+cp "$deepdir/text" "$todir/$deepstr/dir/ThisShouldGoToo"
+runtest "deletion" 'checkit "$RSYNC -avHR --del ./$deepstr/ \"$todir\"" "$chkdir" "$todir"'
+
+runtest "non-deletion" 'checkit "$RSYNC -aiHR --del ./$deepstr/ \"$todir\"" "$chkdir" "$todir"' \
+ | tee "$outfile"
+
+# Make sure no files were deleted
+grep 'deleting ' "$outfile" && test_fail "Erroneous deletions occurred!"
+
+# Relative with merging.
+$RSYNC -ai "$extradir/down" "$chkdir/"
+
+checkit "$RSYNC -aiR $deepstr '$extrafile' '$todir'" "$chkdir" "$todir"
+
+checkit "$RSYNC -aiR --del $deepstr '$extrafile' '$todir'" "$chkdir" "$todir" \
+ | tee "$outfile"
+
+# Make sure no files were deleted
+grep 'deleting ' "$outfile" && test_fail "Erroneous deletions occurred! (2)"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
new file mode 100644
index 0000000..2ab97b6
--- /dev/null
+++ b/testsuite/rsync.fns
@@ -0,0 +1,498 @@
+#!/bin/sh
+
+# Copyright (C) 2001 by Martin Pool <mbp@samba.org>
+
+# General-purpose test functions for rsync.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version
+# 2 as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+tmpdir="$scratchdir"
+fromdir="$tmpdir/from"
+todir="$tmpdir/to"
+chkdir="$tmpdir/chk"
+
+chkfile="$scratchdir/rsync.chk"
+outfile="$scratchdir/rsync.out"
+
+# For itemized output:
+all_plus='+++++++++'
+allspace=' '
+dots='.....' # trailing dots after changes
+tab_ch=' ' # a single tab character
+
+# Berkley's nice.
+PATH="$PATH:/usr/ucb"
+
+if diff -u "$suitedir/rsync.fns" "$suitedir/rsync.fns" >/dev/null 2>&1; then
+ diffopt="-u"
+else
+ diffopt="-c"
+fi
+
+HOME="$scratchdir"
+export HOME
+
+runtest() {
+ echo $ECHO_N "Test $1: $ECHO_C"
+ if eval "$2"; then
+ echo "$ECHO_T done."
+ return 0
+ else
+ echo "$ECHO_T failed!"
+ return 1
+ fi
+}
+
+set_cp_destdir() {
+ while test $# -gt 1; do
+ shift
+ done
+ destdir="$1"
+}
+
+# Perform a "cp -p", making sure that timestamps are really the same,
+# even if the copy rounded microsecond times on the destination file.
+cp_touch() {
+ cp_p "${@}"
+ if test $# -gt 2 || test -d "$2"; then
+ set_cp_destdir "${@}" # sets destdir var
+ while test $# -gt 1; do
+ destname="$destdir/`basename $1`"
+ touch -r "$destname" "$1" "$destname"
+ shift
+ done
+ else
+ touch -r "$2" "$1" "$2"
+ fi
+}
+
+# Call this if you want to filter (stdin -> stdout) verbose messages (-v or
+# -vv) from an rsync run (whittling the output down to just the file messages).
+# This isn't needed if you use -i without -v.
+v_filt() {
+ sed -e '/^building file list /d' \
+ -e '/^sending incremental file list/d' \
+ -e '/^created directory /d' \
+ -e '/^done$/d' \
+ -e '/ --whole-file$/d' \
+ -e '/^total: /d' \
+ -e '/^client charset: /d' \
+ -e '/^server charset: /d' \
+ -e '/^$/,$d'
+}
+
+printmsg() {
+ echo "$1"
+}
+
+rsync_ls_lR() {
+ find "$@" -name .git -prune -o -name auto-build-save -prune -o -print | \
+ sort | sed 's/ /\\ /g' | xargs "$TOOLDIR/tls" $TLS_ARGS
+}
+
+get_testuid() {
+ uid=`id -u 2>/dev/null || true`
+ case "$uid" in
+ [0-9]*) echo "$uid" ;;
+ *) id 2>/dev/null | sed 's/^[^0-9]*\([0-9][0-9]*\).*/\1/' ;;
+ esac
+}
+
+get_rootuid() {
+ uid=`id -u root 2>/dev/null || true`
+ case "$uid" in
+ [0-9]*) echo "$uid" ;;
+ *) echo 0 ;;
+ esac
+}
+
+get_rootgid() {
+ gid=`id -g root 2>/dev/null || true`
+ case "$gid" in
+ [0-9]*) echo "$gid" ;;
+ *) echo 0 ;;
+ esac
+}
+
+# When copying via "cp -p", we want to ensure that a non-root user does not
+# preserve ownership (we want our files to be created as the testing user).
+# For instance, a Cygwin CI run might have git files owned by a different
+# user than the (admin) user running the tests.
+cp_cmd="cp -p"
+if test x`get_testuid` != x0; then
+ case `cp --help 2>/dev/null` in
+ *--no-preserve=*) cp_cmd="cp -p --no-preserve=ownership" ;;
+ esac
+fi
+cp_p() {
+ $cp_cmd "${@}" || test_fail "$cp_cmd failed"
+}
+
+check_perms() {
+ perms=`"$TOOLDIR/tls" "$1" | sed 's/^[-d]\(.........\).*/\1/'`
+ if test $perms = $2; then
+ return 0
+ fi
+ echo "permissions: $perms on $1"
+ echo "should be: $2"
+ test_fail "failed test $3"
+}
+
+rsync_getgroups() {
+ "$TOOLDIR/getgroups"
+}
+
+
+####################
+# Build test directories $todir and $fromdir, with $fromdir full of files.
+
+hands_setup() {
+ # Clean before creation
+ rm -rf "$fromdir"
+ rm -rf "$todir"
+
+ [ -d "$tmpdir" ] || mkdir "$tmpdir"
+ [ -d "$fromdir" ] || mkdir "$fromdir"
+ [ -d "$todir" ] || mkdir "$todir"
+
+ # On some BSD systems, the umask affects the mode of created
+ # symlinks, even though the mode apparently has no effect on how
+ # the links behave in the future, and it cannot be changed using
+ # chmod! rsync always sets its umask to 000 so that it can
+ # accurately recreate permissions, but this script is probably run
+ # with a different umask.
+
+ # This causes a little problem that "ls -l" of the two will not be
+ # the same. So, we need to set our umask before doing any creations.
+
+ # set up test data
+ touch "$fromdir/empty"
+ mkdir "$fromdir/emptydir"
+
+ # a hundred lines of text or so
+ rsync_ls_lR "$srcdir" > "$fromdir/filelist"
+
+ echo $ECHO_N "This file has no trailing lf$ECHO_C" > "$fromdir/nolf"
+ umask 0
+ ln -s nolf "$fromdir/nolf-symlink"
+ umask 022
+
+ cat "$srcdir"/*.c > "$fromdir/text"
+ mkdir "$fromdir/dir"
+ cp "$fromdir/text" "$fromdir/dir"
+ mkdir "$fromdir/dir/subdir"
+ echo some data > "$fromdir/dir/subdir/foobar.baz"
+ mkdir "$fromdir/dir/subdir/subsubdir"
+ if [ -r /etc ]; then
+ ls -ltr /etc > "$fromdir/dir/subdir/subsubdir/etc-ltr-list"
+ else
+ ls -ltr / > "$fromdir/dir/subdir/subsubdir/etc-ltr-list"
+ fi
+ mkdir "$fromdir/dir/subdir/subsubdir2"
+ if [ -r /bin ]; then
+ ls -lt /bin > "$fromdir/dir/subdir/subsubdir2/bin-lt-list"
+ else
+ ls -lt / > "$fromdir/dir/subdir/subsubdir2/bin-lt-list"
+ fi
+
+# echo testing head:
+# ls -lR "$srcdir" | head -10 || echo failed
+}
+
+
+####################
+# Many machines do not have "mkdir -p", so we have to build up long paths.
+# How boring.
+makepath() {
+ for p in "${@}"; do
+ (echo " makepath $p"
+
+ # Absolute Unix path.
+ if echo $p | grep '^/' >/dev/null; then
+ cd /
+ fi
+
+ # This will break if $p contains a space.
+ for c in `echo $p | tr '/' ' '`; do
+ if [ -d "$c" ] || mkdir "$c"; then
+ cd "$c" || return $?
+ else
+ echo "failed to create $c" >&2; return $?
+ fi
+ done)
+ done
+}
+
+
+###########################
+# Run a test (in '$1') then compare directories $2 and $3 to see if
+# there are any difference. If there are, explain them.
+
+# So normally basically $1 should be an rsync command, and $2 and $3
+# the source and destination directories. This is only good when you
+# expect to transfer the whole directory exactly as is. If some files
+# should be excluded, you might need to use something else.
+
+checkit() {
+ failed=
+
+ # We can just write everything to stdout/stderr, because the
+ # wrapper hides it unless there is a problem.
+
+ case "x$TLS_ARGS" in
+ *--atimes*)
+ ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
+ ;;
+ *)
+ ;;
+ esac
+
+ echo "Running: \"$1\""
+ eval "$1"
+ status=$?
+ if [ $status != 0 ]; then
+ failed="$failed status=$status"
+ fi
+
+ case "x$TLS_ARGS" in
+ *--atimes*)
+ ;;
+ *)
+ ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
+ ;;
+ esac
+
+ echo "-------------"
+ echo "check how the directory listings compare with diff:"
+ echo ""
+ ( cd "$3" && rsync_ls_lR . ) > "$tmpdir/ls-to"
+ diff $diffopt "$tmpdir/ls-from" "$tmpdir/ls-to" || failed="$failed dir-diff"
+
+ echo "-------------"
+ echo "check how the files compare with diff:"
+ echo ""
+ if [ "x$4" != x ]; then
+ echo " === Skipping (as directed) ==="
+ else
+ diff -r $diffopt "$2" "$3" || failed="$failed file-diff"
+ fi
+
+ echo "-------------"
+ if [ -z "$failed" ]; then
+ return 0
+ fi
+
+ echo "Failed: $failed"
+ return 1
+}
+
+
+# Run a test in $1 and make sure it has a zero exit status. Capture the
+# output into $outfile and echo it to stdout.
+checktee() {
+ echo "Running: \"$1\""
+ eval "$1" >"$outfile"
+ status=$?
+ cat "$outfile"
+ if [ $status != 0 ]; then
+ echo "Failed: status=$status"
+ return 1
+ fi
+ return 0
+}
+
+
+# Slurp stdin into $chkfile and then call checkdiff2().
+checkdiff() {
+ cat >"$chkfile" # Save off stdin
+ checkdiff2 "${@}"
+}
+
+
+# Run a test in $1 and make sure it has a zero exit status. Capture the output
+# into $outfile. If $2 is set, use it to filter the outfile. If resulting
+# outfile differs from the chkfile data, fail with an error.
+checkdiff2() {
+ failed=
+
+ echo "Running: \"$1\""
+ eval "$1" >"$outfile"
+ status=$?
+ cat "$outfile"
+ if [ $status != 0 ]; then
+ failed="$failed status=$status"
+ fi
+
+ if [ -n "$2" ]; then
+ eval "cat '$outfile' | $2 >'$outfile.new'"
+ mv "$outfile.new" "$outfile"
+ fi
+
+ diff $diffopt "$chkfile" "$outfile" || failed="$failed output differs"
+
+ if [ -n "$failed" ]; then
+ echo "Failed:$failed"
+ return 1
+ fi
+ return 0
+}
+
+
+build_rsyncd_conf() {
+ # Build an appropriate configuration file
+ conf="$scratchdir/test-rsyncd.conf"
+ echo "building configuration $conf"
+
+ port=2612
+ pidfile="$scratchdir/rsyncd.pid"
+ logfile="$scratchdir/rsyncd.log"
+ hostname=`uname -n`
+
+ my_uid=`get_testuid`
+ root_uid=`get_rootuid`
+ root_gid=`get_rootgid`
+
+ uid_setting="uid = $root_uid"
+ gid_setting="gid = $root_gid"
+
+ if test x"$my_uid" != x"$root_uid"; then
+ # Non-root cannot specify uid & gid settings
+ uid_setting="#$uid_setting"
+ gid_setting="#$gid_setting"
+ fi
+
+ cat >"$conf" <<EOF
+# rsyncd configuration file autogenerated by $0
+
+pid file = $pidfile
+use chroot = no
+munge symlinks = no
+hosts allow = localhost 127.0.0.0/24 192.168.0.0/16 10.0.0.0/8 $hostname
+log file = $logfile
+transfer logging = yes
+# We don't define log format here so that the test-hidden module will default
+# to the internal static string (since we had a crash trying to tweak it).
+exclude = ? foobar.baz
+max verbosity = 4
+$uid_setting
+$gid_setting
+
+[test-from]
+ path = $fromdir
+ log format = %i %h [%a] %m (%u) %l %f%L
+ read only = yes
+ comment = r/o
+
+[test-to]
+ path = $todir
+ log format = %i %h [%a] %m (%u) %l %f%L
+ read only = no
+ comment = r/w
+
+[test-scratch]
+ path = $scratchdir
+ log format = %i %h [%a] %m (%u) %l %f%L
+ read only = no
+
+[test-hidden]
+ path = $fromdir
+ list = no
+EOF
+
+ # Build a helper script to ignore exit code 23
+ ignore23="$scratchdir/ignore23"
+ echo "building help script $ignore23"
+
+ cat >"$ignore23" <<'EOT'
+if "${@}"; then
+ exit
+fi
+
+ret=$?
+
+if test $ret = 23; then
+ exit
+fi
+
+exit $ret
+EOT
+chmod +x "$ignore23"
+}
+
+
+build_symlinks() {
+ mkdir "$fromdir"
+ date >"$fromdir/referent"
+ ln -s referent "$fromdir/relative"
+ ln -s "$fromdir/referent" "$fromdir/absolute"
+ ln -s nonexistent "$fromdir/dangling"
+ ln -s "$srcdir/rsync.c" "$fromdir/unsafe"
+}
+
+test_fail() {
+ echo "$@" >&2
+ exit 1
+}
+
+test_skipped() {
+ echo "$@" >&2
+ echo "$@" > "$tmpdir/whyskipped"
+ exit 77
+}
+
+# It failed, but we expected that. Don't dump out error logs,
+# because most users won't want to see them. But do leave
+# the working directory around.
+test_xfail() {
+ echo "$@" >&2
+ exit 78
+}
+
+# Determine what shell command will appropriately test for links.
+ln -s foo "$scratchdir/testlink"
+for cmd in test /bin/test /usr/bin/test /usr/ucb/bin/test /usr/ucb/test; do
+ for switch in -h -L; do
+ if $cmd $switch "$scratchdir/testlink" 2>/dev/null; then
+ # how nice
+ TEST_SYMLINK_CMD="$cmd $switch"
+ # i wonder if break 2 is portable?
+ break 2
+ fi
+ done
+done
+# ok, now get rid of it
+rm "$scratchdir/testlink"
+
+
+if [ "x$TEST_SYMLINK_CMD" = 'x' ]; then
+ test_fail "Couldn't determine how to test for symlinks"
+else
+ echo "Testing for symlinks using '$TEST_SYMLINK_CMD'"
+fi
+
+
+# Test whether something is a link, allowing for shell peculiarities
+is_a_link() {
+ # note the variable contains the first option and therefore is not quoted
+ $TEST_SYMLINK_CMD "$1"
+}
+
+
+# We need to set the umask to be reproducible. Note also that when we
+# do some daemon tests as root, we will setuid() and therefore the
+# directory has to be writable by the nobody user in some cases. The
+# best thing is probably to explicitly chmod those directories after
+# creation.
+
+umask 022
diff --git a/testsuite/ssh-basic.test b/testsuite/ssh-basic.test
new file mode 100644
index 0000000..1559ca2
--- /dev/null
+++ b/testsuite/ssh-basic.test
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+# Copyright (C) 1998,1999 Philip Hands <phil@hands.com>
+# Copyright (C) 2001 by Martin Pool <mbp@samba.org>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING)
+
+# This script tests ssh, if possible. It's called by runtests.sh
+
+. "$suitedir/rsync.fns"
+
+SSH="$scratchdir/src/support/lsh.sh"
+
+if test x"$rsync_enable_ssh_tests" = xyes; then
+ if type ssh >/dev/null; then
+ SSH=ssh
+ fi
+fi
+
+if [ "`$SSH -o'BatchMode yes' localhost echo yes`" != "yes" ]; then
+ test_skipped "Skipping SSH tests because ssh connection to localhost not authorised"
+fi
+
+echo "Using remote shell: $SSH"
+
+# Create some files for rsync to copy
+hands_setup
+
+runtest "ssh: basic test" 'checkit "$RSYNC -avH -e \"$SSH\" --rsync-path=\"$RSYNC\" \"$fromdir/\" \"localhost:$todir\"" "$fromdir/" "$todir"'
+
+mv "$todir/text" "$todir/ThisShouldGo"
+
+runtest "ssh: renamed file" 'checkit "$RSYNC --delete -avH -e \"$SSH\" --rsync-path=\"$RSYNC\" \"$fromdir/\" \"localhost:$todir\"" "$fromdir/" "$todir"'
diff --git a/testsuite/symlink-ignore.test b/testsuite/symlink-ignore.test
new file mode 100644
index 0000000..7055a92
--- /dev/null
+++ b/testsuite/symlink-ignore.test
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+# Copyright (C) 2001 by Martin Pool <mbp@samba.org>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test rsync's somewhat over-featured symlink control: the default
+# behaviour is that symlinks should not be copied at all.
+
+. "$suitedir/rsync.fns"
+
+build_symlinks || test_fail "failed to build symlinks"
+
+# Copy recursively, but without -l or -L or -a, and all the symlinks
+# should be missing.
+$RSYNC -r "$fromdir/" "$todir" || test_fail "$RSYNC returned $?"
+
+[ -f "$todir/referent" ] || test_fail "referent was not copied"
+[ -d "$todir/from" ] && test_fail "extra level of directories"
+if is_a_link "$todir/dangling"; then
+ test_fail "dangling symlink was copied"
+fi
+
+if is_a_link "$todir/relative"; then
+ test_fail "relative symlink was copied"
+fi
+
+if is_a_link "$todir/absolute"; then
+ test_fail "absolute symlink was copied"
+fi
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/trimslash.test b/testsuite/trimslash.test
new file mode 100644
index 0000000..2efaa07
--- /dev/null
+++ b/testsuite/trimslash.test
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# Copyright (C) 2002 by Martin Pool <mbp@samba.org>
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test tiny function to trim trailing slashes.
+
+. "$suitedir/rsync.fns"
+
+"$TOOLDIR/trimslash" "/usr/local/bin" "/usr/local/bin/" "/usr/local/bin///" \
+ "//a//" "////" \
+ "/Users/Weird Macintosh Name/// Ooh, translucent plastic/" \
+ > "$scratchdir/slash.out"
+diff $diffopt "$scratchdir/slash.out" - <<EOF
+/usr/local/bin
+/usr/local/bin
+/usr/local/bin
+//a
+/
+/Users/Weird Macintosh Name/// Ooh, translucent plastic
+EOF
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/unsafe-byname.test b/testsuite/unsafe-byname.test
new file mode 100644
index 0000000..75e7201
--- /dev/null
+++ b/testsuite/unsafe-byname.test
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+# Copyright (C) 2002 by Martin Pool
+
+# Call directly into unsafe_symlink and test its handling of various filenames
+
+. "$suitedir/rsync.fns"
+
+test_unsafe() {
+ # $1 is the target of a symlink
+ # $2 is the directory we're copying
+ # $3 is the expected outcome: "safe" if the link lies within $2,
+ # or "unsafe" otherwise
+
+ result=`"$TOOLDIR/t_unsafe" "$1" "$2"` || test_fail "Failed to check $1 $2"
+ if [ "$result" != "$3" ]; then
+ test_fail "t_unsafe $1 $2 returned \"$result\", expected \"$3\""
+ fi
+}
+
+test_unsafe file from safe
+test_unsafe dir/file from safe
+test_unsafe dir/./file from safe
+test_unsafe dir/. from safe
+test_unsafe dir/ from safe
+
+test_unsafe /etc/passwd from unsafe
+test_unsafe //../etc/passwd from unsafe
+test_unsafe //./etc/passwd from unsafe
+
+test_unsafe ./foo from safe
+test_unsafe ../foo from unsafe
+test_unsafe ./../foo from unsafe
+test_unsafe .//../foo from unsafe
+test_unsafe ./../foo from/.. unsafe
+test_unsafe ../dest from/dir safe
+test_unsafe ../../dest from//dir unsafe
+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 '' from unsafe
+
+# Based on tests from unsafe-links by Vladimír Michl
+test_unsafe ../../unsafe/unsafefile from/safe unsafe
+test_unsafe ..//../unsafe/unsafefile from/safe unsafe
+test_unsafe ../files/file1 from/safe safe
+
+test_unsafe ../../unsafe/unsafefile safe unsafe
+test_unsafe ../files/file1 safe unsafe
+
+test_unsafe ../../unsafe/unsafefile `pwd`/from/safe safe
+test_unsafe ../files/file1 `pwd`/from/safe safe
diff --git a/testsuite/unsafe-links.test b/testsuite/unsafe-links.test
new file mode 100644
index 0000000..2d209eb
--- /dev/null
+++ b/testsuite/unsafe-links.test
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+# Originally by Vladimír Michl <Vladimir.Michl@hlubocky.del.cz>
+
+. "$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
+}
+
+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/"
+
+echo "rsync with relative path and just -a"
+$RSYNC -avv from/safe/ to
+test_symlink to/links/file1
+test_symlink to/links/file2
+test_symlink to/links/unsafefile
+
+echo "rsync with relative path and -a --copy-links"
+$RSYNC -avv --copy-links from/safe/ to
+test_regular to/links/file1
+test_regular to/links/file2
+test_regular to/links/unsafefile
+
+echo "rsync with relative path and --copy-unsafe-links"
+$RSYNC -avv --copy-unsafe-links from/safe/ to
+test_symlink to/links/file1
+test_symlink to/links/file2
+test_regular to/links/unsafefile
+
+rm -rf to
+echo "rsync with relative2 path"
+(cd from; $RSYNC -avv --copy-unsafe-links safe/ ../to)
+test_symlink to/links/file1
+test_symlink to/links/file2
+test_regular to/links/unsafefile
+
+rm -rf to
+echo "rsync with absolute path"
+$RSYNC -avv --copy-unsafe-links `pwd`/from/safe/ to
+test_symlink to/links/file1
+test_symlink to/links/file2
+test_regular to/links/unsafefile
diff --git a/testsuite/wildmatch.test b/testsuite/wildmatch.test
new file mode 100644
index 0000000..cfe7584
--- /dev/null
+++ b/testsuite/wildmatch.test
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+# Copyright (C) 2003-2022 Wayne Davison
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test the wildmatch functionality
+
+. "$suitedir/rsync.fns"
+
+# This test exercises the wildmatch() function (with no options) and the
+# wildmatch_join() function (using -x and/or -e).
+for opts in "" -x1 "-x1 -e1" "-x1 -e1se" -x2 "-x2 -ese" -x3 "-x3 -e1" -x4 "-x4 -e2e" -x5 "-x5 -es"; do
+ echo Running wildtest with "$opts"
+ "$TOOLDIR/wildtest" $opts "$srcdir/wildtest.txt" >"$scratchdir/wild.out"
+ diff $diffopt "$scratchdir/wild.out" - <<EOF
+No wildmatch errors found.
+EOF
+done
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/testsuite/xattrs.test b/testsuite/xattrs.test
new file mode 100644
index 0000000..d94d5f9
--- /dev/null
+++ b/testsuite/xattrs.test
@@ -0,0 +1,239 @@
+#!/bin/sh
+
+# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that rsync handles basic xattr preservation.
+
+. $suitedir/rsync.fns
+lnkdir="$tmpdir/lnk"
+
+$RSYNC -VV | grep '"xattrs": true' >/dev/null || test_skipped "Rsync is configured without xattr support"
+
+case "$HOST_OS" in
+darwin*)
+ xset() {
+ xnam="$1"
+ xval="$2"
+ shift 2
+ xattr -s "$xnam" "$xval" "${@}"
+ }
+ xls() {
+ xattr -l "${@}" | sed "s/^[ $tab_ch]*//"
+ }
+ RSYNC_PREFIX='rsync'
+ RUSR='rsync.nonuser'
+ ;;
+solaris*)
+ xset() {
+ xnam="$1"
+ xval="$2"
+ shift 2
+ for fn in "${@}"; do
+ runat "$fn" "$SHELL_PATH" <<EOF
+echo "${xval}" > "${xnam}"
+EOF
+ done
+ }
+ xls() {
+ for fn in "${@}"; do
+ runat "$fn" "$SHELL_PATH" <<EOF
+for x in *; do echo "\$x=\`cat \$x\`"; done
+EOF
+ done
+ }
+ RSYNC_PREFIX='rsync'
+ RUSR='rsync.nonuser'
+ ;;
+freebsd*)
+ xset() {
+ xnam="$1"
+ xval="$2"
+ shift 2
+ setextattr -h user "$xnam" "$xval" "${@}"
+ }
+ xls() {
+ for f in "${@}"; do lsextattr -q -h user "$f" | tr '[[:space:]]' '\n' | sort | xargs -I % getextattr -h user % "$f"; done
+ }
+ RSYNC_PREFIX='rsync'
+ RUSR='rsync'
+ ;;
+*)
+ xset() {
+ xnam="$1"
+ xval="$2"
+ shift 2
+ setfattr -n "$xnam" -v "$xval" "${@}"
+ }
+ xls() {
+ getfattr -d "${@}"
+ }
+ RSYNC_PREFIX='user.rsync'
+ RUSR='user.rsync'
+ ;;
+esac
+
+makepath "$lnkdir" "$fromdir/foo/bar"
+echo now >"$fromdir/file0"
+echo something >"$fromdir/file1"
+echo else >"$fromdir/file2"
+echo deep >"$fromdir/foo/file3"
+echo normal >"$fromdir/file4"
+echo deeper >"$fromdir/foo/bar/file5"
+
+makepath "$chkdir/foo"
+echo wow >"$chkdir/file1"
+cp_touch "$fromdir/foo/file3" "$chkdir/foo"
+
+dirs='foo foo/bar'
+files='file0 file1 file2 foo/file3 file4 foo/bar/file5'
+
+uid_gid=`"$TOOLDIR/tls" "$fromdir/foo" | sed 's/^.* \([0-9][0-9]*\)\.\([0-9][0-9]*\) .*/\1:\2/'`
+
+cd "$fromdir"
+
+xset user.foo foo file0 2>/dev/null || test_skipped "Unable to set an xattr"
+xset user.bar bar file0
+
+xset user.short 'this is short' file1
+xset user.long 'this is a long attribute that will be truncated in the initial data send' file1
+xset user.good 'this is good' file1
+xset user.nice 'this is nice' file1
+
+xset user.foo foo file2
+xset user.bar bar file2
+xset user.long 'a long attribute for our new file that tests to ensure that this works' file2
+
+xset user.dir1 'need to test directory xattrs too' foo
+xset user.dir2 'another xattr' foo
+xset user.dir3 'this is one last one for the moment' foo
+
+xset user.dir4 'another dir test' foo/bar
+xset user.dir5 'one last one' foo/bar
+
+xset user.foo 'new foo' foo/file3 foo/bar/file5
+xset user.bar 'new bar' foo/file3 foo/bar/file5
+xset user.long 'this is also a long attribute that will be truncated in the initial data send' foo/file3 foo/bar/file5
+xset $RUSR.equal 'this long attribute should remain the same and not need to be transferred' foo/file3 foo/bar/file5
+
+xset user.dir0 'old extra value' "$chkdir/foo"
+xset user.dir1 'old dir value' "$chkdir/foo"
+
+xset user.short 'old short' "$chkdir/file1"
+xset user.extra 'remove me' "$chkdir/file1"
+
+xset user.foo 'old foo' "$chkdir/foo/file3"
+xset $RUSR.equal 'this long attribute should remain the same and not need to be transferred' "$chkdir/foo/file3"
+
+case $0 in
+*hlink*)
+ ln foo/bar/file5 foo/bar/file6 || test_skipped "Can't create hardlink"
+ files="$files foo/bar/file6"
+ dashH='-H'
+ altDest='--link-dest'
+ ;;
+*)
+ dashH=''
+ altDest='--copy-dest'
+ ;;
+esac
+
+xls $dirs $files >"$scratchdir/xattrs.txt"
+
+XFILT='-f-x_system.* -f-x_security.*'
+
+# OK, let's try a simple xattr copy.
+checkit "$RSYNC -avX $XFILT $dashH --super . '$chkdir/'" "$fromdir" "$chkdir"
+
+cd "$chkdir"
+xls $dirs $files | diff $diffopt "$scratchdir/xattrs.txt" -
+
+cd "$fromdir"
+
+if [ -n "$dashH" ]; then
+ for fn in $files; do
+ name=`basename $fn`
+ ln $fn ../lnk/$name
+ done
+fi
+
+checkit "$RSYNC -aiX $XFILT $dashH --super $altDest=../chk . ../to" "$fromdir" "$todir"
+
+cd "$todir"
+xls $dirs $files | diff $diffopt "$scratchdir/xattrs.txt" -
+
+[ -n "$dashH" ] && rm -rf "$lnkdir"
+
+cd "$fromdir"
+rm -rf "$todir"
+
+xset user.nice 'this is nice, but different' file1
+
+xls $dirs $files >"$scratchdir/xattrs.txt"
+
+checkit "$RSYNC -aiX $XFILT $dashH --fake-super --link-dest=../chk . ../to" "$chkdir" "$todir"
+
+cd "$todir"
+xls $dirs $files | diff $diffopt "$scratchdir/xattrs.txt" -
+
+sed -n -e '/^[^d ][^ ]* *[^ ][^ ]* *[^ ][^ ]* *1 /p' "$scratchdir/ls-to" >"$scratchdir/ls-diff-all"
+grep -F -v './file1' "$scratchdir/ls-diff-all" >"$scratchdir/ls-diff" || :
+if [ -s "$scratchdir/ls-diff" ]; then
+ echo "Missing hard links on:"
+ cat "$scratchdir/ls-diff"
+ exit 1
+fi
+if [ ! -s "$scratchdir/ls-diff-all" ]; then
+ echo "Too many hard links on file1!"
+ exit 1
+fi
+
+cd "$chkdir"
+chmod go-rwx . $dirs $files
+
+xset user.nice 'this is nice, but different' file1
+xset $RSYNC_PREFIX.%stat "40000 0,0 $uid_gid" $dirs
+xset $RSYNC_PREFIX.%stat "100000 0,0 $uid_gid" $files
+
+xls $dirs $files >"$scratchdir/xattrs.txt"
+
+cd "$fromdir"
+rm -rf "$todir"
+
+# When run by a non-root tester, this checks if no-user-perm files/dirs can be copied.
+checkit "$RSYNC -aiX $XFILT $dashH --fake-super --chmod=a= . ../to" "$chkdir" "$todir" # 2>"$scratchdir/errors.txt"
+
+cd "$todir"
+xls $dirs $files | diff $diffopt "$scratchdir/xattrs.txt" -
+
+cd "$fromdir"
+rm -rf "$todir" "$chkdir"
+
+$RSYNC -aX file1 file2
+$RSYNC -aX file1 file2 ../chk/
+$RSYNC -aX --del ../chk/ .
+$RSYNC -aX file1 ../lnk/
+[ -n "$dashH" ] && ln "$chkdir/file1" ../lnk/extra-link
+
+xls file1 file2 >"$scratchdir/xattrs.txt"
+
+checkit "$RSYNC -aiiX $XFILT $dashH $altDest=../lnk . ../to" "$chkdir" "$todir"
+
+[ -n "$dashH" ] && rm ../lnk/extra-link
+
+cd "$todir"
+xls file1 file2 | diff $diffopt "$scratchdir/xattrs.txt" -
+
+cd "$fromdir"
+rm "$todir/file2"
+
+echo extra >file1
+$RSYNC -aX . ../chk/
+
+checkit "$RSYNC -aiiX $XFILT . ../to" "$chkdir" "$todir"
+
+cd "$todir"
+xls file1 file2 | diff $diffopt "$scratchdir/xattrs.txt" -
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0