From 057f65617330de90f3a44d8375ba2662eba3761f Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Sun, 26 Nov 2023 16:54:32 +0100 Subject: [PATCH 1/5] Support for Windows --- pkg/osutil/osutil_windows.go | 49 ++++++++++++++++++++++ vm/gvisor/gvisor.go | 4 +- vm/gvisor/gvisor_windows.go | 8 ++++ vm/vmimpl/console.go | 1 + vm/vmimpl/console_windows.go | 80 ++++++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 vm/gvisor/gvisor_windows.go create mode 100644 vm/vmimpl/console_windows.go diff --git a/pkg/osutil/osutil_windows.go b/pkg/osutil/osutil_windows.go index 8a0e8b77a11f..3321acb86e05 100644 --- a/pkg/osutil/osutil_windows.go +++ b/pkg/osutil/osutil_windows.go @@ -5,11 +5,56 @@ package osutil import ( "fmt" + "io" "os" "os/exec" + "path/filepath" + "strconv" "syscall" ) +// ProcessTempDir creates a new temp dir in where and returns its path and an unique index. +// It also cleans up old, unused temp dirs after dead processes. +func ProcessTempDir(where string) (string, error) { + for i := 0; i < 1e3; i++ { + path := filepath.Join(where, fmt.Sprintf("instance-%v", i)) + pidfile := filepath.Join(path, ".pid") + err := os.Mkdir(path, DefaultDirPerm) + if os.IsExist(err) { + // Try to clean up. + if cleanupTempDir(path, pidfile) { + i-- + } + continue + } + if err != nil { + return "", err + } + if err := WriteFile(pidfile, []byte(strconv.Itoa(syscall.Getpid()))); err != nil { + return "", err + } + return path, nil + } + return "", fmt.Errorf("too many live instances") +} + +func cleanupTempDir(path, pidfile string) bool { + data, err := os.ReadFile(pidfile) + if err == nil && len(data) > 0 { + pid, err := strconv.Atoi(string(data)) + if err == nil && pid > 1 { + err := exec.Command("taskkill", "/f", "/pid", strconv.Itoa(pid)).Run() + if err == nil { + if os.Remove(pidfile) == nil { + return os.RemoveAll(path) == nil + } + } + } + } + // If err != nil, assume that the pid file is not created yet. + return false +} + func HandleInterrupts(shutdown chan struct{}) { } @@ -32,6 +77,10 @@ func CloseMemMappedFile(f *os.File, mem []byte) error { return fmt.Errorf("CloseMemMappedFile is not implemented") } +func LongPipe() (io.ReadCloser, io.WriteCloser, error) { + return nil, nil, fmt.Errorf("LongPipe is not implemented") +} + func ProcessExitStatus(ps *os.ProcessState) int { return ps.Sys().(syscall.WaitStatus).ExitStatus() } diff --git a/vm/gvisor/gvisor.go b/vm/gvisor/gvisor.go index 5ca3e0971d23..ffcb8d5a050f 100644 --- a/vm/gvisor/gvisor.go +++ b/vm/gvisor/gvisor.go @@ -1,8 +1,10 @@ +//go:build !windows + // Copyright 2018 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. - // Package gvisor provides support for gVisor, user-space kernel, testing. // See https://github.com/google/gvisor +// package gvisor import ( diff --git a/vm/gvisor/gvisor_windows.go b/vm/gvisor/gvisor_windows.go new file mode 100644 index 000000000000..5cffec3568f3 --- /dev/null +++ b/vm/gvisor/gvisor_windows.go @@ -0,0 +1,8 @@ +//go:build windows + +// Copyright 2018 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. +// Package gvisor provides support for gVisor, user-space kernel, testing. +// See https://github.com/google/gvisor +// +package gvisor diff --git a/vm/vmimpl/console.go b/vm/vmimpl/console.go index 6744ac10c3a9..a2dedeb5cd73 100644 --- a/vm/vmimpl/console.go +++ b/vm/vmimpl/console.go @@ -1,5 +1,6 @@ // Copyright 2017 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. +//go:build !windows package vmimpl diff --git a/vm/vmimpl/console_windows.go b/vm/vmimpl/console_windows.go new file mode 100644 index 000000000000..00a8832fc182 --- /dev/null +++ b/vm/vmimpl/console_windows.go @@ -0,0 +1,80 @@ +// Copyright 2017 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. +//go:build windows + +package vmimpl + +import ( + "fmt" + "io" + "os/exec" + "sync" + + _ "github.com/google/syzkaller/pkg/osutil" +) + +// Merely to fix build. +const ( + unixCBAUD = 0 + unixCRTSCTS = 0 + syscallTCGETS = 0 + syscallTCSETS = 0 +) + +func OpenConsole(con string) (rc io.ReadCloser, err error) { + return nil, fmt.Errorf("failed to get console termios on Windows: %v", err) +} + +type tty struct { + mu sync.Mutex + fd int +} + +// OpenRemoteKernelLog accesses to the host where Android VM runs on, not Android VM itself. +// The host stores all kernel outputs of Android VM so in case of crashes nothing will be lost. +func OpenRemoteKernelLog(ip, console string) (rc io.ReadCloser, err error) { + return nil, fmt.Errorf("failed to connect to console server on Windows: %v", err) +} + +// Open dmesg remotely. +func OpenRemoteConsole(bin string, args ...string) (rc io.ReadCloser, err error) { + return nil, fmt.Errorf("failed to start adb: %v", err) +} + +// OpenAdbConsole provides fallback console output using 'adb shell dmesg -w'. +func OpenAdbConsole(bin, dev string) (rc io.ReadCloser, err error) { + return OpenRemoteConsole(bin, "-s", dev, "shell") +} + +type remoteCon struct { + closeMu sync.Mutex + readMu sync.Mutex + cmd *exec.Cmd + rpipe io.ReadCloser +} + +func (t *remoteCon) Read(buf []byte) (int, error) { + t.readMu.Lock() + n, err := t.rpipe.Read(buf) + t.readMu.Unlock() + return n, err +} + +func (t *remoteCon) Close() error { + t.closeMu.Lock() + cmd := t.cmd + t.cmd = nil + t.closeMu.Unlock() + if cmd == nil { + return nil + } + + cmd.Process.Kill() + + t.readMu.Lock() + t.rpipe.Close() + t.readMu.Unlock() + + cmd.Process.Wait() + return nil +} From a216e668fbdb4428f687e07752b58f61383fbd8b Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Sun, 26 Nov 2023 18:42:56 +0100 Subject: [PATCH 2/5] Return that functions are not implemented. --- vm/vmimpl/console_windows.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vm/vmimpl/console_windows.go b/vm/vmimpl/console_windows.go index 00a8832fc182..7708291eeeff 100644 --- a/vm/vmimpl/console_windows.go +++ b/vm/vmimpl/console_windows.go @@ -9,8 +9,6 @@ import ( "io" "os/exec" "sync" - - _ "github.com/google/syzkaller/pkg/osutil" ) // Merely to fix build. @@ -22,7 +20,7 @@ const ( ) func OpenConsole(con string) (rc io.ReadCloser, err error) { - return nil, fmt.Errorf("failed to get console termios on Windows: %v", err) + return nil, fmt.Errorf("OpenConsole not implemented. Failed to get console termios on Windows") } type tty struct { @@ -33,12 +31,12 @@ type tty struct { // OpenRemoteKernelLog accesses to the host where Android VM runs on, not Android VM itself. // The host stores all kernel outputs of Android VM so in case of crashes nothing will be lost. func OpenRemoteKernelLog(ip, console string) (rc io.ReadCloser, err error) { - return nil, fmt.Errorf("failed to connect to console server on Windows: %v", err) + return nil, fmt.Errorf("OpenRemoteKernelLog not implemented. Failed to connect to console server on Windows") } // Open dmesg remotely. func OpenRemoteConsole(bin string, args ...string) (rc io.ReadCloser, err error) { - return nil, fmt.Errorf("failed to start adb: %v", err) + return nil, fmt.Errorf("OpenRemoteConsole not implemented. Failed to start adb") } // OpenAdbConsole provides fallback console output using 'adb shell dmesg -w'. From ec7a4ad6b9e5066c478d8c28e6cabfb10b710f16 Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Wed, 29 Nov 2023 07:42:29 +0100 Subject: [PATCH 3/5] Add basic support for reporting --- pkg/report/report.go | 2 +- pkg/report/windows.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 pkg/report/windows.go diff --git a/pkg/report/report.go b/pkg/report/report.go index 4bf0adeadb22..130373fba9f7 100644 --- a/pkg/report/report.go +++ b/pkg/report/report.go @@ -146,7 +146,7 @@ var ctors = map[string]fn{ targets.NetBSD: ctorNetbsd, targets.OpenBSD: ctorOpenbsd, targets.Fuchsia: ctorFuchsia, - targets.Windows: ctorStub, + targets.Windows: ctorWindows, } type config struct { diff --git a/pkg/report/windows.go b/pkg/report/windows.go new file mode 100644 index 000000000000..a27440b2e1dd --- /dev/null +++ b/pkg/report/windows.go @@ -0,0 +1,30 @@ +// Copyright 2017 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package report + +type windows struct { + *config +} + +func ctorWindows(cfg *config) (reporterImpl, []string, error) { + ctx := &windows{ + config: cfg, + } + return ctx, nil, nil +} + +func (ctx *windows) ContainsCrash(output []byte) bool { + // panic("not implemented") + return false +} + +func (ctx *windows) Parse(output []byte) *Report { + // panic("not implemented") + return nil +} + +func (ctx *windows) Symbolize(rep *Report) error { + // panic("not implemented") + return nil +} From b79cbfed48d5a0bf41ba71b86c1d2c69a9f991ed Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Sun, 26 Nov 2023 20:59:47 +0100 Subject: [PATCH 4/5] Support for vbox --- vm/vbox/vbox.go | 245 ++++++++++++++++++++++++++++++++++++++++++++++++ vm/vm.go | 1 + 2 files changed, 246 insertions(+) create mode 100644 vm/vbox/vbox.go diff --git a/vm/vbox/vbox.go b/vm/vbox/vbox.go new file mode 100644 index 000000000000..5e170a54518c --- /dev/null +++ b/vm/vbox/vbox.go @@ -0,0 +1,245 @@ +// Copyright 2023 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package vbox + +import ( + "fmt" + "io" + "net" + "os" + "os/exec" + "path/filepath" + "strconv" + "time" + "strings" + "regexp" + + "github.com/google/syzkaller/pkg/config" + "github.com/google/syzkaller/pkg/log" + "github.com/google/syzkaller/pkg/osutil" + "github.com/google/syzkaller/pkg/report" + "github.com/google/syzkaller/vm/vmimpl" +) + +func init() { + vmimpl.Register("vbox", ctor, false) +} + +type Config struct { + BaseVM string `json:"base_vm"` // name of the base vm + Serial string `json:"serial"` // name of the base serial location (linux: /tmp or windows: \\.\pipe\) + Count int `json:"count"` // number of VMs to run in parallel + Options string `json:"options"` // any additional options (like --options=Link for faster cloning) +} + +type Pool struct { + env *vmimpl.Env + cfg *Config +} + +type instance struct { + cfg *Config + baseVM string + vmname string + ipAddr string + serialname string + closed chan bool + debug bool + sshuser string + sshkey string + forwardPort int +} + +func ctor(env *vmimpl.Env) (vmimpl.Pool, error) { + cfg := &Config{} + if err := config.LoadData(env.Config, cfg); err != nil { + return nil, err + } + if cfg.BaseVM == "" { + return nil, fmt.Errorf("config param base_vm is empty") + } + if cfg.Serial == "" { + return nil, fmt.Errorf("config param Serial is empty") + } + if cfg.Count < 1 || cfg.Count > 128 { + return nil, fmt.Errorf("invalid config param count: %v, want [1, 128]", cfg.Count) + } + if _, err := exec.LookPath("VBoxManage"); err != nil { + return nil, fmt.Errorf("cannot find VBoxManage") + } + if env.Debug && cfg.Count > 1 { + log.Logf(0, "limiting number of VMs from %v to 1 in debug mode", cfg.Count) + cfg.Count = 1 + } + pool := &Pool{ + cfg: cfg, + env: env, + } + return pool, nil +} + +func (pool *Pool) Count() int { + return pool.cfg.Count +} + +func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) { + createTime := strconv.FormatInt(time.Now().UnixNano(), 10) + vmname := "syzk-" + createTime + sshkey := pool.env.SSHKey + sshuser := pool.env.SSHUser + inst := &instance{ + cfg: pool.cfg, + debug: pool.env.Debug, + baseVM: pool.cfg.BaseVM, + vmname: vmname, + serialname: vmname, + sshkey: sshkey, + sshuser: sshuser, + closed: make(chan bool), + } + if err := inst.clone(); err != nil { + return nil, err + } + if err := inst.boot(); err != nil { + return nil, err + } + return inst, nil +} + +func (inst *instance) clone() error { + if inst.debug { + log.Logf(0, "cloning %v to %v", inst.baseVM, inst.vmname) + } + if _, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "clonevm", inst.baseVM, "--name="+inst.vmname, "--register","--mode=all",inst.cfg.Options); err != nil { + return err + } + serialPrefix := filepath.Dir(inst.cfg.Serial) + serial := filepath.Join(serialPrefix, inst.serialname) + if inst.debug { + log.Logf(0, "setting serial %v to %v", inst.vmname, inst.serialname) + } + if _, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "modifyvm", inst.vmname, "--uartmode1", "server", serial ); err != nil { + return err + } + return nil +} + +func (inst *instance) boot() error { + if inst.debug { + log.Logf(0, "starting %v", inst.vmname) + } + if _, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "startvm", inst.vmname, "--type=headless"); err != nil { + return err + } + if inst.debug { + log.Logf(0, "getting IP of %v", inst.vmname) + } + ip, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "guestproperty", "wait", inst.vmname, "VirtualBox/GuestInfo/Net/0/V4/IP","--timeout 300000") // in msec = 1000 * 5 * 60 + if err != nil { + return err + } + if strings.Contains(string(ip), "VBoxManage: error") { + log.Logf(0, "Error waiting for VM %v to output IP", inst.vmname) + return fmt.Errorf("Error waiting for VM to output IP") + } + re := regexp.MustCompile(`((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}`) + match := re.FindStringSubmatch(string(ip)) + inst.ipAddr = match[1] + if inst.debug { + log.Logf(0, "VM %v has IP: %v", inst.vmname, inst.ipAddr) + } + return nil +} + +func (inst *instance) Forward(port int) (string, error) { + if inst.forwardPort != 0 { + return "", fmt.Errorf("isolated: Forward port already set") + } + if port == 0 { + return "", fmt.Errorf("isolated: Forward port is zero") + } + inst.forwardPort = port + return fmt.Sprintf("127.0.0.1:%v", port), nil +} + +func (inst *instance) Close() { + if inst.debug { + log.Logf(0, "stopping %v", inst.vmname) + } + osutil.RunCmd(5*time.Minute, "", "VBoxManage", "controlvm", inst.vmname, "poweroff") + if inst.debug { + log.Logf(0, "deleting %v", inst.vmname) + } + osutil.RunCmd(5*time.Minute, "", "VBoxManage", "unregistervm", inst.vmname, "--delete") + close(inst.closed) +} + +func (inst *instance) Copy(hostSrc string) (string, error) { + base := filepath.Base(hostSrc) + vmDst := filepath.Join("/", base) + + args := append(vmimpl.SCPArgs(inst.debug, inst.sshkey, 22), + hostSrc, fmt.Sprintf("%v@%v:%v", inst.sshuser, inst.ipAddr, vmDst)) + + if inst.debug { + log.Logf(0, "running command: scp %#v", args) + } + + _, err := osutil.RunCmd(3*time.Minute, "", "scp", args...) + if err != nil { + return "", err + } + return vmDst, nil +} + +func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) ( + <-chan []byte, <-chan error, error) { + serialPrefix := filepath.Dir(inst.cfg.Serial) + serial := filepath.Join(serialPrefix, inst.serialname) + dmesg, err := net.Dial("unix", serial) + if err != nil { + return nil, nil, err + } + + rpipe, wpipe, err := osutil.LongPipe() + if err != nil { + dmesg.Close() + return nil, nil, err + } + + args := vmimpl.SSHArgs(inst.debug, inst.sshkey, 22) + // Forward target port as part of the ssh connection (reverse proxy) + if inst.forwardPort != 0 { + proxy := fmt.Sprintf("%v:127.0.0.1:%v", inst.forwardPort, inst.forwardPort) + args = append(args, "-R", proxy) + } + args = append(args, inst.sshuser+"@"+inst.ipAddr, "cd / && exec "+command) + if inst.debug { + log.Logf(0, "running command: ssh %#v", args) + } + cmd := osutil.Command("ssh", args...) + cmd.Stdout = wpipe + cmd.Stderr = wpipe + if err := cmd.Start(); err != nil { + dmesg.Close() + rpipe.Close() + wpipe.Close() + return nil, nil, err + } + wpipe.Close() + + var tee io.Writer + if inst.debug { + tee = os.Stdout + } + merger := vmimpl.NewOutputMerger(tee) + merger.Add("dmesg", dmesg) + merger.Add("ssh", rpipe) + + return vmimpl.Multiplex(cmd, merger, dmesg, timeout, stop, inst.closed, inst.debug) +} + +func (inst *instance) Diagnose(rep *report.Report) ([]byte, bool) { + return nil, false +} diff --git a/vm/vm.go b/vm/vm.go index fc2e0eb2c7f2..9f2914c7d9e5 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -36,6 +36,7 @@ import ( _ "github.com/google/syzkaller/vm/proxyapp" _ "github.com/google/syzkaller/vm/qemu" _ "github.com/google/syzkaller/vm/starnix" + _ "github.com/google/syzkaller/vm/vbox" _ "github.com/google/syzkaller/vm/vmm" _ "github.com/google/syzkaller/vm/vmware" ) From e0ab1e355f49bbd7d913f3077ccd09599dd761de Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Wed, 29 Nov 2023 07:37:52 +0100 Subject: [PATCH 5/5] virtualbox flexibility and better debugging --- vm/vbox/vbox.go | 68 +++++++++++++++++++++++++++++++++------------ vm/vmimpl/vmimpl.go | 8 ++++-- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/vm/vbox/vbox.go b/vm/vbox/vbox.go index 5e170a54518c..3afca0843b5a 100644 --- a/vm/vbox/vbox.go +++ b/vm/vbox/vbox.go @@ -30,6 +30,10 @@ type Config struct { BaseVM string `json:"base_vm"` // name of the base vm Serial string `json:"serial"` // name of the base serial location (linux: /tmp or windows: \\.\pipe\) Count int `json:"count"` // number of VMs to run in parallel + PrefixCmd string `json:"prefixcmd"` // prefix command with (e.g. exec) + GuestDir string `json:"guestdir"` // /tmp or something else + Mode string `json:"mode"` // --type=headless or --type=gui + Snapshot string `json:"snapshot"` // --snapshot="syzstart" Options string `json:"options"` // any additional options (like --options=Link for faster cloning) } @@ -59,6 +63,12 @@ func ctor(env *vmimpl.Env) (vmimpl.Pool, error) { if cfg.BaseVM == "" { return nil, fmt.Errorf("config param base_vm is empty") } + if cfg.Mode == "" { + cfg.Mode = "--type=headless" + } + if cfg.GuestDir == "" { + cfg.GuestDir = "/" + } if cfg.Serial == "" { return nil, fmt.Errorf("config param Serial is empty") } @@ -85,7 +95,7 @@ func (pool *Pool) Count() int { func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) { createTime := strconv.FormatInt(time.Now().UnixNano(), 10) - vmname := "syzk-" + createTime + vmname := pool.cfg.BaseVM+"-syzk-" + createTime sshkey := pool.env.SSHKey sshuser := pool.env.SSHUser inst := &instance{ @@ -108,18 +118,24 @@ func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) { } func (inst *instance) clone() error { + /// VBoxManage snapshot win11-fuzz take syzstart + //if inst.debug { + // log.Logf(0, "snapshot %v to %v", inst.baseVM, inst.cfg.Snapshot) + //} + //if _, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "snapshot", inst.baseVM, "take",inst.cfg.Snapshot); err != nil { + // return err + //} if inst.debug { log.Logf(0, "cloning %v to %v", inst.baseVM, inst.vmname) } - if _, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "clonevm", inst.baseVM, "--name="+inst.vmname, "--register","--mode=all",inst.cfg.Options); err != nil { + if _, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "clonevm", inst.baseVM, "--name="+inst.vmname, "--register","--mode=machine",inst.cfg.Snapshot, inst.cfg.Options); err != nil { return err } - serialPrefix := filepath.Dir(inst.cfg.Serial) - serial := filepath.Join(serialPrefix, inst.serialname) + serial := filepath.Join(inst.cfg.Serial, inst.serialname) if inst.debug { - log.Logf(0, "setting serial %v to %v", inst.vmname, inst.serialname) + log.Logf(0, "setting serial %v to %v", inst.vmname, serial) } - if _, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "modifyvm", inst.vmname, "--uartmode1", "server", serial ); err != nil { + if _, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "modifyvm", inst.vmname, "--uartmode1", "file", serial ); err != nil { return err } return nil @@ -129,13 +145,18 @@ func (inst *instance) boot() error { if inst.debug { log.Logf(0, "starting %v", inst.vmname) } - if _, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "startvm", inst.vmname, "--type=headless"); err != nil { + if _, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "startvm", inst.vmname, inst.cfg.Mode); err != nil { return err } if inst.debug { log.Logf(0, "getting IP of %v", inst.vmname) } - ip, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "guestproperty", "wait", inst.vmname, "VirtualBox/GuestInfo/Net/0/V4/IP","--timeout 300000") // in msec = 1000 * 5 * 60 + ip, err := osutil.RunCmd(5*time.Minute, "", "VBoxManage", "guestproperty", "wait", inst.vmname, "/VirtualBox/GuestInfo/Net/0/V4/IP","--timeout", "300000") // in msec = 1000 * 5 * 60 + if err != nil { + return err + } + time.Sleep(5 * time.Second) // give time to settle + ip, err = osutil.RunCmd(1*time.Minute, "", "VBoxManage", "guestproperty", "get", inst.vmname, "/VirtualBox/GuestInfo/Net/0/V4/IP") if err != nil { return err } @@ -143,7 +164,7 @@ func (inst *instance) boot() error { log.Logf(0, "Error waiting for VM %v to output IP", inst.vmname) return fmt.Errorf("Error waiting for VM to output IP") } - re := regexp.MustCompile(`((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}`) + re := regexp.MustCompile(`(((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4})`) match := re.FindStringSubmatch(string(ip)) inst.ipAddr = match[1] if inst.debug { @@ -177,7 +198,7 @@ func (inst *instance) Close() { func (inst *instance) Copy(hostSrc string) (string, error) { base := filepath.Base(hostSrc) - vmDst := filepath.Join("/", base) + vmDst := filepath.Join(inst.cfg.GuestDir, base) args := append(vmimpl.SCPArgs(inst.debug, inst.sshkey, 22), hostSrc, fmt.Sprintf("%v@%v:%v", inst.sshuser, inst.ipAddr, vmDst)) @@ -195,16 +216,23 @@ func (inst *instance) Copy(hostSrc string) (string, error) { func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) ( <-chan []byte, <-chan error, error) { - serialPrefix := filepath.Dir(inst.cfg.Serial) - serial := filepath.Join(serialPrefix, inst.serialname) + serial := filepath.Join(inst.cfg.Serial, inst.serialname) + if inst.debug { + log.Logf(0, "connecting to serial at: %v", serial) + } dmesg, err := net.Dial("unix", serial) if err != nil { - return nil, nil, err + log.Logf(0, "error connecting to serial %v: %v", serial, err) + // return nil, nil, err + dmesg = nil } rpipe, wpipe, err := osutil.LongPipe() if err != nil { - dmesg.Close() + log.Logf(0, "osutil.LongPipe() error: %v", err) + if dmesg != nil { + dmesg.Close() + } return nil, nil, err } @@ -214,15 +242,19 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin proxy := fmt.Sprintf("%v:127.0.0.1:%v", inst.forwardPort, inst.forwardPort) args = append(args, "-R", proxy) } - args = append(args, inst.sshuser+"@"+inst.ipAddr, "cd / && exec "+command) + args = append(args, inst.sshuser+"@"+inst.ipAddr, "cd "+inst.cfg.GuestDir+" && " +inst.cfg.PrefixCmd+" "+command) if inst.debug { log.Logf(0, "running command: ssh %#v", args) + log.Logf(0, "running command (human): ssh %s", strings.Join(args, " ")) } cmd := osutil.Command("ssh", args...) cmd.Stdout = wpipe cmd.Stderr = wpipe if err := cmd.Start(); err != nil { - dmesg.Close() + log.Logf(0, "cmd.Start() error: %v", err) + if dmesg != nil { + dmesg.Close() + } rpipe.Close() wpipe.Close() return nil, nil, err @@ -234,7 +266,9 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin tee = os.Stdout } merger := vmimpl.NewOutputMerger(tee) - merger.Add("dmesg", dmesg) + if dmesg != nil { + merger.Add("dmesg", dmesg) + } merger.Add("ssh", rpipe) return vmimpl.Multiplex(cmd, merger, dmesg, timeout, stop, inst.closed, inst.debug) diff --git a/vm/vmimpl/vmimpl.go b/vm/vmimpl/vmimpl.go index 0a4ada0285e5..5b2990886841 100644 --- a/vm/vmimpl/vmimpl.go +++ b/vm/vmimpl/vmimpl.go @@ -168,7 +168,9 @@ func Multiplex(cmd *exec.Cmd, merger *OutputMerger, console io.Closer, timeout t signal(fmt.Errorf("instance closed")) case err := <-merger.Err: cmd.Process.Kill() - console.Close() + if console != nil { + console.Close() + } merger.Wait() if cmdErr := cmd.Wait(); cmdErr == nil { // If the command exited successfully, we got EOF error from merger. @@ -179,7 +181,9 @@ func Multiplex(cmd *exec.Cmd, merger *OutputMerger, console io.Closer, timeout t return } cmd.Process.Kill() - console.Close() + if console != nil { + console.Close() + } merger.Wait() cmd.Wait() }()