github.com/sneal/packer@v0.5.2/builder/qemu/driver.go (about) 1 package qemu 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "github.com/mitchellh/multistep" 8 "io" 9 "log" 10 "os/exec" 11 "regexp" 12 "strings" 13 "sync" 14 "syscall" 15 "time" 16 "unicode" 17 ) 18 19 type DriverCancelCallback func(state multistep.StateBag) bool 20 21 // A driver is able to talk to qemu-system-x86_64 and perform certain 22 // operations with it. 23 type Driver interface { 24 // Stop stops a running machine, forcefully. 25 Stop() error 26 27 // Qemu executes the given command via qemu-system-x86_64 28 Qemu(qemuArgs ...string) error 29 30 // wait on shutdown of the VM with option to cancel 31 WaitForShutdown(<-chan struct{}) bool 32 33 // Qemu executes the given command via qemu-img 34 QemuImg(...string) error 35 36 // Verify checks to make sure that this driver should function 37 // properly. If there is any indication the driver can't function, 38 // this will return an error. 39 Verify() error 40 41 // Version reads the version of Qemu that is installed. 42 Version() (string, error) 43 } 44 45 type QemuDriver struct { 46 QemuPath string 47 QemuImgPath string 48 49 vmCmd *exec.Cmd 50 vmEndCh <-chan int 51 lock sync.Mutex 52 } 53 54 func (d *QemuDriver) Stop() error { 55 d.lock.Lock() 56 defer d.lock.Unlock() 57 58 if d.vmCmd != nil { 59 if err := d.vmCmd.Process.Kill(); err != nil { 60 return err 61 } 62 } 63 64 return nil 65 } 66 67 func (d *QemuDriver) Qemu(qemuArgs ...string) error { 68 d.lock.Lock() 69 defer d.lock.Unlock() 70 71 if d.vmCmd != nil { 72 panic("Existing VM state found") 73 } 74 75 stdout_r, stdout_w := io.Pipe() 76 stderr_r, stderr_w := io.Pipe() 77 78 log.Printf("Executing %s: %#v", d.QemuPath, qemuArgs) 79 cmd := exec.Command(d.QemuPath, qemuArgs...) 80 cmd.Stdout = stdout_w 81 cmd.Stderr = stderr_w 82 83 err := cmd.Start() 84 if err != nil { 85 err = fmt.Errorf("Error starting VM: %s", err) 86 return err 87 } 88 89 go logReader("Qemu stdout", stdout_r) 90 go logReader("Qemu stderr", stderr_r) 91 92 log.Printf("Started Qemu. Pid: %d", cmd.Process.Pid) 93 94 // Wait for Qemu to complete in the background, and mark when its done 95 endCh := make(chan int, 1) 96 go func() { 97 defer stderr_w.Close() 98 defer stdout_w.Close() 99 100 var exitCode int = 0 101 if err := cmd.Wait(); err != nil { 102 if exiterr, ok := err.(*exec.ExitError); ok { 103 // The program has exited with an exit code != 0 104 if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { 105 exitCode = status.ExitStatus() 106 } else { 107 exitCode = 254 108 } 109 } 110 } 111 112 endCh <- exitCode 113 114 d.lock.Lock() 115 defer d.lock.Unlock() 116 d.vmCmd = nil 117 d.vmEndCh = nil 118 }() 119 120 // Wait at least a couple seconds for an early fail from Qemu so 121 // we can report that. 122 select { 123 case exit := <-endCh: 124 if exit != 0 { 125 return fmt.Errorf("Qemu failed to start. Please run with logs to get more info.") 126 } 127 case <-time.After(2 * time.Second): 128 } 129 130 // Setup our state so we know we are running 131 d.vmCmd = cmd 132 d.vmEndCh = endCh 133 134 return nil 135 } 136 137 func (d *QemuDriver) WaitForShutdown(cancelCh <-chan struct{}) bool { 138 d.lock.Lock() 139 endCh := d.vmEndCh 140 d.lock.Unlock() 141 142 if endCh == nil { 143 return true 144 } 145 146 select { 147 case <-endCh: 148 return true 149 case <-cancelCh: 150 return false 151 } 152 } 153 154 func (d *QemuDriver) QemuImg(args ...string) error { 155 var stdout, stderr bytes.Buffer 156 157 log.Printf("Executing qemu-img: %#v", args) 158 cmd := exec.Command(d.QemuImgPath, args...) 159 cmd.Stdout = &stdout 160 cmd.Stderr = &stderr 161 err := cmd.Run() 162 163 stdoutString := strings.TrimSpace(stdout.String()) 164 stderrString := strings.TrimSpace(stderr.String()) 165 166 if _, ok := err.(*exec.ExitError); ok { 167 err = fmt.Errorf("QemuImg error: %s", stderrString) 168 } 169 170 log.Printf("stdout: %s", stdoutString) 171 log.Printf("stderr: %s", stderrString) 172 173 return err 174 } 175 176 func (d *QemuDriver) Verify() error { 177 return nil 178 } 179 180 func (d *QemuDriver) Version() (string, error) { 181 var stdout bytes.Buffer 182 183 cmd := exec.Command(d.QemuPath, "-version") 184 cmd.Stdout = &stdout 185 if err := cmd.Run(); err != nil { 186 return "", err 187 } 188 189 versionOutput := strings.TrimSpace(stdout.String()) 190 log.Printf("Qemu --version output: %s", versionOutput) 191 versionRe := regexp.MustCompile("qemu-kvm-[0-9]\\.[0-9]") 192 matches := versionRe.Split(versionOutput, 2) 193 if len(matches) == 0 { 194 return "", fmt.Errorf("No version found: %s", versionOutput) 195 } 196 197 log.Printf("Qemu version: %s", matches[0]) 198 return matches[0], nil 199 } 200 201 func logReader(name string, r io.Reader) { 202 bufR := bufio.NewReader(r) 203 for { 204 line, err := bufR.ReadString('\n') 205 if line != "" { 206 line = strings.TrimRightFunc(line, unicode.IsSpace) 207 log.Printf("%s: %s", name, line) 208 } 209 210 if err == io.EOF { 211 break 212 } 213 } 214 }