Necesito algunas operaciones de archivos simples en una máquina Linux, para un instalador de servicios. El código es .NET 5.0.
Mi versión actual usa Process.Start() para ejecutar comandos de shell para cambiar el propietario de archivos y directorios y establecer permisos.
Esto es bastante lento (utilizo métodos asincrónicos), especialmente en comparación con los equivalentes de Windows.
Veo que libc accesible para llamar desde .NET tiene métodos chmod y chown , sin embargo, quiere parámetros uid y gid . Mi aplicación no conoce los identificadores, al menos sin usar Shell para ello.
Hasta ahora obtuve algo como esto:
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);Entonces... ¿cómo obtener esos 2 ints requeridos?
ACTUALIZAR
¿Por qué alguien ve esta pregunta (especialmente considerando su título) como un duplicado de una pregunta sobre cosas similares pero diferentes?
Sé cómo cambiar el propietario y los permisos de los archivos de Linux de muchas maneras. La forma más fácil es usar el shell de Linux. La forma más rápida y fácil es usar la biblioteca Mono.Posix.NETStandard , que llama a libc internamente.
Mi pregunta específica es ¿CÓMO SE HACE? ¿COMO FUNCIONA?
Para ser aún más específico: aquí está la página del manual de Linux para getpwnam() : https://man7.org/linux/man-pages/man3/getpwnam.3.html
¿Cómo llamarlo desde C# usando p/invoke? Veo en muchos ejemplos, que cuando reemplazan char* con string , de alguna manera funciona mágicamente. Creé una estructura como esta:
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 */ }; ... y traté de usarlo como parámetro de out para la firma. No me da error, pero simplemente no funciona. La estructura que obtengo está vacía.
De nuevo, estamos usando Platform Invoke, en C#, estamos llamando a libc y queremos obtener resultados de una estructura. Por lo que busqué en Google, no se puede buscar en Google. Solo hay un código fuente Mono , que usa un módulo externo que implementa lo que necesito. Sospecho que también lo hicieron por razones de rendimiento, utilizando algunas herramientas especiales, porque en los comentarios se destaca que se genera el código.
Mi pregunta es nuevamente, cómo, usando la definición de página del manual de Linux, cree una firma de método apropiada para que C# pueda extraer esos 2 enteros de getpwnam() .
También tenía curiosidad por saber si algo así podría existir ya en .NET, pero supongo que no.
Entonces, me oxidé con p/invoke. Mi problema fue que olvidé que cuando la función nativa devuelve un puntero a una estructura, no hay conversión automática, tengo que dejar el puntero en la firma, así que:
[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; }Vamos a crear tipos de estilo .NET completamente administrados:
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; } }Y se puede usar así:
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; }