Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions src/Lucene.Net.Misc/Lucene.Net.Misc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,9 @@
<PackageTags>$(PackageTags);miscellaneous</PackageTags>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<NoWarn>$(NoWarn);1591;1573</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>



<ItemGroup>
<Compile Remove="Store\*" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Lucene.Net\Lucene.Net.csproj" />
</ItemGroup>
Expand Down
256 changes: 220 additions & 36 deletions src/Lucene.Net.Misc/Store/NativePosixUtil.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
using Lucene.Net.Util;
using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
#if FEATURE_SUPPORTEDOSPLATFORMATTRIBUTE
using System.Runtime.Versioning;
#endif

namespace org.apache.lucene.store
namespace Lucene.Net.Store
{

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
Expand All @@ -20,45 +27,222 @@ namespace org.apache.lucene.store
* limitations under the License.
*/

ignore

/// <summary>
/// Provides JNI access to native methods such as madvise() for
/// <seealso cref="NativeUnixDirectory"/>
/// Provides access to native POSIX methods such as <c>madvise()</c> for
/// <see cref="NativeUnixDirectory"/>.
/// <para/>
/// LUCENENET specific: the original Lucene implementation called these through a JNI
/// shim compiled from <c>NativePosixUtil.cpp</c>. This port replaces that native build
/// step with direct P/Invoke into <c>libc</c>, so it can only be used on Unix-like
/// platforms (Linux and macOS); it is not supported on Microsoft Windows.
/// </summary>
public final class NativePosixUtil
#if FEATURE_SUPPORTEDOSPLATFORMATTRIBUTE
[UnsupportedOSPlatform("windows")]
#endif
public static class NativePosixUtil
{
public final static int NORMAL = 0;
public final static int SEQUENTIAL = 1;
public final static int RANDOM = 2;
public final static int WILLNEED = 3;
public final static int DONTNEED = 4;
public final static int NOREUSE = 5;

//JAVA TO C# CONVERTER NOTE: This static initializer block is converted to a static constructor, but there is no current class:
static ImpliedClass()
{
//JAVA TO C# CONVERTER TODO TASK: The library is specified in the 'DllImport' attribute for .NET:
// System.loadLibrary("NativePosixUtil");
}

private static native int posix_fadvise(FileDescriptor fd, long offset, long len, int advise) throws IOException;
public static native int posix_madvise(ByteBuffer buf, int advise) throws IOException;
public static native int madvise(ByteBuffer buf, int advise) throws IOException;
public static native FileDescriptor open_direct(string filename, bool read) throws IOException;
public static native long pread(FileDescriptor fd, long pos, ByteBuffer byteBuf) throws IOException;

public static void advise(FileDescriptor fd, long offset, long len, int advise) throws IOException
{
//JAVA TO C# CONVERTER WARNING: The original Java variable was marked 'final':
//ORIGINAL LINE: final int code = posix_fadvise(fd, offset, len, advise);
int code = posix_fadvise(fd, offset, len, advise);
if (code != 0)
// These constants mirror the Java NativePosixUtil ordering. Note this is NOT the same
// ordering as the OS POSIX_FADV_*/POSIX_MADV_* values (SEQUENTIAL/RANDOM are swapped);
// MapAdvice() translates to the OS values.
public const int NORMAL = 0;
public const int SEQUENTIAL = 1;
public const int RANDOM = 2;
public const int WILLNEED = 3;
public const int DONTNEED = 4;
public const int NOREUSE = 5;

/// <summary>
/// Opens a file for direct (un-cached) I/O.
/// <para/>
/// On Linux this uses <c>O_DIRECT | O_NOATIME</c>; on macOS it opens normally and then
/// applies <c>fcntl(F_NOCACHE)</c>, matching the original native implementation.
/// </summary>
/// <param name="filename"> the file to open </param>
/// <param name="read"> <c>true</c> to open read-only; <c>false</c> to open read-write (creating if needed) </param>
/// <returns> a <see cref="SafeFileHandle"/> wrapping the open file descriptor </returns>
/// <exception cref="IOException"> If the file could not be opened </exception>
/// <exception cref="PlatformNotSupportedException"> If running on Microsoft Windows </exception>
public static SafeFileHandle OpenDirect(string filename, bool read)
{
throw new Exception("posix_fadvise failed code=" + code);
EnsureUnix();

// Upstream's C++ called open(fname, O_RDWR | O_CREAT | DIRECT_FLAG, 0666) for the write path.
// We cannot create-with-mode through P/Invoke: open() is variadic (int open(const char*, int, ...))
// and the mode argument rides the varargs ABI. On platforms where varargs are not passed in the
// same registers as fixed args (notably macOS, including Apple Silicon arm64), a fixed-signature
// P/Invoke delivers garbage for mode, producing a file without owner-read permission, so the
// subsequent O_RDONLY open of the same file fails with EACCES. To stay correct and ABI-agnostic
// we never pass mode: the file is created up front by the BCL (which applies the normal mode and
// umask), and we then open it with the plain two-argument open() - no O_CREAT, no varargs.
if (!read)
{
// mirrors O_CREAT: ensure the file exists with a sane mode before opening it
using (System.IO.File.Open(filename, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite)) { }
}

int fd;
if (Constants.MAC_OS_X)
{
fd = read
? NativeMethods.open(filename, NativeMethods.O_RDONLY)
: NativeMethods.open(filename, NativeMethods.O_RDWR);
if (fd < 0)
{
throw NewIOException("open", filename);
}

// macOS has no O_DIRECT; disable the page cache for this descriptor instead.
if (NativeMethods.fcntl(fd, NativeMethods.F_NOCACHE, 1) < 0)
{
int err = Marshal.GetLastWin32Error();
NativeMethods.close(fd);
throw NewIOException("fcntl(F_NOCACHE)", filename, err);
}
}
else // Linux (and other Unixes, best-effort)
{
fd = read
? NativeMethods.open(filename, NativeMethods.O_RDONLY | NativeMethods.O_DIRECT | NativeMethods.O_NOATIME)
: NativeMethods.open(filename, NativeMethods.O_RDWR | NativeMethods.O_DIRECT | NativeMethods.O_NOATIME);
if (fd < 0)
{
throw NewIOException("open", filename);
}
}

return new SafeFileHandle((IntPtr)fd, ownsHandle: true);
}
}
}

/// <summary>
/// Positioned read of up to <paramref name="length"/> bytes from <paramref name="fd"/> at
/// absolute offset <paramref name="pos"/> into the native buffer at <paramref name="buffer"/>,
/// without changing the file's current offset.
/// </summary>
/// <returns> the number of bytes read (may be less than <paramref name="length"/> near EOF) </returns>
/// <exception cref="IOException"> If the read failed </exception>
public static long Pread(SafeFileHandle fd, long pos, IntPtr buffer, int length)
{
EnsureUnix();
long n = (long)NativeMethods.pread(Fd(fd), buffer, (nuint)length, pos);
if (n < 0)
{
throw NewIOException("pread", null);
Comment thread
paulirwin marked this conversation as resolved.
}
return n;
}

/// <summary>
/// Issues a <c>posix_madvise()</c> hint over the native buffer at <paramref name="buffer"/>. </summary>
public static int PosixMAdvise(IntPtr buffer, int length, int advise)
{
EnsureUnix();
return NativeMethods.posix_madvise(buffer, (nuint)length, MapAdvice(advise));
}

/// <summary>
/// Issues a <c>madvise()</c> hint over the native buffer at <paramref name="buffer"/>. </summary>
public static int MAdvise(IntPtr buffer, int length, int advise)
{
EnsureUnix();
return NativeMethods.madvise(buffer, (nuint)length, MapAdvice(advise));
}

/// <summary>
/// Issues a <c>posix_fadvise()</c> hint for the given range of <paramref name="fd"/>,
/// throwing if the call reports a non-zero error code.
/// </summary>
/// <exception cref="IOException"> If <c>posix_fadvise</c> returned a non-zero code </exception>
public static void Advise(SafeFileHandle fd, long offset, long len, int advise)
{
EnsureUnix();
int code = NativeMethods.posix_fadvise(Fd(fd), offset, len, MapAdvice(advise));
if (code != 0)
{
// LUCENENET: upstream throws RuntimeException; we use IOException as this is an I/O failure.
throw new IOException("posix_fadvise failed code=" + code);
Comment thread
paulirwin marked this conversation as resolved.
Outdated
}
}

/// <summary>
/// Translates the Java-style advice ordinal (see the public constants) to the OS
/// <c>POSIX_FADV_*</c>/<c>POSIX_MADV_*</c> value. Only <c>SEQUENTIAL</c> and <c>RANDOM</c>
/// differ in ordering between the two.
/// </summary>
private static int MapAdvice(int advise)
{
switch (advise)
{
case SEQUENTIAL: return 2; // POSIX_*_SEQUENTIAL
case RANDOM: return 1; // POSIX_*_RANDOM
default: return advise; // NORMAL=0, WILLNEED=3, DONTNEED=4, NOREUSE=5 are identical
}
}

private static int Fd(SafeFileHandle handle) => (int)handle.DangerousGetHandle();

private static IOException NewIOException(string operation, string filename)
=> NewIOException(operation, filename, Marshal.GetLastWin32Error());

private static IOException NewIOException(string operation, string filename, int errno)
{
string where = filename is null ? operation : $"{operation} {filename}";
// On Unix with SetLastError, GetLastWin32Error() returns errno. Win32Exception's message
// is not meaningful on Unix, so include the raw errno for diagnosis.
return new IOException($"{where} failed (errno {errno})");
Comment thread
paulirwin marked this conversation as resolved.
Outdated
}

internal static void EnsureUnix()
{
if (Constants.WINDOWS)
{
throw new PlatformNotSupportedException(
$"{nameof(NativePosixUtil)} requires Linux or macOS direct I/O and is not supported on Microsoft Windows.");
}
}

/// <summary>
/// P/Invoke declarations for the <c>libc</c> functions used here. These replace the
/// JNI/C++ native methods of the original implementation.
/// </summary>
private static class NativeMethods
{
private const string LIBC = "libc";

// open() flags. O_DIRECT/O_NOATIME are Linux-only. We never pass O_CREAT/mode: see OpenDirect.
internal const int O_RDONLY = 0x0;
internal const int O_RDWR = 0x2;

// O_DIRECT is architecture-dependent on Linux: most arches (x86/x86-64) use the asm-generic
// value 0x4000, but Arm/Arm64 (and a few others) swap it with O_DIRECTORY and use 0x10000.
// Using the wrong value silently means "O_DIRECTORY", which makes open() of a regular file
// fail with ENOTDIR. O_NOATIME (0x40000) is the same across these arches.
internal static readonly int O_DIRECT =
RuntimeInformation.OSArchitecture is Architecture.Arm or Architecture.Arm64
? 0x10000
: 0x4000;
internal const int O_NOATIME = 0x40000; // Linux
internal const int F_NOCACHE = 48; // macOS

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

[DllImport(LIBC, SetLastError = true)]
internal static extern int close(int fd);

[DllImport(LIBC, SetLastError = true)]
internal static extern int fcntl(int fd, int cmd, int arg);

[DllImport(LIBC, SetLastError = true)]
internal static extern nint pread(int fd, IntPtr buf, nuint count, long offset);

[DllImport(LIBC, SetLastError = true)]
internal static extern int posix_fadvise(int fd, long offset, long len, int advice);

[DllImport(LIBC, SetLastError = true)]
internal static extern int posix_madvise(IntPtr addr, nuint length, int advice);

[DllImport(LIBC, SetLastError = true)]
internal static extern int madvise(IntPtr addr, nuint length, int advice);
}
}
}
Loading
Loading