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  }