From 96314657778d5d4fc95634974bd868300233f955 Mon Sep 17 00:00:00 2001 From: Kevin Rejko Date: Thu, 20 Aug 2026 15:40:49 -0600 Subject: [PATCH] Bind fstat$INODE64 on macOS x64 macOS exposes two incompatible fstat ABIs. On x86_64 the bare "fstat" symbol is the pre-10.5 variant whose struct predates the 64-bit inode layout, so reading the result as MacStatInformation takes st_mode from the wrong offset. C code never hits this because redirects fstat to fstat$INODE64, but a raw P/Invoke binds the legacy symbol. The practical effect is that HandleIsRegularFile misreports every regular file, so ScanFileDiscovery discards all of them: LinkSkipped msg=[Non-regular files are not scanned.] path=/.../book.m4b An unmatched scan over a populated root then completes successfully with zero candidates, because directory identity only compares captured-vs-current values (consistent garbage still matches) while files fail an absolute check. Verified against the same file on both ABIs, parsing the modern struct: arm64 fstat mode=0x81c0 type=0x8000 regular=True size=375267 arm64 fstat$INODE64 symbol not present x86_64 fstat mode=0x25c3 type=0x2000 regular=False size=0 x86_64 fstat$INODE64 mode=0x81c0 type=0x8000 regular=True size=375267 Dispatch on process architecture: x64 needs the $INODE64 suffix, arm64 must keep the bare symbol because the suffixed one does not exist there and binding it throws EntryPointNotFoundException. Co-Authored-By: Claude Opus 5 (1M context) --- .../PinnedDirectoryCreation.NativeInterop.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/listenarr.infrastructure/FileSystem/PinnedDirectoryCreation.NativeInterop.cs b/listenarr.infrastructure/FileSystem/PinnedDirectoryCreation.NativeInterop.cs index 3b060f76e..ff5635f88 100644 --- a/listenarr.infrastructure/FileSystem/PinnedDirectoryCreation.NativeInterop.cs +++ b/listenarr.infrastructure/FileSystem/PinnedDirectoryCreation.NativeInterop.cs @@ -321,11 +321,28 @@ private static extern int FcntlGetPath( int command, IntPtr buffer); + // macOS exposes two incompatible fstat ABIs. On x86_64 the bare "fstat" symbol is the + // pre-10.5 variant whose struct predates the 64-bit inode layout, so reading it as + // MacStatInformation yields garbage - st_mode lands on the wrong offset and every regular + // file is misreported (observed: type 0x2000, a character device). C code never hits this + // because redirects fstat to fstat$INODE64; a raw P/Invoke does not. + // On arm64 there is no $INODE64 suffix - the bare symbol is already the modern ABI, and + // binding "fstat$INODE64" there fails with EntryPointNotFoundException. + [DllImport("libc", EntryPoint = "fstat$INODE64", SetLastError = true)] + private static extern int FStatMacInode64( + int fileDescriptor, + out MacStatInformation information); + [DllImport("libc", EntryPoint = "fstat", SetLastError = true)] - private static extern int FStatMac( + private static extern int FStatMacNative( int fileDescriptor, out MacStatInformation information); + private static int FStatMac(int fileDescriptor, out MacStatInformation information) => + RuntimeInformation.ProcessArchitecture == Architecture.X64 + ? FStatMacInode64(fileDescriptor, out information) + : FStatMacNative(fileDescriptor, out information); + [DllImport("libc", EntryPoint = "fsync", SetLastError = true)] private static extern int FSync(int fileDescriptor);