首页 > 系统相关 >dotnet 测试在 Linux 系统上的 Environment.GetFolderPath 行为

dotnet 测试在 Linux 系统上的 Environment.GetFolderPath 行为

时间:2024-09-12 20:04:19浏览次数:1  
标签:case XDG return string GetFolderPath Environment Linux SpecialFolder home

由于 Environment.GetFolderPath 可以传入的参数里面,有许多都是 Windows 系统特有的,在 Linux 上不存在的,也没有映射对应的文件夹。本文将在 WSL Debian 和 UOS 系统上测试 Environment.GetFolderPath 行为

测试使用 Environment.SpecialFolder 的各个枚举获取路径的代码如下

            foreach (var name in Enum.GetNames<Environment.SpecialFolder>())
            {
                Console.WriteLine($"{name} = {Environment.GetFolderPath(Enum.Parse<Environment.SpecialFolder>(name))}");
            }

在 WSL Debian 的运行结果如下

Desktop =
Programs =
MyDocuments =
Personal =
Favorites =
Startup =
Recent =
SendTo =
StartMenu =
MyMusic =
MyVideos =
DesktopDirectory =
MyComputer =
NetworkShortcuts =
Fonts =
Templates =
CommonStartMenu =
CommonPrograms =
CommonStartup =
CommonDesktopDirectory =
ApplicationData = /home/user/.config
PrinterShortcuts =
LocalApplicationData = /home/user/.local/share
InternetCache =
Cookies =
History =
CommonApplicationData = /usr/share
Windows =
System =
ProgramFiles =
MyPictures =
UserProfile = /home/user
SystemX86 =
ProgramFilesX86 =
CommonProgramFiles =
CommonProgramFilesX86 =
CommonTemplates =
CommonDocuments =
CommonAdminTools =
AdminTools =
CommonMusic =
CommonPictures =
CommonVideos =
Resources =
LocalizedResources =
CommonOemLinks =
CDBurning =

在 UOS 系统的运行结果如下

Desktop = /home/lin/Desktop
Programs = 
MyDocuments = /home/lin/Documents
Personal = /home/lin/Documents
Favorites = 
Startup = 
Recent = 
SendTo = 
StartMenu = 
MyMusic = /home/lin/Music
MyVideos = /home/lin/Videos
DesktopDirectory = /home/lin/Desktop
MyComputer = 
NetworkShortcuts = 
Fonts = 
Templates = /home/lin/.Templates
CommonStartMenu = 
CommonPrograms = 
CommonStartup = 
CommonDesktopDirectory = 
ApplicationData = /home/lin/.config
PrinterShortcuts = 
LocalApplicationData = /home/lin/.local/share
InternetCache = 
Cookies = 
History = 
CommonApplicationData = /usr/share
Windows = 
System = 
ProgramFiles = 
MyPictures = /home/lin/Pictures
UserProfile = /home/lin
SystemX86 = 
ProgramFilesX86 = 
CommonProgramFiles = 
CommonProgramFilesX86 = 
CommonTemplates = 
CommonDocuments = 
CommonAdminTools = 
AdminTools = 
CommonMusic = 
CommonPictures = 
CommonVideos = 
Resources = 
LocalizedResources = 
CommonOemLinks = 
CDBurning = 

可以看到 UOS 上有更多的属性是存在值的,存在一些行为差异

另外,根据 UOS 官方文档 的如下说明:

软件包不允许直接向\(HOME目录直接写入文件,后期系统将会使用沙箱技术重新定向\)HOME,任何依赖该特性的行为都可能失效。
应用使用如下环境变量指示的目录写入应用数据和配置:

    $XDG_DATA_HOME
    $XDG_CONFIG_HOME
    $XDG_CACHE_HOME

对于appid为org.deepin.browser的应用,其写入目录为:

    $XDG_DATA_HOME/org.deepin.browser
    $XDG_CONFIG_HOME/org.deepin.browser
    $XDG_CACHE_HOME/org.deepin.browser

我同时也测试了以上的 XDG_DATA_HOMEXDG_CONFIG_HOMEXDG_CACHE_HOME 环境变量的内容,在我的设备上的输出如下

XDG_DATA_HOME = /home/lin/.local/share
XDG_CONFIG_HOME = /home/lin/.config
XDG_CACHE_HOME = /home/lin/.cache

可以看到 XDG_DATA_HOMELocalApplicationData 是对应的值。而 XDG_CONFIG_HOMEApplicationData 是对应的值

本文以上代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 61a7e77b8b86e17ccf2b5d1a9d0460d09cc95036

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 61a7e77b8b86e17ccf2b5d1a9d0460d09cc95036

获取代码之后,进入 NurbeakairweWaharbaner 文件夹

这里的 XDG 是 X Desktop Group 的缩写,更多 XDG 知识请参阅:

在 dotnet 的 runtime 底层的 Environment.GetFolderPath 实现如下

    public static partial class Environment
    {
        private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option)
        {
            // Get the path for the SpecialFolder
            string path = GetFolderPathCoreWithoutValidation(folder) ?? string.Empty;
            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;
            }

            Debug.Assert(option == SpecialFolderOption.Create);

            Directory.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:
                    return home;

                case SpecialFolder.Templates:
                    return ReadXdgDirectory(home, "XDG_TEMPLATES_DIR", "Templates");
                // TODO: Consider merging the OSX path with the rest of the Apple systems here:
                // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs
#if TARGET_OSX
                case SpecialFolder.Desktop:
                case SpecialFolder.DesktopDirectory:
                    return Interop.Sys.SearchPath(NSSearchPathDirectory.NSDesktopDirectory);
                case SpecialFolder.ApplicationData:
                case SpecialFolder.LocalApplicationData:
                    return Interop.Sys.SearchPath(NSSearchPathDirectory.NSApplicationSupportDirectory);
                case SpecialFolder.MyDocuments: // same value as Personal
                    return Interop.Sys.SearchPath(NSSearchPathDirectory.NSDocumentDirectory);
                case SpecialFolder.MyMusic:
                    return Interop.Sys.SearchPath(NSSearchPathDirectory.NSMusicDirectory);
                case SpecialFolder.MyVideos:
                    return Interop.Sys.SearchPath(NSSearchPathDirectory.NSMoviesDirectory);
                case SpecialFolder.MyPictures:
                    return Interop.Sys.SearchPath(NSSearchPathDirectory.NSPicturesDirectory);
                case SpecialFolder.Fonts:
                    return Path.Combine(home, "Library", "Fonts");
                case SpecialFolder.Favorites:
                    return Path.Combine(home, "Library", "Favorites");
                case SpecialFolder.InternetCache:
                    return Interop.Sys.SearchPath(NSSearchPathDirectory.NSCachesDirectory);
#else
                case SpecialFolder.Desktop:
                case SpecialFolder.DesktopDirectory:
                    return ReadXdgDirectory(home, "XDG_DESKTOP_DIR", "Desktop");
                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 (data is null || !data.StartsWith('/'))
                    {
                        data = Path.Combine(home, ".local", "share");
                    }
                    return data;
                case SpecialFolder.MyDocuments: // same value as Personal
                    return ReadXdgDirectory(home, "XDG_DOCUMENTS_DIR", "Documents");
                case SpecialFolder.MyMusic:
                    return ReadXdgDirectory(home, "XDG_MUSIC_DIR", "Music");
                case SpecialFolder.MyVideos:
                    return ReadXdgDirectory(home, "XDG_VIDEOS_DIR", "Videos");
                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;
        }

        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 (config is null || !config.StartsWith('/'))
            {
                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 (envPath is not null && envPath.StartsWith('/'))
            {
                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++;
        }
    }

注: 在 dotnet 6.0.26 和 dotnet 7 版本,获取的 MyDocuments 的值将会和 UserProfile 相同,都是指向 $HOME 环境变量的路径,如以下代码

                case SpecialFolder.UserProfile:
                case SpecialFolder.MyDocuments: // same value as Personal
                     return home;

更详细的代码如下

        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;
        }

以上代码地址: https://github.com/dotnet/runtime/blob/v6.0.26/src/libraries/System.Private.CoreLib/src/System/Environment.GetFolderPathCore.Unix.cs

在 dotnet 8 的 Fix some incorrect SpecialFolder entries for Unix by Miepee · Pull Request #68610 · dotnet/runtime 的更改里面,优化了各路径的读取方法,从而更改了 MyDocuments 的返回值路径

详细文档请看 .NET 8 中断性变更:Unix 上的 GetFolderPath 行为 - .NET Microsoft Learn

以上不仅变更了在 Linux 上的行为也变更了在安卓 macOS 等的行为

