fix(health): use forward-slash virtual paths for rclone VFS notifications - #849
Conversation
|
Added a third site in 292ee77: Caught it in my own logs: Note the bare Heads-up on merge order: this touches a line in |
292ee77 to
8e57dc9
Compare
|
Can you add a test? |
|
Found a fourth site after deploying this and watching the logs: It normalizes the target path but walks the ancestry with One good entry, two that match nothing. This one also predates #846. Worth noting the first three sites are confirmed working in production: since deploying,
|
|
Added tests in d415aa2. Rather than only unit-testing the four inline call sites, I moved the normalization into The caller-side fixes stay, since
Also tidied the PR description. It previously carried a suggestion about using forget-only instead of forget+refresh for deletions. I have since instrumented the machine properly and the CPU spin I was attributing to refresh cost happens with zero files open and rclone doing no work, hours away from any repair, so that suggestion was not supported by the evidence and I have dropped it. |
d415aa2 to
2047481
Compare
|
YOu add a new code but you didn't test the other paths |
|
Sorry, here you go! Added tests for the changed paths in 184417b. The importer refresh ancestry was an inline closure with no way to test it, so it is now
I left |
|
No CRITICAL or HIGH issues. Not blocking — safe to merge. Reviewed PR #849 at Verification I ran
Security: nothing. No secrets, no injection surface, no new traversal exposure — FindingsMEDIUM — the Windows behavior this PR fixes has zero CI coverage (
So the separator property is never asserted anywhere. Compounding it: LOW — duplicated normalization loop. LOW — LOW — LOW (pre-existing) — One aside: |
|
Thanks for the detailed pass, all five addressed in 68cda44. Windows coverage. Took your suggestion: Duplicated loop. Now Callers hand-rolling it. All four use Doc comment. Corrected, it no longer calls the first entry a directory. Leading slash. Cleaned. Worth flagging that this one had a trap: cleaning inside Left |
…ions filepath.Dir is OS-aware, so on Windows the directories handed to vfs/forget and vfs/refresh carry "\" separators. rclone's VFS is forward-slash on every platform, so those paths never match a node and the invalidation silently does nothing: vfs/forget echoes back whatever it is given and reports success even for a path that does not resolve. Observed on a Windows host with external rclone: 18 "Successfully notified rclone VFS" entries in one night, all no-ops, alongside "dirs":["tv\Show.Name.S01E01..."]. Use path.Dir on a filepath.ToSlash'd value at the two sites that build virtual paths. ToSlash also normalizes legacy rows that still carry backslashes, so this holds whether or not the path-canonicalization migration has run. No behaviour change on Linux or Docker: on POSIX os.PathSeparator is '/', so filepath.ToSlash returns the string unchanged and filepath.Dir and path.Dir run the same Clean logic. The remaining filepath.Dir calls in library_sync.go operate on real OS paths (symlink target resolution, filesystem walking) and are unchanged.
… too EnqueueRefresh receives filepath.Dir(mvf.name), which on Windows yields "\dir" (or a bare "\" for a file at the mount root). Those reach vfs/forget and vfs/refresh unchanged and match nothing, so the coalesced refresh after a safety-folder move is a no-op there. Observed on a Windows host: "dirs":["movies/Anything.for.Love.1993...","\movies","\\"] Same treatment as the health-side notifications: these are virtual paths, so use path.Dir on a ToSlash'd value. No behaviour change on POSIX, where ToSlash is a no-op and path.Dir and filepath.Dir agree.
notifyVFSWith normalizes the target path for rclone but walks its ancestry with filepath.Dir first, so on Windows the parent and grandparent entries are backslash-separated and normalizeForRclone (which only trims a leading "/") leaves them that way. The "/" guards never match either, since the Windows root is "\", so a useless bare root entry is appended to every batch. Observed on a Windows host, one correctly-formed entry beside two that match nothing: "dirs":["tv/MasterChef.US.S16E15...","\tv","\\"] Walk the ancestry on the ToSlash'd form with path.Dir, and have normalizeForRclone convert separators as well as trim the prefix. No behaviour change on POSIX: filepath.ToSlash is a no-op there and path.Dir and filepath.Dir agree, so both the values and the guards resolve exactly as before. The filepath.Dir at line 116 builds a real OS path for os.Stat and is deliberately unchanged.
…ests Per review feedback, adds test coverage for the separator handling. The four call sites this PR fixes were found one at a time, each from a stray backslash in a production log, which suggests per-site fixes are fragile: a new caller reintroduces the bug and nothing surfaces it, because vfs/forget echoes back whatever it is handed and reports success even for a directory that does not resolve. So normalize in RefreshDir itself, in both implementations. Callers should still build correct virtual paths (and the caller-side fixes stay, since vfs_notifier's "/" root guards can only be fixed there), but a missed site now degrades to a working call rather than a silent no-op. ToVFSPath is a thin wrapper over filepath.ToSlash so the intent has a name and a test. On POSIX it is a no-op: ToSlash only rewrites when the OS separator is not '/', leaving a backslash - a legal POSIX filename character - untouched. Tests cover the unchanged forward-slash case on every platform, the platform-specific separator contract, and the boundary property that no Windows separator can reach rclone.
Adds coverage for the call sites this PR changes, not just the helper. internal/importer/postprocessor: the refresh ancestry was an inline closure with no way to test it, so it is now refreshDirsFor. Table test covers a nested release directory, a file one level under the mount root, a file at the root (no ancestors to add), a path with no leading slash, and spaces. Plus a Windows-only case asserting no separator rclone cannot read reaches it, since on POSIX a backslash is a legal filename character and must be left alone. internal/nzbfilesystem: drives updateFileHealthOnError through a repair with a real coalescer wired to a fake rclone client, and asserts the enqueued directory is the forward-slash parent of the repaired file. The existing harness passes a nil coalescer, so the enqueued value was never observable. internal/health/checker.go already has coverage from javi11#846: TestHealthChecker_NotifyRcloneVFS asserts a forward-slash directory reaches RefreshDir.
Addresses review feedback on this PR.
The Windows behaviour had no CI coverage: every separator assertion was
guarded by runtime.GOOS and skipped on the ubuntu runner, so the property
this PR exists to fix was never checked anywhere. Split the rewrite into a
pure toSlashSep(s, sep byte) so tests can drive it with '\' on any
platform; ToVFSPath is now that bound to os.PathSeparator.
Also:
- ToVFSPaths replaces the loop duplicated in both RefreshDir bodies.
- The four call sites now use rclonecli.ToVFSPath instead of hand-rolling
filepath.ToSlash, which is the point of having the wrapper. This left
path/filepath referenced only from a comment in health/checker.go, so
that import is removed.
- refreshDirsFor's doc comment no longer claims the first entry is a
directory; for a single-file import it is the file, unchanged behaviour.
- refreshDirsFor cleans once up front, so "//tv//Show//ep.mkv" no longer
reaches rclone with a leading slash and "tv/Show/" no longer keeps its
trailing one.
The clean is done before the ancestry walk rather than inside normalize:
walking the raw path makes Dir("/tv/Show/") equal "/tv/Show", so the first
two entries came out identical. Covered by the new trailing-slash case.
68cda44 to
b374635
Compare
Summary
On Windows the directories handed to
vfs/forgetandvfs/refreshcarry\separators, so they match no VFS node and the invalidation silently does nothing.filepath.Diris OS-aware. These are virtual paths, and rclone's VFS is forward-slash on every platform, so they need the POSIXpathpackage instead.Why it fails silently
vfs/forgetechoes back whatever it is given and reports success regardless of whether the path resolves:So nothing surfaces as an error. From a Windows host running external rclone, before the fix:
{"level":"ERROR","msg":"Failed to notify rclone VFS to forget/refresh directories", "dirs":["tv\\SpongeBob.SquarePants.S17E15.Night.School.Knuckleheads.720p.HDTV.AAC2.0.x264-Slurpuff"], ...}In one night: 18 "Successfully notified rclone VFS" entries that invalidated nothing, plus 16 outright failures.
Four sites
internal/health/checker.goNotifyRcloneVFSdirectoryinternal/health/library_sync.gointernal/nzbfilesystem/metadata_remote_file.gointernal/importer/postprocessor/vfs_notifier.goThe first two came from #846; the last two predate it.
The
vfs_notifierone also had a second defect: it walked the ancestry withfilepath.Dirbefore normalizing, and its!= "/"guards never match on Windows where the root is\, so a useless bare-root entry was appended to every batch:Also normalized at the RC boundary
I found these four one at a time, each from a stray backslash in a log, which suggests per-site fixes are fragile: a new caller reintroduces the bug and nothing surfaces it. So
RefreshDirnow normalizes too, in both implementations. The caller-side fixes stay, sincevfs_notifier's root guards can only be fixed at the caller, but a missed site degrades to a working call instead of a silent no-op.ToVFSPathis a thin named wrapper overfilepath.ToSlashso the intent is testable.No behaviour change on Linux or Docker
On POSIX,
os.PathSeparatoris/, sofilepath.ToSlashreturns the string unchanged andfilepath.Dirandpath.Dirrun the sameCleanlogic. Output is byte-for-byte identical. A backslash is a legal POSIX filename character and is deliberately left alone.Measured with
GOOS=windows:filepath.Dir(current)path.Dir(ToSlash(…))tv/SpongeBob.S17E15/ep.mkvtv\SpongeBob.S17E15tv/SpongeBob.S17E15movies/Film.2024/film.mkvmovies\Film.2024movies/Film.2024/file-at-mount-root.mkv\/tv\Legacy.Row\ep.mkv(legacy row)tv\Legacy.Rowtv/Legacy.RowDeliberately unchanged
filepath.Dircalls that operate on real OS paths stay as they are: symlink target resolution and filesystem walking inlibrary_sync.go(1284, 1456, 1660), and thefilepath.Join(cfg.MountPath, …)foros.Statinvfs_notifier.go.Testing
pkg/rclonecli/paths_test.go: forward-slash input unchanged on every platform, the platform-specific separator contract, and the boundary property that no Windows separator reaches rclone.go test ./...passes.Failed to notify rclone VFShas gone from 16 in a night to 0, and the directories now arrive correctly forward-slashed.