如何在没有 Mono.Posix 和 p/invoke 的情况下在 .NET 5 / .NET 6 中获得 Linux 文件权限?

How to get Linux file permissions in .NET 5 / .NET 6 without Mono.Posix with p/invoke?

我最近发现,我可以相对容易地从 .NET 进行 Linux 系统调用。

比如看我是否需要sudo我就这样签名:

internal class Syscall {

[DllImport("libc", SetLastError = true)]
internal static extern uint geteuid();

// ...
}
public static bool IsRoot => Syscall.geteuid() == 0;

整洁。比其他任何事情都容易和快得多,对吧? 这是最简单的系统调用,其他使用字符串和结构。

在对文档进行一些挖掘和自己测试之后,我发现默认编组器可以将 libc 中的字符串直接映射到 char* 中的 string,其他大部分东西只需要使用一些乐趣手动映射 IntPtr 到结构。

所以我以类似的方式快速映射了 chmodchownlchowngetgrnamgetpwnamgetuidsymlink。全部在我的 Ubuntu 虚拟机上测试,有效。

我什至制作了我自己的超级简洁的 Chmod 实现,其工作方式与 shell chmod 相同,后者接受 u+wX 等相对权限。并遍历文件系统。

这就是我失眠的地方。我需要原始权限,我读到它们可以通过 stat 调用获得。可能会出什么问题?

首先,我使用 Linux 手动文档制作了 Stat 结构: https://man7.org/linux/man-pages/man2/stat.2.html

然后我做了合适的extern.

第一个惊喜:找不到入口点。

我挖了又挖又挖了一些。直到我刚刚打开我的 libc 二进制文件并搜索类似于 stat 的东西。答对了!我找到了 __xstat 点。就是这样,我更改了我的签名,我在文档中读到除了指定 ver 参数(应该设置为 3) - 它应该 identical stat.

没有。调用通过,但总是return-1,不returnStat结构。

然后我找到了 __xstat 的一些来源,它检查 ver 参数是否与内核版本匹配。诡异的!但我尝试通过 5。因为它是我使用的当前内核版本。还有一些其他数字,如“3”和“0”。没有运气。什么都不管用。我也测试了__xstat64。同样的结果,我的意思是没有结果。

然后我发现 .NET 开发人员之间关于 GitHub 的讨论,调用 stat 非常棘手,因为它在每个内核上都不同。等等,什么!?

是的,我知道它在 Mono.Posix.NETStandard 1.0.0 包中,我使用它并且有效。 (这就是他们推荐的。)

但由于我只是在学习平台调用“voodoo”——我不能就这样离开它。为什么除了 stat 调用之外的一切都没有问题,为什么会有异常?这是一个完全基本的东西。然后在“为什么”之后是“如何?”。

他们在 Mono 中做到了。我在 GitHub 上挖掘 Mono 源代码发现,它是少数几个实际上不是从 libc 而是从他们自己的 C 程序集调用的函数之一: https://github.com/mono/mono/blob/main/support/sys-stat.c

很有趣,但我仍然很难理解它是如何工作的。

顺便说一句,将 Mono 添加到我的项目会使我编译的可执行 Linux x64 文件从 200kb 增加到 1200kb。字面上添加1个读取单个数字的功能!顺便说一句,它有一个许可证问题,包签名说 MIT,链接的源文件说 MPL。我的包裹要求用户接受这个奇怪的许可证。我的意思是,接受 MIT,虽然我不太确定它是真的 MIT 还是 MPL。我自己的包使用MIT.

那么 - 从 dotnet 调用 libc 时有哪些(其他)陷阱?有没有更简单的方法调用stat()?是否有其他途径从 .NET 获取权限?我发现 .NET 本身在内部就是这样做的。它获取可从 FileInfo 获得的文件权限。但是,属性被“翻译”为 Windows 结构,大部分信息在翻译中丢失。

我最后一次尝试:

[DllImport("libc", SetLastError = true)]
internal static extern int __xstat(int ver, string path, out Stat stat);

internal struct Stat {

    public ulong st_dev;        // device
    public ulong st_ino;        // inode
    public uint st_mode;        // protection
    public ulong st_nlink;      // number of hard links
    public uint st_uid;         // user ID of owner
    public uint st_gid;         // group ID of owner
    public ulong st_rdev;       // device type (if inode device)
    public long st_size;        // total size, in bytes
    public long st_blksize;     // blocksize for filesystem I/O
    public long st_blocks;      // number of blocks allocated
    public long st_atime;       // time of last access
    public long st_mtime;       // time of last modification
    public long st_ctime;       // time of last status change
    public long st_atime_nsec;  // Timespec.tv_nsec partner to st_atime
    public long st_mtime_nsec;  // Timespec.tv_nsec partner to st_mtime
    public long st_ctime_nsec;  // Timespec.tv_nsec partner to st_ctime

}

被称为 Syscall.__xstat(5, path, out Stat stat)。 Returns -1 对于我尝试过的任何路径。

当然

public static Permissions GetPermissions(string path) {
    if (Mono.Unix.Native.Syscall.stat(path, out var stat) != 0) throw new InvalidOperationException($"Stat call failed for {path}");
    return new Permissions((uint)stat.st_mode);
}

有效。它只需要多 1MB ;) 我知道,这没什么,但我有外部依赖只是为了 1 个简单的功能。

根据我的研究 - Stat 结构因内核而异。我怀疑如果我尝试了一些其他版本,一个最终会工作,但它根本没有解决问题,因为它可以在目标机器上更新后停止工作。

我的猜测是,当 Linux 中需要并允许更改结构时,必须有一种通用接口/兼容性机制允许用户在不了解系统和库版本的情况下获得权限具体目标机器。

我以为 libc 就是那样的东西,但它似乎不完全是,或者在 Linux 的其他地方有更高级别的接口,我不是这个意思shell 这里 ;)

我主要是Windows背景,我经常用Windowsp/invoke。我为 Windows 7 编写的大部分代码仍然适用于 Windows 11。旧的 Win32 调用没有改变,除了一些非常系统的 UI 特定调用。

所以,我发布最后一个答案是错误的。我发现,libc 二进制文件包含类似 __xstat 的内容,我调用了它。

错了!顾名思义,它是一种私有函数,旨在成为实现细节,而不是 API.

的一部分

所以我又找到了一个正常名称的函数:statx。它完全符合我的需要,它在此处有很好的记录(-ish):

https://man7.org/linux/man-pages/man2/statx.2.html

结构和值如下: https://code.woboq.org/qt5/include/bits/statx.h.html https://code.woboq.org/userspace/glibc/io/fcntl.h.html

TL;DR - 有效。

我发现作为 dirfd 参数传递的 -100 (AT_FDCWD) 使相对路径相对于当前工作目录。

我还发现将零作为标志传递是有效的(相当于 AT_STATX_SYNC_AS_STAT),而函数 returns 对于常规本地文件系统来说应该是什么。

代码如下:

[DllImport(LIBC, SetLastError = true)]
internal static extern int statx(int dirfd, string path, int flags, uint mask, out Statx data);

/// <summary>
/// POSIX statx data structure.
/// </summary>
internal struct Statx {

    /// <summary>
    /// Mask of bits indicating filled fields.
    /// </summary>
    internal uint Mask;
    /// <summary>
    /// Block size for filesystem I/O.
    /// </summary>
    internal uint BlockSize;
    /// <summary>
    /// Extra file attribute indicators
    /// </summary>
    internal ulong Attributes;
    /// <summary>
    /// Number of hard links.
    /// </summary>
    internal uint HardLinks;
    /// <summary>
    /// User ID of owner.
    /// </summary>
    internal uint Uid;
    /// <summary>
    /// Group ID of owner.
    /// </summary>
    internal uint Gid;
    /// <summary>
    /// File type and mode.
    /// </summary>
    internal ushort Mode;
    private ushort Padding01;
    /// <summary>
    /// Inode number.
    /// </summary>
    internal ulong Inode;
    /// <summary>
    /// Total size in bytes.
    /// </summary>
    internal ulong Size;
    /// <summary>
    /// Number of 512B blocks allocated.
    /// </summary>
    internal ulong Blocks;
    /// <summary>
    /// Mask to show what's supported in <see cref="Attributes"/>.
    /// </summary>
    internal ulong AttributesMask;
    /// <summary>
    /// Last access time.
    /// </summary>
    internal StatxTimeStamp AccessTime;
    /// <summary>
    /// Creation time.
    /// </summary>
    internal StatxTimeStamp CreationTime;
    /// <summary>
    /// Last status change time.
    /// </summary>
    internal StatxTimeStamp StatusChangeTime;
    /// <summary>
    /// Last modification time.
    /// </summary>
    internal StatxTimeStamp LastModificationTime;
    internal uint RDevIdMajor;
    internal uint RDevIdMinor;
    internal uint DevIdMajor;
    internal uint DevIdMinor;
    internal ulong MountId;
    private ulong Padding02;
    private ulong Padding03;
    private ulong Padding04;
    private ulong Padding05;
    private ulong Padding06;
    private ulong Padding07;
    private ulong Padding08;
    private ulong Padding09;
    private ulong Padding10;
    private ulong Padding11;
    private ulong Padding12;
    private ulong Padding13;
    private ulong Padding14;
    private ulong Padding15;
}

/// <summary>
/// Time stamp structure used by statx.
/// </summary>
public struct StatxTimeStamp {

    /// <summary>
    /// Seconds since the Epoch (UNIX time).
    /// </summary>
    public long Seconds;

    /// <summary>
    /// Nanoseconds since <see cref="Seconds"/>.
    /// </summary>
    public uint Nanoseconds;

}

仅作记录,fxstatat() 和 statx() 结构在 MAC 上没有给我正确的文件权限值。