3

I need some simple file operations on a Linux machine, for a service installer. The code is .NET 5.0.

My current version uses Process.Start() to execute shell commands to change the owner of files and directories and set permissions.

This is quite slow (I use asynchronous methods), especially compared to Windows equivalents.

I see libc accessible to call from .NET has methods chmod and chown, however it wants uid and gid parameters. My application doesn't know the ids, at least without using shell for it.

So far I got something like this:

const string LIBC = "libc";

[DllImport(LIBC, SetLastError = true)]
private static extern int chmod(string path, uint mode);

[DllImport(LIBC, SetLastError = true)]
private static extern int chown(string path, int owner, int group);

So... how to get those 2 ints required?

UPDATE

Why anyone see this question (especially considering its title) as duplicate of question about similar, yet different things.

I know how to change owner and permissions of Linux files in many ways. The easiest way is to use Linux shell. The quickest and easiest way is to use Mono.Posix.NETStandard library, that call libc internally.

My specific question is HOW IT IS MADE? HOW DOES IT WORK?

To be even more specific: Here's the Linux manual page for getpwnam(): https://man7.org/linux/man-pages/man3/getpwnam.3.html

How to just call it from C# using p/invoke? I see in many examples, that when they replace char* with string it somehow magically works. I created a struct like this:

public struct PASSWD {
    public string pw_name;       /* username */
    public string pw_passwd;     /* user password */
    public int pw_uid;        /* user ID */
    public int pw_gid;        /* group ID */
    public string pw_gecos;      /* user information */
    public string pw_dir;        /* home directory */
    public string pw_shell;      /* shell program */
};

...and tried to use it as out parameter for the signature. I get no error, but it just doesn't work. The struct I get is empty.

So again, we are using Platform Invoke, in C#, we are calling libc and we want to get results from a structure. As far as I googled - it's not google-able. There is only Mono source code, that uses external module that implements what I need. I suspect they made it for performance reasons, also - using some special tools, because in comments stands that the code is generated.

My question is again, how, using Linux manual page definition create appropriate method signature for C# to be able to extract those 2 integers from getpwnam().

I also was curious if something like that might already exist in .NET itself, but I guess it doesn't.

Harry
  • 4,524
  • 4
  • 42
  • 81
  • 1
    Same library has getpwnam and getgrnam functions which allow you to obtain user and group ids given their name. – Evk Oct 28 '21 at 08:34
  • I know, it's `Mono`, but I don't want to add `Mono` dependency. I checked the source code, but this part is quite convoluted. Especially `getpwnam` that depends on other native libraries besides `libc`. – Harry Oct 28 '21 at 08:49
  • 1
    Does this answer your question? [File permissions on Linux/Unix with .NET Core](https://stackoverflow.com/questions/45132081/file-permissions-on-linux-unix-with-net-core) – Fildor Oct 28 '21 at 09:02
  • 1
    Not sure what you mean, those functions are part of standard LibC library, and even though implementation might depend on other native libraries - that should not be your concern. You just pinvoke and call it. Mono is also not related to this. You _can_ use `Mono.Posix' nuget package to get all this bindings for free (it's not related to Mono runtime in any way by the way), or you can reimplement this yourself if you want to. – Evk Oct 28 '21 at 09:04
  • I wanted to just implement chown, I know it's in Mono.Posix.NETStandard. Well, I'll probably use it, but I was just curious how it's made. – Harry Oct 28 '21 at 09:09

1 Answers1

1

So, I got rusty with p/invoke. My issue was I forgot, that when native function returns a pointer to a structure, there is no automatic conversion, I have to leave pointer in signature, so:

[DllImport(LIBC, SetLastError = true)]
internal static extern IntPtr getgrnam(string name);

[DllImport(LIBC, SetLastError = true)]
internal static extern IntPtr getpwnam(string name);

internal struct Group {

    public string Name;
    public string Password;
    public uint Gid;
    public IntPtr Members;

}

internal struct Passwd {

    public string Name;
    public string Password;
    public uint Uid;
    public uint Gid;
    public string GECOS;
    public string Directory;
    public string Shell;

}

Lets create fully managed .NET style types:

public sealed class GroupInfo {

    public string Name { get; }
    public uint Id { get; }
    public string[] Members { get; }

    internal GroupInfo(Syscall.Group group) {
        Name = group.Name;
        Id = group.Gid;
        Members = GetMembers(group.Members).ToArray();
    }

    private static IEnumerable<string> GetMembers(IntPtr members) {
        IntPtr p;
        for (int i = 0; (p = Marshal.ReadIntPtr(members, i * IntPtr.Size)) != IntPtr.Zero; i++)
            yield return Marshal.PtrToStringAnsi(p)!;
    }

}

public class UserInfo {

    public string Name { get; }
    public uint Uid { get; }
    public uint Gid { get; }
    public string? Directory { get; }
    public string? Shell { get; }

    internal UserInfo(Syscall.Passwd passwd) {
        Name = passwd.Name;
        Uid = passwd.Uid;
        Gid = passwd.Gid;
        Directory = passwd.Directory;
        Shell = passwd.Shell;
    }

}

And it can be used like this:

public static UserInfo? GetUserInfo(string name) {
    var result = Syscall.getpwnam(name);
    if (result != IntPtr.Zero) return new UserInfo(Marshal.PtrToStructure<Syscall.Passwd>(result));
    return null;
}

public static GroupInfo? GetGroupInfo(string name) {
    var result = Syscall.getgrnam(name);
    if (result != IntPtr.Zero) return new GroupInfo(Marshal.PtrToStructure<Syscall.Group>(result));
    return null;
}
Harry
  • 4,524
  • 4
  • 42
  • 81
  • If you just needed UID, there's a simple way https://stackoverflow.com/questions/69777374/how-to-get-linux-file-permissions-in-net-5-net-6-without-mono-posix-with-p-i – Luiz Felipe Jul 01 '22 at 19:32