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