aboutsummaryrefslogtreecommitdiffstats
path: root/vm
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2016-08-22 13:48:51 -0700
committerDmitry Vyukov <dvyukov@google.com>2016-08-22 13:48:51 -0700
commit6eb48645a439657056220674433dc2e99ab2d8eb (patch)
tree70cb9c80f673e745ea76280ef5a92e4bf69e731e /vm
parent96cc1ccc795c04887cb8b35c731c0e68be674eab (diff)
vm/qemu: support 9p host-based image
If "image" is set to "9p" in config file, qemu VM will create a minimalistic image based on readonly-mapped host filesystem. The main things that we need are working sshd and ssh-keygen. /tmp, /etc/, /var, /root are remounted as tmpfs.
Diffstat (limited to 'vm')
-rw-r--r--vm/qemu/qemu.go96
1 files changed, 87 insertions, 9 deletions
diff --git a/vm/qemu/qemu.go b/vm/qemu/qemu.go
index 8633f4f2f..bc8fffbb6 100644
--- a/vm/qemu/qemu.go
+++ b/vm/qemu/qemu.go
@@ -5,6 +5,7 @@ package qemu
import (
"fmt"
+ "io/ioutil"
"math/rand"
"net"
"os"
@@ -67,6 +68,18 @@ func ctorImpl(cfg *vm.Config) (vm.Instance, error) {
return nil, err
}
+ if cfg.Image == "9p" {
+ inst.cfg.Sshkey = filepath.Join(inst.cfg.Workdir, "key")
+ keygen := exec.Command("ssh-keygen", "-t", "rsa", "-b", "2048", "-N", "", "-C", "", "-f", inst.cfg.Sshkey)
+ if out, err := keygen.CombinedOutput(); err != nil {
+ return nil, fmt.Errorf("failed to execute ssh-keygen: %v\n%s", err, out)
+ }
+ initFile := filepath.Join(cfg.Workdir, "init.sh")
+ if err := ioutil.WriteFile(initFile, []byte(strings.Replace(initScript, "{{KEY}}", inst.cfg.Sshkey, -1)), 0777); err != nil {
+ return nil, fmt.Errorf("failed to create init file: %v", err)
+ }
+ }
+
var err error
inst.rpipe, inst.wpipe, err = os.Pipe()
if err != nil {
@@ -88,11 +101,17 @@ func validateConfig(cfg *vm.Config) error {
if cfg.Bin == "" {
cfg.Bin = "qemu-system-x86_64"
}
- if _, err := os.Stat(cfg.Image); err != nil {
- return fmt.Errorf("image file '%v' does not exist: %v", cfg.Image, err)
- }
- if _, err := os.Stat(cfg.Sshkey); err != nil {
- return fmt.Errorf("ssh key '%v' does not exist: %v", cfg.Sshkey, err)
+ if cfg.Image == "9p" {
+ if cfg.Kernel == "" {
+ return fmt.Errorf("9p image requires kernel")
+ }
+ } else {
+ if _, err := os.Stat(cfg.Image); err != nil {
+ return fmt.Errorf("image file '%v' does not exist: %v", cfg.Image, err)
+ }
+ if _, err := os.Stat(cfg.Sshkey); err != nil {
+ return fmt.Errorf("ssh key '%v' does not exist: %v", cfg.Sshkey, err)
+ }
}
if cfg.Cpu <= 0 || cfg.Cpu > 1024 {
return fmt.Errorf("bad qemu cpu: %v, want [1-1024]", cfg.Cpu)
@@ -131,8 +150,6 @@ func (inst *instance) Boot() error {
}
// TODO: ignores inst.cfg.Cpu
args := []string{
- "-hda", inst.cfg.Image,
- "-snapshot",
"-m", strconv.Itoa(inst.cfg.Mem),
"-net", "nic",
"-net", fmt.Sprintf("user,host=%v,hostfwd=tcp::%v-:22", hostAddr, inst.port),
@@ -145,15 +162,33 @@ func (inst *instance) Boot() error {
"-usb", "-usbdevice", "mouse", "-usbdevice", "tablet",
"-soundhw", "all",
}
+ if inst.cfg.Image == "9p" {
+ args = append(args,
+ "-fsdev", "local,id=fsdev0,path=/,security_model=none,readonly",
+ "-device", "virtio-9p-pci,fsdev=fsdev0,mount_tag=/dev/root",
+ )
+ } else {
+ args = append(args,
+ "-hda", inst.cfg.Image,
+ "-snapshot",
+ )
+ }
if inst.cfg.Initrd != "" {
args = append(args,
"-initrd", inst.cfg.Initrd,
)
}
if inst.cfg.Kernel != "" {
+ cmdline := "console=ttyS0 oops=panic panic_on_warn=1 panic=-1 ftrace_dump_on_oops=orig_cpu debug earlyprintk=serial slub_debug=UZ "
+ if inst.cfg.Image == "9p" {
+ cmdline += "root=/dev/root rootfstype=9p rootflags=trans=virtio,version=9p2000.L,cache=loose "
+ cmdline += "init=" + filepath.Join(inst.cfg.Workdir, "init.sh") + " "
+ } else {
+ cmdline += "root=/dev/sda "
+ }
args = append(args,
"-kernel", inst.cfg.Kernel,
- "-append", "console=ttyS0 root=/dev/sda debug earlyprintk=serial slub_debug=UZ "+inst.cfg.Cmdline,
+ "-append", cmdline+inst.cfg.Cmdline,
)
}
qemu := exec.Command(inst.cfg.Bin, args...)
@@ -249,7 +284,11 @@ func (inst *instance) Forward(port int) (string, error) {
}
func (inst *instance) Copy(hostSrc string) (string, error) {
- vmDst := filepath.Join("/", filepath.Base(hostSrc))
+ basePath := "/"
+ if inst.cfg.Image == "9p" {
+ basePath = "/tmp"
+ }
+ vmDst := filepath.Join(basePath, filepath.Base(hostSrc))
args := append(inst.sshArgs("-P"), hostSrc, "root@localhost:"+vmDst)
cmd := exec.Command("scp", args...)
if err := cmd.Start(); err != nil {
@@ -322,6 +361,7 @@ func (inst *instance) sshArgs(portArg string) []string {
return []string{
"-i", inst.cfg.Sshkey,
portArg, strconv.Itoa(inst.port),
+ "-F", "/dev/null",
"-o", "ConnectionAttempts=10",
"-o", "ConnectTimeout=10",
"-o", "BatchMode=yes",
@@ -331,3 +371,41 @@ func (inst *instance) sshArgs(portArg string) []string {
"-o", "LogLevel=error",
}
}
+
+const initScript = `#! /bin/bash
+set -eux
+mount -t proc none /proc
+mount -t sysfs none /sys
+mount -t debugfs nodev /sys/kernel/debug/
+mount -t tmpfs none /tmp
+mount -t tmpfs none /var
+mount -t tmpfs none /etc
+mount -t tmpfs none /root
+touch /etc/fstab
+echo "root::0:0:root:/root:/bin/bash" > /etc/passwd
+mkdir -p /etc/ssh
+cp {{KEY}}.pub /root/
+chmod 0700 /root
+chmod 0600 /root/key.pub
+mkdir -p /var/run/sshd/
+chmod 700 /var/run/sshd
+cat > /etc/ssh/sshd_config <<EOF
+ Port 22
+ Protocol 2
+ UsePrivilegeSeparation no
+ HostKey {{KEY}}
+ PermitRootLogin yes
+ AuthenticationMethods publickey
+ ChallengeResponseAuthentication no
+ AuthorizedKeysFile /root/key.pub
+ IgnoreUserKnownHosts yes
+ AllowUsers root
+ LogLevel INFO
+ TCPKeepAlive yes
+ RSAAuthentication yes
+ PubkeyAuthentication yes
+EOF
+/sbin/dhclient eth0
+/usr/sbin/sshd -e -D
+/sbin/halt -f
+`