fix(#2015): store correct path of symlinks - #2025
Open
h-2 wants to merge 1 commit into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue #2015 — "Unknown path" when adding a folder containing symlinks
#2015
Analysed and fixed 2026-07-29 on FreeBSD 15.1 against
master(29.3+). Theoriginal report was OwnTone 29.0 on FreeBSD 14.3, so this is neither version-
nor platform-specific.
The bug
When a library directory contains a symlink to a file, the scanner writes
a
filesrow that contradicts itself:directory_idpoints at the directory that was scanned — where the symlinklives.
path/virtual_pathhold the dereferenced target path.Those two are then used by different lookups, which consequently disagree:
addmatches onvirtual_path—mpd_queue_add()(src/mpd.c:1581)builds
f.virtual_path LIKE '/<path>%'.lsinforesolves viadirectory_id.So the very same file is visible when browsing a directory but not
addressable by that directory's path.
addmatches zero rows, falls throughto
library_queue_item_add()(src/mpd.c:1628), which only understandslibrary:and http(s) URIs, and returnsLIBRARY_PATH_INVALID— surfacing asunknown path.This also explains the confusing client-dependent behaviour in the report:
Cantata works because it issues
lsinfofirst and then adds each file by theresolved path it got back; ncmpcpp and MALP pass the directory path straight
to
addand fail.Reproduction
Resulting DB state — note there is no
directoriesrow forfiles/at all:Note this is not limited to targets outside the library. A symlink whose
target lies inside the library is affected identically (
addon the symlink'sdirectory fails there too); it is merely less visible, because the file remains
reachable via its real directory.
The fix
One line: register the file at the path it was found at, rather than at the
dereferenced path.
Symlinks are still dereferenced for
stat()inread_attributes(), so size,mtime and change detection are unchanged. Only the identity stored in the
database changes, and only for symlinks — for a regular file
entryandresolved_pathare the same string (read_attributes()doesstrcpy(resolved_path, path),src/library/filescanner.c:805).After the fix
directory_idandvirtual_pathagree, so path-based anddirectory-based lookups return the same thing. Because it corrects the stored
data rather than one query, it fixes every consumer at once — MPD, DAAP and
the JSON API — instead of only the code path that happened to be reported.
What is not affected
This is worth stating plainly, because the change sounds broader than it is:
entryandresolved_pathare identical strings, so the stored path isbyte-for-byte what it was before.
follow_symlinks = false— completely unaffected. That check(
src/library/filescanner.c:901)continues before the modified code isreached, so symlinks never get there.
(
push_dir(&dirstack, resolved_path, dir_id)) is deliberately left alone, soa symlinked directory is still dereferenced and its contents are still stored
under the target path exactly as before. This is the usage the documentation
recommends, and it keeps the tree walk operating on real paths.
The only setups that see any change at all are those that symlink individual
files.
What changes
For symlinked files,
addon the containing directory starts working. Beyondthat:
simply now registered where the symlink is instead of at a path outside the
library. No duplicate arises, because a target outside the library is never
scanned in its own right.
at its real path, once at the symlink's path — where it previously collapsed
into one row. Insert-vs-update is keyed on the exact path string
(
db_file_id_bypath(),src/library/filescanner.c:591); there is noinode-based de-duplication, so two distinct paths yield two rows. Both are
fully functional, and both keep their artwork (see below).
Artwork
One caveat, affecting only users who symlink individual files from
outside the library — precisely the group whose
addis broken today.Both file-based artwork sources key off
files.path:source_item_own_get()(src/artwork.c:1611) looks for<trackname>.{jpg,png}beside the stored path, and the album-level
directorysource derives itsdirectory by slicing
f.path(Q_GROUP_DIRS,src/db.c:2323). Once thestored path is the symlink's location, external cover files sitting next to
the target are no longer found.
Measured, with properly tagged files:
cover.pngnext to the real filecover.pngnext to the real fileSo:
opening a symlink transparently opens the target (verified: "Artwork for
'Other Song' found in source 'embedded'").
entry: the album-level source enumerates the directories of all files in
the album, which still includes the real file's directory.
Those users can symlink the cover alongside the track, or rely on embedded
art.
That is a real trade-off and should not be hidden — but it applies solely to a
configuration that is broken today anyway, and it trades a hard failure
(tracks that cannot be queued at all) for a cosmetic one.
Testing performed
ncmpcpp's browser; the server log matches the original report line for line.
addsucceeds for the symlink directory, with andwithout trailing slash, for ancestors, for
file:root, and for theindividual file; each adds exactly one queue item.
filesrow hasdirectory_idconsistent with itspath.
symlinked directories still behave exactly as before.
rows of the table above.