// Copyright 2024 WIT.COM Inc Licensed GPL 3.0 // just a monstrous hack to get things working. // todo: rework this? // 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 package virtigolib import ( "encoding/xml" "fmt" "os" "go.wit.com/log" "libvirt.org/go/libvirtxml" ) func DumpNonStandardXML(domcfg *libvirtxml.Domain) (string, error) { var result string // dump type if domcfg.Type == "kvm" { domcfg.Type = "" } else { fmt.Printf("type: %+v\n", domcfg.Type) } // dump normal OS settings var standardOS bool = false if domcfg.OS != nil { if domcfg.OS.Type != nil { // OS Type: &{Arch:x86_64 Machine:pc-i440fx-5.2 Type:hvm} t := domcfg.OS.Type if t.Arch == "x86_64" || t.Machine == "pc-i440fx-5.2" { standardOS = true } } if domcfg.OS.NVRam != nil { result += fmt.Sprintf("nvram: %+v\n", domcfg.OS.NVRam) standardOS = false } if domcfg.OS.Loader != nil { result += fmt.Sprintf("loader: %+v\n", domcfg.OS.Loader) standardOS = false } } if standardOS { domcfg.OS = nil } else { fmt.Printf("OS: %+v\n", domcfg.OS) fmt.Printf("OS Type: %+v\n", domcfg.OS.Type) } // ignore XMLName and IOThreads probably // skip is hard coded in isDomainEmpty() function // fmt.Printf("XMLName: %+v\n", domcfg.XMLName) // fmt.Printf("IOThreads: %+v\n", domcfg.IOThreads) // 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 != nil { 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 // is this always "host-passthrough" and "host-model"? // only Fabrice knows :) if domcfg.CPU != nil { switch domcfg.CPU.Mode { case "host-passthrough": domcfg.CPU = nil case "host-model": domcfg.CPU = nil case "custom": updatedXML, _ := xml.MarshalIndent(domcfg.CPU, "", " ") log.Info("Ignoring custom CPU Start") fmt.Println(string(updatedXML)) log.Info("Ignoring custom CPU End (--xml-ignore-cpu=true)") //if argv.IgnoreCpu { // domcfg.CPU = nil //} default: fmt.Printf("unknown CPU: %+v\n", domcfg.CPU) fmt.Printf("unknown CPU Model: %+v\n", domcfg.CPU.Model) fmt.Printf("unknown CPU Mode: %+v\n", domcfg.CPU.Mode) updatedXML, _ := xml.MarshalIndent(domcfg.CPU, "", " ") log.Info("Non-Standard XML Start") fmt.Println(string(updatedXML)) log.Info("Non-Standard XML End") } } var secnormal bool = true if len(domcfg.SecLabel) != 0 { for _, sec := range domcfg.SecLabel { switch sec.Model { case "apparmor": // log.Info("ignoring SecLabel apparmor. not supported yet") // log.Info("you must set this later if you need this") // xmlAny(sec) case "dac": // log.Info("ignoring SecLabel dac. not supported yet") // log.Info("you must set this later if you need this") // xmlAny(sec) default: fmt.Printf("unknown SecLabel: %+v\n", sec) fmt.Printf("unknown SecLabel.Model: %+v\n", sec.Model) xmlAny(sec) secnormal = false } } } if secnormal { domcfg.SecLabel = nil } // ignore Metadata // this is probably something about what kind of OS you might be running // todo: get this directly from the disk image if domcfg.Metadata != nil { var s string s = domcfg.Metadata.XML result += fmt.Sprintln("Not saving Domain.Metadata.XML:", s) log.Info("Not saving Domain.Metadata.XML:", s) log.Info("todo: get this from disk image") domcfg.Metadata = nil } // ignore Resource if domcfg.Resource != nil { if domcfg.Resource.Partition == "/machine" { domcfg.Resource = nil } else { result += fmt.Sprintf("non-standard Domain.Resource: %+v\n", domcfg.Resource) fmt.Printf("non-standard Domain.Resource: %+v\n", domcfg.Resource) } } // ignore Resource if domcfg.ID != nil { // ignore domain id domcfg.ID = nil } // this will move elsewhere in the protobuf someday // ignore all these for now if domcfg.OnPoweroff != "" { // normally "destroy" domcfg.OnPoweroff = "" } if domcfg.OnCrash != "" { // normally "restart", often "destroy" domcfg.OnCrash = "" } if domcfg.OnReboot != "" { // normally "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": case "piix3-uhci": case "qemu-xhci": // 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 "sata": // fmt.Printf("SATA: %s, %d\n", controller.Model, *controller.Index) // fmt.Printf("SATA: %+v\n", controller) case "scsi": switch controller.Model { case "virtio-scsi": // fmt.Printf("IGNORE SCSI: lsilogic\n") case "lsilogic": // fmt.Printf("IGNORE SCSI: lsilogic\n") default: keepPCI = append(keepPCI, controller) normalPCI = false } case "pci": // these are the strings I've found so far switch controller.Model { case "pci-root": case "pcie-root": case "pcie-root-port": case "pcie-to-pci-bridge": default: fmt.Printf("PCI: %s, %d\n", controller.Model, *controller.Index) // Domain:0xc0002d2760 Bus:0xc0002d2768 Slot:0xc0002d2770 Function:0xc0002d2778 MultiFunction: if controller.Address == nil { fmt.Printf("PCI: controller.Address = nil\n") } else { 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("? controllerType: %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 i, g := range domcfg.Devices.Graphics { if g.VNC != nil { // ignore vnc settings // fmt.Printf("Ignore Graphics VNC settings: %d %+v\n", i, g) continue } if g.Spice != nil { // this is all moved to updateDroplet() // this is a spice definition, just ignore it // because port mappings and network access will be handled // somewhere else someday // 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" { // should ignore either way } else { // print out, but ignore the port number // fmt.Printf("Spice Port = %d\n", s.Port) } continue } // figure out what to do with non-spice stuff fmt.Printf("Unknown Graphics: %d %+v\n", i, g) normalSpice = false } } if normalSpice { domcfg.Devices.Graphics = nil } // blank out emulator. should be in dom0 switch domcfg.Devices.Emulator { case "": domcfg.Devices.Emulator = "" case "/usr/bin/kvm": domcfg.Devices.Emulator = "" case "/usr/bin/kvm-spice": domcfg.Devices.Emulator = "" default: fmt.Printf("Unknown Emulator: %s\n", domcfg.Devices.Emulator) } // 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 } // this is probably for spice to have keyboard and mouse input normalSpice = true if domcfg.Devices.RedirDevs != nil { for _, c := range domcfg.Devices.RedirDevs { s := c.Source if s != nil { if s.SpiceVMC != nil { // this is the normal USB redirection (I guess) } else { normalSpice = false } } else { normalSpice = false } // fmt.Printf("? RedirDevs: %+v\n", c) // fmt.Printf("? RedirDevs Source: %+v\n", s) // fmt.Printf("? RedirDevs SpiceVMC: %d\n", *s.SpiceVMC) // fmt.Printf("? RedirDevs Address: %+v\n", c.Address) // fmt.Printf("? RedirDevs USB: %+v\n", c.Address.USB) } } if normalSpice { domcfg.Devices.RedirDevs = nil } var normalRNGs bool = true if domcfg.Devices.RNGs != nil { for _, rng := range domcfg.Devices.RNGs { if rng.Model == "virtio" { // nothing to do for this } else { fmt.Printf("? RNGs: %+v\n", rng) normalRNGs = false } } } if normalRNGs { domcfg.Devices.RNGs = nil } // don't copy this over here yet. // probably most domU's don't really use/need it set to what is in the XML var normalVideo bool = true if domcfg.Devices.Videos != nil { for _, v := range domcfg.Devices.Videos { switch v.Model.Type { case "qxl": if v.Model.VRam == 65536 { // standard qxl video } else { fmt.Printf("? Video: %+v\n", v) fmt.Printf("? Video Model: %+v\n", v.Model) normalVideo = false } case "cirrus": case "virtio": // this should always be standard //fmt.Printf("? Video: %+v\n", v) //fmt.Printf("? Video Model: %+v\n", v.Model) //fmt.Printf("? Video Address: %+v\n", v.Address) //fmt.Printf("? Video PCI: %+v\n", v.Address.PCI) default: fmt.Printf("? Video: %+v\n", v) fmt.Printf("? Video Model: %+v\n", v.Model) normalVideo = false } } } if normalVideo { domcfg.Devices.Videos = nil } var normalAudio bool = true if domcfg.Devices.Audios != nil { for _, a := range domcfg.Devices.Audios { if a.SPICE != nil { // nothing to do for this } else { fmt.Printf("? Audio: %+v\n", a) normalAudio = false } } } if normalAudio { domcfg.Devices.Audios = nil } var normalWatchdog bool = true if domcfg.Devices.Watchdogs != nil { for _, dog := range domcfg.Devices.Watchdogs { // fmt.Printf("? Watchdog: %+v\n", dog) if dog.Model == "itco" { // nothing to do for this } else { fmt.Printf("? Watchdog: %+v\n", dog) normalWatchdog = false } } } if normalWatchdog { domcfg.Devices.Watchdogs = nil } end, err := finalEmptyCheck(domcfg) if end != "" { result += fmt.Sprintln(end) } return result, err } // this tries the final zero'ing out of the XML // todo: if this fails, put the remaining XML in the protobuf file? func finalEmptyCheck(domcfg *libvirtxml.Domain) (string, error) { // dumpLibvirtxmlDomainNames() if libvirtxmlDomainDevicesEmpty(*domcfg.Devices) { // fmt.Println("Domain Devices are empty") domcfg.Devices = nil } else { return warnUserOfNonStandardXML(domcfg) } if libvirtxmlDomainEmpty(*domcfg) { domcfg = nil return warnUserOfNonStandardXML(domcfg) } final, err := warnUserOfNonStandardXML(domcfg) if err != nil { fmt.Printf("todo: improve this libvirtXML parsing. %v\n", err) os.Exit(-1) } return final, nil } func warnUserOfNonStandardXML(domcfg *libvirtxml.Domain) (string, error) { updatedXML, err := xml.MarshalIndent(domcfg, "", " ") if err != nil { fmt.Printf("Failed to marshal updated XML: %v\n", err) return "", err } final := string(updatedXML) if final == "" { // everything seems to have been parsed pretty standard return "", nil } log.Info("Non-Standard XML Start") fmt.Println(string(updatedXML)) log.Info("Non-Standard XML End") log.Info("") log.Info("This XML must be removed by hand. Put this in the protobuf?") return string(updatedXML), nil }