github.com/shaardie/u-root@v4.0.1-0.20190127173353-f24a1c26aa2e+incompatible/pkg/qemu/qemu.go (about) 1 // Copyright 2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package qemu provides a Go API for starting QEMU VMs. 6 // 7 // qemu is mainly suitable for running QEMU-based integration tests. 8 // 9 // The environment variable `UROOT_QEMU` overrides the path to QEMU and the 10 // first few arguments (defaults to "qemu"). For example, I use: 11 // 12 // UROOT_QEMU='qemu-system-x86_64 -L . -m 4096 -enable-kvm' 13 // 14 // For CI, this environment variable is set in `.circleci/images/integration/Dockerfile`. 15 package qemu 16 17 import ( 18 "fmt" 19 "io" 20 "os" 21 "regexp" 22 "strings" 23 "time" 24 25 expect "github.com/google/goexpect" 26 ) 27 28 // DefaultTimeout for `Expect` and `ExpectRE` functions. 29 var DefaultTimeout = 7 * time.Second 30 31 // TimeoutMultiplier increases all timeouts proportionally. Useful when running 32 // QEMU on a slow machine. 33 var TimeoutMultiplier = 2.0 34 35 // Options are VM start-up parameters. 36 type Options struct { 37 // QEMUPath is the path to the QEMU binary to invoke. 38 // 39 // If left unspecified, the UROOT_QEMU env var will be used. 40 // If the env var is unspecified, "qemu" is the default. 41 QEMUPath string 42 43 // Path to the kernel to boot. 44 Kernel string 45 46 // Path to the initramfs. 47 Initramfs string 48 49 // Extra kernel command-line arguments. 50 KernelArgs string 51 52 // Where to send serial output. 53 SerialOutput io.WriteCloser 54 55 // Timeout is the expect timeout. 56 Timeout time.Duration 57 58 // Devices are devices to expose to the QEMU VM. 59 Devices []Device 60 } 61 62 // Start starts a QEMU VM. 63 func (o *Options) Start() (*VM, error) { 64 cmdline := o.Cmdline() 65 66 gExpect, ch, err := expect.SpawnWithArgs(cmdline, -1, 67 expect.Tee(o.SerialOutput), 68 expect.CheckDuration(2*time.Millisecond)) 69 if err != nil { 70 return nil, err 71 } 72 return &VM{ 73 Options: o, 74 errCh: ch, 75 cmdline: cmdline, 76 gExpect: gExpect, 77 }, nil 78 } 79 80 // cmdline returns the command line arguments used to start QEMU. These 81 // arguments are derived from the given QEMU struct. 82 func (o *Options) Cmdline() []string { 83 var args []string 84 if len(o.QEMUPath) > 0 { 85 args = append(args, o.QEMUPath) 86 } else { 87 // Read first few arguments for env. 88 env := os.Getenv("UROOT_QEMU") 89 if env == "" { 90 env = "qemu" // default 91 } 92 args = append(args, strings.Fields(env)...) 93 } 94 95 // Disable graphics because we are using serial. 96 args = append(args, "-nographic") 97 98 // Arguments passed to the kernel: 99 // 100 // - earlyprintk=ttyS0: print very early debug messages to the serial 101 // - console=ttyS0: /dev/console points to /dev/ttyS0 (the serial port) 102 // - o.KernelArgs: extra, optional kernel arguments 103 if len(o.Kernel) != 0 { 104 args = append(args, "-kernel", o.Kernel) 105 args = append(args, "-append", o.KernelArgs) 106 } 107 if len(o.Initramfs) != 0 { 108 args = append(args, "-initrd", o.Initramfs) 109 } 110 111 for _, dev := range o.Devices { 112 if dev != nil { 113 if c := dev.Cmdline(); c != nil { 114 args = append(args, c...) 115 } 116 } 117 } 118 return args 119 } 120 121 // VM is a running QEMU virtual machine. 122 type VM struct { 123 Options *Options 124 cmdline []string 125 errCh <-chan error 126 gExpect *expect.GExpect 127 } 128 129 // Wait waits for the VM to exit. 130 func (v *VM) Wait() error { 131 return <-v.errCh 132 } 133 134 // Cmdline is the command-line the VM was started with. 135 func (v *VM) Cmdline() []string { 136 // Maybe return a copy? 137 return v.cmdline 138 } 139 140 // CmdlineQuoted quotes any of QEMU's command line arguments containing a space 141 // so it is easy to copy-n-paste into a shell for debugging. 142 func (v *VM) CmdlineQuoted() string { 143 args := make([]string, len(v.cmdline)) 144 for i, arg := range v.cmdline { 145 if strings.ContainsAny(arg, " \t\n") { 146 args[i] = fmt.Sprintf("'%s'", arg) 147 } else { 148 args[i] = arg 149 } 150 } 151 return strings.Join(args, " ") 152 } 153 154 // Close stops QEMU. 155 func (v *VM) Close() { 156 v.gExpect.Close() 157 v.gExpect = nil 158 } 159 160 // Send sends a string to QEMU's serial. 161 func (v *VM) Send(in string) { 162 v.gExpect.Send(in) 163 } 164 165 func (v *VM) TimeoutOr() time.Duration { 166 if v.Options.Timeout == 0 { 167 return DefaultTimeout 168 } 169 return v.Options.Timeout 170 } 171 172 // Expect returns an error if the given string is not found in vEMU's serial 173 // output within `DefaultTimeout`. 174 func (v *VM) Expect(search string) error { 175 return v.ExpectTimeout(search, v.TimeoutOr()) 176 } 177 178 // ExpectTimeout returns an error if the given string is not found in vEMU's serial 179 // output within the given timeout. 180 func (v *VM) ExpectTimeout(search string, timeout time.Duration) error { 181 _, err := v.ExpectRETimeout(regexp.MustCompile(regexp.QuoteMeta(search)), timeout) 182 return err 183 } 184 185 // ExpectRE returns an error if the given regular expression is not found in 186 // vEMU's serial output within `DefaultTimeout`. The matched string is 187 // returned. 188 func (v *VM) ExpectRE(pattern *regexp.Regexp) (string, error) { 189 return v.ExpectRETimeout(pattern, v.TimeoutOr()) 190 } 191 192 // ExpectRETimeout returns an error if the given regular expression is not 193 // found in vEMU's serial output within the given timeout. The matched string 194 // is returned. 195 func (v *VM) ExpectRETimeout(pattern *regexp.Regexp, timeout time.Duration) (string, error) { 196 scaled := time.Duration(float64(timeout) * TimeoutMultiplier) 197 str, _, err := v.gExpect.Expect(pattern, scaled) 198 return str, err 199 }