NAME
App::FileSync::Verify - check a tree against its recorded manifest
SYNOPSIS
my $report = App::FileSync::Verify::verify(
root => $master, entries => $index, ignore => $ignore, sample => 50,
);
print $report->{ok} ? "clean\n" : "drift\n";
FUNCTIONS
verify
Purpose: report where a tree no longer matches its record.
Args: root (required, a directory), entries (the recorded manifest), ignore (an App::FileSync::Ignore), deep (rehash every file), sample (without deep, rehash this many unchanged-looking files chosen at random).
Without deep, entries whose size and mtime still match are trusted, which is what makes a verify of a large tree cheap.
Returns: a hashref:
root, ok (1 when nothing differs), deep, files (regular files found),
known (entries in the record), rehashed, trusted, sampled,
modified => [ { p, expected, got } | { p, reason } | { p, expected, got, sampled } ],
missing => [ { p, t } ],
extra => [ { p, t } ]
Each list is sorted by path.
Side Effects: reads files; never writes.
Usage:
my $report = App::FileSync::Verify::verify(root => $root, entries => $base, deep => 1);
EXAMPLE
my $report = App::FileSync::Verify::verify(root => '/srv/filesync/master/docs', entries => $index);
print "missing: $_->{p}\n" for @{ $report->{missing} };
API SPECIFICATION
INPUT
{
root => { type => 'string', min => 1 },
entries => { type => 'hashref', optional => 1 },
ignore => { type => 'object', optional => 1 },
deep => { type => 'boolean', optional => 1 },
sample => { type => 'integer', min => 0, optional => 1 },
}
OUTPUT
{
type => 'hashref',
schema => {
ok => { type => 'integer', min => 0, max => 1 },
modified => { type => 'arrayref' },
missing => { type => 'arrayref' },
extra => { type => 'arrayref' },
},
}
MESSAGES
Key Meaning Resolution
verify.no_root root is not a directory check the path
verify.type_changed (a reason in "modified") a file became a directory or back
FORMAL SPECIFICATION
let M = scan(root).manifest ; E = entries
missing = { p ∈ dom E | p ¬in; dom M ∧ ¬(E(p).t = d ∧ isdir(root/p)) }
modified = { p ∈ dom E ∩ dom M | E(p).t ≠ M(p).t ∨ (E(p).t = f ∧ E(p).h ≠ M(p).h) }
extra = dom M \ dom E \ (missing ∪ modified)
ok ⇔ missing = modified = extra = ∅
PSEUDOCODE
scan the tree, reusing recorded hashes unless deep
for each recorded path: missing, type changed, or hash differs
every scanned path not recorded is extra
if sampling: rehash that many trusted files and add any that differ
sort the three lists and count
LIMITATIONS
A shallow verify trusts size and mtime, so it cannot see a change that kept both; use
deeporsamplefor that.The report says what differs, not which side is right. After a restore from backup the index, not the tree, is usually the stale one; see
filesync-admin reindex.
AUTHOR
Nigel Horne, <njh at nigelhorne.com>
LICENCE AND COPYRIGHT
Copyright 2026 Nigel Horne.
Usage is subject to the GPL2 licence terms. If you use it, please let me know.