using System; using System.Diagnostics; using System.Runtime.InteropServices; #if EXE_PROJECT namespace EXE_PROJECT // Use a different namespace so the executable can still use this class' members without an implicit dependency on the BizHawk.Common library, and without resorting to code duplication. #else namespace BizHawk.Common #endif { public static class OSTailoredCode { /// macOS doesn't use PlatformID.MacOSX public static readonly DistinctOS CurrentOS = Environment.OSVersion.Platform == PlatformID.Unix ? SimpleSubshell("uname", "-s", "Can't determine OS") == "Darwin" ? DistinctOS.macOS : DistinctOS.Linux : DistinctOS.Windows; public static readonly bool IsUnixHost = CurrentOS != DistinctOS.Windows; private static readonly Lazy _LinkedLibManager = new Lazy(() => CurrentOS switch { DistinctOS.Linux => (ILinkedLibManager) new UnixMonoLLManager(), DistinctOS.macOS => new UnixMonoLLManager(), DistinctOS.Windows => new WindowsLLManager(), _ => throw new ArgumentOutOfRangeException() }); public static ILinkedLibManager LinkedLibManager => _LinkedLibManager.Value; /// this interface's inheritors hide OS-specific implementation details public interface ILinkedLibManager { int FreeByPtr(IntPtr hModule); IntPtr? GetProcAddrOrNull(IntPtr hModule, string procName); /// could not find symbol IntPtr GetProcAddrOrThrow(IntPtr hModule, string procName); IntPtr? LoadOrNull(string dllToLoad); /// could not find library IntPtr LoadOrThrow(string dllToLoad); } private class UnixMonoLLManager : ILinkedLibManager { [DllImport("libdl.so.2")] private static extern int dlclose(IntPtr handle); [DllImport("libdl.so.2")] private static extern IntPtr dlerror(); [DllImport("libdl.so.2")] private static extern IntPtr dlopen(string fileName, int flags); [DllImport("libdl.so.2")] private static extern IntPtr dlsym(IntPtr handle, string symbol); public int FreeByPtr(IntPtr hModule) => dlclose(hModule); public IntPtr? GetProcAddrOrNull(IntPtr hModule, string procName) { var p = dlsym(hModule, procName); return p == IntPtr.Zero ? (IntPtr?) null : p; } public IntPtr GetProcAddrOrThrow(IntPtr hModule, string procName) { _ = dlerror(); // the Internet said to do this var p = GetProcAddrOrNull(hModule, procName); if (p != null) return p.Value; var errCharPtr = dlerror(); throw new InvalidOperationException($"error in {nameof(dlsym)}{(errCharPtr == IntPtr.Zero ? string.Empty : $": {Marshal.PtrToStringAnsi(errCharPtr)}")}"); } public IntPtr? LoadOrNull(string dllToLoad) { const int RTLD_NOW = 2; var p = dlopen(dllToLoad, RTLD_NOW); return p == IntPtr.Zero ? (IntPtr?) null : p; } public IntPtr LoadOrThrow(string dllToLoad) => LoadOrNull(dllToLoad) ?? throw new InvalidOperationException($"got null pointer from {nameof(dlopen)}, error: {Marshal.PtrToStringAnsi(dlerror())}"); } private class WindowsLLManager : ILinkedLibManager { // comments reference extern functions removed from SevenZip.NativeMethods [DllImport("kernel32.dll")] private static extern bool FreeLibrary(IntPtr hModule); // return type was annotated MarshalAs(UnmanagedType.Bool) [DllImport("kernel32.dll")] private static extern uint GetLastError(); [DllImport("kernel32.dll", SetLastError = true)] // had BestFitMapping = false, ThrowOnUnmappableChar = true private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); // param procName was annotated `[MarshalAs(UnmanagedType.LPStr)]` [DllImport("kernel32.dll", SetLastError = true)] // had BestFitMapping = false, ThrowOnUnmappableChar = true private static extern IntPtr LoadLibrary(string dllToLoad); // param dllToLoad was annotated `[MarshalAs(UnmanagedType.LPStr)]` public int FreeByPtr(IntPtr hModule) => FreeLibrary(hModule) ? 0 : 1; public IntPtr? GetProcAddrOrNull(IntPtr hModule, string procName) { var p = GetProcAddress(hModule, procName); return p == IntPtr.Zero ? (IntPtr?) null : p; } public IntPtr GetProcAddrOrThrow(IntPtr hModule, string procName) => GetProcAddrOrNull(hModule, procName) ?? throw new InvalidOperationException($"got null pointer from {nameof(GetProcAddress)}, error code: {GetLastError()}"); public IntPtr? LoadOrNull(string dllToLoad) { var p = LoadLibrary(dllToLoad); return p == IntPtr.Zero ? (IntPtr?) null : p; } public IntPtr LoadOrThrow(string dllToLoad) => LoadOrNull(dllToLoad) ?? throw new InvalidOperationException($"got null pointer from {nameof(LoadLibrary)}, error code: {GetLastError()}"); } public enum DistinctOS : byte { Linux, macOS, Windows } /// POSIX $0 /// POSIX $* (space-delimited) /// stdout is discarded if false /// stderr is discarded if false /// OS is implicit and needs to be checked at callsite. Returned has not been started. public static Process ConstructSubshell(string cmd, string args, bool checkStdout = true, bool checkStderr = false) => new Process { StartInfo = new ProcessStartInfo { Arguments = args, CreateNoWindow = true, FileName = cmd, RedirectStandardError = checkStderr, RedirectStandardInput = true, RedirectStandardOutput = checkStdout, UseShellExecute = false } }; /// POSIX $0 /// POSIX $* (space-delimited) /// used in exception /// first line of stdout /// stdout is empty /// OS is implicit and needs to be checked at callsite public static string SimpleSubshell(string cmd, string args, string noOutputMsg) { using var proc = ConstructSubshell(cmd, args); proc.Start(); var stdout = proc.StandardOutput; if (stdout.EndOfStream) throw new Exception($"{noOutputMsg} ({cmd} wrote nothing to stdout)"); return stdout.ReadLine(); } } }