using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Runtime.Serialization; // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnassignedReadonlyField // ReSharper disable UnusedMember.Global // ReSharper disable InconsistentNaming namespace nalpm { public enum errno_t { ERR_OK = 0, ERR_MEMORY, ERR_SYSTEM, ERR_BADPERMS, ERR_NOT_A_FILE, ERR_NOT_A_DIR, ERR_WRONG_ARGS, ERR_DISK_SPACE, /* Interface */ ERR_HANDLE_NULL, ERR_HANDLE_NOT_NULL, ERR_HANDLE_LOCK, /* Databases */ ERR_DB_OPEN, ERR_DB_CREATE, ERR_DB_NULL, ERR_DB_NOT_NULL, ERR_DB_NOT_FOUND, ERR_DB_INVALID, ERR_DB_INVALID_SIG, ERR_DB_VERSION, ERR_DB_WRITE, ERR_DB_REMOVE, /* Servers */ ERR_SERVER_BAD_URL, ERR_SERVER_NONE, /* Transactions */ ERR_TRANS_NOT_NULL, ERR_TRANS_NULL, ERR_TRANS_DUP_TARGET, ERR_TRANS_NOT_INITIALIZED, ERR_TRANS_NOT_PREPARED, ERR_TRANS_ABORT, ERR_TRANS_TYPE, ERR_TRANS_NOT_LOCKED, ERR_TRANS_HOOK_FAILED, /* Packages */ ERR_PKG_NOT_FOUND, ERR_PKG_IGNORED, ERR_PKG_INVALID, ERR_PKG_INVALID_CHECKSUM, ERR_PKG_INVALID_SIG, ERR_PKG_MISSING_SIG, ERR_PKG_OPEN, ERR_PKG_CANT_REMOVE, ERR_PKG_INVALID_NAME, ERR_PKG_INVALID_ARCH, ERR_PKG_REPO_NOT_FOUND, /* Signatures */ ERR_SIG_MISSING, ERR_SIG_INVALID, /* Dependencies */ ERR_UNSATISFIED_DEPS, ERR_CONFLICTING_DEPS, ERR_FILE_CONFLICTS, /* Misc */ ERR_RETRIEVE, ERR_INVALID_REGEX, /* External library errors */ ERR_LIBARCHIVE, ERR_LIBCURL, ERR_EXTERNAL_DOWNLOAD, ERR_GPGME, /* Missing compile-time features */ ERR_MISSING_CAPABILITY_SIGNATURES } /** Package install reasons. */ enum alpm_pkgreason_t { /** Explicitly requested by the user. */ PKG_REASON_EXPLICIT = 0, /** Installed as a dependency for another package. */ PKG_REASON_DEPEND = 1 } /** Location a package object was loaded from. */ enum alpm_pkgfrom_t { PKG_FROM_FILE = 1, PKG_FROM_LOCALDB, PKG_FROM_SYNCDB } /** Method used to validate a package. */ enum alpm_pkgvalidation_t { PKG_VALIDATION_UNKNOWN = 0, PKG_VALIDATION_NONE = (1 << 0), PKG_VALIDATION_MD5SUM = (1 << 1), PKG_VALIDATION_SHA256SUM = (1 << 2), PKG_VALIDATION_SIGNATURE = (1 << 3) } /** Types of version constraints in dependency specs. */ public enum alpm_depmod_t { /** No version constraint */ DEP_MOD_ANY = 1, /** Test version equality (package=x.y.z) */ DEP_MOD_EQ, /** Test for at least a version (package>=x.y.z) */ DEP_MOD_GE, /** Test for at most a version (package<=x.y.z) */ DEP_MOD_LE, /** Test for greater than some version (package>x.y.z) */ DEP_MOD_GT, /** Test for less than some version (package(ptr); } } /** Missing dependency */ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct alpm_depmissing_t { public string target; public AlpmDependPtr depend; /* this is used only in the case of a remove dependency error */ public string causingpkg; } [StructLayout(LayoutKind.Sequential)] /** Conflict */ public struct alpm_conflict_t { public ulong package1_hash; public ulong package2_hash; public string package1; public string package2; public AlpmDependPtr reason; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] /** File conflict */ struct alpm_fileconflict_t { public string target; public alpm_fileconflicttype_t type; public string file; public string ctarget; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] /** Package group */ public readonly struct alpm_group_t { /** group name */ public readonly string name; /** list of alpm_pkg_t packages */ public readonly unsafe alpm_list_t* packages; } /* TODO: alpm_pkg_get_files - need to wrap */ /** File in a package */ public readonly struct alpm_file_t { public readonly string name; public readonly UIntPtr size; public readonly uint mode; } /** Package filelist container */ public struct _alpm_filelist_t { public UIntPtr count; private IntPtr files; /* FIXME: This is broken as shit */ public alpm_file_t[] Unmarshal() { var iCount = (int) this.count; var byteCount = Marshal.SizeOf() * iCount; var byteBuffer = new byte[byteCount]; Marshal.Copy(files, byteBuffer, 0, byteBuffer.Length); var result = new alpm_file_t[iCount]; Buffer.BlockCopy(byteBuffer, 0, result, 0, byteBuffer.Length); return result; } } /** Local package or package file backup entry */ public struct _alpm_backup_t { string name; string hash; } public class alpm_pgpkey_t { public byte[] data; public string fingerprint; public string uid; public string name; public string email; public long created; public long expires; public uint revoked; public char pubkey_algo; } public class PgpKeyInMarshaler : ICustomMarshaler { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] private class NativePgpKey { public IntPtr data; public string fingerprint; public string uid; public string name; public string email; public long created; public long expires; public uint length; public uint revoked; public char pubkey_algo; } public void CleanUpManagedData(object ManagedObj) { } public void CleanUpNativeData(IntPtr pNativeData) { } public int GetNativeDataSize() { throw new NotImplementedException(); } public IntPtr MarshalManagedToNative(object ManagedObj) { throw new NotImplementedException(); } public object MarshalNativeToManaged(IntPtr pNativeData) { var raw = Marshal.PtrToStructure(pNativeData); var managed = new alpm_pgpkey_t() { data = new byte[raw.length], fingerprint = raw.fingerprint, uid = raw.uid, name = raw.name, email = raw.email, created = raw.created, expires = raw.expires, revoked = raw.revoked, pubkey_algo = raw.pubkey_algo }; Marshal.Copy(raw.data, managed.data, 0, (int) raw.length); return managed; } } /** * Signature result. Contains the key, status, and validity of a given * signature. */ public readonly struct alpm_sigresult_t { [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(PgpKeyInMarshaler))] readonly alpm_pgpkey_t key; readonly alpm_sigstatus_t status; readonly alpm_sigvalidity_t validity; } /** * Signature list. Contains the number of signatures found and a pointer to an * array of results. The array is of size count. */ public class SigListInMarshaler : ICustomMarshaler { public void CleanUpManagedData(object ManagedObj) { } public void CleanUpNativeData(IntPtr pNativeData) { } public int GetNativeDataSize() { throw new NotImplementedException(); } public IntPtr MarshalManagedToNative(object ManagedObj) { throw new NotImplementedException(); } public object MarshalNativeToManaged(IntPtr pNativeData) { UIntPtr count; IntPtr data; unsafe { count = *(UIntPtr*) pNativeData; data = *(IntPtr*) (pNativeData + sizeof(UIntPtr)); } var iCount = (int) count; var result = new alpm_sigresult_t[iCount]; // NOTE: I expect this to fail cuz i didn't implement the above GetNativeDataSize for (var i = 0; i < iCount; ++i, data += Marshal.SizeOf()) { result[i] = Marshal.PtrToStructure(data); } return result; } } /* * Hooks */ enum alpm_hook_when_t { HOOK_PRE_TRANSACTION = 1, HOOK_POST_TRANSACTION } /* * Logging facilities */ /** Logging Levels */ enum _alpm_loglevel_t { LOG_ERROR = 1, LOG_WARNING = (1 << 1), LOG_DEBUG = (1 << 2), LOG_FUNCTION = (1 << 3) } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct alpm_pkg_p { private IntPtr ptr; } [StructLayout(LayoutKind.Sequential)] public struct alpm_list_t { public IntPtr data; public unsafe alpm_list_t* prev; public unsafe alpm_list_t* next; } public interface IAlpmItemsAccessor { SafeAlpmHandle Handle { get; } public IntPtr GetItems(); public int SetItems(IntPtr list); public int AddItem(TElement item); public int RemoveItem(TElement item); public IntPtr FindItem(TElement item); public TElement PtrToItem(IntPtr p); } public class SafeAlpmHandle : SafeHandle { private SafeAlpmHandle() : base(IntPtr.Zero, true) { } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return alpm.alpm_release(handle) == 0; } public override bool IsInvalid => handle == IntPtr.Zero; } public sealed class Handle : IDisposable { private readonly SafeAlpmHandle _handle; public Handle(string root, string dbpath) { var err = errno_t.ERR_OK; _handle = alpm.alpm_initialize(root, dbpath, ref err); if (_handle.IsInvalid) { throw new Exception(err); } } public void Dispose() { _handle.Dispose(); } private Database DatabaseAccess(IntPtr ptr) { if (ptr == IntPtr.Zero) { throw new Exception(_handle); } Database db; RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { // adding this ref and getting the Database object the handle must not interrupted var addedRef = false; _handle.DangerousAddRef(ref addedRef); if (!addedRef) throw new ObjectDisposedException(GetType().FullName); db = new Database(_handle, new DatabaseHandle(ptr)); } return db; } public Database LocalDB => DatabaseAccess(alpm.alpm_get_localdb(_handle)); public Database RegisterSyncDB(string treename, SigLevel sigLevel) => DatabaseAccess(alpm.alpm_register_syncdb(_handle, treename, sigLevel)); public readonly struct CacheDirsAccessor : IAlpmItemsAccessor { public CacheDirsAccessor(SafeAlpmHandle handle) => Handle = handle; public SafeAlpmHandle Handle { get; } public IntPtr GetItems() => alpm.alpm_option_get_cachedirs(Handle); public int SetItems(IntPtr list) => alpm.alpm_option_set_cachedirs(Handle, list); public int AddItem(string item) => alpm.alpm_option_add_cachedir(Handle, item); public int RemoveItem(string item) => alpm.alpm_option_remove_cachedir(Handle, item); public IntPtr FindItem(string item) => alpm.alpm_list_find_str(GetItems(), item); public string PtrToItem(IntPtr p) => Marshal.PtrToStringUTF8(p)!; } #nullable enable public struct CollectionWrapper : ICollection where TImpl : IAlpmItemsAccessor, ICollection { private TImpl _impl; public CollectionWrapper(TImpl impl) { _impl = impl; } IEnumerator IEnumerable.GetEnumerator() { return (IEnumerator) GetEnumerator(); } public IEnumerator GetEnumerator() { var result = new List(); var intPtr = _impl.GetItems(); unsafe { for (var list = (alpm_list_t*) intPtr; list != null; list = alpm.alpm_list_next(list)) { result.Add(_impl.PtrToItem(list->data)); } } return result.GetEnumerator(); } public void Add(TElement item) { _impl.AddItem(item); } public void Clear() { _impl.SetItems(IntPtr.Zero); } public bool Contains(TElement item) => _impl.FindItem(item) != IntPtr.Zero; public void CopyTo(TElement[] array, int arrayIndex) { var intPtr = _impl.GetItems(); unsafe { for (var list = (alpm_list_t*) intPtr; list != null; list = alpm.alpm_list_next(list)) { array[arrayIndex++] = _impl.PtrToItem(list->data); } } } public bool Remove(TElement item) { var err = _impl.RemoveItem(item); if (err < 0) { throw new Exception(alpm.alpm_errno(_impl.Handle)); } return err == 0; } public int Count => (int) alpm.alpm_list_count(_impl.GetItems()); public bool IsReadOnly => false; } private readonly struct CacheDirCollection : ICollection { private readonly SafeAlpmHandle _handle; public CacheDirCollection(SafeAlpmHandle handle) { _handle = handle; } public IEnumerator GetEnumerator() { var result = new List(); var intPtr = alpm.alpm_option_get_cachedirs(_handle); unsafe { for (var list = (alpm_list_t*) intPtr; list != null; list = alpm.alpm_list_next(list)) { result.Add(Marshal.PtrToStringUTF8(list->data)); } } return result.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public void Add(string item) { var handle = _handle; Alpm.ErrWrap(_handle, () => alpm.alpm_option_add_cachedir(handle, item)); } public void Clear() { var handle = _handle; Alpm.ErrWrap(_handle, () => alpm.alpm_option_set_cachedirs(handle, IntPtr.Zero)); } public bool Contains(string item) => alpm.alpm_list_find_str(alpm.alpm_option_get_cachedirs(_handle), item) != IntPtr.Zero; public void CopyTo(string[] array, int arrayIndex) { var intPtr = alpm.alpm_option_get_cachedirs(_handle); unsafe { for (var list = (alpm_list_t*) intPtr; list != null; list = alpm.alpm_list_next(list)) { array[arrayIndex++] = Marshal.PtrToStringUTF8(list->data); } } } public bool Remove(string item) { var err = alpm.alpm_option_remove_cachedir(_handle, item); if (err < 0) { throw new Exception(alpm.alpm_errno(_handle)); } return err == 0; } public int Count => (int) alpm.alpm_list_count(alpm.alpm_option_get_cachedirs(_handle)); public bool IsReadOnly => false; } public ICollection CacheDirs { get => new CacheDirCollection(_handle); set { foreach (var s in value) { Alpm.ErrWrap(_handle, () => alpm.alpm_option_add_cachedir(_handle, s)); } } } } public enum caps { ALPM_CAPABILITY_NLS = (1 << 0), ALPM_CAPABILITY_DOWNLOADER = (1 << 1), ALPM_CAPABILITY_SIGNATURES = (1 << 2) } [Serializable()] public class Exception : System.Exception { public Exception() { } public Exception(SafeAlpmHandle handle) : base(Alpm.StrError(alpm.alpm_errno(handle))) { } public Exception(errno_t errno) : base(Alpm.StrError(errno)) { } public Exception(errno_t errno, System.Exception inner) : base(Alpm.StrError(errno), inner) { } protected Exception(SerializationInfo info, StreamingContext context) : base(info, context) { } } public class DatabaseHandle : SafeHandle { private DatabaseHandle() : base(IntPtr.Zero, true) { } public DatabaseHandle(IntPtr h) : this() { SetHandle(h); } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return alpm.alpm_db_unregister(handle) == 0; } public override bool IsInvalid => handle == IntPtr.Zero; } public sealed class Database : IDisposable { private SafeAlpmHandle _handle; private DatabaseHandle _db; public Database(SafeAlpmHandle handle, DatabaseHandle db) { _handle = handle; _db = db; } private readonly struct ServerCollection : ICollection { private readonly SafeAlpmHandle _handle; private readonly DatabaseHandle _db; public ServerCollection(SafeAlpmHandle handle, DatabaseHandle db) { _handle = handle; _db = db; } public IEnumerator GetEnumerator() { var result = new List(); var intPtr = alpm.alpm_db_get_servers(_db); unsafe { for (var list = (alpm_list_t*) intPtr; list != null; list = alpm.alpm_list_next(list)) { result.Add(Marshal.PtrToStringUTF8(list->data)); } } return result.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public void Add(string item) { var db = _db; Alpm.ErrWrap(_handle, () => alpm.alpm_db_add_server(db, item)); } public void Clear() { var db = _db; Alpm.ErrWrap(_handle, () => alpm.alpm_db_set_servers(db, IntPtr.Zero)); } public bool Contains(string item) => alpm.alpm_list_find_str(alpm.alpm_db_get_servers(_db), item) != IntPtr.Zero; public void CopyTo(string[] array, int arrayIndex) { var intPtr = alpm.alpm_db_get_servers(_db); unsafe { for (var list = (alpm_list_t*) intPtr; list != null; list = alpm.alpm_list_next(list)) { array[arrayIndex++] = Marshal.PtrToStringUTF8(list->data); } } } public bool Remove(string item) { var err = alpm.alpm_db_remove_server(_db, item); if (err < 0) { throw new Exception(alpm.alpm_errno(_handle)); } return err == 0; } public int Count => (int) alpm.alpm_list_count(alpm.alpm_db_get_servers(_db)); public bool IsReadOnly => false; } public ICollection Servers { get => new ServerCollection(_handle, _db); set { foreach (var s in value) { Alpm.ErrWrap(_handle, () => alpm.alpm_db_add_server(_db, s)); } } } public string Name => Marshal.PtrToStringUTF8(alpm.alpm_db_get_name(_db)); public SigLevel SigLevel => alpm.alpm_db_get_siglevel(_db); public errno_t Valid => alpm.alpm_db_get_valid(_db) != 0 ? alpm.alpm_errno(_handle) : errno_t.ERR_OK; public bool Update(bool force) { var err = alpm.alpm_db_update(force ? 1 : 0, _db); if (err < 0) { throw new Exception(_handle); } return err == 0; } public void Dispose() { _db.Dispose(); RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { _handle.DangerousRelease(); } } } public static class alpm { [DllImport(nameof(alpm))] public static extern IntPtr alpm_option_get_cachedirs(SafeAlpmHandle handle); [DllImport(nameof(alpm))] public static extern int alpm_option_set_cachedirs(SafeAlpmHandle handle, IntPtr cachedirs); [DllImport(nameof(alpm), CharSet = CharSet.Ansi)] public static extern int alpm_option_add_cachedir(SafeAlpmHandle handle, string cachedir); [DllImport(nameof(alpm), CharSet = CharSet.Ansi)] public static extern int alpm_option_remove_cachedir(SafeAlpmHandle handle, string cachedir); [DllImport(nameof(alpm))] public static extern errno_t alpm_errno(SafeAlpmHandle handle); [DllImport(nameof(alpm))] public static extern IntPtr alpm_strerror(errno_t err); [DllImport(nameof(alpm))] public static extern IntPtr alpm_get_localdb(SafeAlpmHandle handle); [DllImport(nameof(alpm))] public static extern unsafe alpm_list_t* alpm_option_get_syncdbs(); [DllImport(nameof(alpm), CharSet = CharSet.Ansi)] public static extern IntPtr alpm_register_syncdb(SafeAlpmHandle handle, string treename, SigLevel sigLevel); [DllImport(nameof(alpm))] public static extern int alpm_db_unregister_all_syncdbs(SafeAlpmHandle handle); [DllImport(nameof(alpm))] public static extern unsafe alpm_list_t* alpm_list_next(alpm_list_t* list); [DllImport(nameof(alpm))] public static extern UIntPtr alpm_list_count(IntPtr list); [DllImport(nameof(alpm))] public static extern void alpm_list_free(IntPtr list); [DllImport(nameof(alpm), CharSet = CharSet.Ansi)] public static extern IntPtr alpm_list_find_str(IntPtr list, string needle); [DllImport(nameof(alpm))] public static extern int alpm_unlock(Handle handle); [DllImport(nameof(alpm))] public static extern IntPtr alpm_version(); [DllImport(nameof(alpm))] public static extern caps alpm_capabilities(); [DllImport(nameof(alpm), CharSet = CharSet.Ansi)] public static extern SafeAlpmHandle alpm_initialize(string root, string dbpath, ref errno_t err); [DllImport(nameof(alpm))] public static extern int alpm_release(IntPtr handle); [DllImport(nameof(alpm))] public static extern int alpm_db_remove_server(DatabaseHandle db, string url); [DllImport(nameof(alpm))] public static extern int alpm_db_unregister(IntPtr db); [DllImport(nameof(alpm))] public static extern IntPtr alpm_db_get_name(DatabaseHandle db); [DllImport(nameof(alpm))] public static extern SigLevel alpm_db_get_siglevel(DatabaseHandle db); [DllImport(nameof(alpm))] public static extern int alpm_db_get_valid(DatabaseHandle db); [DllImport(nameof(alpm))] public static extern int alpm_db_update(int force, DatabaseHandle db); [DllImport(nameof(alpm))] public static extern IntPtr alpm_db_get_servers(DatabaseHandle db); [DllImport(nameof(alpm))] public static extern int alpm_db_set_servers(DatabaseHandle db, IntPtr list); [DllImport(nameof(alpm), CharSet = CharSet.Ansi)] public static extern int alpm_db_add_server(DatabaseHandle db, string url); } internal static class Alpm { public static string Version() { return Marshal.PtrToStringUTF8(alpm.alpm_version()); } public static string StrError(errno_t err) { return Marshal.PtrToStringUTF8(alpm.alpm_strerror(err)); } internal static void ErrWrap(SafeAlpmHandle h, Func f) { var err = f(); if (err != 0) { throw new Exception(alpm.alpm_errno(h)); } } } }