github.com/mem/u-root@v2.0.1-0.20181004165302-9b18b4636a33+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 is suitable for running QEMU-based integration tests. 6 // 7 // The environment variable `UROOT_QEMU` overrides the path to QEMU and the 8 // first few arguments (defaults to "qemu"). For example, I use: 9 // 10 // UROOT_QEMU='qemu-system-x86_64 -L . -m 4096 -enable-kvm' 11 // 12 // For CI, this environment variable is set in `.circleci/images/integration/Dockerfile`. 13 package qemu 14 15 import ( 16 "errors" 17 "io" 18 "os" 19 "regexp" 20 "strings" 21 "time" 22 23 "github.com/google/goexpect" 24 ) 25 26 // DefaultTimeout for `Expect` and `ExpectRE` functions. 27 var DefaultTimeout = 7 * time.Second 28 29 // TimeoutMultiplier increases all timeouts proportionally. Useful when running 30 // QEMU on a slow machine. 31 var TimeoutMultiplier = 2.0 32 33 // QEMU is filled and pass to `Start()`. 34 type QEMU struct { 35 // Path to the bzImage kernel 36 Kernel string 37 38 // Path to the initramfs 39 InitRAMFS string 40 41 // Extra kernel arguments 42 KernelArgs string 43 44 // Extra QEMU arguments 45 ExtraArgs []string 46 47 // Where to send serial output. 48 SerialOutput io.WriteCloser 49 50 gExpect *expect.GExpect 51 } 52 53 // CmdLine returns the command line arguments used to start QEMU. These 54 // arguments are derived from the given QEMU struct. 55 func (q *QEMU) CmdLine() []string { 56 // Read first few arguments for env. 57 env := os.Getenv("UROOT_QEMU") 58 if env == "" { 59 env = "qemu" // default 60 } 61 args := strings.Fields(env) 62 63 // Disable graphics because we are using serial. 64 args = append(args, "-nographic") 65 66 // Arguments passed to the kernel: 67 // 68 // - earlyprintk=ttyS0: print very early debug messages to the serial 69 // - console=ttyS0: /dev/console points to /dev/ttyS0 (the serial port) 70 // - q.KernelArgs: extra, optional kernel arguments 71 if q.Kernel != "" { 72 args = append(args, "-kernel", q.Kernel) 73 args = append(args, "-append", "console=ttyS0 earlyprintk=ttyS0") 74 if q.KernelArgs != "" { 75 args[len(args)-1] += " " + q.KernelArgs 76 } 77 } 78 if q.InitRAMFS != "" { 79 args = append(args, "-initrd", q.InitRAMFS) 80 } 81 82 return append(args, q.ExtraArgs...) 83 } 84 85 // CmdLineQuoted quotes any of QEMU's command line arguments containing a space 86 // so it is easy to copy-n-paste into a shell for debugging. 87 func (q *QEMU) CmdLineQuoted() (cmdline string) { 88 args := q.CmdLine() 89 for i, v := range q.CmdLine() { 90 if strings.ContainsAny(v, " \t\n") { 91 args[i] = "'" + v + "'" 92 } 93 } 94 return strings.Join(args, " ") 95 } 96 97 // Start QEMU. 98 func (q *QEMU) Start() error { 99 if q.gExpect != nil { 100 return errors.New("QEMU already started") 101 } 102 var err error 103 q.gExpect, _, err = expect.SpawnWithArgs(q.CmdLine(), -1, 104 expect.Tee(q.SerialOutput), 105 expect.CheckDuration(2*time.Millisecond)) 106 return err 107 } 108 109 // Close stops QEMU. 110 func (q *QEMU) Close() { 111 q.gExpect.Close() 112 q.gExpect = nil 113 } 114 115 // Send sends a string to QEMU's serial. 116 func (q *QEMU) Send(in string) { 117 q.gExpect.Send(in) 118 } 119 120 // Expect returns an error if the given string is not found in QEMU's serial 121 // output within `DefaultTimeout`. 122 func (q *QEMU) Expect(search string) error { 123 return q.ExpectTimeout(search, DefaultTimeout) 124 } 125 126 // ExpectTimeout returns an error if the given string is not found in QEMU's serial 127 // output within the given timeout. 128 func (q *QEMU) ExpectTimeout(search string, timeout time.Duration) error { 129 _, err := q.ExpectRETimeout(regexp.MustCompile(regexp.QuoteMeta(search)), timeout) 130 return err 131 } 132 133 // ExpectRE returns an error if the given regular expression is not found in 134 // QEMU's serial output within `DefaultTimeout`. The matched string is 135 // returned. 136 func (q *QEMU) ExpectRE(pattern *regexp.Regexp) (string, error) { 137 return q.ExpectRETimeout(pattern, DefaultTimeout) 138 } 139 140 // ExpectRETimeout returns an error if the given regular expression is not 141 // found in QEMU's serial output within the given timeout. The matched string 142 // is returned. 143 func (q *QEMU) ExpectRETimeout(pattern *regexp.Regexp, timeout time.Duration) (string, error) { 144 scaled := time.Duration(float64(timeout) * TimeoutMultiplier) 145 str, _, err := q.gExpect.Expect(pattern, scaled) 146 return str, err 147 }