标签:case,XDG,return,string,GetFolderPath,Environment,Linux,SpecialFolder,home
From: https://www.cnblogs.com/lindexi/p/17970814

相关文章

  • Linux定时任务
    1、什么是定时任务在指定的时间周期运行指定的任务。只要是一个开发语言,都有定时任务。2、Linux中的定时任务(crontab)1)定时任务如何开启和关闭cron服务的相关命令:启动定时任务的服务:systemctlstartcrond关闭定时任务的服务:systemctlstopcrond重启定时任务的服......
  • Ubantu和Centos7一键shell更换镜像源与Linux系统Python3环境安装
    目录前言1.一键更换源1.1创建文件1.2向环境赋予可执行的权限 2.Linux系统配置Python3环境2.1查看当前python环境2.2更换源 2.3安装所需的依赖2.4.下载python环境文件2.5.解压文件2.6进行编译2.7 开始安装2.8 设置软连接2.9测试是否安装成功前言......
  • 【Linux】Linux介绍及CentOS虚拟机环境搭建
    内容大纲介绍文章目录内容大纲介绍1.计算机简介2.Linux系统介绍3.虚拟化软件介绍4.Linux环境搭建5.扩展_虚拟机的快照6.Linux的目录介绍1.计算机简介概述全称叫电子计算机,英文名叫Computer,俗称叫:电脑,简称叫:PC,就是有硬件和软件组成的电子设备.组......
  • Linux系统搭建性能测试监控体系
    一.安装Grafana1.Grafana介绍:Grafana是一个开源的监控和可视化工具,用于显示和跟踪各种指标,数据和日志,支持多种源,包括influxDB、prometheus、mangoDB,Redis,Mysql,PostgreSQL等。它提供多种图标类型,饼图,支持设置预警机制,当监控指标超出预定阈值时,可以通过email、webhook等方式......
  • 【linux】一种基于虚拟串口的方式使两个应用通讯
    在Linux系统中,两个应用之间通过串口(SerialPort)进行通信是一种常见的通信方式,特别是在嵌入式系统、工业自动化等领域。串口通信通常涉及到对串口设备的配置和读写操作。以下是一个基本的步骤指南,说明如何在Linux中设置两个应用以通过串口进行通信:1.确认串口设备首先,你需要确......
  • ArchLinux安装简明指南
    本指南将介绍如何不借用archinstall脚本来安装纯命令行界面的ArchLinux到64位系统上。(UEFI+GPT)零、安装前准备首先当然是先进入liveiso环境。增大字号:setfontter-132n测试网络连接是否顺畅:pingarchlinux.org-c5验证系统是否在UEFI模式下启动ls/sys/firmware/efi/......
  • 搭建基于Grafana+Prometheus+Node_exporter的性能监控与分析平台(Linux版)
    搭建基于Grafana+Prometheus+Node_exporter的性能监控与分析平台(Linux版)在现代IT环境中,系统监控与分析是确保应用稳定性和高效性的关键。Prometheus与Grafana的结合,为我们提供了一个强大而灵活的监控解决方案,能够实时地收集、处理并展示系统性能指标。本文将详细介绍如何在Linux......
  • linux管理命令-7
    RPM软件包简介RPM包文件名特征软件名-版本信息.操作系统.硬件架构.rpmfirefox-91.9.0-1.el8_5.x86_64.rpm软件名:firefox软件包名:firefox-91.9.0-1.el8_5.x86_64.rpm[root@nb~]# mount  /dev/cdrom    /mntmount:/dev/sr0写保护,将以只读方式挂载[root@nb~]# ls  /m......
  • linux管理命令-7
    用户账号简介作用:1.可以登陆操作系统2.不同的用户具备不同的权限唯一标识:UID(编号从0开始的编号,默认最大60000)管理员root的UID:永远为0普通用户的UID:默认从1000开始组账号简介作用:方便管理用户唯一标识:GID(编号从0开始的编号,默认最大60000)原则:Linux一个用户必须至少属于一个组组......
  • linux字体安装
    fc-cache命令安装fc-cache是字体配置的一部分,它是fontconfig包的一部分。fc-cache命令用于建立字体信息的缓存,这有助于提高系统识别已安装字体的速度。如果你需要安装fc-cache命令,通常意味着你需要安装fontconfig包。具体安装方法取决于你的操作系统。yuminstallfontconfig涮新......