github.com/mitchellh/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  }