Skip to content

fix(files): file registration records the proc-fd link size (64 bytes) instead of the real file length - #901

Open
kevinheneveld wants to merge 1 commit into
Listenarrs:canaryfrom
kevinheneveld:fix/proc-fd-file-size
Open

fix(files): file registration records the proc-fd link size (64 bytes) instead of the real file length#901
kevinheneveld wants to merge 1 commit into
Listenarrs:canaryfrom
kevinheneveld:fix/proc-fd-file-size

Conversation

@kevinheneveld

Copy link
Copy Markdown
Contributor

The bug

Since #717, AudiobookFileService reads the registered file's size from the lease's metadata path (new FileInfo(metadataPath).Length). On Linux that path is a /proc/self/fd/<n> magic link, and FileSystemInfo stats the link inode itself — proc magic symlinks report a constant st_size of 64. Result: every file registered through a pinned lease persists Size = 64, regardless of the real file.

Live evidence from my instance (v1.3.0): 361 of 27,912 AudiobookFiles rows have Size = 64 exactly — every one created after the deploy that brought in #717 (sources: download, LibraryScan, MetadataRescan). The UI shows "0 KB" wherever sizes render (the duplicate-ASIN comparison table is where I caught it).

The fix

Open the metadata path and take the stream length. Opening follows the magic link to the exact pinned object the lease holds open, so the size is read from the real file without reintroducing the rename race the metadata path exists to avoid. Applied at both write sites:

  • AudiobookFileService.cs — initial registration (Size = null when the open fails, as before)
  • AudiobookFileService.PhysicalGeneration.cs — generation-replacement snapshots (falls back to the previously recorded size)

Notes

  • ExtractMetadataAsync's cache key uses LastWriteTimeUtc of the same metadata path, which has the same lstat behavior on Linux (the link's mtime, not the file's). It only weakens cache invalidation — the key still includes the physical object identity — so I left it out of scope here; happy to fold it in if you'd like.
  • Existing rows with Size = 64 stay wrong until re-registered; on my instance I'm backfilling them from disk. If you want, a follow-up startup reconciliation could repair Size = 64 rows whose path resolves, but I didn't want to bundle that policy decision into this fix.

🤖 Generated with Claude Code

…) instead of the real file length

Since Listenarrs#717, file registration reads sizes from the lease's metadata path,
which on Linux is a /proc/self/fd magic link. FileSystemInfo.Length reads
the link inode itself (lstat), and proc magic symlinks report a constant
st_size of 64 — so every file registered through a pinned lease persists
Size = 64 regardless of the actual file. The UI then shows 0 KB wherever
sizes are rendered (duplicate comparison, library size totals, quality
scoring inputs).

Open the metadata path instead and take the stream length: opening follows
the magic link to the exact pinned object the lease holds, so the length
is read from the real file without reintroducing the rename race the
metadata path exists to avoid. Applies to both initial registration and
physical-generation replacement snapshots (the latter falls back to the
prior recorded size when the open fails).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kevinheneveld
kevinheneveld requested a review from a team August 25, 2026 21:45
kevinheneveld added a commit to kevinheneveld/Listenarr that referenced this pull request Aug 26, 2026
…fd link (PR Listenarrs#901)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 26, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 27, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 28, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 28, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 31, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Aug 31, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 1, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 1, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 1, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 1, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 2, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
m4bard added a commit to m4bard/Listenarr that referenced this pull request Sep 3, 2026
…rrs#901

This PR fixed two things behind the same /proc descriptor link: the
probe's extension guard rejecting the candidate, and FileInfo reporting
the link's own 64 bytes instead of the file's length.

Listenarrs#901 fixes the size half, and fixes it better. It opens the metadata
path and takes the stream length unconditionally. The version here read
the length through the lease's generation-bound stream but fell back to
FileInfo on the metadata path when the lease did not expose one, which
on Linux is the original 64-byte bug again. That half also had no test
of its own here; both test files in this branch cover the scan boundary.

So the size change is dropped and the two shared files go back to
canary. What remains is the half Listenarrs#901 does not touch: the scan passed
the lease's metadata path as both the byte source and the media
identity, and on Linux that path is an extensionless /proc link, so the
audio-extension guard rejected the candidate before ffprobe ever ran.
Passing the candidate as the identity alongside the metadata path as the
byte source keeps the guard working on the real filename.

The two PRs no longer touch a file in common.

(cherry picked from commit 2af63b9)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant