From d723d1a8f5738cd163d6ed46671297b2618c897b Mon Sep 17 00:00:00 2001 From: Mikhail Klementev Date: Tue, 18 Sep 2018 21:45:21 +0000 Subject: [PATCH] Implements start/stop --- qemu-kernel.go | 144 ++++++++++++++++++++++++++++++++++++++++++++ qemu-kernel_test.go | 47 +++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 qemu-kernel.go create mode 100644 qemu-kernel_test.go diff --git a/qemu-kernel.go b/qemu-kernel.go new file mode 100644 index 0000000..0738049 --- /dev/null +++ b/qemu-kernel.go @@ -0,0 +1,144 @@ +// Copyright 2018 Mikhail Klementev. All rights reserved. +// Use of this source code is governed by a GPLv3 license +// (or later) that can be found in the LICENSE file. + +package qemukernel + +import ( + "errors" + "fmt" + "io" + "os" + "os/exec" + "syscall" + "time" +) + +func readBytesUntilEOF(pipe io.ReadCloser) (buf []byte, err error) { + bufSize := 1024 + for err != io.EOF { + stdout := make([]byte, bufSize) + var n int + + n, err = pipe.Read(stdout) + if err != nil && err != io.EOF { + return + } + + buf = append(buf, stdout[:n]...) + } + + if err == io.EOF { + err = nil + } + return +} + +func readUntilEOF(pipe io.ReadCloser) (str string, err error) { + buf, err := readBytesUntilEOF(pipe) + str = string(buf) + return +} + +type arch string + +const ( + X86_64 arch = "x86_64" + I386 = "i386" + // TODO add other + + unsupported = "unsupported" // for test purposes +) + +// Kernel describe kernel parameters for qemu +type Kernel struct { + Name string + Path string +} + +// QemuSystem describe qemu parameters and runned process +type QemuSystem struct { + arch arch + kernel Kernel + + // accessible while qemu is runned + cmd *exec.Cmd + pipe struct { + stdin io.WriteCloser + stderr io.ReadCloser + stdout io.ReadCloser + } + died bool + + // accessible after qemu is closed + Stdout, Stderr string + exitErr error +} + +// NewQemuSystem constructor +func NewQemuSystem(arch arch, kernel Kernel) (q QemuSystem, err error) { + if _, err = exec.LookPath("qemu-system-" + string(arch)); err != nil { + return + } + q.arch = arch + + if _, err = os.Stat(kernel.Path); err != nil { + return + } + q.kernel = kernel + + return +} + +// Start qemu process +func (q *QemuSystem) Start() (err error) { + q.cmd = exec.Command("qemu-system-"+string(q.arch), + // TODO + "-snapshot", + "-nographic") + + if q.pipe.stdin, err = q.cmd.StdinPipe(); err != nil { + return + } + + if q.pipe.stdout, err = q.cmd.StdoutPipe(); err != nil { + return + } + + if q.pipe.stderr, err = q.cmd.StderrPipe(); err != nil { + return + } + + err = q.cmd.Start() + if err != nil { + return + } + + go func() { + q.Stdout, _ = readUntilEOF(q.pipe.stdout) + q.Stderr, _ = readUntilEOF(q.pipe.stderr) + q.exitErr = q.cmd.Wait() + q.died = true + }() + + time.Sleep(time.Second / 10) // wait for immediately die + + if q.died { + err = errors.New("qemu died immediately: " + q.Stderr) + } + + return +} + +// Stop qemu process +func (q *QemuSystem) Stop() { + // 1 00/01 01 01 SOH (Ctrl-A) START OF HEADING + fmt.Fprintf(q.pipe.stdin, "%cx", 1) + // wait for die + time.Sleep(time.Second / 10) + if !q.died { + q.cmd.Process.Signal(syscall.SIGTERM) + time.Sleep(time.Second / 10) + q.cmd.Process.Signal(syscall.SIGKILL) + } +} diff --git a/qemu-kernel_test.go b/qemu-kernel_test.go new file mode 100644 index 0000000..cd797b1 --- /dev/null +++ b/qemu-kernel_test.go @@ -0,0 +1,47 @@ +// Copyright 2018 Mikhail Klementev. All rights reserved. +// Use of this source code is governed by a GPLv3 license +// (or later) that can be found in the LICENSE file. + +package qemukernel + +import ( + "testing" +) + +func TestQemuSystemNew_InvalidKernelPath(t *testing.T) { + kernel := Kernel{Name: "Invalid", Path: "/invalid/path"} + if _, err := NewQemuSystem(X86_64, kernel); err == nil { + t.Fatal(err) + } +} + +func TestQemuSystemNew_InvalidQemuArch(t *testing.T) { + // FIXME put kernel image to path not just "any valid path" + kernel := Kernel{Name: "Valid path", Path: "/bin/sh"} + if _, err := NewQemuSystem(unsupported, kernel); err == nil { + t.Fatal(err) + } +} + +func TestQemuSystemNew(t *testing.T) { + // FIXME put kernel image to path not just "any valid path" + kernel := Kernel{Name: "Valid path", Path: "/bin/sh"} + if _, err := NewQemuSystem(X86_64, kernel); err != nil { + t.Fatal(err) + } +} + +func TestQemuSystemStart(t *testing.T) { + // TODO check kernel path on other distros than gentoo + kernel := Kernel{Name: "Host kernel", Path: "/boot/vmlinuz-4.18.8"} + qemu, err := NewQemuSystem(X86_64, kernel) + if err != nil { + t.Fatal(err) + } + + if err = qemu.Start(); err != nil { + t.Fatal(err) + } + + qemu.Stop() +}