Skip to content
Advertisement

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

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:

JavaScript
JavaScript

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:

JavaScript

Called like Syscall.__xstat(5, path, out Stat stat). Returns -1 for any path I tried.

Of course

JavaScript

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.

Advertisement

Answer

So, I was wrong posting the last answer. I found out, the libc binary contained something like __xstat and I called it.

Wrong! As the name would suggest, it was a kind of a private function, something intended to be an implementation detail, not a part of the API.

So I found another function with a normal name: statx. It does exactly what I need, it is well(-ish) documented here:

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

Here’s the structure and values: https://code.woboq.org/qt5/include/bits/statx.h.html https://code.woboq.org/userspace/glibc/io/fcntl.h.html

TL;DR – it works.

I figured out that -100 (AT_FDCWD) passed as dirfd parameter makes relative paths relative to the current working directory.

I also figured out that passing zeros as flags works (as equivalent to AT_STATX_SYNC_AS_STAT), and the function returns what it should for a regular local filesystem.

So here’s the code:

JavaScript
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement