Skip to content

Commit df10293

Browse files
paulirwinclaude
andcommitted
Fix NativeUnixDirectory direct I/O open on macOS and ARM64 Linux (#1342)
Two platform bugs caused TestNativeUnixDirectory* to fail (5 on macOS, all on ARM64 Linux): - macOS EACCES: the write path opened with open(path, O_RDWR|O_CREAT, 0666) through a fixed-signature P/Invoke, but C's open() is variadic so the mode argument rides the varargs ABI; on macOS (incl. Apple Silicon) the callee read garbage and created files without owner-read permission, so the subsequent O_RDONLY open failed with EACCES. Now pre-create the file with the BCL (applying the normal mode + umask) and open with the two-argument open() (no O_CREAT, no varargs). Dropped the broken 3-arg open and the O_CREAT/S_RW constants. - ARM64 Linux ENOTDIR: O_DIRECT was hardcoded to 0x4000, which is correct on x86/x86-64 but is O_DIRECTORY on Arm/Arm64 (the two are swapped; O_DIRECT there is 0x10000), so open() of a regular file failed with ENOTDIR. Now select O_DIRECT by RuntimeInformation.OSArchitecture. Also tighten the input refill EOF check from n < 0 to n <= 0: upstream used FileChannel.read() (returns -1 at EOF) but the port wraps libc pread() (returns 0 at EOF), so read-past-EOF would otherwise silently return zeros. Verified: TestNativeUnixDirectory* 23/23 pass on macOS arm64, Linux aarch64, and Linux x86-64 (the latter two in mcr.microsoft.com/dotnet/sdk:10.0 with an O_DIRECT-capable TMPDIR). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2bfbf0e commit df10293

2 files changed

Lines changed: 30 additions & 10 deletions

File tree

src/Lucene.Net.Misc/Store/NativePosixUtil.cs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,26 @@ public static SafeFileHandle OpenDirect(string filename, bool read)
6666
{
6767
EnsureUnix();
6868

69+
// Upstream's C++ called open(fname, O_RDWR | O_CREAT | DIRECT_FLAG, 0666) for the write path.
70+
// We cannot create-with-mode through P/Invoke: open() is variadic (int open(const char*, int, ...))
71+
// and the mode argument rides the varargs ABI. On platforms where varargs are not passed in the
72+
// same registers as fixed args (notably macOS, including Apple Silicon arm64), a fixed-signature
73+
// P/Invoke delivers garbage for mode, producing a file without owner-read permission, so the
74+
// subsequent O_RDONLY open of the same file fails with EACCES. To stay correct and ABI-agnostic
75+
// we never pass mode: the file is created up front by the BCL (which applies the normal mode and
76+
// umask), and we then open it with the plain two-argument open() - no O_CREAT, no varargs.
77+
if (!read)
78+
{
79+
// mirrors O_CREAT: ensure the file exists with a sane mode before opening it
80+
using (System.IO.File.Open(filename, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite)) { }
81+
}
82+
6983
int fd;
7084
if (Constants.MAC_OS_X)
7185
{
7286
fd = read
7387
? NativeMethods.open(filename, NativeMethods.O_RDONLY)
74-
: NativeMethods.open(filename, NativeMethods.O_RDWR | NativeMethods.O_CREAT, NativeMethods.S_RW);
88+
: NativeMethods.open(filename, NativeMethods.O_RDWR);
7589
if (fd < 0)
7690
{
7791
throw NewIOException("open", filename);
@@ -89,7 +103,7 @@ public static SafeFileHandle OpenDirect(string filename, bool read)
89103
{
90104
fd = read
91105
? NativeMethods.open(filename, NativeMethods.O_RDONLY | NativeMethods.O_DIRECT | NativeMethods.O_NOATIME)
92-
: NativeMethods.open(filename, NativeMethods.O_RDWR | NativeMethods.O_CREAT | NativeMethods.O_DIRECT | NativeMethods.O_NOATIME, NativeMethods.S_RW);
106+
: NativeMethods.open(filename, NativeMethods.O_RDWR | NativeMethods.O_DIRECT | NativeMethods.O_NOATIME);
93107
if (fd < 0)
94108
{
95109
throw NewIOException("open", filename);
@@ -194,21 +208,24 @@ private static class NativeMethods
194208
{
195209
private const string LIBC = "libc";
196210

197-
// open() flags. O_CREAT differs between Linux and macOS; O_DIRECT/O_NOATIME are Linux-only.
211+
// open() flags. O_DIRECT/O_NOATIME are Linux-only. We never pass O_CREAT/mode: see OpenDirect.
198212
internal const int O_RDONLY = 0x0;
199213
internal const int O_RDWR = 0x2;
200-
internal static readonly int O_CREAT = Constants.MAC_OS_X ? 0x0200 : 0x40;
201-
internal const int O_DIRECT = 0x4000; // Linux
214+
215+
// O_DIRECT is architecture-dependent on Linux: most arches (x86/x86-64) use the asm-generic
216+
// value 0x4000, but Arm/Arm64 (and a few others) swap it with O_DIRECTORY and use 0x10000.
217+
// Using the wrong value silently means "O_DIRECTORY", which makes open() of a regular file
218+
// fail with ENOTDIR. O_NOATIME (0x40000) is the same across these arches.
219+
internal static readonly int O_DIRECT =
220+
RuntimeInformation.OSArchitecture is Architecture.Arm or Architecture.Arm64
221+
? 0x10000
222+
: 0x4000;
202223
internal const int O_NOATIME = 0x40000; // Linux
203224
internal const int F_NOCACHE = 48; // macOS
204-
internal const int S_RW = 0x1B6; // 0666
205225

206226
[DllImport(LIBC, SetLastError = true, EntryPoint = "open")]
207227
internal static extern int open([MarshalAs(UnmanagedType.LPStr)] string pathname, int flags);
208228

209-
[DllImport(LIBC, SetLastError = true, EntryPoint = "open")]
210-
internal static extern int open([MarshalAs(UnmanagedType.LPStr)] string pathname, int flags, int mode);
211-
212229
[DllImport(LIBC, SetLastError = true)]
213230
internal static extern int close(int fd);
214231

src/Lucene.Net.Misc/Store/NativeUnixDirectory.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,10 @@ private void Refill()
526526
{
527527
throw new IOException(ioe.Message + ": " + this, ioe);
528528
}
529-
if (n < 0)
529+
// Upstream used FileChannel.read(), which returns -1 at EOF. Here Pread() wraps libc
530+
// pread(), which returns 0 at EOF (a genuine error already threw above), so a refill that
531+
// reads nothing means we have stepped entirely past the end of the file.
532+
if (n <= 0)
530533
{
531534
throw EOFException.Create("read past EOF: " + this);
532535
}

0 commit comments

Comments
 (0)