我最近发现,我可以相对容易地从.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
映射到结构中就可以了。
因此,我以类似的方式快速映射了chmod
、chown
、lchown
、getgrnam
、getpwnam
、getuid
、symlink
。所有这些都在我的Ubuntu虚拟机上测试过,有效。
我甚至制作了自己的超级整洁的Chmod
实现,它的工作方式与接受u+wX
等相对权限的shellchmod
相同。并遍历文件系统。
这就是我失去一个晚上的地方。我需要原始权限,我读到它们可以通过stat
调用获得。可能出了什么问题?
首先,我使用Linux
手册文档制作了Stat
结构:https://man7.org/linux/man-pages/man2/stat.2.html
然后我制作了合适的extern
。
第一个惊喜:找不到入口点。
我挖了又挖又挖。直到我刚刚打开我的libc
二进制文件并搜索类似于stat
的东西。答对 了我找到了__xstat
点。就是这样,我更改了我的签名,我在文档中读到,除了指定ver
参数(应该设置为3
)之外,它的工作方式应该与stat
相同。
它没有。调用通过,但总是返回-1,不返回Stat
结构。
然后我找到了__xstat
的一些来源,它在那里检查ver
参数是否与内核版本匹配。奇怪!但我试着通过5
。因为这是我使用的当前内核版本。还有一些其他数字,如"3"one_answers"0"。运气不好。什么都不管用。我还测试了__xstat64
。同样的结果,我的意思是没有结果。
然后我发现.NET
开发人员在GitHub上讨论过,调用stat
非常棘手,因为每个内核的调用都不同。等等,什么
是的,我知道它在Mono.Posix.NETStandard 1.0.0
包中,我使用它并且它有效。(这就是他们推荐的。)
但由于我只是在学习平台调用";伏都教我不能就这样离开。为什么除了stat
调用之外的所有调用都能正常工作,为什么会出现异常?这是一件完全基本的事情。然后在";为什么";来了";如何&";。
他们在Mono
中做到了这一点。我在GitHub上挖掘了Mono
的源代码,发现它是少数几个实际上不是从libc
调用的函数之一,而是从它们自己的C程序集调用的:https://github.com/mono/mono/blob/main/support/sys-stat.c
很有趣,但我仍然很难理解它是如何工作的。
顺便说一句,在我的项目中添加Mono
将我编译的可执行Linux x64文件从200kb增加到1200kb。添加一个读取单个数字的功能!顺便说一句,它有一个许可证问题,包签名说MIT
,链接的源文件说MPL
。我的软件包要求用户接受这个奇怪的许可证。我的意思是,接受MIT
,尽管我不太确定它真的是MIT
还是MPL
。我自己的包使用MIT
。
那么,当从dotnet调用libc
时,(其他)捕获和gotcha是什么?有没有更简单的方法来调用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)
一样调用。返回我尝试的任何路径的-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;)我知道,这没什么,但我有一个简单函数的外部依赖。
根据我的研究,Stat
结构因内核而异。我怀疑如果我尝试了一些其他版本,其中一个最终会起作用,但它根本不能解决问题,因为它可能在目标机器上更新后停止工作。
我的猜测是,当结构在Linux中被要求并允许更改时,必须有一种通用的接口/兼容性机制,允许用户在不详细了解特定目标机器上的系统和库版本的情况下获得权限。
我原以为libc
就是这样的,但它似乎要么不完全是这样,要么在Linux的其他地方有一个更高级别的接口,我在这里不是指shell;)
我主要有Windows背景,我经常使用Windows p/invoke。我为Windows7编写的大部分代码仍然可以在Windows11上运行。旧的Win32
调用没有改变,除了一些非常特定于系统UI的调用。
所以,我发布最后一个答案是错误的。我发现libc
二进制文件包含类似__xstat的东西,我称之为
错了!顾名思义,它是一种私有函数,旨在作为实现细节,而不是API的一部分。
所以我找到了另一个正常名称的函数:statx
。它正是我所需要的,它在这里有很好的记录:
https://man7.org/linux/man-pages/man2/statx.2.html
以下是结构和价值观:https://code.woboq.org/qt5/include/bits/statx.h.htmlhttps://code.woboq.org/userspace/glibc/io/fcntl.h.html
TL;DR-它有效。
我发现作为dirfd
参数传递的-100(AT_FDCWD
)使相对路径相对于当前工作目录。
我还发现,将零作为标志进行传递是有效的(相当于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;
}
只是为了记录,fxstatat()和statx()结构在MAC上没有给我正确的文件权限值。