I recently found, that I can make Linux system calls from .NET relatively easy.
For example, to see if I need sudo
I just make a signature like this:
internal class Syscall {
[DllImport("libc", SetLastError = true)]
internal static extern uint geteuid();
// ...
}
public static bool IsRoot => Syscall.geteuid() == 0;
Neat. So much easier and faster than everything else, right? This is the simplest possible system call, other use strings and structures.
After some digging in the documentation and testing for myself, I found that strings from libc
can be mapped directly to string
from char*
by default marshaller, most of the other stuff require just using some fun with manually mapping IntPtr
to structures.
So in similar way I quickly mapped chmod
, chown
, lchown
, getgrnam
, getpwnam
, getuid
, symlink
. All tested on my Ubuntu VM, works.
I even made my own super neat Chmod
implementation that works identically to shell chmod
that accepts relative permissions like u+wX
. And walks through the filesystem.
And that's where I lost a night. I needed original permissions, and I read they can be obtained with stat
call. What could possibly go wrong?
First I made Stat
structure using Linux
manual documentation:
https://man7.org/linux/man-pages/man2/stat.2.html
Then I made the appropriate extern
.
First surprise: the entry point not found.
I digged, and digged and digged some more. Until I just opened my libc
binary and searched for something similar to stat
. Bingo! I found __xstat
point. That was it, I changed my signature, I read in documentation that beside specifying ver
parameter (that should be set to 3
) - it should work identical to stat
.
It didn't. The call passes, but it always return -1, does not return Stat
structure.
Then I found some sources of the __xstat
where it checks if the ver
parameter matches the kernel version. WEIRD! But I tried passing 5
. Because it's the current kernel version I use. Also some other numbers like '3' and '0'. No luck. Nothing works. I also tested __xstat64
. Same result, I mean no result.
Then I found a discussion on GitHub between .NET
developers, that calling stat
is super tricky, because it's different on every kernel. Wait, WHAT!?
Yes, I know it is in Mono.Posix.NETStandard 1.0.0
package, I use it and it works. (And that's what the guys recommended.)
But since I'm just learning platform invoke "voodoo" - I just cannot leave it like that. Why everything but the stat
call works without any problem, why is there the exception? It is a completely BASIC thing. Then after "why" comes "HOW?".
They did it in Mono
. I digged in Mono
sources on GitHub to find, that it's one of the few function not actually called from libc
but from their own assembly in C:
https://github.com/mono/mono/blob/main/support/sys-stat.c
Interesting, but I still struggle to understand how it works.
BTW, adding Mono
to my project increased my compiled executable Linux x64 file from 200kb to 1200kb. To add literally 1 function of reading a single number! BTW, it has a license issue, package signature says MIT
, source file linked says MPL
. And my package is asking users to accept this curious license. I mean, to accept MIT
, though I'm not quite sure whether it's really MIT
or MPL
. My own package uses MIT
.
So - what are the (other) catches and gotchas when calling libc
from dotnet? Is there a simpler way to call stat()
? Is there an alternative route to get the permissions from .NET
? I figured out the .NET
itself DOES that internally. It gets the file permissions obtainable from FileInfo
. However, the attributed are "translated" to Windows structure, and most of the information is lost in translation.
My last try:
[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
}
Called like Syscall.__xstat(5, path, out Stat stat)
. Returns -1
for any path I tried.
Of course
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);
}
works. It only takes 1MB more ;) I know, it's nothing, but I have external dependency just for 1 simple function.
From what I researched - the Stat
structure differs from kernel to kernel. I suspect if I tried some other versions, one would finally work, but it doesn't solve the problem at all, because it can stop working after an update on target machine.
My guess is when the structure is required and allowed to change in Linux, there must be a kind of common interface / compatibility mechanism allowing users to get permissions without detailed knowledge about system and library versions on a specific target machine.
I thought libc
was just something like that, but it seems like either it's not exactly it, or there is a bit higher level interface somewhere else in Linux and I don't mean shell here ;)
I have mainly Windows background, I used Windows p/invoke a lot. Most of the code I wrote for Windows 7 still works on Windows 11. Old Win32
calls haven't changed, except some very system UI specific ones.
kernel32.dll
ABI fixed, its so much easier to do Interop instead of doing native processor syscalls. POSIX defines syscalls, but doesn't define ABI, so, WTF. Then you have libc wich defines the ABI, fun thing. – Belia