Verifying copies
jrs-s.net
jrs-s.net
What you want is a tar that is deterministically identical regardless of the underlying file system's order. Sadly, no such option exists in GNU tar, but that doesn't quite mean the end of the quest. Behold:
find bin -print0 | sort -Vz | tar -cf bin.tar --no-recursion --null -T -
The first two shouldn't be hard to parse. Find files, separated by NULL characters (so any whitespace or other special characters won't be an issue), pass that onto "sort" that uses a version sort (identical rules to dpkg version sorting, and will disregard the locale sort) and interpret entries as being between NULLs (as well as emit them, likewise). The tar command should be pretty self-explanatory, but the "-T -" tells it to read stdin for a file list, with --null specifying that the input list is separated by NULLs, and --no-recursion prevents it from recursing into directories, instead just adding an entry in the tar for directories -- the find|sort will take care of the directory members already, sorted. Optionally add --numeric-owner to tar, if you want to compare trees on different computers, but results might vary (rsync and tar normally send/store both the id and name, and if the "name" exists on the local machine, uses that instead of id).
It produces output that is meant to be usefully diffable.
That said, how does it handle firstpath/somefile.bin and secondpath/somefile.bin being identical? This breaks your "diffable" output because the paths are different.
$ mkdir foo
$ echo foo > foo/bar
$ cp -a foo foo2
$ summain -r foo > foo.summain
$ summain -r foo2 > foo2.summain
$ diff foo*.summain
Sometimes I also use the following "one" liner, if it's local.
diff <(sort <(cd /path/to/source; \
find . -type f -print0 | \
xargs -0 sha1sum)) \
<(sort <(cd /path/to/destination; \
find . -type f -print0 | \
xargs -0 sha1sum))
Another nice rsync tip is that if you aren't using -a, you should at least use -t (preserve modification time). This will make a second rsync faster as it can skip files if the modification time and size match.You might trust that, but you'd be wrong:
$ cd /tmp
$ mkdir foo
$ cd foo
$ mkdir bar
$ mkdir baz
$ touch bar/quux
$ rsync -a ./bar ./baz
$ ls baz
bar
$ ls -R baz
baz:
bar
baz/bar:
quux
You see, without a final '/' rsync will put the source into the target, rather than synchronising the source and the target. Also, if you really want a sync as opposed to just a full copy, you probably want to add --delete (which will delete files in the target which don't exist in the source.So you probably want rsync -a --delete source/ target/.
(let ((src #P"/tmp/foo/bar/")
(dst #P"/tmp/foo/baz/"))
(flet ((sha256sum (path)
"Return the SHA256 checksum of PATH as a string (which is
good enough for our purposes here)."
(first (split-sequence:split-sequence
#\Space
(uiop:run-program `("sha256sum" ,(namestring path))
:output :string)))))
(loop for src-path in (directory (uiop:merge-pathnames* "**/*.*" src))
for dst-path = (uiop:merge-pathnames* (uiop:subpathp src-path src) dst)
for file = (uiop:truename* dst-path)
if file
when (uiop:file-pathname-p file)
do (unless (string= (sha256sum src-path) (sha256sum dst-path))
(warn "~a does not match ~a" dst-path src-path))
end
else
do (warn "~a not found" dst-path))))It would be interesting to see the minimal counter-examples to widely-believed properties that don't hold (not just for rsync but for dfferent CLI tools).
(See John Hughes, testing Dropbox with QuickCheck: https://vimeo.com/158002499)
EDIT: s/Jon/John/
.. which is, almost all of the time, good enough and requires much less bandwidth between the copies. These days it might be better to pick SHA256.