How to get UID and GID on Linux using p/invoke?

0

Issue

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.

Solution

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

Answered By – Harry

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave A Reply

Your email address will not be published.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More