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

Har*_*rry 7 .net c# linux pinvoke libc

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

例如,要查看我是否需要,sudo我只需签名如下:

internal class Syscall {

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

// ...
}
Run Code Online (Sandbox Code Playgroud)
public static bool IsRoot => Syscall.geteuid() == 0;
Run Code Online (Sandbox Code Playgroud)

整洁的。比其他一切都更容易和更快,对吗?这是最简单的系统调用,其他使用字符串和结构。

经过一番深入研究文档并自行测试后,我发现默认编组器libc可以将字符串 from 直接映射到stringfrom char*,大多数其他内容只需要使用一些手动映射IntPtr到结构的乐趣。

所以以类似的方式我快速绘制chmodchown,,,,,,,, 。所有这些都在我的 Ubuntu VM 上进行了测试,有效。lchowngetgrnamgetpwnamgetuidsymlink

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

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

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

然后我做了相应的extern

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

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

事实并非如此。调用通过,但总是返回-1,不返回Stat结构体。

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

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

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

但由于我刚刚学习平台调用“voodoo” - 我不能就这样离开它。为什么除了stat调用之外的所有操作都没有任何问题,为什么会出现异常?这是一个完全基本的事情。在“为什么”之后是“如何?”。

他们做到了Mono。我深入MonoGitHub 上的源代码发现,它是少数几个实际上不是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结构,并且大部分信息在翻译中丢失。

我最后一次尝试:

public static bool IsRoot => Syscall.geteuid() == 0;
Run Code Online (Sandbox Code Playgroud)

称为像Syscall.__xstat(5, path, out Stat stat)。返回-1我尝试过的任何路径。

当然

[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

}
Run Code Online (Sandbox Code Playgroud)

作品。它只需要 1MB 多;)我知道,这没什么,但我只是为了 1 个简单的函数而有外部依赖。

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

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

我以为libc只是类似的东西,但似乎要么不完全是这样,要么 Linux 中的其他地方有更高级别的接口,我在这里指的不是 shell ;)

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

Har*_*rry 7

所以,我错误地发布了最后一个答案。我发现,libc二进制文件包含类似 __xstat 的东西,我称之为它。

错误的!顾名思义,它是一种私有函数,旨在成为实现细节,而不是 API 的一部分。

所以我找到了另一个具有正常名称的函数:statx。它完全符合我的需要,这里有很好的记录:

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 - 它有效。

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

我还发现将零作为标志传递是有效的(相当于AT_STATX_SYNC_AS_STAT),并且该函数返回常规本地文件系统应有的内容。

所以这是代码:

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

}
Run Code Online (Sandbox Code Playgroud)