From 55859158fbd0bf54473a0baeb486045a025c7c5d Mon Sep 17 00:00:00 2001 From: vnugent Date: Sun, 24 Mar 2024 21:01:06 -0400 Subject: Squashed commit of the following: commit 6c1667be23597513537f8190e2f55d65eb9b7c7a Author: vnugent Date: Fri Mar 22 12:01:53 2024 -0400 refactor: Overhauled native library loading and lazy init commit ebf688f2f974295beabf7b5def7e6f6f150551d0 Author: vnugent Date: Wed Mar 20 22:16:17 2024 -0400 refactor: Update compression header files and macros + Ci build commit 9c7b564911080ccd5cbbb9851a0757b05e1e9047 Author: vnugent Date: Tue Mar 19 21:54:49 2024 -0400 refactor: JWK overhaul & add length getter to FileUpload commit 6d8c3444e09561e5957491b3cc1ae858e0abdd14 Author: vnugent Date: Mon Mar 18 16:13:20 2024 -0400 feat: Add FNV1a software checksum and basic correction tests commit 00d182088cecefc08ca80b1faee9bed3f215f40b Author: vnugent Date: Fri Mar 15 01:05:27 2024 -0400 chore: #6 Use utils filewatcher instead of built-in commit d513c10d9895c6693519ef1d459c6a5a76929436 Author: vnugent Date: Sun Mar 10 21:58:14 2024 -0400 source tree project location updated --- lib/Utils/src/Native/NatveLibraryResolver.cs | 299 +++++++++++++++++++++++++++ lib/Utils/src/Native/SafeLibraryHandle.cs | 260 ++++++++++------------- 2 files changed, 409 insertions(+), 150 deletions(-) create mode 100644 lib/Utils/src/Native/NatveLibraryResolver.cs (limited to 'lib/Utils/src/Native') diff --git a/lib/Utils/src/Native/NatveLibraryResolver.cs b/lib/Utils/src/Native/NatveLibraryResolver.cs new file mode 100644 index 0000000..b888f42 --- /dev/null +++ b/lib/Utils/src/Native/NatveLibraryResolver.cs @@ -0,0 +1,299 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: NatveLibraryResolver.cs +* +* NatveLibraryResolver.cs is part of VNLib.Utils which is part of +* the larger VNLib collection of libraries and utilities. +* +* VNLib.Utils is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Utils is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. +*/ + +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Diagnostics; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Diagnostics.CodeAnalysis; + +namespace VNLib.Utils.Native +{ + /// + /// Uses a supplied library state to resolve and load a platform native library + /// into the process memory space. + /// + /// The raw caller-supplied library path to resolve + /// A assembly loading the desired library + /// The dll loader search path requirements + internal readonly struct NatveLibraryResolver(string suppliedLibraryPath, Assembly assembly, DllImportSearchPath searchPath) + { + private readonly string _libFileName = Path.GetFileName(suppliedLibraryPath); + private readonly string? _relativeDir = Path.GetDirectoryName(suppliedLibraryPath); + + internal readonly bool HasRelativeDir => _relativeDir != null; + + internal readonly bool IsFullPath => Path.IsPathRooted(suppliedLibraryPath); + + /// + /// Resolves and attempts to load the current library into the current process. + /// + /// The if the library was successfully resolved + /// True if the library was resolved and loaded into the process, false otherwise + internal readonly bool ResolveAndLoadLibrary([NotNullWhen(true)] out SafeLibraryHandle? library) + { + //Try naive load if the path is an absolute path + if (IsFullPath && _tryLoad(suppliedLibraryPath, out library)) + { + return true; + } + + //Try to load the library from the relative directory + if (HasRelativeDir && TryLoadRelativeToWorkingDir(out library)) + { + return true; + } + + //Path is just a file name, so search for prefixes and extensions loading directly + if (TryNaiveLoadLibrary(out library)) + { + return true; + } + + //Try searching for the file in directories + return TryLoadFromSafeDirs(out library); + } + + /* + * Naive load builds file names that are platform dependent + * and probes for the library using the NativeLibrary.TryLoad rules + * to load the library. + * + * If the file has an extension, it will attempt to load the library + * directly using the file name. Otherwise, it will continue to probe + */ + private readonly bool TryNaiveLoadLibrary([NotNullWhen(true)] out SafeLibraryHandle? library) + { + foreach (string probingFileName in GetProbingFileNames(_libFileName)) + { + if (_tryLoad(probingFileName, out library)) + { + return true; + } + } + + library = null; + return false; + } + + /* + * Attempts to probe library files names that are located inside safe directories + * specified by the caller using the DllImportSearchPath enum. + */ + private readonly bool TryLoadFromSafeDirs([NotNullWhen(true)] out SafeLibraryHandle? library) + { + //Try enumerating safe directories + if (searchPath.HasFlag(DllImportSearchPath.SafeDirectories)) + { + foreach (string dir in GetSpecialDirPaths()) + { + if (TryLoadInDirectory(dir, out library)) + { + return true; + } + } + } + + //Check application directory first (including subdirectories) + if (searchPath.HasFlag(DllImportSearchPath.ApplicationDirectory)) + { + //get the current directory + if (TryLoadInDirectory(Directory.GetCurrentDirectory(), out library)) + { + return true; + } + } + + //See if search in the calling assembly directory + if (searchPath.HasFlag(DllImportSearchPath.AssemblyDirectory)) + { + //Get the calling assmblies directory + string libDir = Assembly.GetCallingAssembly().Location; + Debug.WriteLine("Native library searching for calling assembly location:{0} ", libDir); + if (TryLoadInDirectory(libDir, out library)) + { + return true; + } + } + + //Search system32 dir + if (searchPath.HasFlag(DllImportSearchPath.System32)) + { + string sys32Dir = Environment.GetFolderPath(Environment.SpecialFolder.SystemX86); + + //Get the system directory + if (TryLoadInDirectory(sys32Dir, out library)) + { + return true; + } + } + + library = null; + return false; + } + + /* + * Users may specify realtive directories to search for the library + * in the current working directory, so this function attempts to load + * the library from the relative directory if the user has specified one + */ + private readonly bool TryLoadRelativeToWorkingDir([NotNullWhen(true)] out SafeLibraryHandle? library) + { + string libDir = Directory.GetCurrentDirectory(); + return TryLoadInDirectory(Path.Combine(libDir, _relativeDir!), out library); + } + + /* + * Attempts to load libraries that are located in the specified directory + * by probing for the library file name in the directory path using + * prefixes and extensions + */ + private readonly bool TryLoadInDirectory(string baseDir, [NotNullWhen(true)] out SafeLibraryHandle? library) + { + IEnumerable fullProbingPaths = GetProbingFileNames(_libFileName).Select(p => Path.Combine(baseDir, p)); + + foreach (string probingFilePath in fullProbingPaths) + { + if (_tryLoad(probingFilePath, out library)) + { + return true; + } + } + + library = null; + return false; + } + + /* + * core load function + */ + internal readonly bool _tryLoad(string filePath, [NotNullWhen(true)] out SafeLibraryHandle? library) + { + //Attempt a naive load + if (NativeLibrary.TryLoad(filePath, assembly, searchPath, out IntPtr libHandle)) + { + library = SafeLibraryHandle.FromExisting(libHandle, true); + return true; + } + + library = null; + return false; + } + + private static readonly Environment.SpecialFolder[] SafeDirs = + [ + Environment.SpecialFolder.SystemX86, + Environment.SpecialFolder.System, + Environment.SpecialFolder.Windows, + Environment.SpecialFolder.ProgramFilesX86, + Environment.SpecialFolder.ProgramFiles + ]; + + private static IEnumerable GetSpecialDirPaths() => SafeDirs.Select(Environment.GetFolderPath); + + private static IEnumerable GetProbingFileNames(string libraryFileName) + { + //Inlcude the library file name if it has an extension + if (Path.HasExtension(libraryFileName)) + { + yield return libraryFileName; + } + + foreach (string prefix in GetNativeLibPrefixs()) + { + foreach (string libExtension in GetNativeLibExtension()) + { + yield return GetLibraryFileName(prefix, libraryFileName, libExtension); + } + } + + static string GetLibraryFileName(string prefix, string libPath, string extension) + { + //Get dir name from the lib path if it has one + + + libPath = Path.Combine(prefix, libPath); + + //If the library path already has an extension, just search for the file + if (!Path.HasExtension(libPath)) + { + //slice the lib to its file name + libPath = Path.GetFileName(libPath); + + if (extension.Length > 0) + { + libPath = Path.ChangeExtension(libPath, extension); + } + } + + return libPath; + } + + static string[] GetNativeLibPrefixs() + { + if (OperatingSystem.IsWindows()) + { + return [""]; + } + else if (OperatingSystem.IsMacOS()) + { + return ["", "lib"]; + } + else if (OperatingSystem.IsLinux()) + { + return ["", "lib_", "lib"]; + } + else + { + Debug.Fail("Unknown OS type"); + return []; + } + } + + static string[] GetNativeLibExtension() + { + if (OperatingSystem.IsWindows()) + { + return ["", ".dll"]; + } + else if (OperatingSystem.IsMacOS()) + { + return ["", ".dylib"]; + } + else if (OperatingSystem.IsLinux()) + { + return ["", ".so", ".so.1"]; + } + else + { + Debug.Fail("Unknown OS type"); + return []; + } + } + } + } +} diff --git a/lib/Utils/src/Native/SafeLibraryHandle.cs b/lib/Utils/src/Native/SafeLibraryHandle.cs index ed9dd16..5fb2283 100644 --- a/lib/Utils/src/Native/SafeLibraryHandle.cs +++ b/lib/Utils/src/Native/SafeLibraryHandle.cs @@ -23,10 +23,7 @@ */ using System; -using System.IO; -using System.Linq; using System.Reflection; -using System.Diagnostics; using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; @@ -42,128 +39,7 @@ namespace VNLib.Utils.Native /// public override bool IsInvalid => handle == IntPtr.Zero; - private SafeLibraryHandle(IntPtr libHandle) : base(IntPtr.Zero, true) - { - //Init handle - SetHandle(libHandle); - } - - /// - /// Finds and loads the specified native libary into the current process by its name at runtime - /// - /// The path (or name of libary) to search for - /// - /// The used to search for libaries - /// within the current filesystem - /// - /// The loaded - /// - /// - public static SafeLibraryHandle LoadLibrary(string libPath, DllImportSearchPath searchPath = DllImportSearchPath.ApplicationDirectory) - { - ArgumentNullException.ThrowIfNull(libPath); - //See if the path includes a file extension - return TryLoadLibrary(libPath, searchPath, out SafeLibraryHandle? lib) - ? lib - : throw new DllNotFoundException($"The library {libPath} or one of its dependencies could not be found"); - } - - /// - /// Attempts to load the specified native libary into the current process by its name at runtime - /// - ///The path (or name of libary) to search for - /// - /// The used to search for libaries - /// within the current filesystem - /// - /// The handle to the libary if successfully loaded - /// True if the libary was found and loaded into the current process - public static bool TryLoadLibrary(string libPath, DllImportSearchPath searchPath, [NotNullWhen(true)] out SafeLibraryHandle? lib) - { - lib = null; - //Allow full rooted paths - if (Path.IsPathRooted(libPath)) - { - //Attempt a native load - if (NativeLibrary.TryLoad(libPath, out IntPtr libHandle)) - { - lib = new(libHandle); - return true; - } - return false; - } - //Check application directory first (including subdirectories) - if ((searchPath & DllImportSearchPath.ApplicationDirectory) > 0) - { - //get the current directory - string libDir = Directory.GetCurrentDirectory(); - if (TryLoadLibraryInternal(libDir, libPath, SearchOption.TopDirectoryOnly, out lib)) - { - return true; - } - } - //See if search in the calling assembly directory - if ((searchPath & DllImportSearchPath.AssemblyDirectory) > 0) - { - //Get the calling assmblies directory - string libDir = Assembly.GetCallingAssembly().Location; - Debug.WriteLine("Native library searching for calling assembly location:{0} ", libDir); - if (TryLoadLibraryInternal(libDir, libPath, SearchOption.TopDirectoryOnly, out lib)) - { - return true; - } - } - //Search system32 dir - if ((searchPath & DllImportSearchPath.System32) > 0) - { - //Get the system directory - string libDir = Environment.GetFolderPath(Environment.SpecialFolder.SystemX86); - if (TryLoadLibraryInternal(libDir, libPath, SearchOption.TopDirectoryOnly, out lib)) - { - return true; - } - } - //Attempt a native load - { - if (NativeLibrary.TryLoad(libPath, out IntPtr libHandle)) - { - lib = new(libHandle); - return true; - } - return false; - } - } - - private static bool TryLoadLibraryInternal(string libDir, string libPath, SearchOption dirSearchOptions, [NotNullWhen(true)] out SafeLibraryHandle? libary) - { - //Try to find the libary file - string? libFile = GetLibraryFile(libDir, libPath, dirSearchOptions); - //Load libary - if (libFile != null && NativeLibrary.TryLoad(libFile, out IntPtr libHandle)) - { - libary = new SafeLibraryHandle(libHandle); - return true; - } - libary = null; - return false; - } - - private static string? GetLibraryFile(string dirPath, string libPath, SearchOption search) - { - //If the library path already has an extension, just search for the file - if (Path.HasExtension(libPath)) - { - return Directory.EnumerateFiles(dirPath, libPath, search).FirstOrDefault(); - } - else - { - //slice the lib to its file name - libPath = Path.GetFileName(libPath); - libPath = Path.ChangeExtension(libPath, OperatingSystem.IsWindows() ? ".dll" : ".so"); - //Select the first file that matches the name - return Directory.EnumerateFiles(dirPath, libPath, search).FirstOrDefault(); - } - } + private SafeLibraryHandle(IntPtr libHandle, bool ownsHandle) : base(IntPtr.Zero, ownsHandle) => SetHandle(libHandle); /// /// Loads a native function pointer from the library of the specified name and @@ -179,7 +55,7 @@ namespace VNLib.Utils.Native { //Increment handle count before obtaining a method bool success = false; - DangerousAddRef(ref success); + DangerousAddRef(ref success); ObjectDisposedException.ThrowIf(success == false, this); @@ -218,40 +94,124 @@ namespace VNLib.Utils.Native return Marshal.GetDelegateForFunctionPointer(nativeMethod); } + /// + protected override bool ReleaseHandle() + { + //Free the library and set the handle as invalid + NativeLibrary.Free(handle); + SetHandleAsInvalid(); + return true; + } + /// - /// Loads a native method from the library of the specified name and managed delegate + /// Finds and loads the specified native libary into the current process by its name at runtime. + /// This function defaults to the executing assembly /// - /// The native method delegate type - /// The name of the native method - /// A wapper handle around the native method delegate + /// The path (or name of libary) to search for + /// + /// The used to search for libaries + /// within the current filesystem + /// + /// The loaded /// - /// If the handle is closed or invalid - /// When the specified entrypoint could not be found - [Obsolete("Updated naming, use GetFunction() instead")] - public SafeMethodHandle GetMethod(string methodName) where T : Delegate => GetFunction(methodName); + /// + public static SafeLibraryHandle LoadLibrary(string libPath, DllImportSearchPath searchPath = DllImportSearchPath.ApplicationDirectory) + { + //See if the path includes a file extension + return TryLoadLibrary(libPath, searchPath, out SafeLibraryHandle? lib) + ? lib + : throw new DllNotFoundException($"The library '{libPath}' or one of its dependencies could not be found"); + } /// - /// Gets an delegate wrapper for the specified method without tracking its referrence. - /// The caller must manage the referrence count in order - /// to not leak resources or cause process corruption + /// Finds and loads the specified native libary into the current process by its name at runtime /// - /// The native method delegate type - /// The name of the native method - /// A the delegate wrapper on the native method + /// The path (or name of libary) to search for + /// + /// The used to search for libaries + /// within the current filesystem + /// + /// The assembly loading the native library + /// The loaded /// - /// If the handle is closed or invalid - /// When the specified entrypoint could not be found - [Obsolete("Updated naming, use DangerousGetFunction() instead")] - public T DangerousGetMethod(string methodName) where T : Delegate => DangerousGetFunction(methodName); + /// + public static SafeLibraryHandle LoadLibrary( + string libPath, + Assembly assembly, + DllImportSearchPath searchPath = DllImportSearchPath.ApplicationDirectory + ) + { + //See if the path includes a file extension + return TryLoadLibrary(libPath, assembly, searchPath, out SafeLibraryHandle? lib) + ? lib + : throw new DllNotFoundException($"The library '{libPath}' or one of its dependencies could not be found"); + } + /// + /// Creates a new from an existing library pointer. + /// + /// A pointer to the existing (and loaded) library + /// A value that specifies whether the wrapper owns the library handle now + /// A safe library wrapper around the existing library pointer + /// + public unsafe static SafeLibraryHandle FromExisting(nint libHandle, bool ownsHandle) + { + ArgumentNullException.ThrowIfNull(libHandle.ToPointer(), nameof(libHandle)); + return new(libHandle, ownsHandle); + } - /// - protected override bool ReleaseHandle() + /// + /// Attempts to load the specified native libary into the current process by its name at runtime. + /// This function defaults to the executing assembly + /// + ///The path (or name of libary) to search for + /// + /// The used to search for libaries + /// within the current filesystem + /// + /// The handle to the libary if successfully loaded + /// True if the libary was found and loaded into the current process + public static bool TryLoadLibrary( + string libPath, + DllImportSearchPath searchPath, + [NotNullWhen(true)] out SafeLibraryHandle? library + ) { - //Free the library and set the handle as invalid - NativeLibrary.Free(handle); - SetHandleAsInvalid(); - return true; + return TryLoadLibrary( + libPath, + Assembly.GetExecutingAssembly(), //Use the executing assembly as the default loading assembly + searchPath, + out library + ); + } + + + + /// + /// Attempts to load the specified native libary into the current process by its name at runtime + /// + ///The path (or name of libary) to search for + /// + /// The used to search for libaries + /// within the current filesystem + /// + /// The handle to the libary if successfully loaded + /// The assembly loading the native library + /// True if the libary was found and loaded into the current process + /// + public static bool TryLoadLibrary( + string libPath, + Assembly assembly, + DllImportSearchPath searchPath, + [NotNullWhen(true)] out SafeLibraryHandle? library + ) + { + ArgumentNullException.ThrowIfNull(libPath); + ArgumentNullException.ThrowIfNull(assembly); + + NatveLibraryResolver resolver = new(libPath, assembly, searchPath); + + return resolver.ResolveAndLoadLibrary(out library); } } } -- cgit