首页 > 解决方案 > 如何在 Linux 上使用 p/invoke 获取 UID 和 GID?

问题描述

对于服务安装程序,我需要在 Linux 机器上进行一些简单的文件操作。代码是 .NET 5.0。

我当前的版本用于Process.Start()执行 shell 命令来更改文件和目录的所有者并设置权限。

这很慢(我使用异步方法),尤其是与 Windows 等效项相比。

我看到libc可以从 .NET 调用具有方法chmodchown,但是它想要uidgid参数。我的应用程序不知道 id,至少不使用 shell。

到目前为止,我得到了这样的东西:

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

那么...如何获得所需的这 2 个整数?

更新

为什么有人认为这个问题(特别是考虑到它的标题)是关于类似但不同事物的问题的重复。

我知道如何以多种方式更改 Linux 文件的所有者和权限。最简单的方法是使用 Linux shell。最快和最简单的方法是使用内部Mono.Posix.NETStandard调用的库。libc

我的具体问题是它是如何制造的?它是如何工作的?

更具体地说:这是 Linux 手册页getpwnam()https ://man7.org/linux/man-pages/man3/getpwnam.3.html

如何使用 p/invoke 从 C# 调用它?我在很多例子中看到,当他们char*用它替换时,string它会神奇地起作用。我创建了一个这样的结构:

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

...并尝试将其用作out签名的参数。我没有收到任何错误,但它不起作用。我得到的结构是空的。

因此,我们再次使用 Platform Invoke,在 C# 中,我们正在调用libc并且我们希望从结构中获取结果。据我用谷歌搜索 - 它不能用谷歌搜索。只有Mono源代码,它使用实现我需要的外部模块。我怀疑他们这样做是出于性能原因,也是 - 使用一些特殊工具,因为在注释中表示代码是生成的。

我的问题再次是,如何使用 Linux 手册页定义为 C# 创建适当的方法签名,以便能够从getpwnam().

我也很好奇 .NET 本身中是否已经存在类似的东西,但我想它不存在。

标签: c#.netlinuxpinvoke

解决方案


所以,我对 p/invoke 生疏了。我的问题是我忘记了,当本机函数返回指向结构的指针时,没有自动转换,我必须将指针留在签名中,所以:

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

}

让我们创建完全托管的 .NET 样式类型:

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

}

它可以像这样使用:

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

推荐阅读