Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/IDNT.AppBasics.Virtualization.Libvirt.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2;net47;net461;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>netstandard2;net47;net461;netcoreapp3.1;net9.0</TargetFrameworks>
<PackageId>IDNT.AppBasics.Virtualization.Libvirt</PackageId>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Authors>IDNT and the Livirt-dotnet contributors.</Authors>
Expand Down Expand Up @@ -69,11 +69,12 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
<PackageReference Include="System.Drawing.Common" Version="4.7.2" />
</ItemGroup>
<ItemGroup>
<None Update="IDNT.AppBasics.Virtualization.Libvirt.dll.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion src/Virtualization/Libvirt/LibvirtDomain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public void SetConsolePassword(string password)
case "qemu":
case "kvm":
string result = null;
if (NativeVirQemu.MonitorCommand(_domainPtr, $"change vnc password \"{password}\"", ref result,
if (NativeVirQemu.MonitorCommand(_domainPtr, $"change vnc password \"{password}\"", out result,
VirDomainQemuMonitorCommandFlags.VIR_DOMAIN_QEMU_MONITOR_COMMAND_HMP) < 0)
throw new LibvirtException($"SetConsolePassword failed: {result}");
Trace.WriteLine($"set console output: '{result}'");
Expand Down
16 changes: 16 additions & 0 deletions src/Virtualization/Libvirt/Native/NativeVirConnect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,22 @@ public static extern int StoragePoolEventRegisterAny(IntPtr conn, IntPtr pool, i
[MarshalAs(UnmanagedType.FunctionPtr)] VirConnectStoragePoolGenericEventCallback cb,
IntPtr opaque, [MarshalAs(UnmanagedType.FunctionPtr)] VirFreeCallback ff);


/// <summary>
/// Adds a callback to receive notifications of graphics events.
/// </summary>
/// <param name="conn">pointer to the connection</param>
/// <param name="dom">pointer to the domain or NULL for any</param>
/// <param name="eventId">events to listen to</param>
/// <param name="cb">callback to the function handling domain events</param>
/// <param name="opaque">opaque data to pass on to the callback</param>
/// <param name="ff">optional function to deallocate opaque when not used anymore</param>
/// <returns>t shall take a reference to it, by calling virDomainRef. The reference can be released once the object is no longer required by calling virDomainFree. Returns 0 on success, -1 on failure</returns>

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value documentation appears to be copied from another method and starts with a malformed fragment 't shall take a reference...'. This should describe the return value for DomainEventGraphicsRegisterAny. Update to something like: 'Returns a callback identifier on success, -1 on failure. The callback identifier can be used to deregister the callback.'

Suggested change
/// <returns>t shall take a reference to it, by calling virDomainRef. The reference can be released once the object is no longer required by calling virDomainFree. Returns 0 on success, -1 on failure</returns>
/// <returns>Returns a callback identifier on success, -1 on failure. The callback identifier can be used to deregister the callback.</returns>

Copilot uses AI. Check for mistakes.
[DllImport("libvirt-0.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "virConnectDomainEventRegisterAny")]
public static extern int DomainEventGraphicsRegisterAny(IntPtr conn, IntPtr pool, VirDomainEventID eventId,

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimise the use of unmanaged code.

Copilot uses AI. Check for mistakes.
[MarshalAs(UnmanagedType.FunctionPtr)] VirConnectDomainEventGraphicsCallback cb,
IntPtr opaque, [MarshalAs(UnmanagedType.FunctionPtr)] VirFreeCallback ff);

/// <summary>
/// Adds a callback to receive notifications of storage pool events.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/Virtualization/Libvirt/Native/NativeVirDomain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,16 @@ public static int InterfaceStats(IntPtr dom, string path, out VirDomainInterface
[DllImport("libvirt-0.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "virDomainUndefine")]
public static extern int Undefine(IntPtr domain);

/// <summary>
/// Undefine a domain. If the domain is running, it's converted to transient domain, without stopping it. If the domain is inactive, the domain configuration is removed.
/// </summary>
/// <param name="domain">pointer to a defined domain</param>
/// <param name="flags">an OR'ed set of virDomainUndefineFlags</param>
/// <returns>0 in case of success, -1 in case of error

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML documentation comment for the return value is incomplete - it's missing the closing </returns> tag. Add </returns> at the end of line 752.

Suggested change
/// <returns>0 in case of success, -1 in case of error
/// <returns>0 in case of success, -1 in case of error</returns>

Copilot uses AI. Check for mistakes.

[DllImport("libvirt-0.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "virDomainUndefineFlags")]
public static extern int UndefineFlags(IntPtr domain, VirDomainUndefineFlagsValues flags);

/// <summary>
Comment on lines +755 to 757

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimise the use of unmanaged code.

Suggested change
public static extern int UndefineFlags(IntPtr domain, VirDomainUndefineFlagsValues flags);
/// <summary>
private static extern int UndefineFlags(IntPtr domain, VirDomainUndefineFlagsValues flags);
/// <summary>
/// Managed wrapper for UndefineFlags. Undefines a domain with the specified flags.
/// </summary>
/// <param name="domain">Pointer to a defined domain</param>
/// <param name="flags">An OR'ed set of virDomainUndefineFlags</param>
/// <returns>True if successful, false otherwise</returns>
public static bool UndefineDomainWithFlags(IntPtr domain, VirDomainUndefineFlagsValues flags)
{
int result = UndefineFlags(domain, flags);
return result == 0;
}
/// <summary>

Copilot uses AI. Check for mistakes.
/// This function returns block device (disk) stats for block devices attached to the domain. The path parameter is the name of the block device. Get this by calling virDomainGetXMLDesc and finding the target dev='...' attribute within //domain/devices/disk. (For example, "xvda"). Domains may have more than one block device. To get stats for each you should make multiple calls to this function. Individual fields within the stats structure may be returned as -1, which indicates that the hypervisor does not support that particular statistic.
/// </summary>
Expand Down
38 changes: 24 additions & 14 deletions src/Virtualization/Libvirt/Native/NativeVirQemu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,34 @@ namespace IDNT.AppBasics.Virtualization.Libvirt.Native
///<summary>
/// class for libvirt qemu specific methods
///</summary>
public class NativeVirQemu
public static class NativeVirQemu
{
private const int MaxStringLength = 1024;
[DllImport("libvirt-qemu.so.0", CallingConvention = CallingConvention.Cdecl, EntryPoint = "virDomainQemuMonitorCommand")]
private static extern int virDomainQemuMonitorCommand(

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimise the use of unmanaged code.

Copilot uses AI. Check for mistakes.
IntPtr domain,
[MarshalAs(UnmanagedType.LPUTF8Str)] string cmd,
out IntPtr result,
VirDomainQemuMonitorCommandFlags flags);

[DllImport("libvirt-qemu-0.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "virDomainQemuMonitorCommand")]
private static extern int MonitorCommandImpl(IntPtr domain, string cmd, [Out] StringBuilder result, uint flags);
[DllImport("libvirt.so.0", CallingConvention = CallingConvention.Cdecl, EntryPoint = "virFree")]
private static extern int virFree(IntPtr ptr);
Comment on lines +43 to +44

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimise the use of unmanaged code.

Suggested change
[DllImport("libvirt.so.0", CallingConvention = CallingConvention.Cdecl, EntryPoint = "virFree")]
private static extern int virFree(IntPtr ptr);

Copilot uses AI. Check for mistakes.

public static int MonitorCommand(IntPtr domain, [MarshalAs(UnmanagedType.LPStr)]string cmd, ref string result, VirDomainQemuMonitorCommandFlags flags)
public static int MonitorCommand(IntPtr domain, string cmd, out string result, VirDomainQemuMonitorCommandFlags flags)
{
var sb = new StringBuilder();
//IntPtr buf = Marshal.AllocCoTaskMem(MaxStringLength + 1);
//Marshal.WriteByte(buf, MaxStringLength, 0);
//IntPtr buf2 = buf;
int ret = MonitorCommandImpl(domain, cmd, sb, (uint)flags);
if (ret == 0)
result = sb.ToString();
//result = MarshalHelper.ptrToString(buf);
//Marshal.FreeCoTaskMem(buf);
IntPtr resultPtr;
int ret = virDomainQemuMonitorCommand(domain, cmd, out resultPtr, flags);

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace this call with a call to managed code if possible.

Copilot uses AI. Check for mistakes.

if (ret < 0 || resultPtr == IntPtr.Zero)
{
result = null;
return ret;
}

result = Marshal.PtrToStringUTF8(resultPtr);

// Free memory allocated by libvirt
NativeFunctions.Free(resultPtr);

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory allocated by libvirt should be freed using virFree (imported at line 43-44), not NativeFunctions.Free which calls the Windows C runtime's free(). Using the wrong free function can cause crashes or memory corruption. Replace with virFree(resultPtr);

Suggested change
NativeFunctions.Free(resultPtr);
virFree(resultPtr);

Copilot uses AI. Check for mistakes.

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace this call with a call to managed code if possible.

Suggested change
NativeFunctions.Free(resultPtr);
virFree(resultPtr);

Copilot uses AI. Check for mistakes.

return ret;
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/Virtualization/Libvirt/Native/VirConnectDomainEventCallback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,23 @@ namespace IDNT.AppBasics.Virtualization.Libvirt.Native
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void VirConnectDomainEventCallback(IntPtr conn, IntPtr dom, [MarshalAs(UnmanagedType.I4)] VirDomainEventType evt, int detail, IntPtr opaque);


/// <summary>
/// The callback signature to use when registering for an event of type VIR_DOMAIN_EVENT_ID_GRAPHICS with virConnectDomainEventRegisterAny()
/// </summary>
/// <param name="conn">virConnect connection </param>
/// <param name="dom">The domain on which the event occured</param>

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'occured' to 'occurred'.

Suggested change
/// <param name="dom">The domain on which the event occured</param>
/// <param name="dom">The domain on which the event occurred</param>

Copilot uses AI. Check for mistakes.
/// <param name="phase">The specific phase of the graphics event</param>
/// <param name="local">Local address information</param>
/// <param name="remote">Remote address information</param>
/// <param name="authScheme">Authentication scheme used</param>
/// <param name="subject">Subject of the event</param>
/// <param name="opaque">Opaque user data</param>
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void VirConnectDomainEventGraphicsCallback(IntPtr conn, IntPtr dom, VirDomainEventGraphicsPhase phase,
ref VirDomainEventGraphicsAddress local,
ref VirDomainEventGraphicsAddress remote,
[MarshalAs(UnmanagedType.LPStr)] string authScheme,
ref VirDomainEventGraphicsSubject subject,
IntPtr opaque);
}
59 changes: 59 additions & 0 deletions src/Virtualization/Libvirt/Native/VirDomainEventGraphicsPhase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
Comment on lines +2 to +4

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports System.Collections.Generic, System.Linq, and System.Threading.Tasks are unused in this file. Only System and System.Runtime.InteropServices are required. Remove the unused imports.

Suggested change
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

Copilot uses AI. Check for mistakes.
using System.Runtime.InteropServices;

namespace IDNT.AppBasics.Virtualization.Libvirt.Native
{
public enum VirDomainEventGraphicsPhase
{

/// <summary>
/// Initial socket connection established
/// </summary>
VIR_DOMAIN_EVENT_GRAPHICS_CONNECT = 0,

/// <summary>
/// Authentication & setup completed
/// </summary>
VIR_DOMAIN_EVENT_GRAPHICS_INITIALIZE = 1,
/// <summary>
/// Client disconnected
/// </summary>
VIR_DOMAIN_EVENT_GRAPHICS_DISCONNECT = 2,
/// <summary>
/// Final socket disconnection
/// </summary>
VIR_DOMAIN_EVENT_GRAPHICS_LAST = 3
}

[StructLayout(LayoutKind.Sequential)]
public struct VirDomainEventGraphicsAddress
{
public int family;

[MarshalAs(UnmanagedType.LPStr)]
public string node;

[MarshalAs(UnmanagedType.LPStr)]
public string service;
}

[StructLayout(LayoutKind.Sequential)]
public struct VirDomainEventGraphicsSubjectIdentity
{
[MarshalAs(UnmanagedType.LPStr)]
public string type;

[MarshalAs(UnmanagedType.LPStr)]
public string name;
}

[StructLayout(LayoutKind.Sequential)]
public struct VirDomainEventGraphicsSubject
{
public int nidentity;
public IntPtr identities; // pointer to virDomainEventGraphicsSubjectIdentity
}
}
45 changes: 45 additions & 0 deletions src/Virtualization/Libvirt/Native/virDomainUndefineFlagsValues.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
Comment on lines +2 to +4

Copilot AI Nov 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports System.Collections.Generic, System.Linq, and System.Threading.Tasks are unused in this file. Only System is needed for the [Flags] attribute. Remove the unused imports to keep the code clean.

Suggested change
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

Copilot uses AI. Check for mistakes.

namespace IDNT.AppBasics.Virtualization.Libvirt.Native
{
/// <summary>
/// VirDomainUndefineFlagsValues
/// </summary>
[Flags]
public enum VirDomainUndefineFlagsValues
{
/// <summary>
/// Also remove any managed save
/// </summary>
VIR_DOMAIN_UNDEFINE_MANAGED_SAVE = 1,

/// <summary>
/// If last use of domain, then also remove any snapshot metadata
/// </summary>
VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA = 2,
/// <summary>
/// Also remove any nvram file
/// </summary>
VIR_DOMAIN_UNDEFINE_NVRAM = 4,
/// <summary>
/// Keep nvram file
/// </summary>
VIR_DOMAIN_UNDEFINE_KEEP_NVRAM = 8,
/// <summary>
/// If last use of domain, then also remove any checkpoint metadata
/// </summary>
VIR_DOMAIN_UNDEFINE_CHECKPOINTS_METADATA = 16,
/// <summary>
/// Also remove any TPM state
/// </summary>
VIR_DOMAIN_UNDEFINE_TPM = 32,
/// <summary>
/// Keep TPM state Future undefine control flags should come here.
/// </summary>
VIR_DOMAIN_UNDEFINE_KEEP_TPM = 64,

}
}