From 6a1cd484913b03325c9e1d87bd4a25c7544d418b Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Fri, 27 Dec 2024 13:26:19 +0000 Subject: [PATCH 1/2] realpath in c# --- src/Cli/dotnet/StatInterop.cs | 70 ------------------- .../EnvironmentProvider.cs | 29 ++++++-- .../Microsoft.DotNet.NativeWrapper/Interop.cs | 16 +---- 3 files changed, 25 insertions(+), 90 deletions(-) delete mode 100644 src/Cli/dotnet/StatInterop.cs diff --git a/src/Cli/dotnet/StatInterop.cs b/src/Cli/dotnet/StatInterop.cs deleted file mode 100644 index b902bb81a70a..000000000000 --- a/src/Cli/dotnet/StatInterop.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Cli -{ - // https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/Interop/Unix/System.Native/Interop.Stat.cs - - internal static class StatInterop - { - // Even though csc will by default use a sequential layout, a CS0649 warning as error - // is produced for un-assigned fields when no StructLayout is specified. - // - // Explicitly saying Sequential disables that warning/error for consumers which only - // use Stat in debug builds. - [StructLayout(LayoutKind.Sequential)] - internal struct FileStatus - { - internal FileStatusFlags Flags; - internal int Mode; - internal uint Uid; - internal uint Gid; - internal long Size; - internal long ATime; - internal long ATimeNsec; - internal long MTime; - internal long MTimeNsec; - internal long CTime; - internal long CTimeNsec; - internal long BirthTime; - internal long BirthTimeNsec; - internal long Dev; - internal long RDev; - internal long Ino; - internal uint UserFlags; - } - - [Flags] - internal enum Permissions - { - Mask = S_IRWXU | S_IRWXG | S_IRWXO, - - S_IRWXU = S_IRUSR | S_IWUSR | S_IXUSR, - S_IRUSR = 0x100, - S_IWUSR = 0x80, - S_IXUSR = 0x40, - - S_IRWXG = S_IRGRP | S_IWGRP | S_IXGRP, - S_IRGRP = 0x20, - S_IWGRP = 0x10, - S_IXGRP = 0x8, - - S_IRWXO = S_IROTH | S_IWOTH | S_IXOTH, - S_IROTH = 0x4, - S_IWOTH = 0x2, - S_IXOTH = 0x1, - - S_IXUGO = S_IXUSR | S_IXGRP | S_IXOTH, - } - - [Flags] - internal enum FileStatusFlags - { - None = 0, - HasBirthTime = 1, - } - - [DllImport("libSystem.Native", EntryPoint = "SystemNative_LStat", SetLastError = true)] - internal static extern int LStat(string path, out FileStatus output); - } -} diff --git a/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs b/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs index 96c203cc3f4d..c13f1b3b87a8 100644 --- a/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs +++ b/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs @@ -59,7 +59,7 @@ private IEnumerable SearchPaths } string? dotnetExe; -#if NETCOREAPP +#if NET // The dotnet executable is loading only the .NET version of this code so there is no point checking // the current process path on .NET Framework. We are expected to find dotnet on PATH. dotnetExe = _getCurrentProcessPath(); @@ -70,12 +70,31 @@ private IEnumerable SearchPaths { string? dotnetExeFromPath = GetCommandPath(Constants.DotNet); - if (dotnetExeFromPath != null && !Interop.RunningOnWindows) +#if NET + if (dotnetExeFromPath != null && !OperatingSystem.IsWindows()) { // e.g. on Linux the 'dotnet' command from PATH is a symlink so we need to // resolve it to get the actual path to the binary - dotnetExeFromPath = Interop.Unix.realpath(dotnetExeFromPath) ?? dotnetExeFromPath; + dotnetExeFromPath = GetRealPath(dotnetExeFromPath) ?? dotnetExeFromPath; + + static string? GetRealPath(string path) + { + string fullPath = Path.GetFullPath(path); + if (!File.Exists(fullPath)) + return null; + + FileInfo info = new(fullPath); + if (info.LinkTarget != null) + { + var resolvedTarget = info.ResolveLinkTarget(returnFinalTarget: true); + if (resolvedTarget != null) + return resolvedTarget.FullName; + } + + return fullPath; + } } +#endif if (!string.IsNullOrWhiteSpace(dotnetExeFromPath)) { @@ -86,7 +105,7 @@ private IEnumerable SearchPaths log?.Invoke($"GetDotnetExeDirectory: dotnet command path not found. Using current process"); log?.Invoke($"GetDotnetExeDirectory: Path variable: {_getEnvironmentVariable(Constants.PATH)}"); -#if !NETCOREAPP +#if !NET // If we failed to find dotnet on PATH, we revert to the old behavior of returning the current process // path. This is really an error state but we're keeping the contract of always returning a non-empty // path for backward compatibility. @@ -123,7 +142,7 @@ private IEnumerable SearchPaths private static string? GetCurrentProcessPath() { string? currentProcessPath; -#if NET6_0_OR_GREATER +#if NET currentProcessPath = Environment.ProcessPath; #else currentProcessPath = Process.GetCurrentProcess().MainModule.FileName; diff --git a/src/Resolvers/Microsoft.DotNet.NativeWrapper/Interop.cs b/src/Resolvers/Microsoft.DotNet.NativeWrapper/Interop.cs index 3ce65639989e..7ffb6f3882a6 100644 --- a/src/Resolvers/Microsoft.DotNet.NativeWrapper/Interop.cs +++ b/src/Resolvers/Microsoft.DotNet.NativeWrapper/Interop.cs @@ -28,7 +28,7 @@ static Interop() } // MSBuild SDK resolvers are required to be AnyCPU, but we have a native dependency and .NETFramework does not - // have a built-in facility for dynamically loading user native dlls for the appropriate platform. We therefore + // have a built-in facility for dynamically loading user native dlls for the appropriate platform. We therefore // preload the version with the correct architecture (from a corresponding sub-folder relative to us) on static // construction so that subsequent P/Invokes can find it. private static void PreloadWindowsLibrary(string dllFileName) @@ -179,20 +179,6 @@ internal delegate void hostfxr_get_available_sdks_result_fn( internal static extern int hostfxr_get_available_sdks( string? exe_dir, hostfxr_get_available_sdks_result_fn result); - - [DllImport("libc", CharSet = UTF8, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr realpath(string path, IntPtr buffer); - - [DllImport("libc", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)] - private static extern void free(IntPtr ptr); - - public static string? realpath(string path) - { - var ptr = realpath(path, IntPtr.Zero); - var result = PtrToStringUTF8(ptr); - free(ptr); - return result; - } } } } From 7d22dcaf322893d7671fbd5c6e90da71915967c8 Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Fri, 27 Dec 2024 18:42:14 +0200 Subject: [PATCH 2/2] Update EnvironmentProvider.cs --- .../EnvironmentProvider.cs | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs b/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs index c13f1b3b87a8..360bc71f0b4b 100644 --- a/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs +++ b/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs @@ -75,23 +75,33 @@ private IEnumerable SearchPaths { // e.g. on Linux the 'dotnet' command from PATH is a symlink so we need to // resolve it to get the actual path to the binary - dotnetExeFromPath = GetRealPath(dotnetExeFromPath) ?? dotnetExeFromPath; + dotnetExeFromPath = GetRealPath(dotnetExeFromPath); - static string? GetRealPath(string path) + static string GetRealPath(string path) { - string fullPath = Path.GetFullPath(path); - if (!File.Exists(fullPath)) - return null; - - FileInfo info = new(fullPath); - if (info.LinkTarget != null) + FileInfo fileInfo = new FileInfo(path); + if (fileInfo.LinkTarget != null) { - var resolvedTarget = info.ResolveLinkTarget(returnFinalTarget: true); - if (resolvedTarget != null) - return resolvedTarget.FullName; + var resolved = fileInfo.ResolveLinkTarget(true); + return resolved?.Exists is true ? resolved.FullName : path; } - - return fullPath; + + string invariantPart = string.Empty; + DirectoryInfo? parentDirectory = fileInfo.Directory; + while (parentDirectory is not null) + { + invariantPart = path[parentDirectory.FullName.Length..]; + if (parentDirectory.LinkTarget != null) + { + var resolved = parentDirectory.ResolveLinkTarget(true); + if (resolved?.Exists is true) + return Path.Join(resolved.FullName, invariantPart); + } + + parentDirectory = parentDirectory.Parent; + } + + return path; } } #endif