diff --git a/Alpm/Alpm.csproj b/Alpm/Alpm.csproj index 7d9d041..69449f0 100644 --- a/Alpm/Alpm.csproj +++ b/Alpm/Alpm.csproj @@ -7,4 +7,8 @@ Foodsoft.Alpm + + + + diff --git a/Alpm/AlpmException.cs b/Alpm/AlpmException.cs index 349e6b1..9d2305a 100644 --- a/Alpm/AlpmException.cs +++ b/Alpm/AlpmException.cs @@ -8,8 +8,8 @@ namespace Foodsoft.Alpm { public AlpmException() { } internal AlpmException(SafeAlpmHandle handle) : base(alpm.alpm_strerror(alpm.alpm_errno(handle))) { } - public AlpmException(Error errno) : base(alpm.alpm_strerror(errno)) { } - public AlpmException(Error errno, Exception inner) : base(alpm.alpm_strerror(errno), inner) { } + public AlpmException(ErrorCode errno) : base(alpm.alpm_strerror(errno)) { } + public AlpmException(ErrorCode errno, Exception inner) : base(alpm.alpm_strerror(errno), inner) { } protected AlpmException(SerializationInfo info, StreamingContext context) : base(info, context) { } diff --git a/Alpm/Backup.cs b/Alpm/Backup.cs index 0fbf072..557f71f 100644 --- a/Alpm/Backup.cs +++ b/Alpm/Backup.cs @@ -6,7 +6,7 @@ namespace Foodsoft.Alpm public readonly struct Backup : IEquatable { public string Name { get; } - public string Hash { get; } + public string? Hash { get; } public bool Equals(Backup other) { @@ -24,17 +24,17 @@ namespace Foodsoft.Alpm } [StructLayout(LayoutKind.Sequential)] - private readonly unsafe struct NativeBackup + private readonly struct NativeBackup { - internal readonly sbyte* name; - internal readonly sbyte* hash; + internal readonly IntPtr name; + internal readonly IntPtr hash; } internal unsafe Backup(IntPtr ptr) { var native = (NativeBackup*) ptr; - Name = new string(native->name); - Hash = new string(native->hash); + Name = Marshal.PtrToStringUTF8(native->name)!; + Hash = Marshal.PtrToStringUTF8(native->hash)!; } } } \ No newline at end of file diff --git a/Alpm/CachePackage.cs b/Alpm/CachePackage.cs index 47fbd29..749e5ee 100644 --- a/Alpm/CachePackage.cs +++ b/Alpm/CachePackage.cs @@ -15,7 +15,7 @@ namespace Foodsoft.Alpm var ret = alpm.alpm_pkg_checkmd5sum(Handle); if (ret == 0) return true; var errno = alpm.alpm_errno(Handle.SafeAlpmHandle); - return errno == Error.PkgInvalid ? false : throw new AlpmException(errno); + return errno == ErrorCode.PkgInvalid ? false : throw new AlpmException(errno); } public void SetInstallReason(InstallReason reason) diff --git a/Alpm/Database.cs b/Alpm/Database.cs index f2c590d..a85381f 100644 --- a/Alpm/Database.cs +++ b/Alpm/Database.cs @@ -42,7 +42,7 @@ namespace Foodsoft.Alpm // ReSharper disable once LoopCanBeConvertedToQuery foreach (var s in value) if (alpm.alpm_list_append_strdup(ref listPtr, s) == IntPtr.Zero) - throw new AlpmException(Error.Memory); + throw new AlpmException(ErrorCode.Memory); success = true; } @@ -63,8 +63,8 @@ namespace Foodsoft.Alpm public SigLevel SigLevel => alpm.alpm_db_get_siglevel(_handle); - public Error Valid => - alpm.alpm_db_get_valid(_handle) != 0 ? alpm.alpm_errno(_handle.SafeAlpmHandle) : Error.OK; + public ErrorCode Valid => + alpm.alpm_db_get_valid(_handle) != 0 ? alpm.alpm_errno(_handle.SafeAlpmHandle) : ErrorCode.OK; public CachePackageList PackageCache { @@ -118,7 +118,7 @@ namespace Foodsoft.Alpm if (pkgPtr == IntPtr.Zero) { var err = alpm.alpm_errno(_handle.SafeAlpmHandle); - if (err == Error.PkgNotFound) + if (err == ErrorCode.PkgNotFound) return null; throw new AlpmException(err); } @@ -134,7 +134,7 @@ namespace Foodsoft.Alpm if (result == IntPtr.Zero) { var errNo = alpm.alpm_errno(_handle.SafeAlpmHandle); - if (errNo != Error.OK) + if (errNo != ErrorCode.OK) throw new AlpmException(errNo); } diff --git a/Alpm/Depend.cs b/Alpm/Depend.cs index 7edae39..d3ea9db 100644 --- a/Alpm/Depend.cs +++ b/Alpm/Depend.cs @@ -5,22 +5,12 @@ namespace Foodsoft.Alpm { public class Depend : IEquatable { - public enum ModType - { - Any = 1, - Equal, - GreaterThanOrEqual, - LessThanOrEqual, - GreaterThan, - LessThan - } - internal unsafe Depend(IntPtr ptr) { var native = (NativeDepend*) ptr; - Name = new string(native->name); - Version = new string(native->version); - Description = new string(native->description); + Name = Marshal.PtrToStringUTF8(native->name)!; + Version = Marshal.PtrToStringUTF8(native->version)!; + Description = Marshal.PtrToStringUTF8(native->description)!; NameHash = native->name_hash; Mod = native->mod; } @@ -48,13 +38,23 @@ namespace Foodsoft.Alpm } [StructLayout(LayoutKind.Sequential)] - private readonly unsafe struct NativeDepend + private readonly struct NativeDepend { - internal readonly sbyte* name; - internal readonly sbyte* version; - internal readonly sbyte* description; + internal readonly IntPtr name; + internal readonly IntPtr version; + internal readonly IntPtr description; internal readonly ulong name_hash; internal readonly ModType mod; } } + + public enum ModType + { + Any = 1, + Equal, + GreaterThanOrEqual, + LessThanOrEqual, + GreaterThan, + LessThan + } } \ No newline at end of file diff --git a/Alpm/Detail.cs b/Alpm/Detail.cs index 25f9fee..a304322 100644 --- a/Alpm/Detail.cs +++ b/Alpm/Detail.cs @@ -80,7 +80,7 @@ namespace Foodsoft.Alpm Encoding.UTF8.GetBytes(pSrc, s.Length, pDest, poolBuffer.Length - start); pDest[nbWritten] = 0; if (alpm.alpm_list_append(ref listPtr, (IntPtr) pDest) == IntPtr.Zero) - throw new AlpmException(Error.Memory); + throw new AlpmException(ErrorCode.Memory); start += nbWritten + 1; } diff --git a/Alpm/Enums.cs b/Alpm/ErrorCode.cs similarity index 72% rename from Alpm/Enums.cs rename to Alpm/ErrorCode.cs index c9fee62..464ebd6 100644 --- a/Alpm/Enums.cs +++ b/Alpm/ErrorCode.cs @@ -1,8 +1,6 @@ -using System; - namespace Foodsoft.Alpm { - public enum Error + public enum ErrorCode { OK = 0, Memory, @@ -80,28 +78,4 @@ namespace Foodsoft.Alpm /*MissingCompileTimeFeatures*/ MissingCapabilitySignatures } - - [Flags] - public enum LogLevel - { - Error = 1, - Warning = (1 << 1), - Debug = (1 << 2), - Function = (1 << 3) - } - - [Flags] - public enum SigLevel - { - Package = (1 << 0), - PackageOptional = (1 << 1), - PackageMarginalOK = (1 << 2), - PackageUnknownOK = (1 << 3), - - Database = (1 << 10), - DatabaseOptional = (1 << 11), - DatabaseMarginalOK = (1 << 12), - DatabaseUnknownOK = (1 << 13), - UseDefault = (1 << 31) - } } \ No newline at end of file diff --git a/Alpm/ExtensionMethods.cs b/Alpm/ExtensionMethods.cs index aad94f3..b9d147f 100644 --- a/Alpm/ExtensionMethods.cs +++ b/Alpm/ExtensionMethods.cs @@ -28,7 +28,7 @@ namespace Foodsoft.Alpm refs[pkgPtr] = pkg; if (alpm.alpm_list_append(ref listPtr, handle.DangerousGetHandle()) == IntPtr.Zero) - throw new AlpmException(Error.Memory); + throw new AlpmException(ErrorCode.Memory); } var foundPtr = alpm.alpm_find_satisfier(listPtr, depString); diff --git a/Alpm/Handle.cs b/Alpm/Handle.cs index fb0fe1f..51ac99c 100644 --- a/Alpm/Handle.cs +++ b/Alpm/Handle.cs @@ -1,15 +1,19 @@ using System; using System.Collections.Generic; +using Foodsoft.Alpm.Interop; namespace Foodsoft.Alpm { public sealed class Handle : IDisposable { + private readonly object _eventLock = new object(); private readonly SafeAlpmHandle _handle; - public Handle(string root, string dbpath) + private EventHandler _logEvent; + + public Handle(string root, string dbPath) { - _handle = alpm.alpm_initialize(root, dbpath, out var err); + _handle = alpm.alpm_initialize(root, dbPath, out var err); if (_handle.IsInvalid) throw new AlpmException(err); } @@ -22,6 +26,8 @@ namespace Foodsoft.Alpm set => Detail.SetStringCollection(value, _handle, s => alpm.alpm_option_add_cachedir(_handle, s)); } + public LogLevel LogLevel { get; set; } + public void Dispose() { _handle.Dispose(); @@ -55,5 +61,42 @@ namespace Foodsoft.Alpm { return alpm.alpm_pkg_should_ignore(_handle, pkg.Handle) == 0; } + + public event EventHandler Log + { + add + { + void LogCallback(LogLevel level, IntPtr format, IntPtr args) + { + if (level > LogLevel) return; + _logEvent?.Invoke(this, + new LogEventArgs {Level = level, Message = CFormatter.Format(format, args)}); + } + + lock (_eventLock) + { + var wasEmpty = _logEvent == null; + _logEvent += value; + if (wasEmpty) + alpm.alpm_option_set_logcb(_handle, LogCallback); + } + } + remove + { + lock (_eventLock) + { + // ReSharper disable once DelegateSubtraction + _logEvent -= value; + if (_logEvent == null) + alpm.alpm_option_set_logcb(_handle, null); + } + } + } + + public struct LogEventArgs + { + public LogLevel Level; + public string Message; + } } } \ No newline at end of file diff --git a/Alpm/Interop/CFormatter.cs b/Alpm/Interop/CFormatter.cs new file mode 100644 index 0000000..873da2f --- /dev/null +++ b/Alpm/Interop/CFormatter.cs @@ -0,0 +1,88 @@ +using System; +using System.Buffers; +using System.Runtime.InteropServices; +using System.Text; + +namespace Foodsoft.Alpm.Interop +{ + internal static class CFormatter + { + private static readonly int vaListCopyNWords; + + private static readonly Vsnprintf? vsnprintfFunc; + + static CFormatter() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || + RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) + { + vaListCopyNWords = RuntimeInformation.ProcessArchitecture switch + { + Architecture.X86 => 1, + Architecture.X64 => 3, + Architecture.Arm => 1, + Architecture.Arm64 => 4, + _ => vaListCopyNWords + }; + vsnprintfFunc = vsnprintfLibc; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + vaListCopyNWords = 1; + vsnprintfFunc = vsnprintfUCrt; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + vaListCopyNWords = RuntimeInformation.ProcessArchitecture switch + { + Architecture.X86 => 1, + Architecture.X64 => 3, + Architecture.Arm => 1, + Architecture.Arm64 => 1, + _ => vaListCopyNWords + }; + vsnprintfFunc = vsnprintfLibc; + } + } + + internal static unsafe string Format(IntPtr format, IntPtr vaList) + { + const int bufSize = 256; + if (vsnprintfFunc == null || vaListCopyNWords <= 0) + return "[Formatter unsupported for platform]"; + + var vaListBuf = (void**) vaList; + var vaListCopy = stackalloc void*[vaListCopyNWords]; + for (var i = 0; i < vaListCopyNWords; i++) vaListCopy[i] = vaListBuf[i]; + + var strBuf = stackalloc byte[bufSize]; + var strBufPtr = (IntPtr) strBuf; + + var nbNeed = vsnprintfFunc(strBufPtr, (UIntPtr) bufSize, format, vaList); + + if (nbNeed < bufSize) + return Marshal.PtrToStringUTF8(strBufPtr, nbNeed); + + var pool = ArrayPool.Shared; + + var buffer = pool.Rent(nbNeed + 1); + fixed (byte* pb = buffer) + { + nbNeed = vsnprintfFunc((IntPtr) pb, (UIntPtr) buffer.Length, format, (IntPtr) vaListCopy); + } + + var result = Encoding.UTF8.GetString(buffer, 0, nbNeed); + pool.Return(buffer); + return result; + } + + private delegate int Vsnprintf(IntPtr buffer, UIntPtr size, IntPtr format, IntPtr args); + + + [DllImport("libc", EntryPoint = "vsnprintf", CallingConvention = CallingConvention.Cdecl)] + internal static extern int vsnprintfLibc(IntPtr buffer, UIntPtr size, IntPtr format, IntPtr args); + + [DllImport("ucrt", EntryPoint = "vsnprintf", CallingConvention = CallingConvention.Cdecl)] + internal static extern int vsnprintfUCrt(IntPtr buffer, UIntPtr size, IntPtr format, IntPtr args); + } +} \ No newline at end of file diff --git a/Alpm/Marshalling/UTF8In.cs b/Alpm/Interop/UTF8In.cs similarity index 89% rename from Alpm/Marshalling/UTF8In.cs rename to Alpm/Interop/UTF8In.cs index 0ab686a..3d8059d 100644 --- a/Alpm/Marshalling/UTF8In.cs +++ b/Alpm/Interop/UTF8In.cs @@ -1,5 +1,7 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using JetBrains.Annotations; namespace Foodsoft.Alpm.Marshalling { @@ -30,9 +32,8 @@ namespace Foodsoft.Alpm.Marshalling { return Marshal.PtrToStringUTF8(pNativeData)!; } - - // ReSharper disable once UnusedMember.Local - // ReSharper disable once UnusedParameter.Local + + [UsedImplicitly] private static ICustomMarshaler GetInstance(string cookie) { return _instance; diff --git a/Alpm/Marshalling/UTF8Return.cs b/Alpm/Interop/UTF8Return.cs similarity index 88% rename from Alpm/Marshalling/UTF8Return.cs rename to Alpm/Interop/UTF8Return.cs index 39ffb81..fb578f1 100644 --- a/Alpm/Marshalling/UTF8Return.cs +++ b/Alpm/Interop/UTF8Return.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using JetBrains.Annotations; namespace Foodsoft.Alpm.Marshalling { @@ -7,8 +8,7 @@ namespace Foodsoft.Alpm.Marshalling { private static readonly UTF8Return _instance = default; - // ReSharper disable once UnusedMember.Local - // ReSharper disable once UnusedParameter.Local + [UsedImplicitly] private static ICustomMarshaler GetInstance(string cookie) { return _instance; diff --git a/Alpm/LogLevel.cs b/Alpm/LogLevel.cs new file mode 100644 index 0000000..9c344f4 --- /dev/null +++ b/Alpm/LogLevel.cs @@ -0,0 +1,13 @@ +using System; + +namespace Foodsoft.Alpm +{ + [Flags] + public enum LogLevel + { + Error = 1, + Warning = (1 << 1), + Debug = (1 << 2), + Function = (1 << 3) + } +} \ No newline at end of file diff --git a/Alpm/Package.cs b/Alpm/Package.cs index 83bc8e8..a100f3a 100644 --- a/Alpm/Package.cs +++ b/Alpm/Package.cs @@ -5,10 +5,7 @@ namespace Foodsoft.Alpm { public abstract class Package : IPackageData { - internal Package(SafePackageHandle handle) - { - Handle = handle; - } + internal Package(SafePackageHandle handle) => Handle = handle; internal SafePackageHandle Handle { get; } @@ -57,24 +54,15 @@ namespace Foodsoft.Alpm public virtual Database? DB => null; - public void Dispose() - { - Handle.Dispose(); - } + public void Dispose() => Handle.Dispose(); - public IEnumerable ComputeRequiredBy() - { - throw new NotImplementedException(); - } + public IEnumerable ComputeRequiredBy() => throw new NotImplementedException(); public IEnumerable ComputeOptionalFor() { throw new NotImplementedException(); } - public static int VersionCompare(string v1, string v2) - { - return alpm.alpm_pkg_vercmp(v1, v2); - } + public static int VersionCompare(string v1, string v2) => alpm.alpm_pkg_vercmp(v1, v2); } } \ No newline at end of file diff --git a/Alpm/SafeFilePackageHandle.cs b/Alpm/SafeFilePackageHandle.cs index c1229de..8646ea4 100644 --- a/Alpm/SafeFilePackageHandle.cs +++ b/Alpm/SafeFilePackageHandle.cs @@ -4,7 +4,7 @@ using System.Runtime.ConstrainedExecution; namespace Foodsoft.Alpm { - internal class SafeFilePackageHandle : SafePackageHandle + internal sealed class SafeFilePackageHandle : SafePackageHandle { [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] [PrePrepareMethod] diff --git a/Alpm/SigLevel.cs b/Alpm/SigLevel.cs new file mode 100644 index 0000000..1649a7e --- /dev/null +++ b/Alpm/SigLevel.cs @@ -0,0 +1,19 @@ +using System; + +namespace Foodsoft.Alpm +{ + [Flags] + public enum SigLevel + { + Package = (1 << 0), + PackageOptional = (1 << 1), + PackageMarginalOK = (1 << 2), + PackageUnknownOK = (1 << 3), + + Database = (1 << 10), + DatabaseOptional = (1 << 11), + DatabaseMarginalOK = (1 << 12), + DatabaseUnknownOK = (1 << 13), + UseDefault = (1 << 31) + } +} \ No newline at end of file diff --git a/Alpm/alpm.cs b/Alpm/alpm.cs index a744f1d..ace1ef4 100644 --- a/Alpm/alpm.cs +++ b/Alpm/alpm.cs @@ -16,7 +16,6 @@ namespace Foodsoft.Alpm // ReSharper disable once InconsistentNaming internal static class alpm { - // ReSharper disable once InconsistentNaming public delegate void alpm_fn_free(IntPtr ptr); [DllImport(nameof(alpm))] @@ -145,6 +144,12 @@ namespace Foodsoft.Alpm [DllImport(nameof(alpm))] public static extern int alpm_pkg_free(IntPtr ptr); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void LogFuncCallBack(LogLevel level, IntPtr format, IntPtr argsAddress); + + [DllImport(nameof(alpm))] + public static extern IntPtr alpm_option_set_logcb(SafeAlpmHandle handle, LogFuncCallBack? cb); + [DllImport(nameof(alpm))] public static extern IntPtr alpm_option_get_cachedirs(SafeAlpmHandle handle); @@ -159,11 +164,11 @@ namespace Foodsoft.Alpm string cachedir); [DllImport(nameof(alpm))] - public static extern Error alpm_errno(SafeAlpmHandle handle); + public static extern ErrorCode alpm_errno(SafeAlpmHandle handle); [DllImport(nameof(alpm))] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Return))] - public static extern string alpm_strerror(Error err); + public static extern string alpm_strerror(ErrorCode err); [DllImport(nameof(alpm))] public static extern IntPtr alpm_get_localdb(SafeAlpmHandle handle); @@ -218,7 +223,7 @@ namespace Foodsoft.Alpm public static extern SafeAlpmHandle alpm_initialize( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8In))] string root, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8In))] - string dbpath, out Error err); + string dbpath, out ErrorCode err); [DllImport(nameof(alpm))] public static extern int alpm_release(IntPtr handle); diff --git a/DotNetAlpm.sln.DotSettings b/DotNetAlpm.sln.DotSettings index 2431c01..09bed35 100644 --- a/DotNetAlpm.sln.DotSettings +++ b/DotNetAlpm.sln.DotSettings @@ -2,11 +2,16 @@ True True True + True True + True True True True True True True - True \ No newline at end of file + True + True + True + True \ No newline at end of file diff --git a/Samples/Program.cs b/Samples/Program.cs index 0eea377..855a38c 100644 --- a/Samples/Program.cs +++ b/Samples/Program.cs @@ -1,27 +1,24 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; using Foodsoft.Alpm; -// ReSharper disable once UnusedType.Global namespace Samples { [Example("installed")] internal class Installed : IExample { - private static string ModToStr(Depend.ModType modType) + private static string ModToStr(ModType modType) { return modType switch { - Depend.ModType.Any => "", - Depend.ModType.Equal => "==", - Depend.ModType.GreaterThanOrEqual => ">=", - Depend.ModType.LessThanOrEqual => "<=", - Depend.ModType.GreaterThan => ">", - Depend.ModType.LessThan => "<", + ModType.Any => "", + ModType.Equal => "==", + ModType.GreaterThanOrEqual => ">=", + ModType.LessThanOrEqual => "<=", + ModType.GreaterThan => ">", + ModType.LessThan => "<", _ => throw new ArgumentOutOfRangeException(nameof(modType), modType, null) }; } @@ -60,7 +57,7 @@ namespace Samples //using var pkg = db.PackageCache.FindSatisfier("gcc=9.3.0-1"); - var result = db.Search(new string[] {"gcc", "objc"}); + var result = db.Search(new[] {"gcc", "objc"}); foreach (var pkg in result) using (pkg) { @@ -82,7 +79,7 @@ namespace Samples var examples = (from t in Assembly.GetExecutingAssembly().GetTypes() let attribute = (Example?) t.GetCustomAttribute(typeof(Example)) where attribute != null - select new {Name = attribute.Name, Type = t}).ToImmutableDictionary((e) => e.Name); + select new {attribute.Name, Type = t}).ToImmutableDictionary((e) => e.Name); if (args.Length < 1) { @@ -110,7 +107,7 @@ namespace Samples } } - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class Example : Attribute { public string Name { get; }