From 9596feff094bbef806eb0a6f3a3988b2ac4043f9 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 24 Mar 2020 19:08:43 +0300 Subject: [PATCH 01/15] Implement Environment.GetFolderPath on iOS --- .../OSX/System.Native/Interop.SearchPath.cs | 15 +++ .../Native/Unix/System.Native/CMakeLists.txt | 4 +- .../Unix/System.Native/pal_searchpath.h | 10 ++ .../Unix/System.Native/pal_searchpath.m | 13 ++ .../src/System/Environment.Unix.cs | 16 ++- .../System.Private.CoreLib.csproj | 6 + .../src/System/Environment.Unix.Mono.cs | 15 ++- .../src/System/Environment.iOS.cs | 115 ++++++++++++++++++ 8 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs create mode 100644 src/libraries/Native/Unix/System.Native/pal_searchpath.h create mode 100644 src/libraries/Native/Unix/System.Native/pal_searchpath.m create mode 100644 src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs diff --git a/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs b/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs new file mode 100644 index 00000000000000..fcc0f6a531c252 --- /dev/null +++ b/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_SearchPath")] + internal static extern string SearchPath(int folderId); + } +} \ No newline at end of file diff --git a/src/libraries/Native/Unix/System.Native/CMakeLists.txt b/src/libraries/Native/Unix/System.Native/CMakeLists.txt index 7bf8031bcb6166..67148f3cf23e2b 100644 --- a/src/libraries/Native/Unix/System.Native/CMakeLists.txt +++ b/src/libraries/Native/Unix/System.Native/CMakeLists.txt @@ -23,7 +23,9 @@ set(NATIVE_SOURCES ) if (CLR_CMAKE_TARGET_IOS) - set(NATIVE_SOURCES ${NATIVE_SOURCES} pal_log.m) + set(NATIVE_SOURCES ${NATIVE_SOURCES} + pal_log.m + pal_searchpath.m) else () set(NATIVE_SOURCES ${NATIVE_SOURCES} pal_console.c) endif () diff --git a/src/libraries/Native/Unix/System.Native/pal_searchpath.h b/src/libraries/Native/Unix/System.Native/pal_searchpath.h new file mode 100644 index 00000000000000..3f19c970bff763 --- /dev/null +++ b/src/libraries/Native/Unix/System.Native/pal_searchpath.h @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#pragma once + +#include "pal_compiler.h" +#include "pal_types.h" + +PALEXPORT const char* SystemNative_SearchPath(int32_t folderId); \ No newline at end of file diff --git a/src/libraries/Native/Unix/System.Native/pal_searchpath.m b/src/libraries/Native/Unix/System.Native/pal_searchpath.m new file mode 100644 index 00000000000000..6d44658fc16e3d --- /dev/null +++ b/src/libraries/Native/Unix/System.Native/pal_searchpath.m @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "pal_searchpath.h" +#import + +const char* SystemNative_SearchPath(int32_t folderId) +{ + NSSearchPathDirectory spd = (NSSearchPathDirectory) folderId; + NSURL* url = [[[NSFileManager defaultManager] URLsForDirectory:spd inDomains:NSUserDomainMask] lastObject]; + return strdup ([[url path] UTF8String]); +} \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index 8e7982e9992bed..b82b2bda5c1304 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -15,7 +15,9 @@ namespace System { public static partial class Environment { +#if !TARGET_IOS private static Func? s_directoryCreateDirectory; +#endif public static bool UserInteractive => true; @@ -56,7 +58,10 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio // Get the path for the SpecialFolder string path = GetFolderPathCoreWithoutValidation(folder); Debug.Assert(path != null); - +#if TARGET_IOS + // ignore verification and SpecialFolderOption.Create on iOS + return path; +#else // If we didn't get one, or if we got one but we're not supposed to verify it, // or if we're supposed to verify it and it passes verification, return the path. if (path.Length == 0 || @@ -86,17 +91,21 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio return path; } +#endif } private static string GetFolderPathCoreWithoutValidation(SpecialFolder folder) { +#if TARGET_IOS + return GetFolderPathiOS(folder); +#else // First handle any paths that involve only static paths, avoiding the overheads of getting user-local paths. // https://www.freedesktop.org/software/systemd/man/file-hierarchy.html switch (folder) { case SpecialFolder.CommonApplicationData: return "/usr/share"; case SpecialFolder.CommonTemplates: return "/usr/share/templates"; -#if TARGET_OSX || TARGET_IOS +#if TARGET_OSX case SpecialFolder.ProgramFiles: return "/Applications"; case SpecialFolder.System: return "/System"; #endif @@ -152,7 +161,7 @@ private static string GetFolderPathCoreWithoutValidation(SpecialFolder folder) case SpecialFolder.MyVideos: return ReadXdgDirectory(home, "XDG_VIDEOS_DIR", "Videos"); -#if TARGET_OSX || TARGET_IOS +#if TARGET_OSX case SpecialFolder.MyMusic: return Path.Combine(home, "Music"); case SpecialFolder.MyPictures: @@ -175,6 +184,7 @@ private static string GetFolderPathCoreWithoutValidation(SpecialFolder folder) // No known path for the SpecialFolder return string.Empty; +#endif } private static string GetXdgConfig(string home) diff --git a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj index a60befa49fa09f..89a4b1d0a63f97 100644 --- a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -291,6 +291,12 @@ + + + + Common\Interop\OSX\Interop.SearchPath.cs + + diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs index 6b186fd13cd392..cf8a820362a247 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs @@ -19,17 +19,26 @@ static string GetEnvironmentVariableCore (string variable) { Debug.Assert(variable != null); + variable = TrimStringOnFirstZero (variable); + + // call getenv directly if s_environment is not yet initialized if (s_environment == null) { using (var h = RuntimeMarshal.MarshalString (variable)) { return internalGetEnvironmentVariable_native (h.Value); } } - variable = TrimStringOnFirstZero (variable); + string value = ""; lock (s_environment) { - s_environment.TryGetValue (variable, out string value); - return value; + if (!s_environment.TryGetValue (variable, out value)) { + // on some platform s_environment can be empty after EnsureEnvironmentCached (), e.g. iOS + using (var h = RuntimeMarshal.MarshalString (variable)) { + value = internalGetEnvironmentVariable_native (h.Value); + s_environment [variable] = value; + } + } } + return value; } static unsafe void SetEnvironmentVariableCore (string variable, string? value) diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs new file mode 100644 index 00000000000000..9cad255d1c0aac --- /dev/null +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Runtime.InteropServices; + +namespace System +{ + partial class Environment + { + private const int NSDocumentDirectoryId = 9; + private const int NSLibraryDirectoryId = 5; + + private static string s_document; + private static string s_library; + + // TODO: fix for tvOS + // The "normal" NSDocumentDirectory is a read-only directory on tvOS + // and that breaks a lot of assumptions in the runtime and the BCL + private static string NSDocumentDirectory => s_document ??= Interop.Sys.SearchPath(NSDocumentDirectoryId); + + // Various user-visible documentation, support, and configuration files + private static string NSLibraryDirectory => s_library ??= Interop.Sys.SearchPath(NSLibraryDirectoryId); + + private static string GetFolderPathiOS(SpecialFolder folder) + { + switch (folder) + { + case SpecialFolder.MyComputer: + case SpecialFolder.Programs: + case SpecialFolder.SendTo: + case SpecialFolder.StartMenu: + case SpecialFolder.Startup: + case SpecialFolder.Cookies: + case SpecialFolder.History: + case SpecialFolder.Recent: + case SpecialFolder.CommonProgramFiles: + case SpecialFolder.System: + case SpecialFolder.NetworkShortcuts: + case SpecialFolder.CommonStartMenu: + case SpecialFolder.CommonPrograms: + case SpecialFolder.CommonStartup: + case SpecialFolder.CommonDesktopDirectory: + case SpecialFolder.PrinterShortcuts: + case SpecialFolder.Windows: + case SpecialFolder.SystemX86: + case SpecialFolder.ProgramFilesX86: + case SpecialFolder.CommonProgramFilesX86: + case SpecialFolder.CommonDocuments: + case SpecialFolder.CommonAdminTools: + case SpecialFolder.AdminTools: + case SpecialFolder.CommonMusic: + case SpecialFolder.CommonPictures: + case SpecialFolder.CommonVideos: + case SpecialFolder.LocalizedResources: + case SpecialFolder.CommonOemLinks: + case SpecialFolder.CDBurning: + return String.Empty; + + case SpecialFolder.Personal: + case SpecialFolder.LocalApplicationData: + return NSDocumentDirectory; + + case SpecialFolder.ApplicationData: + // note: at first glance that looked like a good place to return NSLibraryDirectory + // but it would break isolated storage for existing applications + return Path.Combine(NSDocumentDirectory, ".config"); + + case SpecialFolder.Resources: + return NSLibraryDirectory; // older (8.2 and previous) would return String.Empty + + case SpecialFolder.Desktop: + case SpecialFolder.DesktopDirectory: + return Path.Combine(NSDocumentDirectory, "Desktop"); + + case SpecialFolder.MyMusic: + return Path.Combine(NSDocumentDirectory, "Music"); + + case SpecialFolder.MyPictures: + return Path.Combine(NSDocumentDirectory, "Pictures"); + + case SpecialFolder.Templates: + return Path.Combine(NSDocumentDirectory, "Templates"); + + case SpecialFolder.MyVideos: + return Path.Combine(NSDocumentDirectory, "Videos"); + + case SpecialFolder.CommonTemplates: + return "/usr/share/templates"; + + case SpecialFolder.Fonts: + return Path.Combine(NSDocumentDirectory, ".fonts"); + + case SpecialFolder.Favorites: + return Path.Combine(NSLibraryDirectory, "Favorites"); + + case SpecialFolder.ProgramFiles: + return "/Applications"; + + case SpecialFolder.InternetCache: + return Path.Combine(NSLibraryDirectory, "Caches"); + + case SpecialFolder.UserProfile: + return Environment.GetEnvironmentVariable("HOME"); + + case SpecialFolder.CommonApplicationData: + return "/usr/share"; + + default: + throw new ArgumentException($"Invalid SpecialFolder '{folder}'"); + } + } + } +} \ No newline at end of file From 1429a744255e330a8346e8ac2515fd52c7dc3b8e Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 24 Mar 2020 23:34:17 +0300 Subject: [PATCH 02/15] Address feedback --- .../OSX/System.Native/Interop.SearchPath.cs | 2 +- .../Unix/System.Native/pal_searchpath.h | 2 +- .../Unix/System.Native/pal_searchpath.m | 2 +- .../src/System/Environment.Unix.cs | 2 +- .../src/System/Environment.iOS.cs | 39 ++----------------- 5 files changed, 8 insertions(+), 39 deletions(-) diff --git a/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs b/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs index fcc0f6a531c252..24a2988d366160 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs @@ -12,4 +12,4 @@ internal static partial class Sys [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_SearchPath")] internal static extern string SearchPath(int folderId); } -} \ No newline at end of file +} diff --git a/src/libraries/Native/Unix/System.Native/pal_searchpath.h b/src/libraries/Native/Unix/System.Native/pal_searchpath.h index 3f19c970bff763..21ee57d665cfad 100644 --- a/src/libraries/Native/Unix/System.Native/pal_searchpath.h +++ b/src/libraries/Native/Unix/System.Native/pal_searchpath.h @@ -7,4 +7,4 @@ #include "pal_compiler.h" #include "pal_types.h" -PALEXPORT const char* SystemNative_SearchPath(int32_t folderId); \ No newline at end of file +PALEXPORT const char* SystemNative_SearchPath(int32_t folderId); diff --git a/src/libraries/Native/Unix/System.Native/pal_searchpath.m b/src/libraries/Native/Unix/System.Native/pal_searchpath.m index 6d44658fc16e3d..6bd5a2f8b42b9e 100644 --- a/src/libraries/Native/Unix/System.Native/pal_searchpath.m +++ b/src/libraries/Native/Unix/System.Native/pal_searchpath.m @@ -10,4 +10,4 @@ NSSearchPathDirectory spd = (NSSearchPathDirectory) folderId; NSURL* url = [[[NSFileManager defaultManager] URLsForDirectory:spd inDomains:NSUserDomainMask] lastObject]; return strdup ([[url path] UTF8String]); -} \ No newline at end of file +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index b82b2bda5c1304..9dde1efff4a021 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -97,7 +97,7 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio private static string GetFolderPathCoreWithoutValidation(SpecialFolder folder) { #if TARGET_IOS - return GetFolderPathiOS(folder); + return GetFolderPathIos(folder); #else // First handle any paths that involve only static paths, avoiding the overheads of getting user-local paths. // https://www.freedesktop.org/software/systemd/man/file-hierarchy.html diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs index 9cad255d1c0aac..2793bc3cb1d40c 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -7,7 +7,7 @@ namespace System { - partial class Environment + public static partial class Environment { private const int NSDocumentDirectoryId = 9; private const int NSLibraryDirectoryId = 5; @@ -23,41 +23,10 @@ partial class Environment // Various user-visible documentation, support, and configuration files private static string NSLibraryDirectory => s_library ??= Interop.Sys.SearchPath(NSLibraryDirectoryId); - private static string GetFolderPathiOS(SpecialFolder folder) + private static string GetFolderPathIos(SpecialFolder folder) { switch (folder) { - case SpecialFolder.MyComputer: - case SpecialFolder.Programs: - case SpecialFolder.SendTo: - case SpecialFolder.StartMenu: - case SpecialFolder.Startup: - case SpecialFolder.Cookies: - case SpecialFolder.History: - case SpecialFolder.Recent: - case SpecialFolder.CommonProgramFiles: - case SpecialFolder.System: - case SpecialFolder.NetworkShortcuts: - case SpecialFolder.CommonStartMenu: - case SpecialFolder.CommonPrograms: - case SpecialFolder.CommonStartup: - case SpecialFolder.CommonDesktopDirectory: - case SpecialFolder.PrinterShortcuts: - case SpecialFolder.Windows: - case SpecialFolder.SystemX86: - case SpecialFolder.ProgramFilesX86: - case SpecialFolder.CommonProgramFilesX86: - case SpecialFolder.CommonDocuments: - case SpecialFolder.CommonAdminTools: - case SpecialFolder.AdminTools: - case SpecialFolder.CommonMusic: - case SpecialFolder.CommonPictures: - case SpecialFolder.CommonVideos: - case SpecialFolder.LocalizedResources: - case SpecialFolder.CommonOemLinks: - case SpecialFolder.CDBurning: - return String.Empty; - case SpecialFolder.Personal: case SpecialFolder.LocalApplicationData: return NSDocumentDirectory; @@ -108,8 +77,8 @@ private static string GetFolderPathiOS(SpecialFolder folder) return "/usr/share"; default: - throw new ArgumentException($"Invalid SpecialFolder '{folder}'"); + return string.Empty; } } } -} \ No newline at end of file +} From 67790b3161428e25d15c55caa803a18eac877d2f Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 24 Mar 2020 23:52:10 +0300 Subject: [PATCH 03/15] Move GetFolderPathCore to Environment.Unix.GetFolderPathCore.cs --- .../System.Private.CoreLib.Shared.projitems | 1 + .../Environment.Unix.GetFolderPathCore.cs | 251 ++++++++++++++++++ .../src/System/Environment.Unix.cs | 242 ----------------- .../src/System/Environment.iOS.cs | 2 +- 4 files changed, 253 insertions(+), 243 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index b5a654a61e63b7..689c61594bb142 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1723,6 +1723,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs new file mode 100644 index 00000000000000..22645742a73bfb --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs @@ -0,0 +1,251 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +namespace System +{ + public static partial class Environment + { + private static Func? s_directoryCreateDirectory; + + private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) + { + // Get the path for the SpecialFolder + string path = GetFolderPathCoreWithoutValidation(folder); + Debug.Assert(path != null); + + // If we didn't get one, or if we got one but we're not supposed to verify it, + // or if we're supposed to verify it and it passes verification, return the path. + if (path.Length == 0 || + option == SpecialFolderOption.DoNotVerify || + Interop.Sys.Access(path, Interop.Sys.AccessMode.R_OK) == 0) + { + return path; + } + + // Failed verification. If None, then we're supposed to return an empty string. + // If Create, we're supposed to create it and then return the path. + if (option == SpecialFolderOption.None) + { + return string.Empty; + } + else + { + Debug.Assert(option == SpecialFolderOption.Create); + + Func createDirectory = LazyInitializer.EnsureInitialized(ref s_directoryCreateDirectory, () => + { + Type dirType = Type.GetType("System.IO.Directory, System.IO.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: true)!; + MethodInfo mi = dirType.GetTypeInfo().GetDeclaredMethod("CreateDirectory")!; + return (Func)mi.CreateDelegate(typeof(Func)); + }); + createDirectory(path); + + return path; + } + } + + private static string GetFolderPathCoreWithoutValidation(SpecialFolder folder) + { + // First handle any paths that involve only static paths, avoiding the overheads of getting user-local paths. + // https://www.freedesktop.org/software/systemd/man/file-hierarchy.html + switch (folder) + { + case SpecialFolder.CommonApplicationData: return "/usr/share"; + case SpecialFolder.CommonTemplates: return "/usr/share/templates"; +#if TARGET_OSX + case SpecialFolder.ProgramFiles: return "/Applications"; + case SpecialFolder.System: return "/System"; +#endif + } + + // All other paths are based on the XDG Base Directory Specification: + // https://specifications.freedesktop.org/basedir-spec/latest/ + string? home = null; + try + { + home = PersistedFiles.GetHomeDirectory(); + } + catch (Exception exc) + { + Debug.Fail($"Unable to get home directory: {exc}"); + } + + // Fall back to '/' when we can't determine the home directory. + // This location isn't writable by non-root users which provides some safeguard + // that the application doesn't write data which is meant to be private. + if (string.IsNullOrEmpty(home)) + { + home = "/"; + } + + // TODO: Consider caching (or precomputing and caching) all subsequent results. + // This would significantly improve performance for repeated access, at the expense + // of not being responsive to changes in the underlying environment variables, + // configuration files, etc. + + switch (folder) + { + case SpecialFolder.UserProfile: + case SpecialFolder.MyDocuments: // same value as Personal + return home; + case SpecialFolder.ApplicationData: + return GetXdgConfig(home); + case SpecialFolder.LocalApplicationData: + // "$XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored." + // "If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used." + string? data = GetEnvironmentVariable("XDG_DATA_HOME"); + if (string.IsNullOrEmpty(data) || data[0] != '/') + { + data = Path.Combine(home, ".local", "share"); + } + return data; + + case SpecialFolder.Desktop: + case SpecialFolder.DesktopDirectory: + return ReadXdgDirectory(home, "XDG_DESKTOP_DIR", "Desktop"); + case SpecialFolder.Templates: + return ReadXdgDirectory(home, "XDG_TEMPLATES_DIR", "Templates"); + case SpecialFolder.MyVideos: + return ReadXdgDirectory(home, "XDG_VIDEOS_DIR", "Videos"); + +#if TARGET_OSX + case SpecialFolder.MyMusic: + return Path.Combine(home, "Music"); + case SpecialFolder.MyPictures: + return Path.Combine(home, "Pictures"); + case SpecialFolder.Fonts: + return Path.Combine(home, "Library", "Fonts"); + case SpecialFolder.Favorites: + return Path.Combine(home, "Library", "Favorites"); + case SpecialFolder.InternetCache: + return Path.Combine(home, "Library", "Caches"); +#else + case SpecialFolder.MyMusic: + return ReadXdgDirectory(home, "XDG_MUSIC_DIR", "Music"); + case SpecialFolder.MyPictures: + return ReadXdgDirectory(home, "XDG_PICTURES_DIR", "Pictures"); + case SpecialFolder.Fonts: + return Path.Combine(home, ".fonts"); +#endif + } + + // No known path for the SpecialFolder + return string.Empty; +#endif + } + + private static string GetXdgConfig(string home) + { + // "$XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored." + // "If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used." + string? config = GetEnvironmentVariable("XDG_CONFIG_HOME"); + if (string.IsNullOrEmpty(config) || config[0] != '/') + { + config = Path.Combine(home, ".config"); + } + return config; + } + + private static string ReadXdgDirectory(string homeDir, string key, string fallback) + { + Debug.Assert(!string.IsNullOrEmpty(homeDir), $"Expected non-empty homeDir"); + Debug.Assert(!string.IsNullOrEmpty(key), $"Expected non-empty key"); + Debug.Assert(!string.IsNullOrEmpty(fallback), $"Expected non-empty fallback"); + + string? envPath = GetEnvironmentVariable(key); + if (!string.IsNullOrEmpty(envPath) && envPath[0] == '/') + { + return envPath; + } + + // Use the user-dirs.dirs file to look up the right config. + // Note that the docs also highlight a list of directories in which to look for this file: + // "$XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for configuration files in addition + // to the $XDG_CONFIG_HOME base directory. The directories in $XDG_CONFIG_DIRS should be separated with a colon ':'. If + // $XDG_CONFIG_DIRS is either not set or empty, a value equal to / etc / xdg should be used." + // For simplicity, we don't currently do that. We can add it if/when necessary. + + string userDirsPath = Path.Combine(GetXdgConfig(homeDir), "user-dirs.dirs"); + if (Interop.Sys.Access(userDirsPath, Interop.Sys.AccessMode.R_OK) == 0) + { + try + { + using (var reader = new StreamReader(userDirsPath)) + { + string? line; + while ((line = reader.ReadLine()) != null) + { + // Example lines: + // XDG_DESKTOP_DIR="$HOME/Desktop" + // XDG_PICTURES_DIR = "/absolute/path" + + // Skip past whitespace at beginning of line + int pos = 0; + SkipWhitespace(line, ref pos); + if (pos >= line.Length) continue; + + // Skip past requested key name + if (string.CompareOrdinal(line, pos, key, 0, key.Length) != 0) continue; + pos += key.Length; + + // Skip past whitespace and past '=' + SkipWhitespace(line, ref pos); + if (pos >= line.Length - 4 || line[pos] != '=') continue; // 4 for ="" and at least one char between quotes + pos++; // skip past '=' + + // Skip past whitespace and past first quote + SkipWhitespace(line, ref pos); + if (pos >= line.Length - 3 || line[pos] != '"') continue; // 3 for "" and at least one char between quotes + pos++; // skip past opening '"' + + // Skip past relative prefix if one exists + bool relativeToHome = false; + const string RelativeToHomePrefix = "$HOME/"; + if (string.CompareOrdinal(line, pos, RelativeToHomePrefix, 0, RelativeToHomePrefix.Length) == 0) + { + relativeToHome = true; + pos += RelativeToHomePrefix.Length; + } + else if (line[pos] != '/') // if not relative to home, must be absolute path + { + continue; + } + + // Find end of path + int endPos = line.IndexOf('"', pos); + if (endPos <= pos) continue; + + // Got we need. Now extract it. + string path = line.Substring(pos, endPos - pos); + return relativeToHome ? + Path.Combine(homeDir, path) : + path; + } + } + } + catch (Exception exc) + { + // assembly not found, file not found, errors reading file, etc. Just eat everything. + Debug.Fail($"Failed reading {userDirsPath}: {exc}"); + } + } + + return Path.Combine(homeDir, fallback); + } + + private static void SkipWhitespace(string line, ref int pos) + { + while (pos < line.Length && char.IsWhiteSpace(line[pos])) pos++; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index 9dde1efff4a021..b806cc234953b9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -15,10 +15,6 @@ namespace System { public static partial class Environment { -#if !TARGET_IOS - private static Func? s_directoryCreateDirectory; -#endif - public static bool UserInteractive => true; private static string CurrentDirectoryCore @@ -53,244 +49,6 @@ private static string ExpandEnvironmentVariablesCore(string name) return result.ToString(); } - private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) - { - // Get the path for the SpecialFolder - string path = GetFolderPathCoreWithoutValidation(folder); - Debug.Assert(path != null); -#if TARGET_IOS - // ignore verification and SpecialFolderOption.Create on iOS - return path; -#else - // If we didn't get one, or if we got one but we're not supposed to verify it, - // or if we're supposed to verify it and it passes verification, return the path. - if (path.Length == 0 || - option == SpecialFolderOption.DoNotVerify || - Interop.Sys.Access(path, Interop.Sys.AccessMode.R_OK) == 0) - { - return path; - } - - // Failed verification. If None, then we're supposed to return an empty string. - // If Create, we're supposed to create it and then return the path. - if (option == SpecialFolderOption.None) - { - return string.Empty; - } - else - { - Debug.Assert(option == SpecialFolderOption.Create); - - Func createDirectory = LazyInitializer.EnsureInitialized(ref s_directoryCreateDirectory, () => - { - Type dirType = Type.GetType("System.IO.Directory, System.IO.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: true)!; - MethodInfo mi = dirType.GetTypeInfo().GetDeclaredMethod("CreateDirectory")!; - return (Func)mi.CreateDelegate(typeof(Func)); - }); - createDirectory(path); - - return path; - } -#endif - } - - private static string GetFolderPathCoreWithoutValidation(SpecialFolder folder) - { -#if TARGET_IOS - return GetFolderPathIos(folder); -#else - // First handle any paths that involve only static paths, avoiding the overheads of getting user-local paths. - // https://www.freedesktop.org/software/systemd/man/file-hierarchy.html - switch (folder) - { - case SpecialFolder.CommonApplicationData: return "/usr/share"; - case SpecialFolder.CommonTemplates: return "/usr/share/templates"; -#if TARGET_OSX - case SpecialFolder.ProgramFiles: return "/Applications"; - case SpecialFolder.System: return "/System"; -#endif - } - - // All other paths are based on the XDG Base Directory Specification: - // https://specifications.freedesktop.org/basedir-spec/latest/ - string? home = null; - try - { - home = PersistedFiles.GetHomeDirectory(); - } - catch (Exception exc) - { - Debug.Fail($"Unable to get home directory: {exc}"); - } - - // Fall back to '/' when we can't determine the home directory. - // This location isn't writable by non-root users which provides some safeguard - // that the application doesn't write data which is meant to be private. - if (string.IsNullOrEmpty(home)) - { - home = "/"; - } - - // TODO: Consider caching (or precomputing and caching) all subsequent results. - // This would significantly improve performance for repeated access, at the expense - // of not being responsive to changes in the underlying environment variables, - // configuration files, etc. - - switch (folder) - { - case SpecialFolder.UserProfile: - case SpecialFolder.MyDocuments: // same value as Personal - return home; - case SpecialFolder.ApplicationData: - return GetXdgConfig(home); - case SpecialFolder.LocalApplicationData: - // "$XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored." - // "If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used." - string? data = GetEnvironmentVariable("XDG_DATA_HOME"); - if (string.IsNullOrEmpty(data) || data[0] != '/') - { - data = Path.Combine(home, ".local", "share"); - } - return data; - - case SpecialFolder.Desktop: - case SpecialFolder.DesktopDirectory: - return ReadXdgDirectory(home, "XDG_DESKTOP_DIR", "Desktop"); - case SpecialFolder.Templates: - return ReadXdgDirectory(home, "XDG_TEMPLATES_DIR", "Templates"); - case SpecialFolder.MyVideos: - return ReadXdgDirectory(home, "XDG_VIDEOS_DIR", "Videos"); - -#if TARGET_OSX - case SpecialFolder.MyMusic: - return Path.Combine(home, "Music"); - case SpecialFolder.MyPictures: - return Path.Combine(home, "Pictures"); - case SpecialFolder.Fonts: - return Path.Combine(home, "Library", "Fonts"); - case SpecialFolder.Favorites: - return Path.Combine(home, "Library", "Favorites"); - case SpecialFolder.InternetCache: - return Path.Combine(home, "Library", "Caches"); -#else - case SpecialFolder.MyMusic: - return ReadXdgDirectory(home, "XDG_MUSIC_DIR", "Music"); - case SpecialFolder.MyPictures: - return ReadXdgDirectory(home, "XDG_PICTURES_DIR", "Pictures"); - case SpecialFolder.Fonts: - return Path.Combine(home, ".fonts"); -#endif - } - - // No known path for the SpecialFolder - return string.Empty; -#endif - } - - private static string GetXdgConfig(string home) - { - // "$XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored." - // "If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used." - string? config = GetEnvironmentVariable("XDG_CONFIG_HOME"); - if (string.IsNullOrEmpty(config) || config[0] != '/') - { - config = Path.Combine(home, ".config"); - } - return config; - } - - private static string ReadXdgDirectory(string homeDir, string key, string fallback) - { - Debug.Assert(!string.IsNullOrEmpty(homeDir), $"Expected non-empty homeDir"); - Debug.Assert(!string.IsNullOrEmpty(key), $"Expected non-empty key"); - Debug.Assert(!string.IsNullOrEmpty(fallback), $"Expected non-empty fallback"); - - string? envPath = GetEnvironmentVariable(key); - if (!string.IsNullOrEmpty(envPath) && envPath[0] == '/') - { - return envPath; - } - - // Use the user-dirs.dirs file to look up the right config. - // Note that the docs also highlight a list of directories in which to look for this file: - // "$XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for configuration files in addition - // to the $XDG_CONFIG_HOME base directory. The directories in $XDG_CONFIG_DIRS should be separated with a colon ':'. If - // $XDG_CONFIG_DIRS is either not set or empty, a value equal to / etc / xdg should be used." - // For simplicity, we don't currently do that. We can add it if/when necessary. - - string userDirsPath = Path.Combine(GetXdgConfig(homeDir), "user-dirs.dirs"); - if (Interop.Sys.Access(userDirsPath, Interop.Sys.AccessMode.R_OK) == 0) - { - try - { - using (var reader = new StreamReader(userDirsPath)) - { - string? line; - while ((line = reader.ReadLine()) != null) - { - // Example lines: - // XDG_DESKTOP_DIR="$HOME/Desktop" - // XDG_PICTURES_DIR = "/absolute/path" - - // Skip past whitespace at beginning of line - int pos = 0; - SkipWhitespace(line, ref pos); - if (pos >= line.Length) continue; - - // Skip past requested key name - if (string.CompareOrdinal(line, pos, key, 0, key.Length) != 0) continue; - pos += key.Length; - - // Skip past whitespace and past '=' - SkipWhitespace(line, ref pos); - if (pos >= line.Length - 4 || line[pos] != '=') continue; // 4 for ="" and at least one char between quotes - pos++; // skip past '=' - - // Skip past whitespace and past first quote - SkipWhitespace(line, ref pos); - if (pos >= line.Length - 3 || line[pos] != '"') continue; // 3 for "" and at least one char between quotes - pos++; // skip past opening '"' - - // Skip past relative prefix if one exists - bool relativeToHome = false; - const string RelativeToHomePrefix = "$HOME/"; - if (string.CompareOrdinal(line, pos, RelativeToHomePrefix, 0, RelativeToHomePrefix.Length) == 0) - { - relativeToHome = true; - pos += RelativeToHomePrefix.Length; - } - else if (line[pos] != '/') // if not relative to home, must be absolute path - { - continue; - } - - // Find end of path - int endPos = line.IndexOf('"', pos); - if (endPos <= pos) continue; - - // Got we need. Now extract it. - string path = line.Substring(pos, endPos - pos); - return relativeToHome ? - Path.Combine(homeDir, path) : - path; - } - } - } - catch (Exception exc) - { - // assembly not found, file not found, errors reading file, etc. Just eat everything. - Debug.Fail($"Failed reading {userDirsPath}: {exc}"); - } - } - - return Path.Combine(homeDir, fallback); - } - - private static void SkipWhitespace(string line, ref int pos) - { - while (pos < line.Length && char.IsWhiteSpace(line[pos])) pos++; - } - public static string[] GetLogicalDrives() => Interop.Sys.GetAllMountPoints(); private static bool Is64BitOperatingSystemWhen32BitProcess => false; diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs index 2793bc3cb1d40c..9c59064fc92e6f 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -23,7 +23,7 @@ public static partial class Environment // Various user-visible documentation, support, and configuration files private static string NSLibraryDirectory => s_library ??= Interop.Sys.SearchPath(NSLibraryDirectoryId); - private static string GetFolderPathIos(SpecialFolder folder) + private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) { switch (folder) { From a0b21d778a4d6996559c3f8d67a1acc1bbfea6df Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 25 Mar 2020 00:15:50 +0300 Subject: [PATCH 04/15] Fix build issue --- .../src/System/Environment.Unix.GetFolderPathCore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs index 22645742a73bfb..ad37bba3746e57 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs @@ -141,7 +141,6 @@ private static string GetFolderPathCoreWithoutValidation(SpecialFolder folder) // No known path for the SpecialFolder return string.Empty; -#endif } private static string GetXdgConfig(string home) From 4bcb4b2afc37aeda695ac9fd5ec30354f06de539 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 25 Mar 2020 20:13:39 +0300 Subject: [PATCH 05/15] Address feedback --- .../src/System/Environment.iOS.cs | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs index 9c59064fc92e6f..f3b3707e0ec5aa 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -9,20 +9,31 @@ namespace System { public static partial class Environment { - private const int NSDocumentDirectoryId = 9; + // NSSearchPathDirectory enum + private const int NSApplicationDirectoryId = 1; private const int NSLibraryDirectoryId = 5; + private const int NSUserDirectoryId = 7; + private const int NSDocumentDirectoryId = 9; + private const int NSDesktopDirectoryId = 12; + private const int NSCachesDirectoryId = 13; + private const int NSMoviesDirectoryId = 17; + private const int NSMusicDirectoryId = 18; + private const int NSPicturesDirectoryId = 19; - private static string s_document; - private static string s_library; + // Cache frequently used folders into lazy properties - // TODO: fix for tvOS + // TODO: fix for tvOS (https://github.com/dotnet/runtime/issues/34007) // The "normal" NSDocumentDirectory is a read-only directory on tvOS // and that breaks a lot of assumptions in the runtime and the BCL + private static string s_document; private static string NSDocumentDirectory => s_document ??= Interop.Sys.SearchPath(NSDocumentDirectoryId); - // Various user-visible documentation, support, and configuration files + private static string s_library; private static string NSLibraryDirectory => s_library ??= Interop.Sys.SearchPath(NSLibraryDirectoryId); + private static string s_user; + private static string NSUserDirectory => s_user ??= Interop.Sys.SearchPath(NSUserDirectoryId); + private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) { switch (folder) @@ -41,19 +52,19 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio case SpecialFolder.Desktop: case SpecialFolder.DesktopDirectory: - return Path.Combine(NSDocumentDirectory, "Desktop"); + return Interop.Sys.SearchPath(NSDesktopDirectoryId); case SpecialFolder.MyMusic: - return Path.Combine(NSDocumentDirectory, "Music"); + return Interop.Sys.SearchPath(NSMusicDirectoryId); case SpecialFolder.MyPictures: - return Path.Combine(NSDocumentDirectory, "Pictures"); + return Interop.Sys.SearchPath(NSPicturesDirectoryId); case SpecialFolder.Templates: return Path.Combine(NSDocumentDirectory, "Templates"); case SpecialFolder.MyVideos: - return Path.Combine(NSDocumentDirectory, "Videos"); + return Interop.Sys.SearchPath(NSMoviesDirectoryId); case SpecialFolder.CommonTemplates: return "/usr/share/templates"; @@ -65,13 +76,13 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio return Path.Combine(NSLibraryDirectory, "Favorites"); case SpecialFolder.ProgramFiles: - return "/Applications"; + return Interop.Sys.SearchPath(NSApplicationDirectoryId); case SpecialFolder.InternetCache: - return Path.Combine(NSLibraryDirectory, "Caches"); + return Interop.Sys.SearchPath(NSCachesDirectoryId); case SpecialFolder.UserProfile: - return Environment.GetEnvironmentVariable("HOME"); + return NSUserDirectory; case SpecialFolder.CommonApplicationData: return "/usr/share"; From da3d210a6c4b3f81767901b9048a8577b19ad6a3 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 25 Mar 2020 20:27:35 +0300 Subject: [PATCH 06/15] cache all special directories --- .../OSX/System.Native/Interop.SearchPath.cs | 15 +++- .../src/System/Environment.iOS.cs | 69 ++++++++++--------- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs b/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs index 24a2988d366160..d5a6b4c5442d54 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs @@ -10,6 +10,19 @@ internal static partial class Interop internal static partial class Sys { [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_SearchPath")] - internal static extern string SearchPath(int folderId); + internal static extern string SearchPath(NSSearchPathDirectory folderId); + + internal enum NSSearchPathDirectory + { + NSApplicationDirectory = 1, + NSLibraryDirectory = 5, + NSUserDirectory = 7, + NSDocumentDirectory = 9, + NSDesktopDirectory = 12, + NSCachesDirectory = 13, + NSMoviesDirectory = 17, + NSMusicDirectory = 18, + NSPicturesDirectory = 19 + } } } diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs index f3b3707e0ec5aa..1377dbfa211e93 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -3,41 +3,44 @@ // See the LICENSE file in the project root for more information. using System.IO; +using System.Threading; +using System.Collections.Generic; using System.Runtime.InteropServices; +using NSSearchPathDirectory = Interop.Sys.NSSearchPathDirectory; namespace System { public static partial class Environment { - // NSSearchPathDirectory enum - private const int NSApplicationDirectoryId = 1; - private const int NSLibraryDirectoryId = 5; - private const int NSUserDirectoryId = 7; - private const int NSDocumentDirectoryId = 9; - private const int NSDesktopDirectoryId = 12; - private const int NSCachesDirectoryId = 13; - private const int NSMoviesDirectoryId = 17; - private const int NSMusicDirectoryId = 18; - private const int NSPicturesDirectoryId = 19; - - // Cache frequently used folders into lazy properties - - // TODO: fix for tvOS (https://github.com/dotnet/runtime/issues/34007) - // The "normal" NSDocumentDirectory is a read-only directory on tvOS - // and that breaks a lot of assumptions in the runtime and the BCL - private static string s_document; - private static string NSDocumentDirectory => s_document ??= Interop.Sys.SearchPath(NSDocumentDirectoryId); - - private static string s_library; - private static string NSLibraryDirectory => s_library ??= Interop.Sys.SearchPath(NSLibraryDirectoryId); - - private static string s_user; - private static string NSUserDirectory => s_user ??= Interop.Sys.SearchPath(NSUserDirectoryId); + private static Dictionary s_specialFolders; private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) + { + if (s_specialFolders == null) + { + Interlocked.CompareExchange(ref s_specialFolders, new Dictionary(), null); + } + + string path; + lock (s_specialFolders) + { + if (!s_specialFolders.TryGetValue(folder, out path)) + { + path = GetSpecialFolder(folder); + s_specialFolders[folder] = path; + } + } + return path; + } + + private static string GetSpecialFolder(SpecialFolder folder) { switch (folder) { + // TODO: fix for tvOS (https://github.com/dotnet/runtime/issues/34007) + // The "normal" NSDocumentDirectory is a read-only directory on tvOS + // and that breaks a lot of assumptions in the runtime and the BCL + case SpecialFolder.Personal: case SpecialFolder.LocalApplicationData: return NSDocumentDirectory; @@ -48,23 +51,23 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio return Path.Combine(NSDocumentDirectory, ".config"); case SpecialFolder.Resources: - return NSLibraryDirectory; // older (8.2 and previous) would return String.Empty + return Interop.Sys.SearchPath(NSSearchPathDirectory.NSLibraryDirectory); // older (8.2 and previous) would return String.Empty case SpecialFolder.Desktop: case SpecialFolder.DesktopDirectory: - return Interop.Sys.SearchPath(NSDesktopDirectoryId); + return Interop.Sys.SearchPath(NSSearchPathDirectory.NSDesktopDirectory); case SpecialFolder.MyMusic: - return Interop.Sys.SearchPath(NSMusicDirectoryId); + return Interop.Sys.SearchPath(NSSearchPathDirectory.NSMusicDirectory); case SpecialFolder.MyPictures: - return Interop.Sys.SearchPath(NSPicturesDirectoryId); + return Interop.Sys.SearchPath(NSSearchPathDirectory.NSPicturesDirectory); case SpecialFolder.Templates: return Path.Combine(NSDocumentDirectory, "Templates"); case SpecialFolder.MyVideos: - return Interop.Sys.SearchPath(NSMoviesDirectoryId); + return Interop.Sys.SearchPath(NSSearchPathDirectory.NSMoviesDirectory); case SpecialFolder.CommonTemplates: return "/usr/share/templates"; @@ -73,16 +76,16 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio return Path.Combine(NSDocumentDirectory, ".fonts"); case SpecialFolder.Favorites: - return Path.Combine(NSLibraryDirectory, "Favorites"); + return Path.Combine(Interop.Sys.SearchPath(NSSearchPathDirectory.NSLibraryDirectory), "Favorites"); case SpecialFolder.ProgramFiles: - return Interop.Sys.SearchPath(NSApplicationDirectoryId); + return Interop.Sys.SearchPath(NSSearchPathDirectory.NSApplicationDirectory); case SpecialFolder.InternetCache: - return Interop.Sys.SearchPath(NSCachesDirectoryId); + return Interop.Sys.SearchPath(NSSearchPathDirectory.NSCachesDirectory); case SpecialFolder.UserProfile: - return NSUserDirectory; + return Interop.Sys.SearchPath(NSSearchPathDirectory.NSUserDirectory); case SpecialFolder.CommonApplicationData: return "/usr/share"; From f0f0455c7ebde972016116ba198ca2f9c3153703 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 25 Mar 2020 20:41:59 +0300 Subject: [PATCH 07/15] Fix build issue --- .../System.Private.CoreLib/src/System/Environment.iOS.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs index 1377dbfa211e93..4a74f344d7eac5 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -43,12 +43,12 @@ private static string GetSpecialFolder(SpecialFolder folder) case SpecialFolder.Personal: case SpecialFolder.LocalApplicationData: - return NSDocumentDirectory; + return Interop.Sys.SearchPath(NSSearchPathDirectory.NSDocumentDirectory); case SpecialFolder.ApplicationData: // note: at first glance that looked like a good place to return NSLibraryDirectory // but it would break isolated storage for existing applications - return Path.Combine(NSDocumentDirectory, ".config"); + return Path.Combine(Interop.Sys.SearchPath(NSSearchPathDirectory.NSDocumentDirectory), ".config"); case SpecialFolder.Resources: return Interop.Sys.SearchPath(NSSearchPathDirectory.NSLibraryDirectory); // older (8.2 and previous) would return String.Empty @@ -64,7 +64,7 @@ private static string GetSpecialFolder(SpecialFolder folder) return Interop.Sys.SearchPath(NSSearchPathDirectory.NSPicturesDirectory); case SpecialFolder.Templates: - return Path.Combine(NSDocumentDirectory, "Templates"); + return Path.Combine(Interop.Sys.SearchPath(NSSearchPathDirectory.NSDocumentDirectory), "Templates"); case SpecialFolder.MyVideos: return Interop.Sys.SearchPath(NSSearchPathDirectory.NSMoviesDirectory); @@ -73,7 +73,7 @@ private static string GetSpecialFolder(SpecialFolder folder) return "/usr/share/templates"; case SpecialFolder.Fonts: - return Path.Combine(NSDocumentDirectory, ".fonts"); + return Path.Combine(Interop.Sys.SearchPath(NSSearchPathDirectory.NSDocumentDirectory), ".fonts"); case SpecialFolder.Favorites: return Path.Combine(Interop.Sys.SearchPath(NSSearchPathDirectory.NSLibraryDirectory), "Favorites"); From d2ad41c8d10dd2dde712b84179fbeb27f6fc6091 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 25 Mar 2020 20:53:54 +0300 Subject: [PATCH 08/15] remove a whitespace --- .../System.Private.CoreLib/src/System/Environment.iOS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs index 4a74f344d7eac5..d271e23644098d 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -46,7 +46,7 @@ private static string GetSpecialFolder(SpecialFolder folder) return Interop.Sys.SearchPath(NSSearchPathDirectory.NSDocumentDirectory); case SpecialFolder.ApplicationData: - // note: at first glance that looked like a good place to return NSLibraryDirectory + // note: at first glance that looked like a good place to return NSLibraryDirectory // but it would break isolated storage for existing applications return Path.Combine(Interop.Sys.SearchPath(NSSearchPathDirectory.NSDocumentDirectory), ".config"); From af2041c499fbb7fe05909b5cdd88afe0512b50f3 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 25 Mar 2020 21:11:24 +0300 Subject: [PATCH 09/15] Fix UserProfile issue --- src/libraries/Native/Unix/System.Native/pal_searchpath.m | 3 ++- .../System.Private.CoreLib/src/System/Environment.iOS.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/Native/Unix/System.Native/pal_searchpath.m b/src/libraries/Native/Unix/System.Native/pal_searchpath.m index 6bd5a2f8b42b9e..a4f150da764526 100644 --- a/src/libraries/Native/Unix/System.Native/pal_searchpath.m +++ b/src/libraries/Native/Unix/System.Native/pal_searchpath.m @@ -9,5 +9,6 @@ { NSSearchPathDirectory spd = (NSSearchPathDirectory) folderId; NSURL* url = [[[NSFileManager defaultManager] URLsForDirectory:spd inDomains:NSUserDomainMask] lastObject]; - return strdup ([[url path] UTF8String]); + const char* path = [[url path] UTF8String]; + return path == NULL ? NULL : strdup (path); } diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs index d271e23644098d..7c5f968bb9788f 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -85,7 +85,7 @@ private static string GetSpecialFolder(SpecialFolder folder) return Interop.Sys.SearchPath(NSSearchPathDirectory.NSCachesDirectory); case SpecialFolder.UserProfile: - return Interop.Sys.SearchPath(NSSearchPathDirectory.NSUserDirectory); + return Environment.GetEnvironmentVariable("HOME"); case SpecialFolder.CommonApplicationData: return "/usr/share"; From b98398519873e5a0ebea2e1978dc286586234a43 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Thu, 26 Mar 2020 15:12:01 +0300 Subject: [PATCH 10/15] undo changes in GetEnvironmentVariableCore --- .../src/System/Environment.Unix.Mono.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs index b3601dc25dfe17..e4ab168b1e6b42 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs @@ -19,8 +19,6 @@ private static string GetEnvironmentVariableCore(string variable) { Debug.Assert(variable != null); - variable = TrimStringOnFirstZero(variable); - if (s_environment == null) { using (var h = RuntimeMarshal.MarshalString(variable)) @@ -29,19 +27,12 @@ private static string GetEnvironmentVariableCore(string variable) } } - string value = ""; + variable = TrimStringOnFirstZero(variable); lock (s_environment) { - if (!s_environment.TryGetValue(variable, out value)) - { - using (var h = RuntimeMarshal.MarshalString(variable)) - { - value = internalGetEnvironmentVariable_native(h.Value); - s_environment[variable] = value; - } - } + s_environment.TryGetValue(variable, out string value); + return value; } - return value; } private static unsafe void SetEnvironmentVariableCore(string variable, string? value) From 40718ffeb51a5c6057d7ce3b63f1ead8c4b53f05 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Thu, 26 Mar 2020 22:21:46 +0300 Subject: [PATCH 11/15] Update Environment.Unix.Mono.cs --- .../src/System/Environment.Unix.Mono.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs index e4ab168b1e6b42..464396e98cad63 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs @@ -21,20 +21,30 @@ private static string GetEnvironmentVariableCore(string variable) if (s_environment == null) { - using (var h = RuntimeMarshal.MarshalString(variable)) - { - return internalGetEnvironmentVariable_native(h.Value); - } + return InternalGetEnvironmentVariable(variable); } variable = TrimStringOnFirstZero(variable); lock (s_environment) { - s_environment.TryGetValue(variable, out string value); + string value; + if (!s_environment.TryGetValue(variable, out value)) + { + value = InternalGetEnvironmentVariable(variable); + s_environment[variable] = value; + } return value; } } + private static string InternalGetEnvironmentVariable(string name) + { + using (SafeStringMarshal handle = RuntimeMarshal.MarshalString(name)) + { + return internalGetEnvironmentVariable_native(handle.Value); + } + } + private static unsafe void SetEnvironmentVariableCore(string variable, string? value) { Debug.Assert(variable != null); From 8d265e9e7f6d782b67b95ed75e5dce4d6523c404 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 27 Mar 2020 02:44:22 +0300 Subject: [PATCH 12/15] Extract to InternalGetEnvironmentVariable --- .../src/System/Environment.Unix.Mono.cs | 12 ++---------- .../src/System/Environment.iOS.cs | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs index 464396e98cad63..a3a6cb523aabf2 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs @@ -27,12 +27,7 @@ private static string GetEnvironmentVariableCore(string variable) variable = TrimStringOnFirstZero(variable); lock (s_environment) { - string value; - if (!s_environment.TryGetValue(variable, out value)) - { - value = InternalGetEnvironmentVariable(variable); - s_environment[variable] = value; - } + s_environment.TryGetValue(variable, out string value); return value; } } @@ -107,10 +102,7 @@ private static Dictionary GetSystemEnvironmentVariables() { if (name != null) { - using (var h = RuntimeMarshal.MarshalString(name)) - { - results.Add(name, internalGetEnvironmentVariable_native(h.Value)); - } + results.Add(InternalGetEnvironmentVariable(name)); } } diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs index 7c5f968bb9788f..c260b941e66e4a 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -85,7 +85,7 @@ private static string GetSpecialFolder(SpecialFolder folder) return Interop.Sys.SearchPath(NSSearchPathDirectory.NSCachesDirectory); case SpecialFolder.UserProfile: - return Environment.GetEnvironmentVariable("HOME"); + return InternalGetEnvironmentVariable("HOME"); case SpecialFolder.CommonApplicationData: return "/usr/share"; From 14bbe9738dada4286fa97f1619d60c5d4f0a6d69 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Fri, 27 Mar 2020 13:14:13 +0300 Subject: [PATCH 13/15] Fix build issue --- .../System.Private.CoreLib/src/System/Environment.Unix.Mono.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs index a3a6cb523aabf2..b1ff2502d5a21d 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs @@ -102,7 +102,7 @@ private static Dictionary GetSystemEnvironmentVariables() { if (name != null) { - results.Add(InternalGetEnvironmentVariable(name)); + results.Add(name, InternalGetEnvironmentVariable(name)); } } From 49a6c0897c496801766b0a3376b3f904a34ca1a9 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 31 Mar 2020 12:40:42 +0300 Subject: [PATCH 14/15] Return emtpy string if underlying native function returns null --- .../System.Private.CoreLib/src/System/Environment.iOS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs index c260b941e66e4a..8b47a58101a00b 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -26,7 +26,7 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio { if (!s_specialFolders.TryGetValue(folder, out path)) { - path = GetSpecialFolder(folder); + path = GetSpecialFolder(folder) ?? string.Empty; s_specialFolders[folder] = path; } } From 72ccea7630af7898a4f9ee581f5ae323fb7bfe11 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 31 Mar 2020 14:51:37 +0300 Subject: [PATCH 15/15] Add nullability --- .../src/Interop/OSX/System.Native/Interop.SearchPath.cs | 3 ++- .../System.Private.CoreLib/src/System/Environment.iOS.cs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs b/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs index d5a6b4c5442d54..af3e6110c9bab1 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Native/Interop.SearchPath.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable using System; using System.Runtime.InteropServices; @@ -10,7 +11,7 @@ internal static partial class Interop internal static partial class Sys { [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_SearchPath")] - internal static extern string SearchPath(NSSearchPathDirectory folderId); + internal static extern string? SearchPath(NSSearchPathDirectory folderId); internal enum NSSearchPathDirectory { diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs index 8b47a58101a00b..3b7b55d5565349 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable using System.IO; using System.Threading; using System.Collections.Generic; @@ -12,7 +13,7 @@ namespace System { public static partial class Environment { - private static Dictionary s_specialFolders; + private static Dictionary? s_specialFolders; private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) { @@ -33,7 +34,7 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio return path; } - private static string GetSpecialFolder(SpecialFolder folder) + private static string? GetSpecialFolder(SpecialFolder folder) { switch (folder) {