private static bool DetermineCanModifyStores() { // Check the directory permissions and whether the filesystem supports chmod. // The only real expected failure from this method is that at the very end // `stat.Mode == mode` will fail, because fuseblk (NTFS) returns success on chmod, // but is a no-op. uint userId = Interop.Sys.GetEUid(); string certStoresFeaturePath = PersistedFiles.GetUserFeatureDirectory("cryptography", "x509stores"); Directory.CreateDirectory(certStoresFeaturePath); // Check directory permissions: Interop.Sys.FileStatus dirStat; if (Interop.Sys.Stat(certStoresFeaturePath, out dirStat) != 0) { return(false); } if (dirStat.Uid != userId) { return(false); } if ((dirStat.Mode & (int)Interop.Sys.Permissions.S_IRWXU) != (int)Interop.Sys.Permissions.S_IRWXU) { return(false); } string probeFilename = Path.Combine(certStoresFeaturePath, $"{Guid.NewGuid().ToString("N")}.chmod"); try { using (FileStream stream = new FileStream(probeFilename, FileMode.Create)) { Interop.Sys.FileStatus stat; if (Interop.Sys.FStat(stream.SafeFileHandle, out stat) != 0) { return(false); } if (stat.Uid != userId) { return(false); } // The product code here has a lot of stuff it does. // This capabilities probe will just check that chmod works. int mode = stat.Mode; // Flip all of the O bits. mode ^= (int)Interop.Sys.Permissions.S_IRWXO; if (Interop.Sys.FChMod(stream.SafeFileHandle, mode) < 0) { return(false); } // Verify the chmod applied. if (Interop.Sys.FStat(stream.SafeFileHandle, out stat) != 0) { return(false); } // On fuseblk (NTFS) this will return false, because the fchmod // call returned success without being able to actually apply // mode-bits. return(stat.Mode == mode); } } finally { try { File.Delete(probeFilename); } catch { // Ignore any failure on delete. } } }
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 (IsMac) { switch (folder) { case SpecialFolder.ProgramFiles: return("/Applications"); case SpecialFolder.System: return("/System"); } } // All other paths are based on the XDG Base Directory Specification: // https://specifications.freedesktop.org/basedir-spec/latest/ string home; try { home = PersistedFiles.GetHomeDirectory(); } catch (Exception exc) { Debug.Fail($"Unable to get home directory: {exc}"); home = Path.GetTempPath(); } Debug.Assert(!string.IsNullOrEmpty(home), "Expected non-null or empty 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")); case SpecialFolder.MyMusic: return(IsMac ? Path.Combine(home, "Music") : ReadXdgDirectory(home, "XDG_MUSIC_DIR", "Music")); case SpecialFolder.MyPictures: return(IsMac ? Path.Combine(home, "Pictures") : ReadXdgDirectory(home, "XDG_PICTURES_DIR", "Pictures")); case SpecialFolder.Fonts: return(IsMac ? Path.Combine(home, "Library", "Fonts") : Path.Combine(home, ".fonts")); case SpecialFolder.Favorites: if (IsMac) { return(Path.Combine(home, "Library", "Favorites")); } break; case SpecialFolder.InternetCache: if (IsMac) { return(Path.Combine(home, "Library", "Caches")); } break; } // No known path for the SpecialFolder return(string.Empty); }
private static string?GetSpecialFolder(SpecialFolder folder) { 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 = "/"; } switch (folder) { case SpecialFolder.Personal: case SpecialFolder.LocalApplicationData: return(home); case SpecialFolder.ApplicationData: return(Path.Combine(home, ".config")); case SpecialFolder.Desktop: case SpecialFolder.DesktopDirectory: return(Path.Combine(home, "Desktop")); case SpecialFolder.MyMusic: return(Path.Combine(home, "Music")); case SpecialFolder.MyPictures: return(Path.Combine(home, "Pictures")); case SpecialFolder.Templates: return(Path.Combine(home, "Templates")); case SpecialFolder.MyVideos: return(Path.Combine(home, "Videos")); case SpecialFolder.CommonTemplates: return("/usr/share/templates"); case SpecialFolder.Fonts: return(Path.Combine(home, ".fonts")); case SpecialFolder.UserProfile: return(GetEnvironmentVariable("HOME")); case SpecialFolder.CommonApplicationData: return("/usr/share"); default: return(string.Empty); } }