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