forked from linuxkit/linuxkit
/
run_qemu.go
285 lines (245 loc) · 8.36 KB
/
run_qemu.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
log "github.com/Sirupsen/logrus"
)
// QemuImg is the version of qemu container
const QemuImg = "linuxkit/qemu:17f052263d63c8a2b641ad91c589edcbb8a18c82"
// QemuConfig contains the config for Qemu
type QemuConfig struct {
Prefix string
ISO bool
UEFI bool
Kernel bool
GUI bool
DiskPath string
DiskSize string
FWPath string
Arch string
CPUs string
Memory string
KVM bool
Containerized bool
QemuBinPath string
QemuImgPath string
}
func runQemu(args []string) {
qemuFlags := flag.NewFlagSet("qemu", flag.ExitOnError)
qemuFlags.Usage = func() {
fmt.Printf("USAGE: %s run qemu [options] prefix\n\n", os.Args[0])
fmt.Printf("'prefix' specifies the path to the VM image.\n")
fmt.Printf("\n")
fmt.Printf("Options:\n")
qemuFlags.PrintDefaults()
}
// Determine Flags
qemuGUI := qemuFlags.Bool("gui", false, "Set qemu to use video output instead of stdio")
qemuUEFI := qemuFlags.Bool("uefi", false, "Set UEFI boot from 'prefix'-efi.iso")
qemuIso := qemuFlags.Bool("iso", false, "Set Legacy BIOS boot from 'prefix'.iso")
qemuKernel := qemuFlags.Bool("kernel", true, "Set boot using 'prefix'-bzImage/-initrd/-cmdline")
// Paths and settings for Disks and UEFI firware
qemuDiskPath := qemuFlags.String("diskpath", "", "Path to disk image to use")
qemuDiskSize := qemuFlags.String("disksize", "", "Size of disk to create, only created if it doesn't exist")
qemuFWPath := qemuFlags.String("fwpath", "/usr/share/ovmf/bios.bin", "Path to OVMF firmware for UEFI boot")
// VM configuration
qemuArch := qemuFlags.String("arch", "x86_64", "Type of architecture to use, e.g. x86_64, aarch64")
qemuCPUs := qemuFlags.String("cpus", "1", "Number of CPUs")
qemuMem := qemuFlags.String("mem", "1024", "Amount of memory in MB")
if err := qemuFlags.Parse(args); err != nil {
log.Fatal("Unable to parse args")
}
remArgs := qemuFlags.Args()
if len(remArgs) == 0 {
fmt.Println("Please specify the prefix to the image to boot")
qemuFlags.Usage()
os.Exit(1)
}
prefix := remArgs[0]
// Print warning if conflicting UEFI and ISO flags are set
if *qemuUEFI && *qemuIso {
log.Warnf("Both -iso and -uefi have been used")
}
config := QemuConfig{
Prefix: prefix,
ISO: *qemuIso,
UEFI: *qemuUEFI,
Kernel: *qemuKernel,
GUI: *qemuGUI,
DiskPath: *qemuDiskPath,
DiskSize: *qemuDiskSize,
FWPath: *qemuFWPath,
Arch: *qemuArch,
CPUs: *qemuCPUs,
Memory: *qemuMem,
}
config, qemuArgs := buildQemuCmdline(config)
var err error
if config.Containerized {
err = runQemuContainer(config, qemuArgs)
} else {
err = runQemuLocal(config, qemuArgs)
}
if err != nil {
log.Fatal(err.Error())
}
}
func runQemuLocal(config QemuConfig, args []string) error {
if config.DiskPath != "" {
// If disk doesn't exist then create one
if _, err := os.Stat(config.DiskPath); err != nil {
if os.IsNotExist(err) {
log.Infof("Creating new qemu disk [%s]", config.DiskPath)
qemuImgCmd := exec.Command(config.QemuImgPath, "create", "-f", "qcow2", config.DiskPath, config.DiskSize)
log.Debugf("%v\n", qemuImgCmd.Args)
if err := qemuImgCmd.Run(); err != nil {
return fmt.Errorf("Error creating disk [%s]: %s", config.DiskPath, err.Error())
}
} else {
return err
}
} else {
log.Infof("Using existing disk [%s]", config.DiskPath)
}
}
// Check for OVMF firmware before running
if config.UEFI {
if _, err := os.Stat(config.FWPath); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("File [%s] does not exist, please ensure OVMF is installed", config.FWPath)
}
return err
}
}
qemuCmd := exec.Command(config.QemuBinPath, args...)
// If verbosity is enabled print out the full path/arguments
log.Debugf("%v\n", qemuCmd.Args)
// If we're not using a separate window then link the execution to stdin/out
if config.GUI != true {
qemuCmd.Stdin = os.Stdin
qemuCmd.Stdout = os.Stdout
qemuCmd.Stderr = os.Stderr
}
return qemuCmd.Run()
}
func runQemuContainer(config QemuConfig, args []string) error {
wd, err := os.Getwd()
if err != nil {
return err
}
dockerArgs := []string{"run", "-i", "--rm", "-v", fmt.Sprintf("%s:%s", wd, "/tmp"), "-w", "/tmp"}
if config.KVM {
dockerArgs = append(dockerArgs, "--device", "/dev/kvm")
}
dockerPath, err := exec.LookPath("docker")
if err != nil {
return fmt.Errorf("Unable to find docker in the $PATH")
}
if config.DiskPath != "" {
// If disk doesn't exist then create one
if _, err = os.Stat(config.DiskPath); err != nil {
if os.IsNotExist(err) {
log.Infof("Creating new qemu disk [%s]", config.DiskPath)
imgArgs := append(dockerArgs, QemuImg, "qemu-img", "create", "-f", "qcow2", config.DiskPath, config.DiskSize)
qemuImgCmd := exec.Command(dockerPath, imgArgs...)
log.Debugf("%v\n", qemuImgCmd.Args)
if err = qemuImgCmd.Run(); err != nil {
return fmt.Errorf("Error creating disk [%s]: %s", config.DiskPath, err.Error())
}
} else {
return err
}
} else {
log.Infof("Using existing disk [%s]", config.DiskPath)
}
}
qemuArgs := append(dockerArgs, QemuImg, "qemu-system-"+config.Arch)
qemuArgs = append(qemuArgs, args...)
qemuCmd := exec.Command(dockerPath, qemuArgs...)
// If verbosity is enabled print out the full path/arguments
log.Debugf("%v\n", qemuCmd.Args)
// GUI mode not currently supported in a container. Although it could be in future.
if config.GUI == true {
return fmt.Errorf("GUI mode is only supported when running locally, not in a container")
}
qemuCmd.Stdin = os.Stdin
qemuCmd.Stdout = os.Stdout
qemuCmd.Stderr = os.Stderr
return qemuCmd.Run()
}
func buildQemuCmdline(config QemuConfig) (QemuConfig, []string) {
// Before building qemu arguments, check if qemu is in the PATH or fallback to containerized
qemuBinPath := "qemu-system-" + config.Arch
qemuImgPath := "qemu-img"
var err error
config.QemuBinPath, err = exec.LookPath(qemuBinPath)
if err != nil {
log.Infof("Unable to find %s within the $PATH. Using a container", qemuBinPath)
config.Containerized = true
}
config.QemuImgPath, err = exec.LookPath(qemuImgPath)
if err != nil {
// No need to show the error message twice
if !config.Containerized {
log.Infof("Unable to find %s within the $PATH. Using a container", qemuImgPath)
config.Containerized = true
}
}
// Iterate through the flags and build arguments
var qemuArgs []string
qemuArgs = append(qemuArgs, "-device", "virtio-rng-pci")
qemuArgs = append(qemuArgs, "-smp", config.CPUs)
qemuArgs = append(qemuArgs, "-m", config.Memory)
// Look for kvm device and enable for qemu if it exists
if _, err = os.Stat("/dev/kvm"); os.IsNotExist(err) {
qemuArgs = append(qemuArgs, "-machine", "q35")
} else {
config.KVM = true
qemuArgs = append(qemuArgs, "-enable-kvm")
qemuArgs = append(qemuArgs, "-machine", "q35,accel=kvm:tcg")
}
if config.DiskPath != "" {
qemuArgs = append(qemuArgs, "-drive", "file="+config.DiskPath+",format=qcow2")
}
// Check flags for iso/uefi boot and if so disable kernel boot
if config.ISO {
config.Kernel = false
qemuIsoPath := buildPath(config.Prefix, ".iso")
qemuArgs = append(qemuArgs, "-cdrom", qemuIsoPath)
}
if config.UEFI {
config.Kernel = false
qemuIsoPath := buildPath(config.Prefix, "-efi.iso")
qemuArgs = append(qemuArgs, "-pflash", config.FWPath)
qemuArgs = append(qemuArgs, "-cdrom", qemuIsoPath)
qemuArgs = append(qemuArgs, "-boot", "d")
}
// build kernel boot config from bzImage/initrd/cmdline
if config.Kernel {
qemuKernelPath := buildPath(config.Prefix, "-bzImage")
qemuInitrdPath := buildPath(config.Prefix, "-initrd.img")
qemuArgs = append(qemuArgs, "-kernel", qemuKernelPath)
qemuArgs = append(qemuArgs, "-initrd", qemuInitrdPath)
consoleString, err := ioutil.ReadFile(config.Prefix + "-cmdline")
if err != nil {
log.Infof(" %s\n defaulting to console output", err.Error())
qemuArgs = append(qemuArgs, "-append", "console=ttyS0 console=tty0 page_poison=1")
} else {
qemuArgs = append(qemuArgs, "-append", string(consoleString))
}
}
if config.GUI != true {
qemuArgs = append(qemuArgs, "-nographic")
}
return config, qemuArgs
}
func buildPath(prefix string, postfix string) string {
path := prefix + postfix
if _, err := os.Stat(path); os.IsNotExist(err) {
log.Fatalf("File [%s] does not exist in current directory", path)
}
return path
}