From de395d47b1dc442b0bfe907d8552b48ab06060f1 Mon Sep 17 00:00:00 2001 From: Jeeves Date: Tue, 25 Jun 2024 04:24:08 -0600 Subject: [PATCH] basic zig libvirt structure --- server/flakes/windows/test/flake.lock | 8 +- server/flakes/windows/test/result | 1 + server/flakes/windows/test/volume | 1 + server/src/libvirt.zig | 207 +++++++++++++++++++++----- server/src/main.zig | 67 +++++---- 5 files changed, 210 insertions(+), 74 deletions(-) create mode 120000 server/flakes/windows/test/result create mode 120000 server/flakes/windows/test/volume diff --git a/server/flakes/windows/test/flake.lock b/server/flakes/windows/test/flake.lock index 8052ddb..d0e4fe8 100644 --- a/server/flakes/windows/test/flake.lock +++ b/server/flakes/windows/test/flake.lock @@ -126,11 +126,11 @@ "zig2nix": "zig2nix" }, "locked": { - "lastModified": 1719259792, - "narHash": "sha256-XX8MuiwtbYvIfb2M7l7gG5wT8Lp5HFS7KMuWEtENnF0=", + "lastModified": 1719259899, + "narHash": "sha256-DgYQAFhBPxtx3PRGMqR5AwfQRAiOGHNdnPTtlnedZVA=", "ref": "refs/heads/master", - "rev": "19ab6d3c7058aa553843e4cb5282e4d970d0e4fd", - "revCount": 5, + "rev": "bb3c001b003c3f4104b32e58289deb00e96fb82a", + "revCount": 6, "type": "git", "url": "https://git.jeevio.xyz/jeeves/oslib" }, diff --git a/server/flakes/windows/test/result b/server/flakes/windows/test/result new file mode 120000 index 0000000..efe66b8 --- /dev/null +++ b/server/flakes/windows/test/result @@ -0,0 +1 @@ +/nix/store/ag7g2kil11yp56ndvhi84azjb6wj65mx-NixVirt-volume-wintest.qcow2 \ No newline at end of file diff --git a/server/flakes/windows/test/volume b/server/flakes/windows/test/volume new file mode 120000 index 0000000..efe66b8 --- /dev/null +++ b/server/flakes/windows/test/volume @@ -0,0 +1 @@ +/nix/store/ag7g2kil11yp56ndvhi84azjb6wj65mx-NixVirt-volume-wintest.qcow2 \ No newline at end of file diff --git a/server/src/libvirt.zig b/server/src/libvirt.zig index a851cb4..dd7bf82 100644 --- a/server/src/libvirt.zig +++ b/server/src/libvirt.zig @@ -61,51 +61,199 @@ pub const VirError = error{ // TODO rest }; +fn string(str: [*c]const u8) []const u8 { + return mem.span(str); +} + +fn intFromFlags(comptime T: type, flags: []const T) c_uint { + var flags_int: c_uint = 0; + for (flags) |f| flags_int |= @intFromEnum(f); + return flags_int; +} + +fn Iterator(comptime T: type, comptime Ptr: type, comptime freeFn: *const fn (Ptr) callconv(.C) c_int) type { + return struct { + list: [*]Ptr, + num: c_int, + curr: usize, + + pub fn init( + comptime PP: type, // Parent Pointer + comptime P: type, // Parent + comptime F: type, // Flags + parent: *const P, + flags: []const F, + initFn: *const fn (?*PP, [*c][*c]Ptr, c_uint) callconv(.C) c_int, + ) !Self { + var list: [*]Ptr = undefined; + const num = initFn(parent.ptr, @ptrCast(&list), intFromFlags(F, flags)); + return if (num < 0) handleError() else .{ + .list = list, + .num = num, + .curr = 0, + }; + } + pub fn deinit(self: *Self) void { + var i: usize = 0; + while (i < self.num) : (i += 1) _ = freeFn(self.list[i]); + } + pub fn first(self: *Self) T { + self.curr = 0; + return .{ .ptr = self.list[self.curr] }; + } + pub fn next(self: *Self) ?T { + if (self.curr >= self.num) return null; + const ptr = self.list[self.curr]; + self.curr += 1; + return .{ .ptr = ptr }; + } + const Self = @This(); + }; +} + pub const Connection = struct { - conn: c.virConnectPtr, + ptr: c.virConnectPtr, allocator: mem.Allocator, pub fn connect(uri: []const u8, allocator: mem.Allocator) VirError!Connection { const connection = c.virConnectOpenAuth(@ptrCast(uri), c.virConnectAuthPtrDefault, 0); if (connection) |conn| return .{ - .conn = conn, + .ptr = conn, .allocator = allocator, } else return handleError(); } pub fn close(self: *const Connection) void { - _ = c.virConnectClose(self.conn); + _ = c.virConnectClose(self.ptr); } pub fn getURI(self: *const Connection) ![]u8 { - const uri = c.virConnectGetURI(self.conn); + const uri = c.virConnectGetURI(self.ptr); defer std.c.free(uri); - return try self.allocator.dupe(u8, mem.span(uri)); + const str = string(uri); + return if (str.len == 0) handleError() else try self.allocator.dupe(u8, str); } pub fn freeURI(self: *const Connection, uri: []u8) void { self.allocator.free(uri); } + pub fn numOfPools(self: *const Connection) !u32 { + const num = c.virConnectNumOfStoragePools(self.ptr); + return if (num < 0) handleError() else @intCast(num); + } + pub fn numOfDefinedPools(self: *const Connection) !u32 { + const num = c.virConnectNumOfDefinedStoragePools(self.ptr); + return if (num < 0) handleError() else @intCast(num); + } + + pub fn iteratePools(self: *const Connection, flags: []const Pool.ListFlags) !Pool.PoolIterator { + return Pool.PoolIterator.init( + c.virConnect, + Connection, + Pool.ListFlags, + self, + flags, + c.virConnectListAllStoragePools, + ); + } + pub fn numOfDomains(self: *const Connection) !u32 { - return @intCast(c.virConnectNumOfDomains(self.conn)); + const num = c.virConnectNumOfDomains(self.ptr); + return if (num < 0) handleError() else @intCast(num); } pub fn numOfDefinedDomains(self: *const Connection) !u32 { - return @intCast(c.virConnectNumOfDefinedDomains(self.conn)); + const num = c.virConnectNumOfDefinedDomains(self.ptr); + return if (num < 0) handleError() else @intCast(num); } - pub fn iterateDomains(self: *const Connection, flags: []const Domain.Flags) Domain.Iterator { - var list: [*]c.virDomainPtr = undefined; - var flags_int: c_uint = 0; - for (flags) |f| flags_int |= @intFromEnum(f); - const num = c.virConnectListAllDomains(self.conn, @ptrCast(&list), flags_int); + pub fn iterateDomains(self: *const Connection, flags: []const Domain.ListFlags) !Domain.DomainIterator { + return Domain.DomainIterator.init( + c.virConnect, + Connection, + Domain.ListFlags, + self, + flags, + c.virConnectListAllDomains, + ); + } +}; + +pub const Pool = struct { + ptr: c.virStoragePoolPtr, + arena: heap.ArenaAllocator, + + pub fn init(ptr: c.virStoragePoolPtr, allocator: mem.Allocator) Pool { return .{ - .list = list, - .num = num, - .curr = 0, + .ptr = ptr, + .arena = heap.ArenaAllocator.init(allocator), }; } + pub fn deinit(self: *const Pool) void { + self.arena.deinit(); + } + + pub fn createVolume(pool: *const Pool, xml: []const u8, flags: []Volume.Flags) !Volume { + const volume = c.virStorageVolCreateXML(pool, xml, intFromFlags(Volume.CreateFlags, flags)); + return .{ .ptr = volume }; + } + + pub fn iterateVolumes(self: *const Pool, flags: []const ListFlags) Volume.VolumeIterator { + return Volume.VolumeIterator.init( + c.virStorageVolumePtr, + Pool, + Volume.ListFlags, + self, + flags, + c.virStoragePoolListAllVolumes, + ); + } + + pub const ListFlags = enum(c_uint) { + Inactive = 1 << 0, + Active = 1 << 1, + Persistent = 1 << 2, + Transient = 1 << 3, + Autostart = 1 << 4, + NoAutostart = 1 << 5, + Dir = 1 << 6, + Fs = 1 << 7, + NetFs = 1 << 8, + Logical = 1 << 9, + Disk = 1 << 10, + Iscsi = 1 << 11, + Scsi = 1 << 12, + Mpath = 1 << 13, + Rbd = 1 << 14, + Sheepdog = 1 << 15, + Gluster = 1 << 16, + Zfs = 1 << 17, + Vstorage = 1 << 18, + IscsiDirect = 1 << 19, + }; + + pub const PoolIterator = Iterator(Pool, c.virStoragePoolPtr, c.virStoragePoolFree); + + pub const Volume = struct { + ptr: c.virStorageVolPtr, + + pub fn getName(self: *const Volume) ![]const u8 { + const name = c.virStorageVolGetName(self.ptr); + defer std.c.free(name); + const str = string(name); + return if (str.len == 0) handleError() else try self.arena.allocator().dupe(u8, str); + } + + pub const ListFlags = enum(c_uint) {}; + pub const CreateFlags = enum(c_uint) { + PreallocMetadata = 1 << 0, + Reflink = 1 << 1, + Validate = 1 << 2, + }; + + pub const VolumeIterator = Iterator(Volume, c.virStorageVolPtr, c.virStorageVolFree); + }; }; pub const Domain = struct { @@ -121,31 +269,10 @@ pub const Domain = struct { return if (active == 0) false else true; } - pub const Flags = enum(c_uint) { - ListDomainsActive = c.VIR_CONNECT_LIST_DOMAINS_ACTIVE, - ListDomainsInactive = c.VIR_CONNECT_LIST_DOMAINS_INACTIVE, + pub const ListFlags = enum(c_uint) { + Active = c.VIR_CONNECT_LIST_DOMAINS_ACTIVE, + Inactive = c.VIR_CONNECT_LIST_DOMAINS_INACTIVE, }; - pub const Iterator = struct { - list: [*]c.virDomainPtr, - num: c_int, - curr: usize, - - pub fn deinit(self: *Iterator) void { - var i: usize = 0; - while (i < self.num) : (i += 1) _ = c.virDomainFree(self.list[i]); - } - - pub fn first(self: *Iterator) Domain { - self.curr = 0; - return .{ .ptr = self.list[self.curr] }; - } - - pub fn next(self: *Iterator) ?Domain { - if (self.curr >= self.num) return null; - const ptr = self.list[self.curr]; - self.curr += 1; - return .{ .ptr = ptr }; - } - }; + pub const DomainIterator = Iterator(Domain, c.virDomainPtr, c.virDomainFree); }; diff --git a/server/src/main.zig b/server/src/main.zig index 81affb0..af501dc 100644 --- a/server/src/main.zig +++ b/server/src/main.zig @@ -10,44 +10,51 @@ pub fn main() !void { defer _ = gpa.deinit(); const allocator = gpa.allocator(); - const connection = try libvirt.Connection.connect("qemu+ssh://jeeves@evil.lan/system", allocator); + const connection = try libvirt.Connection.connect("qemu:///system", allocator); defer connection.close(); - var nix = process.Child.init(&[_][]const u8{ "nix", "build", ".#volume" }, allocator); - nix.cwd_dir = try fs.cwd().openDir("flakes/windows/test", .{}); + var flake_dir = try fs.cwd().openDir("flakes/windows/test", .{}); + defer flake_dir.close(); + + var nix = process.Child.init(&[_][]const u8{ "nix", "build", ".#volume", "-o", "volume" }, allocator); + nix.cwd_dir = flake_dir; + _ = try nix.spawnAndWait(); + nix.argv = &[_][]const u8{ "nix", "build", ".#beforeInstall", "-o", "beforeInstall" }; + _ = try nix.spawnAndWait(); + nix.argv = &[_][]const u8{ "nix", "build", ".#afterInstall", "-o", "afterInstall" }; + _ = try nix.spawnAndWait(); + nix.argv = &[_][]const u8{ "nix", "build", ".#beforeBoot", "-o", "beforeBoot" }; + _ = try nix.spawnAndWait(); + nix.argv = &[_][]const u8{ "nix", "build", ".#afterBoot", "-o", "afterBoot" }; _ = try nix.spawnAndWait(); - // const uri = try connection.getURI(); - // defer connection.freeURI(uri); - // std.debug.print("uri: {s}\n", .{uri}); + // const volume_def = try flake_dir.openFile("volume", .{}); + // defer volume_def.close(); + // const volume_xml = try volume_def.readToEndAlloc(allocator, 1024 * 1024); + // defer allocator.free(volume_xml); + // const volume = try connection.defineVolume(volume_xml); - // const num_active = try connection.numOfDomains(); - // const num_inactive = try connection.numOfDefinedDomains(); - // std.debug.print("active: {d}, inactive: {d}\n", .{ num_active, num_inactive }); + // -------------- - // var domain_iter = connection.iterateDomains(&[_]libvirt.Domain.Flags{ - // libvirt.Domain.Flags.ListDomainsActive, - // libvirt.Domain.Flags.ListDomainsInactive, - // }); - // defer domain_iter.deinit(); + const uri = try connection.getURI(); + defer connection.freeURI(uri); + std.debug.print("uri: {s}\n", .{uri}); - // while (domain_iter.next()) |domain| { - // const active = domain.isActive(); - // const name = domain.getName(); - // std.debug.print("name: {s}, active: {any}\n", .{ name, active }); - // } + const num_active = try connection.numOfDomains(); + const num_inactive = try connection.numOfDefinedDomains(); + std.debug.print("active: {d}, inactive: {d}\n", .{ num_active, num_inactive }); - // var flake = try fs.cwd().createFile("flake.nix", .{}); - // defer flake.close(); - // try flake.writeAll( - // \\{ - // \\ description = "vm-flake"; - // \\ inputs.oslib.url = "git+https://git.jeevio.xyz/jeeves/oslib"; - // \\ outputs = { self, oslib }: oslib.vmFlake { - // \\ - // \\ }; - // \\} - // ); + var domain_iter = try connection.iterateDomains(&[_]libvirt.Domain.ListFlags{ + libvirt.Domain.ListFlags.Active, + libvirt.Domain.ListFlags.Inactive, + }); + defer domain_iter.deinit(); + + while (domain_iter.next()) |domain| { + const active = domain.isActive(); + const name = domain.getName(); + std.debug.print("name: {s}, active: {any}\n", .{ name, active }); + } } pub const DomainSpec = struct {