Compare commits

...

6 Commits

Author SHA1 Message Date
0248fa5670 removed yml file 2025-03-30 01:46:48 -04:00
7ae90eca05 fix go mod 2025-03-30 01:39:00 -04:00
b437caaf57 working on multiple nics 2025-03-30 01:38:01 -04:00
c0cabf5c57 updated license 2025-03-28 12:07:12 -04:00
7384a56b07 resturctured code 2025-03-28 12:03:19 -04:00
a0f6f3a6a0 work on stable version 2025-03-28 11:09:50 -04:00
36 changed files with 852 additions and 496 deletions

178
LICENSE
View File

@ -1,21 +1,165 @@
MIT License GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (c) 2025 Ryan Ward Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all This version of the GNU Lesser General Public License incorporates
copies or substantial portions of the Software. the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 0. Additional Definitions.
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE As used herein, "this License" refers to version 3 of the GNU Lesser
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER General Public License, and the "GNU GPL" refers to version 3 of the GNU
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, General Public License.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. "The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

4
go.mod
View File

@ -1,6 +1,6 @@
module github.com/rayaman/go-qemu module github.com/rayaman/go-qemu
go 1.22.0 go 1.23
require ( require (
github.com/abdfnx/gosh v0.4.0 github.com/abdfnx/gosh v0.4.0
@ -8,6 +8,7 @@ require (
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/structs v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
@ -25,4 +26,5 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.6.0 // indirect golang.org/x/crypto v0.6.0 // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/sys v0.20.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
) )

5
go.sum
View File

@ -1,6 +1,8 @@
github.com/abdfnx/gosh v0.4.0 h1:cBvmHw8yV3oXiAcORpi7Oip+MT6/5HBMOrvpn0cZNYM= github.com/abdfnx/gosh v0.4.0 h1:cBvmHw8yV3oXiAcORpi7Oip+MT6/5HBMOrvpn0cZNYM=
github.com/abdfnx/gosh v0.4.0/go.mod h1:MUTJRMc7FpBb/bFnZ2U0hj48zj8284J1cS3AUiIRZ/o= github.com/abdfnx/gosh v0.4.0/go.mod h1:MUTJRMc7FpBb/bFnZ2U0hj48zj8284J1cS3AUiIRZ/o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
@ -8,6 +10,7 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
@ -81,3 +84,5 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

134
main.go
View File

@ -12,16 +12,24 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime"
"syscall"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/melbahja/goph" "github.com/melbahja/goph"
"github.com/rayaman/go-qemu/pkg/image" "github.com/rayaman/go-qemu/pkg/v1/image"
"github.com/rayaman/go-qemu/pkg/image/formats" "github.com/rayaman/go-qemu/pkg/v1/image/formats"
"github.com/rayaman/go-qemu/pkg/types" "github.com/rayaman/go-qemu/pkg/v1/system"
"github.com/rayaman/go-qemu/pkg/v1/types"
"github.com/rayaman/go-qemu/pkg/v1/types/accel"
"github.com/rayaman/go-qemu/pkg/v1/types/arch"
"github.com/rayaman/go-qemu/pkg/v1/types/boot"
"github.com/rayaman/go-qemu/pkg/v1/types/disk"
"github.com/rayaman/go-qemu/pkg/v1/types/memory"
"github.com/rayaman/go-qemu/pkg/v1/types/nic"
"github.com/rayaman/go-qemu/pkg/v1/types/smp"
"github.com/shirou/gopsutil/v3/process" "github.com/shirou/gopsutil/v3/process"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"sigs.k8s.io/yaml"
) )
type Credentials struct { type Credentials struct {
@ -34,14 +42,18 @@ type Credentials struct {
type Controller struct { type Controller struct {
cancel context.CancelFunc cancel context.CancelFunc
Stdout *bytes.Buffer cmd *exec.Cmd
Stderr *bytes.Buffer err []error
} }
func (c *Controller) Stop() { func (c *Controller) Stop() {
c.cancel() c.cancel()
} }
func (c *Controller) Errors() []error {
return c.err
}
func KillProcess(name string) error { func KillProcess(name string) error {
processes, err := process.Processes() processes, err := process.Processes()
if err != nil { if err != nil {
@ -59,45 +71,38 @@ func KillProcess(name string) error {
return fmt.Errorf("process not found") return fmt.Errorf("process not found")
} }
func StartMachine(machine types.Machine) *Controller { func StartMachine(machine system.Machine) *Controller {
ctrl := &Controller{} ctrl := &Controller{}
go func() { go func() {
command := machine.Expand()
ctx, cfunc := context.WithCancel(context.TODO()) ctx, cfunc := context.WithCancel(context.TODO())
ctrl.cancel = cfunc ctrl.cancel = cfunc
var stdout bytes.Buffer var stdout bytes.Buffer
var stderr bytes.Buffer var stderr bytes.Buffer
var cmd *exec.Cmd cmd := exec.Command(fmt.Sprintf("qemu-system-%v", machine.Arch), machine.Expand()...)
if runtime.GOOS == "windows" { cmd.Stdout = &stderr
cmd = exec.Command("powershell.exe", command) cmd.Stderr = &stdout
} else { ctrl.cmd = cmd
cmd = exec.Command("bash", "-c", command)
}
cmd.Stdout = &stdout
cmd.Stderr = &stderr
ctrl.Stderr = &stderr
ctrl.Stdout = &stdout
cmd.SysProcAttr = &syscall.SysProcAttr{}
err := cmd.Start() err := cmd.Start()
if err != nil { if err != nil {
panic(err) fmt.Println("Error:", err)
} }
fmt.Println("running...", cmd.Process.Pid) go func() {
err := cmd.Wait()
//syscall.Kill(cmd.Process.Pid, syscall.SIGTERM) if err != nil {
fmt.Println("Error:", err)
}
cfunc()
}()
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
fmt.Println("Killing Process") fmt.Println("Killing Process")
//err := KillProcess(string(machine.Arch))
//err := cmd.Process.Signal(syscall.SIGTERM)
err := cmd.Process.Kill() err := cmd.Process.Kill()
if err != nil { if err != nil {
panic(err) fmt.Println("Error:", err)
} }
return return
default:
} }
} }
}() }()
@ -120,52 +125,67 @@ func main() {
// if err != nil { // if err != nil {
// panic(err) // panic(err)
// } // }
machine := types.Machine{ machine := system.Machine{
Arch: types.X86_64, Arch: arch.X86_64,
Cores: types.SMP{ Cores: smp.SMP{
Cpus: 4, Cpus: 4,
}, },
Boot: types.Boot{ Boot: boot.Boot{
Order: []types.Drives{types.HARDDISK}, Order: []boot.Drives{boot.HARDDISK},
Menu: types.Off, Menu: types.Off,
}, },
Memory: types.Memory{ Memory: memory.Memory{
Size: 4096, Size: 4096,
}, },
Accel: types.Accel{ Accel: accel.Accel{
Accelerator: types.WHPX, Accelerator: accel.GetAccelerator(arch.X86_64),
}, },
Nic: types.NIC{ Nic: nic.NIC{
Type: types.TAP, Type: nic.TAP,
Options: &types.TapOptions{ Options: nic.Options{
IFName: "qemu-tap", Tap: &nic.TapOptions{
IFName: "qemu-tap",
},
}, },
}, },
NoGraphic: types.Set, NoGraphic: types.Set,
HardDiskA: `./imgs/UbuntuTest.img`, HardDiskA: `./imgs/UbuntuTest.img`,
} }
// ctrl := StartMachine(machine)
command := machine.Expand() data, err := yaml.Marshal(machine)
fmt.Println(command) if err != nil {
// this works, we need to format like this panic(err)
cmd := exec.Command("qemu-system-x86_64", "-m", "size=4096", "-smp", "cpus=4", "-accel", "accel=whpx", "-boot", "order=c,menu=off", "-nic", "tap,ifname=qemu-tap", "-nographic", "-hda", "./imgs/UbuntuTest.img") }
cmd.Start() // Write data to a file
err = os.WriteFile("machine.yaml", data, 0644)
if err != nil {
panic(err)
}
data, err = os.ReadFile("machine.yaml")
if err != nil {
panic(err)
}
mac := system.Machine{}
err = yaml.Unmarshal(data, &mac)
if err != nil {
panic(err)
}
fmt.Println(machine.Expand())
// fmt.Println(mac.Expand())
fmt.Println(accel.GetAccelerator(arch.X86_64))
os.Exit(0)
ctrl := StartMachine(machine)
fmt.Println("Press enter to stop machine") fmt.Println("Press enter to stop machine")
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
_, _ = reader.ReadString('\n') _, _ = reader.ReadString('\n')
cmd.Process.Kill()
// ctrl.Stop() ctrl.Stop()
fmt.Println("Press enter to exit") fmt.Println("Press enter to exit")
_, _ = reader.ReadString('\n') _, _ = reader.ReadString('\n')
// if errout != "" {
// panic(fmt.Errorf("%v", errout))
// }
// if err != nil {
// fmt.Println(err)
// }
// Start new ssh connection with private key. // Start new ssh connection with private key.
//client, err := goph.New("baseimguser", "127.0.0.1", goph.Password("food99")) //client, err := goph.New("baseimguser", "127.0.0.1", goph.Password("food99"))
@ -202,7 +222,7 @@ func main() {
// SetUpMachine(types.GetSize(types.GB, 64)) // SetUpMachine(types.GetSize(types.GB, 64))
} }
func SetUpMachine(size types.Size) (*Credentials, error) { func SetUpMachine(size disk.Size) (*Credentials, error) {
machine_id := uuid.New().String() machine_id := uuid.New().String()
private_path := filepath.Join("keys", machine_id) private_path := filepath.Join("keys", machine_id)
public_path := filepath.Join("keys", machine_id+".pub") public_path := filepath.Join("keys", machine_id+".pub")

View File

@ -1,71 +0,0 @@
package image
import (
"encoding/json"
"os"
"reflect"
"strings"
"github.com/rayaman/go-qemu/pkg/types"
)
// Reperesents a qemu image
type Image interface {
//GetType() types.Format
}
type holder struct {
Format string
Image any
}
// Loades a saved Image into memory
func LoadImage(path string) (Image, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var h = &holder{}
err = json.Unmarshal(data, h)
if err != nil {
return nil, err
}
data, err = json.Marshal(h.Image)
if err != nil {
return nil, err
}
t := reflect.New(reflect.TypeOf(types.GetTypes()[h.Format])).Interface()
err = json.Unmarshal(data, t)
if err != nil {
return nil, err
}
return t, nil
}
// Saves an image to disk
func SaveImage(path string, img Image) error {
data, err := json.MarshalIndent(holder{
Format: strings.ReplaceAll(strings.ToLower(reflect.TypeOf(img).String()), "*formats.", ""),
Image: img,
}, "", "\t")
if err != nil {
return err
}
file, err := os.Create(path)
if err != nil {
return err
}
_, err = file.Write(data)
return err
}

View File

@ -1,3 +0,0 @@
package machine
// qemu-system-x86_64.exe -m 4096 -accel whpx -boot d -smp 4 -net nic -net user,hostfwd=tcp::8888-:22 -hda .\imgs\ubuntu_server_test.img -nographic

View File

@ -1,160 +0,0 @@
package types
import (
"fmt"
"reflect"
"strings"
"github.com/fatih/structs"
"github.com/rayaman/go-qemu/pkg/types/numa"
)
/*
TODO:
-add-fd fd=fd,set=set[,opaque=opaque]
Add 'fd' to fd 'set'
-set group.id.arg=value
set <arg> parameter for item <id> of type <group>
i.e. -set drive.$id.file=/path/to/image
-global driver.property=value
-global driver=driver,property=property,value=value
set a global default for a driver property
*/
type Machine struct {
Arch System // The binary we use
// Amount of memory in MB
Memory Memory `json:"m,omitempty"`
// Number of CPU cores
Cores SMP `json:"smp,omitempty"`
Cpu CHIP `json:"cpu,omitempty"`
Accel Accel `json:"accel,omitempty"`
Boot Boot `json:"boot,omitempty"`
Numa numa.Numa `json:"numa,omitempty" omit:"true"`
MemoryPath string `json:"memory-path,omitempty"`
MemoryPrealloc Flag `json:"memory-prealloc,omitempty"`
Nic NIC `json:"nic,omitempty"`
// Graphics
NoGraphic Flag `json:"nographic,omitempty"`
// Block devices
HardDiskA string `json:"hda,omitempty"`
HardDiskB string `json:"hdb,omitempty"`
HardDiskC string `json:"hdc,omitempty"`
HardDiskD string `json:"hdd,omitempty"`
FloppyA string `json:"fda,omitempty"`
FloppyB string `json:"fdb,omitempty"`
CDROM string `json:"cdrom,omitempty"`
}
func (m *Machine) Expand() string {
fields := structs.Fields(m)
exp := []string{}
for _, field := range fields {
tag := strings.ReplaceAll(field.Tag("json"), ",omitempty", "")
omit := field.Tag("omit")
if tag != "" {
if field.Kind() == reflect.Struct || field.Kind() == reflect.Interface && !field.IsZero() {
if omit != "" {
exp = append(exp, Expand(field.Value()))
} else {
exp = append(exp, Expand(field.Value(), tag))
}
} else {
if !field.IsZero() {
if strings.Contains(fmt.Sprintf("%v", field.Value()), " ") {
exp = append(exp, fmt.Sprintf(`-%v "%v"`, tag, field.Value()))
} else {
exp = append(exp, fmt.Sprintf("-%v %v", tag, field.Value()))
}
}
}
}
}
return strings.ReplaceAll(strings.ReplaceAll(fmt.Sprintf("qemu-system-%v %v", m.Arch, strings.Join(exp, " ")), " flag-on", ""), " ", " ")
}
func Expand(obj any, tag ...string) string {
opts := []string{}
fields := structs.Fields(obj)
useSpace := false
for _, field := range fields {
opt := strings.ReplaceAll(field.Tag("json"), ",omitempty", "")
opt = strings.ReplaceAll(opt, "omitempty", "")
omit := field.Tag("omit")
if !field.IsZero() || field.Kind() == reflect.Bool || strings.Contains(opt, "id") && field.Kind() != reflect.String {
switch value := field.Value().(type) {
case bool:
opts = append(opts, fmt.Sprintf("%v=%v", opt, SW[value]))
case *bool:
opts = append(opts, fmt.Sprintf("%v=%v", opt, SW[*value]))
case []string:
opts = append(opts, fmt.Sprintf("%v=%v", opt, strings.Join(value, "")))
case numa.CPUS:
if value.Last == 0 {
opts = append(opts, fmt.Sprintf("%v=%v", opt, value.First))
} else {
opts = append(opts, fmt.Sprintf("%v=%v-%v", opt, value.First, value.Last))
}
case []numa.Node:
useSpace = true
for _, node := range value {
opts = append(opts, "-numa "+opt+","+Expand(node))
}
case []numa.Dist:
useSpace = true
for _, dist := range value {
opts = append(opts, "-numa "+opt+","+Expand(dist))
}
case []numa.CPU:
useSpace = true
for _, cpu := range value {
opts = append(opts, "-numa "+opt+","+Expand(cpu))
}
case []numa.HMATLB:
useSpace = true
for _, hmatlb := range value {
opts = append(opts, "-numa "+opt+","+Expand(hmatlb))
}
case []numa.HMATCache:
useSpace = true
for _, hmatcache := range value {
opts = append(opts, "-numa "+opt+","+Expand(hmatcache))
}
case Options:
opts = append(opts, value.ExpandOptions())
case []Drives:
opts = append(opts, fmt.Sprintf("%v=%v", opt, strings.Join(ConvertDrives(value), ",")))
default:
if omit == "" {
if strings.Contains(fmt.Sprintf("%v", value), " ") {
opts = append(opts, fmt.Sprintf(`%v="%v"`, opt, value))
} else {
opts = append(opts, fmt.Sprintf("%v=%v", opt, value))
}
} else {
if strings.Contains(fmt.Sprintf("%v", value), " ") {
opts = append(opts, fmt.Sprintf(`"%v"`, value))
} else {
opts = append(opts, fmt.Sprintf("%v", value))
}
}
}
}
}
otag := ""
if len(tag) > 0 {
otag = "-" + tag[0] + " "
}
if useSpace {
return otag + strings.Join(opts, " ")
}
return otag + strings.Join(opts, ",")
}

View File

@ -1,59 +0,0 @@
package types
type (
NICType string
)
const (
TAP NICType = "tap"
Bridge NICType = "bridge"
User NICType = "user"
Socket NICType = "socket"
)
type TapOptions struct {
ID string `json:"id,omitempty"`
IFName string `json:"ifname,omitempty"`
}
func (t *TapOptions) ExpandOptions() string {
return Expand(t)
}
type BridgeOptions struct {
}
func (b *BridgeOptions) ExpandOptions() string {
return ""
}
type UserOptions struct {
}
func (u *UserOptions) ExpandOptions() string {
return ""
}
type SocketOptions struct {
ID string `json:"id,omitempty"`
FD string `json:"fd,omitempty"`
MCast string `json:"mcast,omitempty"`
UDP string `json:"udp,omitempty"`
LocalAddress string `json:"localaddr,omitempty"`
Listen string `json:"listen,omitempty"`
Connect string `json:"connect,omitempty"`
}
func (s *SocketOptions) ExpandOptions() string {
return Expand(s)
}
type Options interface {
ExpandOptions() string
}
type NIC struct {
Type NICType `json:"type,omitempty" omit:"true"`
MacAddress string `json:"mac,omitempty"`
Options Options `json:"options,omitempty"`
}

23
pkg/utils/utils.go Normal file
View File

@ -0,0 +1,23 @@
package utils
import "net"
func GetFreePort() (int, error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, err
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, err
}
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}
// converts bool to on/off format
var SW = map[bool]string{
true: "on",
false: "off",
}

View File

@ -1,6 +1,7 @@
package image package image
import ( import (
"encoding/json"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -8,58 +9,14 @@ import (
"strings" "strings"
"github.com/abdfnx/gosh" "github.com/abdfnx/gosh"
"github.com/rayaman/go-qemu/pkg/types" "github.com/rayaman/go-qemu/pkg/v1/types"
"github.com/rayaman/go-qemu/pkg/v1/types/disk"
) )
func getOptions(m map[string]string) string {
str := []string{}
for k, v := range m {
if len(v) > 0 {
str = append(str, fmt.Sprintf("%v=%v", k, v))
}
}
n_str := strings.Join(str, ",")
if len(n_str) > 0 {
return "-o \"" + strings.Join(str, ",") + "\" "
}
return ""
}
func getData(m map[string]string, key string) (string, bool) {
if d, ok := m[key]; ok {
delete(m, key)
if len(d) == 0 {
return "", false
}
return d, true
}
return "", false
}
func getMap(q any) map[string]string {
m := map[string]string{}
v := reflect.ValueOf(q).Elem()
for j := 0; j < v.NumField(); j++ { // Go through all fields of struct
if !v.Field(j).IsZero() {
index := strings.ReplaceAll(v.Type().Field(j).Tag.Get("json"), ",omitempty", "")
if v.Field(j).Type() == reflect.TypeOf(true) {
m[index] = types.SW[v.Field(j).Bool()]
} else {
m[index] = v.Field(j).String()
}
}
}
return m
}
type Options struct {
IsBaseImage bool `json:"is_base_image"`
}
// create [--object OBJECTDEF] [-q] [-f FMT] [-b BACKING_FILE [-F BACKING_FMT]] [-u] [-o OPTIONS] FILENAME [SIZE] // create [--object OBJECTDEF] [-q] [-f FMT] [-b BACKING_FILE [-F BACKING_FMT]] [-u] [-o OPTIONS] FILENAME [SIZE]
// Creates an image based of the supplied image // Creates an image based of the supplied image structure
func Create(i Image, size types.Size, opts ...Options) error { func Create(i Image, size disk.Size, opts ...Options) error {
data := getMap(i) data := getMap(i)
@ -104,3 +61,54 @@ func Create(i Image, size types.Size, opts ...Options) error {
return nil return nil
} }
// Loads a saved image structure into memory
func LoadImage(path string) (Image, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var h = &holder{}
err = json.Unmarshal(data, h)
if err != nil {
return nil, err
}
data, err = json.Marshal(h.Image)
if err != nil {
return nil, err
}
t := reflect.New(reflect.TypeOf(types.GetTypes()[h.Format])).Interface()
err = json.Unmarshal(data, t)
if err != nil {
return nil, err
}
return t, nil
}
// Saves an image structure to disk
func SaveImage(path string, img Image) error {
data, err := json.MarshalIndent(holder{
Format: strings.ReplaceAll(strings.ToLower(reflect.TypeOf(img).String()), "*formats.", ""),
Image: img,
}, "", "\t")
if err != nil {
return err
}
file, err := os.Create(path)
if err != nil {
return err
}
_, err = file.Write(data)
return err
}

View File

@ -1,7 +1,8 @@
package formats package formats
import ( import (
"github.com/rayaman/go-qemu/pkg/types" "github.com/rayaman/go-qemu/pkg/v1/types"
"github.com/rayaman/go-qemu/pkg/v1/types/image"
) )
func init() { func init() {
@ -12,13 +13,13 @@ type QCOW2 struct {
// Name of the image // Name of the image
ImageName string `json:"image_name"` ImageName string `json:"image_name"`
// Determines the qcow2 version to use. compat=0.10 uses the traditional image format that can be read by any QEMU since 0.10. compat=1.1 enables image format extensions that only QEMU 1.1 and newer understand (this is the default). Amongst others, this includes zero clusters, which allow efficient copy-on-read for sparse images // Determines the qcow2 version to use. compat=0.10 uses the traditional image format that can be read by any QEMU since 0.10. compat=1.1 enables image format extensions that only QEMU 1.1 and newer understand (this is the default). Amongst others, this includes zero clusters, which allow efficient copy-on-read for sparse images
Compat types.Compat `json:"compat,omitempty"` Compat image.Compat `json:"compat,omitempty"`
// File name of a base image // File name of a base image
BackingFile string `json:"backing_file,omitempty"` BackingFile string `json:"backing_file,omitempty"`
// Image format of the base image // Image format of the base image
BackingFmt string `json:"backing_fmt,omitempty"` BackingFmt string `json:"backing_fmt,omitempty"`
// This option configures which compression algorithm will be used for compressed clusters on the image. Note that setting this option doesnt yet cause the image to actually receive compressed writes. It is most commonly used with the -c option of qemu-img convert, but can also be used with the compress filter driver or backup block jobs with compression enabled. // This option configures which compression algorithm will be used for compressed clusters on the image. Note that setting this option doesnt yet cause the image to actually receive compressed writes. It is most commonly used with the -c option of qemu-img convert, but can also be used with the compress filter driver or backup block jobs with compression enabled.
CompressionType types.CompressionType `json:"compression_type,omitempty"` CompressionType image.CompressionType `json:"compression_type,omitempty"`
/* /*
If this option is set to true, the image is encrypted with 128-bit AES-CBC. If this option is set to true, the image is encrypted with 128-bit AES-CBC.
@ -38,7 +39,7 @@ type QCOW2 struct {
// Changes the qcow2 cluster size (must be between 512 and 2M). Smaller cluster sizes can improve the image file size whereas larger cluster sizes generally provide better performance. // Changes the qcow2 cluster size (must be between 512 and 2M). Smaller cluster sizes can improve the image file size whereas larger cluster sizes generally provide better performance.
ClusterSize string `json:"cluster_size,omitempty"` ClusterSize string `json:"cluster_size,omitempty"`
// Preallocation mode (allowed values: off, metadata, falloc, full). An image with preallocated metadata is initially larger but can improve performance when the image needs to grow. falloc and full preallocations are like the same options of raw format, but sets up metadata also. // Preallocation mode (allowed values: off, metadata, falloc, full). An image with preallocated metadata is initially larger but can improve performance when the image needs to grow. falloc and full preallocations are like the same options of raw format, but sets up metadata also.
Preallocation types.Preallocation `json:"preallocation,omitempty"` Preallocation image.Preallocation `json:"preallocation,omitempty"`
/* /*
If this option is set to true, reference count updates are postponed with the goal of avoiding metadata I/O and improving performance. This is particularly interesting with cache=writethrough which doesnt batch metadata updates. The tradeoff is that after a host crash, the reference count tables must be rebuilt, i.e. on the next open an (automatic) qemu-img check -r all is required, which may take some time. If this option is set to true, reference count updates are postponed with the goal of avoiding metadata I/O and improving performance. This is particularly interesting with cache=writethrough which doesnt batch metadata updates. The tradeoff is that after a host crash, the reference count tables must be rebuilt, i.e. on the next open an (automatic) qemu-img check -r all is required, which may take some time.

View File

@ -1,7 +1,8 @@
package formats package formats
import ( import (
"github.com/rayaman/go-qemu/pkg/types" "github.com/rayaman/go-qemu/pkg/v1/types"
"github.com/rayaman/go-qemu/pkg/v1/types/image"
) )
func init() { func init() {
@ -12,5 +13,5 @@ type Raw struct {
// Name of the image // Name of the image
ImageName string `json:"image_name"` ImageName string `json:"image_name"`
// Preallocation mode (allowed values: off, falloc, full). falloc mode preallocates space for image by calling posix_fallocate(). full mode preallocates space for image by writing data to underlying storage. This data may or may not be zero, depending on the storage location. // Preallocation mode (allowed values: off, falloc, full). falloc mode preallocates space for image by calling posix_fallocate(). full mode preallocates space for image by writing data to underlying storage. This data may or may not be zero, depending on the storage location.
Preallocation types.Preallocation `json:"preallocation"` Preallocation image.Preallocation `json:"preallocation"`
} }

50
pkg/v1/image/helpers.go Normal file
View File

@ -0,0 +1,50 @@
package image
import (
"fmt"
"reflect"
"strings"
"github.com/rayaman/go-qemu/pkg/utils"
)
func getOptions(m map[string]string) string {
str := []string{}
for k, v := range m {
if len(v) > 0 {
str = append(str, fmt.Sprintf("%v=%v", k, v))
}
}
n_str := strings.Join(str, ",")
if len(n_str) > 0 {
return "-o \"" + strings.Join(str, ",") + "\" "
}
return ""
}
func getData(m map[string]string, key string) (string, bool) {
if d, ok := m[key]; ok {
delete(m, key)
if len(d) == 0 {
return "", false
}
return d, true
}
return "", false
}
func getMap(q any) map[string]string {
m := map[string]string{}
v := reflect.ValueOf(q).Elem()
for j := 0; j < v.NumField(); j++ { // Go through all fields of struct
if !v.Field(j).IsZero() {
index := strings.ReplaceAll(v.Type().Field(j).Tag.Get("json"), ",omitempty", "")
if v.Field(j).Type() == reflect.TypeOf(true) {
m[index] = utils.SW[v.Field(j).Bool()]
} else {
m[index] = v.Field(j).String()
}
}
}
return m
}

17
pkg/v1/image/types.go Normal file
View File

@ -0,0 +1,17 @@
package image
type (
Options struct {
IsBaseImage bool `json:"is_base_image"`
}
// Reperesents a qemu image
Image interface {
//GetType() types.Format
}
holder struct {
Format string
Image any
}
)

92
pkg/v1/system/machine.go Normal file
View File

@ -0,0 +1,92 @@
package system
import (
"fmt"
"reflect"
"strings"
"github.com/fatih/structs"
"github.com/rayaman/go-qemu/pkg/v1/types"
"github.com/rayaman/go-qemu/pkg/v1/types/accel"
"github.com/rayaman/go-qemu/pkg/v1/types/arch"
"github.com/rayaman/go-qemu/pkg/v1/types/boot"
"github.com/rayaman/go-qemu/pkg/v1/types/chip"
"github.com/rayaman/go-qemu/pkg/v1/types/helpers"
"github.com/rayaman/go-qemu/pkg/v1/types/keyboard"
"github.com/rayaman/go-qemu/pkg/v1/types/memory"
"github.com/rayaman/go-qemu/pkg/v1/types/nic"
"github.com/rayaman/go-qemu/pkg/v1/types/numa"
"github.com/rayaman/go-qemu/pkg/v1/types/smp"
)
/*
TODO:
-add-fd fd=fd,set=set[,opaque=opaque]
Add 'fd' to fd 'set'
-set group.id.arg=value
set <arg> parameter for item <id> of type <group>
i.e. -set drive.$id.file=/path/to/image
-global driver.property=value
-global driver=driver,property=property,value=value
set a global default for a driver property
*/
type Machine struct {
Arch arch.System // The binary we use
// Amount of memory in MB
Memory memory.Memory `json:"m,omitempty"`
// Number of CPU cores
Cores smp.SMP `json:"smp,omitempty"`
Cpu chip.CHIP `json:"cpu,omitempty"`
Accel accel.Accel `json:"accel,omitempty"`
Boot boot.Boot `json:"boot,omitempty"`
Numa numa.Numa `json:"numa,omitempty" omit:"true"` // omit tells the Expand function to leave out the tag name
MemoryPath string `json:"memory-path,omitempty"`
MemoryPrealloc types.Flag `json:"memory-prealloc,omitempty"`
Nic nic.NIC `json:"nic,omitempty"`
KeyboardLayout keyboard.Keyboard `json:"k,omitempty"`
// Graphics
NoGraphic types.Flag `json:"nographic,omitempty"`
// Block devices
HardDiskA string `json:"hda,omitempty"`
HardDiskB string `json:"hdb,omitempty"`
HardDiskC string `json:"hdc,omitempty"`
HardDiskD string `json:"hdd,omitempty"`
FloppyA string `json:"fda,omitempty"`
FloppyB string `json:"fdb,omitempty"`
CDROM string `json:"cdrom,omitempty"`
}
func (m *Machine) Expand() []string {
fields := structs.Fields(m)
exp := []string{}
for _, field := range fields {
tag := strings.ReplaceAll(field.Tag("json"), ",omitempty", "")
omit := field.Tag("omit")
if tag != "" {
if field.Kind() == reflect.Struct || field.Kind() == reflect.Interface && !field.IsZero() {
if omit != "" {
exp = append(exp, helpers.Expand(field.Value())...)
} else {
exp = append(exp, helpers.Expand(field.Value(), tag)...)
}
} else {
if !field.IsZero() {
if fmt.Sprintf("%v", field.Value()) == "flag-on" {
exp = append(exp, "-"+tag)
} else {
exp = append(exp, "-"+tag, fmt.Sprintf("%v", field.Value()))
}
}
}
}
}
exp = helpers.Remove(exp, "")
return exp
}

View File

@ -1,11 +1,26 @@
package types package accel
import (
"bytes"
"fmt"
"os/exec"
"strings"
"github.com/rayaman/go-qemu/pkg/v1/types/arch"
)
type (
VMExit string
Accelerator string
Thread string
KernelIrqchip string
)
const ( const (
KVM Accelerator = "kvm" KVM Accelerator = "kvm"
XEN Accelerator = "xen" XEN Accelerator = "xen"
HVF Accelerator = "hvf" HVF Accelerator = "hvf"
NVMM Accelerator = "nvmm" NVMM Accelerator = "nvmm"
// Windows
WHPX Accelerator = "whpx" WHPX Accelerator = "whpx"
TCG Accelerator = "tcg" TCG Accelerator = "tcg"
@ -22,7 +37,7 @@ const (
) )
type Accel struct { type Accel struct {
Accelerator Accelerator `json:"accel,omitempty"` Accelerator Accelerator `json:"accel,omitempty" omit:"tag"`
// enable Xen integrated Intel graphics passthrough, default=off // enable Xen integrated Intel graphics passthrough, default=off
IGDPassthrough *bool `json:"igd-passthru,omitempty"` IGDPassthrough *bool `json:"igd-passthru,omitempty"`
// controls accelerated irqchip support (default=on // controls accelerated irqchip support (default=on
@ -48,3 +63,32 @@ type Accel struct {
// KVM device path, default /dev/kvm // KVM device path, default /dev/kvm
Device string `json:"device,omitempty"` Device string `json:"device,omitempty"`
} }
// GetAccelerator returns an accelerator for the given architecture, if it cannot be found, TCG will be returned
func GetAccelerator(a arch.System) Accelerator {
cmd := exec.Command(fmt.Sprintf("qemu-system-%v", a), "-accel", "help")
var stdout bytes.Buffer
var stdin bytes.Buffer
cmd.Stdout = &stdout
cmd.Stdin = &stdin
err := cmd.Run()
if err != nil {
return TCG
}
data := string(stdout.Bytes())
list := strings.Split(data, "\n")
for i := 1; i < len(list); i++ {
if strings.Contains(list[i], "tcg") {
continue
}
return Accelerator(list[i])
}
return TCG
}

View File

@ -1,56 +1,8 @@
package types package arch
import "fmt" type System string
type (
Preallocation string
Compat string
CompressionType string
Format string
System string
Accelerator string
VMExit string
Thread string
KernelIrqchip string
Flag string
)
// converts bool to on/off format
var SW = map[bool]string{
true: "on",
false: "off",
}
var (
On *bool = func(b bool) *bool {
return &b
}(true)
Off *bool = func(b bool) *bool {
return &b
}(false)
)
type Param interface {
Expand() string
}
var register = map[string]any{}
var ( var (
// No pre-allocation
OFF Preallocation = "off"
// Allocates qcow2 metadata, and it's still a sparse image.
METADATA Preallocation = "metadata"
// Uses posix_fallocate() to "allocate blocks and marking them as uninitialized", and is relatively faster than writing out zeroes to a file:
FALLOC Preallocation = "falloc"
// Allocates zeroes and makes a non-sparse image.
FULL Preallocation = "full"
Compat_0_10 Compat = "0.10"
Compat_1_1 Compat = "1.1"
Zlib CompressionType = "zlib"
Zstd CompressionType = "zstd"
AARCH64 System = "aarch64" AARCH64 System = "aarch64"
AARCH64W System = "aarch64w" AARCH64W System = "aarch64w"
ALPHA System = "alpha" ALPHA System = "alpha"
@ -109,18 +61,4 @@ var (
XTENSAEB System = "xtensaeb" XTENSAEB System = "xtensaeb"
XTENSAEBW System = "xtensaebw" XTENSAEBW System = "xtensaebw"
XTENSAW System = "xtensaw" XTENSAW System = "xtensaw"
Set Flag = "flag-on"
) )
func GetTypes() map[string]any {
return register
}
func RegisterType(t string, i any) error {
if _, ok := register[t]; !ok {
register[t] = i
return nil
}
return fmt.Errorf("type already registered")
}

View File

@ -1,4 +1,4 @@
package types package boot
// floppy (a), hard disk (c), CD-ROM (d), network (n) // floppy (a), hard disk (c), CD-ROM (d), network (n)
type Drives string type Drives string
@ -20,7 +20,7 @@ const (
type Boot struct { type Boot struct {
// Order of boot // Order of boot
Order []Drives `json:"order,omitempty"` Order []Drives `json:"order,omitempty" omit:"tag"`
Once []Drives `json:"once,omitempty"` Once []Drives `json:"once,omitempty"`
Menu *bool `json:"menu,omitempty"` Menu *bool `json:"menu,omitempty"`
// The file's name that would be passed to bios as logo picture, if menu=on // The file's name that would be passed to bios as logo picture, if menu=on

View File

@ -1,4 +1,4 @@
package types package chip
type CHIP string type CHIP string

View File

@ -1,4 +1,4 @@
package types package disk
import ( import (
"math/big" "math/big"

View File

@ -0,0 +1,94 @@
package helpers
import (
"fmt"
"reflect"
"strings"
"github.com/fatih/structs"
"github.com/rayaman/go-qemu/pkg/utils"
"github.com/rayaman/go-qemu/pkg/v1/types/boot"
"github.com/rayaman/go-qemu/pkg/v1/types/nic"
"github.com/rayaman/go-qemu/pkg/v1/types/numa"
)
func Remove(s []string, r string) []string {
for i, v := range s {
if v == r {
return append(s[:i], s[i+1:]...)
}
}
return s
}
func Expand(obj any, tag ...string) []string {
opts := []string{}
fields := structs.Fields(obj)
for _, field := range fields {
opt := strings.ReplaceAll(field.Tag("json"), ",omitempty", "")
opt = strings.ReplaceAll(opt, "omitempty", "")
opt = strings.ReplaceAll(opt, ",inline", "")
omit := field.Tag("omit")
if !field.IsZero() || field.Kind() == reflect.Bool || strings.Contains(opt, "id") && field.Kind() != reflect.String {
switch value := field.Value().(type) {
case bool:
opts = append(opts, fmt.Sprintf("%v=%v", opt, utils.SW[value]))
case *bool:
opts = append(opts, fmt.Sprintf("%v=%v", opt, utils.SW[*value]))
case []string:
opts = append(opts, fmt.Sprintf("%v=%v", opt, strings.Join(value, "")))
case numa.CPUS:
if value.Last == 0 {
opts = append(opts, fmt.Sprintf("%v=%v", opt, value.First))
} else {
opts = append(opts, fmt.Sprintf("%v=%v-%v", opt, value.First, value.Last))
}
case []numa.Node:
for _, node := range value {
opts = append(opts, "-numa", opt)
opts = append(opts, Expand(node)...)
}
case []numa.Dist:
for _, dist := range value {
opts = append(opts, "-numa", opt)
opts = append(opts, Expand(dist)...)
}
case []numa.CPU:
for _, cpu := range value {
opts = append(opts, "-numa", opt)
opts = append(opts, Expand(cpu)...)
}
case []numa.HMATLB:
for _, hmatlb := range value {
opts = append(opts, "-numa", opt)
opts = append(opts, Expand(hmatlb)...)
}
case []numa.HMATCache:
for _, hmatcache := range value {
opts = append(opts, "-numa", opt)
opts = append(opts, Expand(hmatcache)...)
}
case nic.Options:
opts = append(opts, value.ExpandOptions()...)
case []boot.Drives:
if omit == "tag" {
opts = append(opts, fmt.Sprintf("%v", strings.Join(boot.ConvertDrives(value), ",")))
} else {
opts = append(opts, fmt.Sprintf("%v=%v", opt, strings.Join(boot.ConvertDrives(value), ",")))
}
default:
if omit == "" {
opts = append(opts, fmt.Sprintf("%v=%v", opt, value))
} else if omit == "tag" {
opts = append(opts, fmt.Sprintf("%v", value))
}
}
}
}
if len(tag) > 0 {
return []string{"-" + tag[0], strings.Join(opts, ",")}
} else {
return []string{strings.Join(opts, ",")}
}
}

View File

@ -0,0 +1,25 @@
package image
type (
Preallocation string
Compat string
CompressionType string
Format string
)
var (
// No pre-allocation
OFF Preallocation = "off"
// Allocates qcow2 metadata, and it's still a sparse image.
METADATA Preallocation = "metadata"
// Uses posix_fallocate() to "allocate blocks and marking them as uninitialized", and is relatively faster than writing out zeroes to a file:
FALLOC Preallocation = "falloc"
// Allocates zeroes and makes a non-sparse image.
FULL Preallocation = "full"
Compat_0_10 Compat = "0.10"
Compat_1_1 Compat = "1.1"
Zlib CompressionType = "zlib"
Zstd CompressionType = "zstd"
)

View File

@ -0,0 +1,35 @@
package keyboard
type (
Keyboard string
)
var (
En_US Keyboard = "en-us"
Fr Keyboard = "fr"
Da Keyboard = "da"
De Keyboard = "de"
It Keyboard = "it"
Nl Keyboard = "nl"
No Keyboard = "no"
Pt Keyboard = "pt"
Es Keyboard = "es"
Fr_ch Keyboard = "fr-ch"
De_ch Keyboard = "de-ch"
En_gb Keyboard = "en-gb"
Ko Keyboard = "ko"
Tw Keyboard = "tw"
Ja Keyboard = "ja"
Fr_ca Keyboard = "fr-ca"
Hu Keyboard = "hu"
Pl Keyboard = "pl"
Cz Keyboard = "cz"
Ru Keyboard = "ru"
Lv Keyboard = "lv"
Tr Keyboard = "tr"
Gr Keyboard = "gr"
Ar Keyboard = "ar"
Lt Keyboard = "lt"
Nl_be Keyboard = "nl-be"
Be Keyboard = "be"
)

View File

@ -1,4 +1,4 @@
package types package memory
/* Configure guest RAM /* Configure guest RAM
Note: Some architectures might enforce a specific granularity Note: Some architectures might enforce a specific granularity

117
pkg/v1/types/nic/nic.go Normal file
View File

@ -0,0 +1,117 @@
package nic
import (
"fmt"
"reflect"
"strings"
"github.com/fatih/structs"
"github.com/rayaman/go-qemu/pkg/utils"
)
type (
NICType string
)
// TODO: Implement other types user,socket,brtidge
const (
TAP NICType = "tap"
Bridge NICType = "bridge"
User NICType = "user"
Socket NICType = "socket"
)
type TapOptions struct {
ID string `json:"id,omitempty"`
IFName string `json:"ifname,omitempty"`
}
func (t *TapOptions) ExpandOptions() []string {
return []string{"tap," + strings.Join(expand(t), ",")}
}
type BridgeOptions struct {
}
func (b *BridgeOptions) ExpandOptions() []string {
return []string{"bridge," + strings.Join(expand(b), ",")}
}
type UserOptions struct {
}
func (u *UserOptions) ExpandOptions() []string {
return []string{"user," + strings.Join(expand(u), ",")}
}
type SocketOptions struct {
ID string `json:"id,omitempty"`
FD string `json:"fd,omitempty"`
MCast string `json:"mcast,omitempty"`
UDP string `json:"udp,omitempty"`
Listen string `json:"listen,omitempty"`
LocalAddress string `json:"localaddr,omitempty"`
Connect string `json:"connect,omitempty"`
}
func (s *SocketOptions) ExpandOptions() []string {
return []string{"socket," + strings.Join(expand(s), ",")}
}
type Options struct {
Tap *TapOptions `json:"tap,omitempty"`
Bridge *BridgeOptions `json:"bridge,omitempty"`
User *UserOptions `json:"user,omitempty"`
Socket *SocketOptions `json:"socket,omitempty"`
}
func (o *Options) ExpandOptions() []string {
if o.Tap != nil {
return o.Tap.ExpandOptions()
}
if o.Bridge != nil {
return o.Bridge.ExpandOptions()
}
if o.User != nil {
return o.User.ExpandOptions()
}
if o.Socket != nil {
return o.Socket.ExpandOptions()
}
return []string{}
}
type NIC struct {
Type NICType `json:"type,omitempty" omit:"true"`
MacAddress string `json:"mac,omitempty"`
Options Options `json:"options,omitempty"`
}
func expand(obj any, tag ...string) []string {
opts := []string{}
fields := structs.Fields(obj)
for _, field := range fields {
opt := strings.ReplaceAll(field.Tag("json"), ",omitempty", "")
opt = strings.ReplaceAll(opt, "omitempty", "")
opt = strings.ReplaceAll(opt, ",inline", "")
omit := field.Tag("omit")
if !field.IsZero() || field.Kind() == reflect.Bool || strings.Contains(opt, "id") && field.Kind() != reflect.String {
switch value := field.Value().(type) {
case *bool:
opts = append(opts, fmt.Sprintf("%v=%v", opt, utils.SW[*value]))
default:
if omit == "" {
opts = append(opts, fmt.Sprintf("%v=%v", opt, value))
} else if omit == "tag" {
opts = append(opts, fmt.Sprintf("%v", value))
}
}
}
}
if len(tag) > 0 {
return []string{"-" + tag[0], strings.Join(opts, ",")}
} else {
return []string{strings.Join(opts, ",")}
}
}

View File

@ -1,4 +1,4 @@
package types package smp
/* /*
Note: Different machines may have different subsets of the CPU topology Note: Different machines may have different subsets of the CPU topology
@ -16,7 +16,7 @@ Note: Different machines may have different subsets of the CPU topology
*/ */
type SMP struct { type SMP struct {
// The number of initial CPUs // The number of initial CPUs
Cpus uint `json:"cpus,omitempty"` Cpus uint `json:"cpus,omitempty" omit:"tag"`
// Maximum number of total CPUs, including offline CPUs for hotplug, etc // Maximum number of total CPUs, including offline CPUs for hotplug, etc
MaxCpus uint `json:"maxcpus,omitempty"` MaxCpus uint `json:"maxcpus,omitempty"`
// Number of drawers on the machine board // Number of drawers on the machine board

33
pkg/v1/types/types.go Normal file
View File

@ -0,0 +1,33 @@
package types
import "fmt"
type (
Flag string
)
var (
register = map[string]any{}
On *bool = func(b bool) *bool {
return &b
}(true)
Off *bool = func(b bool) *bool {
return &b
}(false)
Set Flag = "flag-on"
)
func GetTypes() map[string]any {
return register
}
func RegisterType(t string, i any) error {
if _, ok := register[t]; !ok {
register[t] = i
return nil
}
return fmt.Errorf("type already registered")
}