// Copyright 2024 WIT.COM Inc Licensed GPL 3.0 package main import ( "encoding/xml" "fmt" "os" "reflect" "go.wit.com/log" "libvirt.org/go/libvirtxml" ) func makeStandardXml(d *DropletT) *libvirtxml.Domain { log.Info("create new xml file for:", d.pb.Hostname) domcfg := &libvirtxml.Domain{} addDefaults(domcfg, "standard.x86") addDefaults(domcfg, "memory") addDefaults(domcfg, "network") addDefaults(domcfg, "spice") addDefaults(domcfg, "qcow") addDefaults(domcfg, d.pb.Hostname) return domcfg } func writeoutXml(domcfg *libvirtxml.Domain, filename string) bool { xmldoc, err := domcfg.Marshal() if err != nil { fmt.Println("can't make xml file error:\n", err) return false } outfile := "/tmp/" + filename + ".xml" regfile, _ := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE, 0666) fmt.Fprintln(regfile, xmldoc) log.Info("File is in", outfile) regfile.Close() return true } func setDiskFilename(domcfg *libvirtxml.Domain, filename string) { for i, x := range domcfg.Devices.Disks { // Create a new DomainDiskSourceFile struct newSource := &libvirtxml.DomainDiskSourceFile{ File: filename, // Set the file name here } // Assign it to the disk's source domcfg.Devices.Disks[i].Source.File = newSource // fmt.Printf("Disk Source %s\n", name) fmt.Printf("Disk Device %s\n", x.Source.File) } } func addDefaults(d *libvirtxml.Domain, filename string) { fullname := "resources/xml/" + filename + ".xml" pfile, err := resources.ReadFile(fullname) if err != nil { log.Println("ERROR:", err) return } err = d.Unmarshal(string(pfile)) if err != nil { log.Info("Marshal failed on file", filename) return } } func readXml(filename string) (*libvirtxml.Domain, error) { log.Verbose("parse xml file:", filename) pfile, err := os.ReadFile(filename) if err != nil { log.Println("ERROR:", err) return nil, err } domcfg := &libvirtxml.Domain{} err = domcfg.Unmarshal(string(pfile)) if err != nil { log.Info("Marshal failed on file", filename, err) return nil, ErrorParseXML } return domcfg, nil } func (d *DropletT) mergeXml(filename string) error { log.Info("merge xml file:", filename) pfile, err := os.ReadFile(filename) if err != nil { log.Println("ERROR:", err) return ErrorNoFile } err = d.xml.Unmarshal(string(pfile)) if err != nil { log.Info("Marshal failed on file", filename) return ErrorParseXML } return nil } func setSimpleDisk(domcfg *libvirtxml.Domain, filename string) { // Clear out the existing disks (if any) domcfg.Devices.Disks = nil // Define a new disk with "mynew.qcow2" newDisk := libvirtxml.DomainDisk{ Device: "disk", Driver: &libvirtxml.DomainDiskDriver{ Name: "qemu", Type: "qcow2", }, Source: &libvirtxml.DomainDiskSource{ File: &libvirtxml.DomainDiskSourceFile{ File: filename, }, }, Target: &libvirtxml.DomainDiskTarget{ Dev: "vda", Bus: "virtio", }, } // Add the new disk to the domain configuration domcfg.Devices.Disks = append(domcfg.Devices.Disks, newDisk) } func getMacs(domcfg *libvirtxml.Domain) []string { var macs []string // Iterate over the network interfaces and print the MAC addresses for _, iface := range domcfg.Devices.Interfaces { if iface.MAC != nil { // iface.MAC.Address = "aa:bb:aa:bb:aa:ff" fmt.Printf("MAC Address: %+v\n", iface.MAC) // fmt.Printf("Interface: %s, MAC Address: %s\n", iface.Target.Dev, iface.MAC.Address) macs = append(macs, iface.MAC.Address) } else { fmt.Printf("Interface: %s, MAC Address: not available\n", iface.Target.Dev) } } return macs } // removes all the ethernet interfaces func clearEthernet(domcfg *libvirtxml.Domain) { // Clear out the existing disks (if any) domcfg.Devices.Interfaces = nil } // add a new ethernet interface with mac assigned to bridge name func addEthernet(domcfg *libvirtxml.Domain, mac string, brname string) { // Define a new disk with "mynew.qcow2" newNet := libvirtxml.DomainInterface{ MAC: &libvirtxml.DomainInterfaceMAC{ Address: mac, }, Target: &libvirtxml.DomainInterfaceTarget{ Dev: brname, }, } // Add the new disk to the domain configuration domcfg.Devices.Interfaces = append(domcfg.Devices.Interfaces, newNet) } func setRandomMacs(domcfg *libvirtxml.Domain) { for i, x := range domcfg.Devices.Interfaces { // Create a new DomainDiskInterfaces struct newMac := &libvirtxml.DomainInterfaceMAC{ Address: "aa:bb:cc:dd:ee:ff", // make sure this is unique } // Assign it to the disk's source domcfg.Devices.Interfaces[i].MAC = newMac // fmt.Printf("Disk Source %s\n", name) // fmt.Printf("mac addr %+v\n", x.MAC) fmt.Printf("mac addr %s\n", x.MAC.Address) } } // go through the libvirt xml object and dump out everything // that is "standard". This is just a way to double check that // there might be something interesting in a VM // 'standard' here means what I think is standard func dumpNonStandardXML(domcfg *libvirtxml.Domain) { // Add more parts you are interested in fmt.Printf("CPU Model: %+v\n", domcfg.CPU) // dump all the clock stuff if it's standard var normalclock bool = true if domcfg.Clock.Offset != "utc" { normalclock = false } for i, t := range domcfg.Clock.Timer { // fmt.Printf("Test Clock Timer: %d , %s , %+v\n", i, t.Name, t) switch t.Name { case "rtc": if t.TickPolicy != "catchup" { fmt.Printf("Clock Name: %+v , %+v\n", i, t) normalclock = false } case "pit": if t.TickPolicy != "delay" { fmt.Printf("Clock Name: %+v , %+v\n", i, t) normalclock = false } case "hpet": if t.Present != "no" { fmt.Printf("Clock Name: %+v , %+v\n", i, t) normalclock = false } default: fmt.Printf("Clock Name: %+v , %+v\n", i, t) normalclock = false } } if normalclock { domcfg.Clock = nil } else { fmt.Printf("Clock was 'nonstandard' %+v\n", domcfg.Clock.Timer) } // probably just dump Features for now // fmt.Printf("Features: %+v\n", domcfg.Features) // fmt.Printf("Feature VMPort: %+v\n", domcfg.Features.VMPort) // ignore if ACPI is set or not var featurematch bool = true if domcfg.Features.ACPI != nil { domcfg.Features.ACPI = nil } else { featurematch = false } // ignore if APIC is set or not if domcfg.Features.APIC != nil { domcfg.Features.APIC = nil } else { featurematch = false } // what is VMPort anyway? if domcfg.Features.VMPort.State == "off" { domcfg.Features.VMPort = nil } else { featurematch = false } // screwit, if all three of those match just erase // this. not sure what uses it anyway but it's probably obscure // and I'm not using it on any of my machines right now // also, this is dumb that I'm doing this but I want to // fine tooth comb through this right now // also, I don't have a boss so nobody can tell me what to do if featurematch { domcfg.Features = nil } // fmt.Printf("Features: %+v\n", domcfg.Features) // for i, f := range domcfg.Features { // fmt.Printf("Feature: %+v , %+v\n", i, f) // } // these should always just be strings? domcfg.Name = "" domcfg.UUID = "" // todo: actually check these for anything different domcfg.Memory = nil domcfg.CurrentMemory = nil domcfg.VCPU = nil // this will move elsewhere in the protobuf someday if domcfg.OnPoweroff == "destroy" { domcfg.OnPoweroff = "" } if domcfg.OnCrash == "destroy" { domcfg.OnCrash = "" } if domcfg.OnReboot == "restart" { domcfg.OnReboot = "" } // same with PM. move to protobuf domcfg.PM = nil // only keep non-qemu stuff var qemu bool = true for _, disk := range domcfg.Devices.Disks { if disk.Driver.Name != "qemu" { fmt.Printf("- Disk: %s, Device: %s, Source: %s\n", disk.Device, disk.Driver.Name, disk.Source.File.File) fmt.Printf("FOUND NON QEMU DISK\n") fmt.Printf("FOUND NON QEMU DISKS\n") qemu = false } else { } } if qemu { domcfg.Devices.Disks = nil } else { // fmt.Printf("FOUND NON QEMU DISKS\n") } // network interfaces get processed elsewhere domcfg.Devices.Interfaces = nil // look for strange stuff here var normalPCI bool = true var keepPCI []libvirtxml.DomainController for _, controller := range domcfg.Devices.Controllers { switch controller.Type { case "usb": switch controller.Model { case "ich9-ehci1": fmt.Printf("OK USB: %s, %d\n", controller.Model, *controller.Index) case "ich9-uhci1": fmt.Printf("OK USB: %s, %d\n", controller.Model, *controller.Index) case "ich9-uhci2": fmt.Printf("OK USB: %s, %d\n", controller.Model, *controller.Index) case "ich9-uhci3": fmt.Printf("OK USB: %s, %d\n", controller.Model, *controller.Index) default: keepPCI = append(keepPCI, controller) normalPCI = false fmt.Printf("USB: %s, %d\n", controller.Model, *controller.Index) // Domain:0xc0002d2760 Bus:0xc0002d2768 Slot:0xc0002d2770 Function:0xc0002d2778 MultiFunction: pci := controller.Address.PCI fmt.Printf("USB: Domain: %+v Slot %d Function %d\n", *pci.Domain, *pci.Slot, *pci.Function) } case "ide": fmt.Printf("IGNORE IDE\n") case "virtio-serial": fmt.Printf("IGNORE virtio-serial\n") case "scsi": switch controller.Model { case "lsilogic": fmt.Printf("IGNORE SCSI: lsilogic\n") default: keepPCI = append(keepPCI, controller) normalPCI = false } case "pci": fmt.Printf("PCI: %s, %d\n", controller.Model, *controller.Index) // Domain:0xc0002d2760 Bus:0xc0002d2768 Slot:0xc0002d2770 Function:0xc0002d2778 MultiFunction: pci := controller.Address.PCI fmt.Printf("PCI: Domain: %+v Slot %d Function %d\n", *pci.Domain, *pci.Slot, *pci.Function) normalPCI = false keepPCI = append(keepPCI, controller) default: fmt.Printf("? %s: %+v\n", controller.Type, controller) normalPCI = false keepPCI = append(keepPCI, controller) } } if normalPCI { domcfg.Devices.Controllers = nil } else { domcfg.Devices.Controllers = keepPCI } // ignore serial and console domcfg.Devices.Serials = nil domcfg.Devices.Consoles = nil // ignore sound domcfg.Devices.Sounds = nil // ignore input domcfg.Devices.Inputs = nil // ignore MemoryBalloon. This is cool, but no mortal humans // are going to use it at this point. By that I mean me. // someday this will be in protobuf? domcfg.Devices.MemBalloon = nil if domcfg.Devices.Emulator == "/usr/bin/qemu-system-x86_64" { domcfg.Devices.Emulator = "" } // ignore Graphics == Spice when AutoPort = 'yes' var normalSpice bool = true if domcfg.Devices.Graphics != nil { for _, g := range domcfg.Devices.Graphics { if g.Spice != nil { // fmt.Printf("Graphics: %d %+v\n", i, g) var s *libvirtxml.DomainGraphicSpice s = g.Spice // fmt.Printf("Spice: %d %+v %s\n", i, s, s.AutoPort) if s.AutoPort != "yes" { normalSpice = false } } else { normalSpice = false } } } if normalSpice { domcfg.Devices.Graphics = nil } // ignore Channels == SpiceVMC normalSpice = true if domcfg.Devices.Channels != nil { for _, c := range domcfg.Devices.Channels { if c.Source != nil { s := c.Source if s != nil { // fmt.Printf("Channels: %+v\n", s.SpiceVMC) } else { fmt.Printf("? Channels: %+v\n", c) normalSpice = false } } else { fmt.Printf("? Channels: %+v\n", c) normalSpice = false } } } if normalSpice { domcfg.Devices.Channels = nil } var domain libvirtxml.DomainDeviceList t := reflect.TypeOf(domain) fmt.Println("Fields in libvirtxml.DomainDeviceList:") for i := 0; i < t.NumField(); i++ { field := t.Field(i) fmt.Println(field.Name) } updatedXML, err := xml.MarshalIndent(domcfg, "", " ") if err != nil { fmt.Printf("Failed to marshal updated XML: %v\n", err) os.Exit(-1) } // Print the updated XML to verify fmt.Println(string(updatedXML)) os.Exit(-1) }