Compare commits

..

2 Commits

Author SHA1 Message Date
6c2b3c58bd
Update go.yml 2025-03-30 01:40:12 -04:00
8a3b5ba7da
Create go.yml 2025-03-30 01:36:38 -04:00
37 changed files with 524 additions and 852 deletions

28
.github/workflows/go.yml vendored Normal file
View File

@ -0,0 +1,28 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...

178
LICENSE
View File

@ -1,165 +1,21 @@
GNU LESSER GENERAL PUBLIC LICENSE MIT License
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Copyright (c) 2025 Ryan Ward
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:
This version of the GNU Lesser General Public License incorporates The above copyright notice and this permission notice shall be included in all
the terms and conditions of version 3 of the GNU General Public copies or substantial portions of the Software.
License, supplemented by the additional permissions listed below.
0. Additional Definitions. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
As used herein, "this License" refers to version 3 of the GNU Lesser FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
General Public License, and the "GNU GPL" refers to version 3 of the GNU AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
General Public License. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
"The Library" refers to a covered work governed by this License, SOFTWARE.
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.23 go 1.22.0
require ( require (
github.com/abdfnx/gosh v0.4.0 github.com/abdfnx/gosh v0.4.0
@ -8,7 +8,6 @@ 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
@ -26,5 +25,4 @@ 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,8 +1,6 @@
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=
@ -10,7 +8,6 @@ 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=
@ -84,5 +81,3 @@ 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,24 +12,16 @@ 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/v1/image" "github.com/rayaman/go-qemu/pkg/image"
"github.com/rayaman/go-qemu/pkg/v1/image/formats" "github.com/rayaman/go-qemu/pkg/image/formats"
"github.com/rayaman/go-qemu/pkg/v1/system" "github.com/rayaman/go-qemu/pkg/types"
"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 {
@ -42,18 +34,14 @@ type Credentials struct {
type Controller struct { type Controller struct {
cancel context.CancelFunc cancel context.CancelFunc
cmd *exec.Cmd Stdout *bytes.Buffer
err []error Stderr *bytes.Buffer
} }
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 {
@ -71,38 +59,45 @@ func KillProcess(name string) error {
return fmt.Errorf("process not found") return fmt.Errorf("process not found")
} }
func StartMachine(machine system.Machine) *Controller { func StartMachine(machine types.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
cmd := exec.Command(fmt.Sprintf("qemu-system-%v", machine.Arch), machine.Expand()...) var cmd *exec.Cmd
cmd.Stdout = &stderr if runtime.GOOS == "windows" {
cmd.Stderr = &stdout cmd = exec.Command("powershell.exe", command)
ctrl.cmd = cmd } else {
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 {
fmt.Println("Error:", err) panic(err)
} }
go func() { fmt.Println("running...", cmd.Process.Pid)
err := cmd.Wait()
if err != nil { //syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
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 {
fmt.Println("Error:", err) panic(err)
} }
return return
default:
} }
} }
}() }()
@ -125,67 +120,52 @@ func main() {
// if err != nil { // if err != nil {
// panic(err) // panic(err)
// } // }
machine := system.Machine{ machine := types.Machine{
Arch: arch.X86_64, Arch: types.X86_64,
Cores: smp.SMP{ Cores: types.SMP{
Cpus: 4, Cpus: 4,
}, },
Boot: boot.Boot{ Boot: types.Boot{
Order: []boot.Drives{boot.HARDDISK}, Order: []types.Drives{types.HARDDISK},
Menu: types.Off, Menu: types.Off,
}, },
Memory: memory.Memory{ Memory: types.Memory{
Size: 4096, Size: 4096,
}, },
Accel: accel.Accel{ Accel: types.Accel{
Accelerator: accel.GetAccelerator(arch.X86_64), Accelerator: types.WHPX,
}, },
Nic: nic.NIC{ Nic: types.NIC{
Type: nic.TAP, Type: types.TAP,
Options: nic.Options{ Options: &types.TapOptions{
Tap: &nic.TapOptions{ IFName: "qemu-tap",
IFName: "qemu-tap",
},
}, },
}, },
NoGraphic: types.Set, NoGraphic: types.Set,
HardDiskA: `./imgs/UbuntuTest.img`, HardDiskA: `./imgs/UbuntuTest.img`,
} }
// ctrl := StartMachine(machine)
data, err := yaml.Marshal(machine) command := machine.Expand()
if err != nil { fmt.Println(command)
panic(err) // this works, we need to format like this
} 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")
// Write data to a file cmd.Start()
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"))
@ -222,7 +202,7 @@ func main() {
// SetUpMachine(types.GetSize(types.GB, 64)) // SetUpMachine(types.GetSize(types.GB, 64))
} }
func SetUpMachine(size disk.Size) (*Credentials, error) { func SetUpMachine(size types.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,7 +1,6 @@
package image package image
import ( import (
"encoding/json"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -9,14 +8,58 @@ import (
"strings" "strings"
"github.com/abdfnx/gosh" "github.com/abdfnx/gosh"
"github.com/rayaman/go-qemu/pkg/v1/types" "github.com/rayaman/go-qemu/pkg/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 structure // Creates an image based of the supplied image
func Create(i Image, size disk.Size, opts ...Options) error { func Create(i Image, size types.Size, opts ...Options) error {
data := getMap(i) data := getMap(i)
@ -61,54 +104,3 @@ func Create(i Image, size disk.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,8 +1,7 @@
package formats package formats
import ( import (
"github.com/rayaman/go-qemu/pkg/v1/types" "github.com/rayaman/go-qemu/pkg/types"
"github.com/rayaman/go-qemu/pkg/v1/types/image"
) )
func init() { func init() {
@ -13,13 +12,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 image.Compat `json:"compat,omitempty"` Compat types.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 image.CompressionType `json:"compression_type,omitempty"` CompressionType types.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.
@ -39,7 +38,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 image.Preallocation `json:"preallocation,omitempty"` Preallocation types.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,8 +1,7 @@
package formats package formats
import ( import (
"github.com/rayaman/go-qemu/pkg/v1/types" "github.com/rayaman/go-qemu/pkg/types"
"github.com/rayaman/go-qemu/pkg/v1/types/image"
) )
func init() { func init() {
@ -13,5 +12,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 image.Preallocation `json:"preallocation"` Preallocation types.Preallocation `json:"preallocation"`
} }

71
pkg/image/image.go Normal file
View File

@ -0,0 +1,71 @@
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

@ -0,0 +1,3 @@
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,26 +1,11 @@
package accel package types
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"
@ -37,7 +22,7 @@ const (
) )
type Accel struct { type Accel struct {
Accelerator Accelerator `json:"accel,omitempty" omit:"tag"` Accelerator Accelerator `json:"accel,omitempty"`
// 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
@ -63,32 +48,3 @@ 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,4 +1,4 @@
package boot package types
// 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" omit:"tag"` Order []Drives `json:"order,omitempty"`
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 chip package types
type CHIP string type CHIP string

View File

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

160
pkg/types/machine.go Normal file
View File

@ -0,0 +1,160 @@
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,4 +1,4 @@
package memory package types
/* Configure guest RAM /* Configure guest RAM
Note: Some architectures might enforce a specific granularity Note: Some architectures might enforce a specific granularity

59
pkg/types/nic.go Normal file
View File

@ -0,0 +1,59 @@
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"`
}

View File

@ -1,4 +1,4 @@
package smp package types
/* /*
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" omit:"tag"` Cpus uint `json:"cpus,omitempty"`
// 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

View File

@ -1,8 +1,56 @@
package arch package types
type System string import "fmt"
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"
@ -61,4 +109,18 @@ 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,23 +0,0 @@
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,50 +0,0 @@
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
}

View File

@ -1,17 +0,0 @@
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
}
)

View File

@ -1,92 +0,0 @@
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,94 +0,0 @@
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

@ -1,25 +0,0 @@
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

@ -1,35 +0,0 @@
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,117 +0,0 @@
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,33 +0,0 @@
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")
}