From 096f68ea08c3c4baf1bbdc549b257a67ecc87e25 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Tue, 13 Sep 2011 17:38:37 +0200 Subject: [PATCH] initramfs test framework Signed-off-by: Gerd Hoffmann --- initramfs/.gitignore | 3 + initramfs/10-qemu-udev.rules | 5 + initramfs/Makefile | 36 +++++++ initramfs/README | 44 ++++++++ initramfs/init.c | 225 ++++++++++++++++++++++++++++++++++++++++++ initramfs/initramfs-boot | 32 ++++++ initramfs/initramfs-create | 111 +++++++++++++++++++++ initramfs/test-ehci | 3 + initramfs/test-ehci.good | 8 ++ initramfs/test-hello.c | 7 ++ initramfs/test-hello.good | 1 + initramfs/test-uhci | 3 + initramfs/test-uhci.good | 3 + 13 files changed, 481 insertions(+), 0 deletions(-) create mode 100644 initramfs/.gitignore create mode 100644 initramfs/10-qemu-udev.rules create mode 100644 initramfs/Makefile create mode 100644 initramfs/README create mode 100644 initramfs/init.c create mode 100755 initramfs/initramfs-boot create mode 100755 initramfs/initramfs-create create mode 100755 initramfs/test-ehci create mode 100644 initramfs/test-ehci.good create mode 100644 initramfs/test-hello.c create mode 100644 initramfs/test-hello.good create mode 100755 initramfs/test-uhci create mode 100644 initramfs/test-uhci.good diff --git a/initramfs/.gitignore b/initramfs/.gitignore new file mode 100644 index 0000000..8ece42c --- /dev/null +++ b/initramfs/.gitignore @@ -0,0 +1,3 @@ +initramfs.cpio.gz +init +test-hello diff --git a/initramfs/10-qemu-udev.rules b/initramfs/10-qemu-udev.rules new file mode 100644 index 0000000..fb5cc0a --- /dev/null +++ b/initramfs/10-qemu-udev.rules @@ -0,0 +1,5 @@ +# load modules +DRIVER!="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe -b $env{MODALIAS}" + +# virtio console +KERNEL=="vport*", ATTR{name}=="?*", SYMLINK+="virtio-ports/$attr{name}" diff --git a/initramfs/Makefile b/initramfs/Makefile new file mode 100644 index 0000000..2db2b76 --- /dev/null +++ b/initramfs/Makefile @@ -0,0 +1,36 @@ + +CC := gcc +CFLAGS := -O2 -g -Wall +LDFLAGS := -lutil + +TOOLS := init +TEST_C := test-hello +TEST_SH := test-uhci test-ehci + +TARGETS := $(TOOLS) $(TEST_C) initramfs.cpio.gz +TESTS := $(TEST_C) $(TEST_SH) + +run-test-uhci : QEMU_ARGS := -usb -device usb-tablet +run-test-ehci : QEMU_ARGS := -readconfig ../docs/ich9-ehci-uhci.cfg + +default all: $(TARGETS) + +clean: + rm -f $(TARGETS) *.o *~ + rm -f org.qemu.initramfs.log + +init: init.o +test-hello: test-hello.o + +initramfs.cpio.gz: $(TOOLS) $(TESTS) initramfs-create + ./initramfs-create $@ $(TESTS) + +boot shell: $(TARGETS) + ./initramfs-boot + +run-test-%: $(TARGETS) + ./initramfs-boot /tests/test-$* $(QEMU_ARGS) + diff -u org.qemu.initramfs.log test-$*.good + +run-tests: $(patsubst %,run-%,$(TESTS)) + diff --git a/initramfs/README b/initramfs/README new file mode 100644 index 0000000..a9504ad --- /dev/null +++ b/initramfs/README @@ -0,0 +1,44 @@ + +This is an experimental test framework. + +Design goals +------------ + + * Allow running tests within a guest. + * Be small enougth that it can easily be included in + the qemu source tree. + * Don't require setup and/or downloading stuff + (i.e. guest images) from the internet. + * Be easy to use. + + +How it works +------------ + +It creates a linux initramfs from the bits found on the host machine, +then boots the host kernel with the initramfs just created within +qemu. A special init handles the setup (create core devices, mount +filesystems, start udev), command execution and shutdown. + +Obviously requires a linux host. It also needs udev for device setup +and module loading and virtio-serial support for logging. + + +Getting started +--------------- + +Just type "make boot", a few seconds later you should be greeted by +the guests bash prompt. You can look around now. Exiting the shell +will shutdown the guest. You'll find the shell output logged in the +"org.qemu.initramfs.log" file. + + +Run tests +--------- + +Type "make run-test-$name" for a single test or "make run-tests" to +run all of them. This will run the test program instead of the shell, +then compare the actual output with the expected output. + +This can probably be refined ... + diff --git a/initramfs/init.c b/initramfs/init.c new file mode 100644 index 0000000..94a8764 --- /dev/null +++ b/initramfs/init.c @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +static const char *logdev = "/dev/virtio-ports/org.qemu.initramfs.log"; + +static struct { + mode_t mode; + const char *path; + int major; + int minor; +} cdevs[] = { + { 0666, "/dev/null", 1, 3 }, + { 0600, "/dev/console", 5, 1 }, + { 0666, "/dev/ptmx", 5, 2 }, + { 0660, "/dev/kmsg", 1, 11 }, +}; + +static struct { + const char *dest; + const char *type; +} fs[] = { + { "/proc", "proc" }, + { "/sys", "sysfs" }, + { "/sys/kernel/debug", "debugfs" }, + { "/dev/pts", "devpts" }, + { "/dev/shm", "tmpfs" }, +}; + +int run(const char *cmd, ...) +{ + va_list args; + char *argv[16], *arg; + int i, pid; + + va_start(args, cmd); + argv[0] = (char*)cmd; + for (i = 1; i < ARRAY_SIZE(argv)-1; i++) { + arg = va_arg(args, char*); + if (arg == NULL) { + break; + } + argv[i] = arg; + } + argv[i] = NULL; + va_end(args); + + pid = fork(); + if (pid != 0) { + /* parent */ + if (pid < 0) { + perror("fork"); + } + return pid; + } + /* child */ + execv(cmd, argv); + fprintf(stderr, "exec %s: %s\n", cmd, strerror(errno)); + exit(1); +} + +int forward(int from_fd, int to_fd, int log_fd) +{ + int len, pos, rc; + char buf[512]; + + len = read(from_fd, buf, sizeof(buf)); + if (len < 0) { + return len; + } + for (pos = 0; pos < len; pos += rc) { + rc = write(to_fd, buf + pos, len - pos); + if (rc < 0) { + return rc; + } + } + if (log_fd != -1) { + for (pos = 0; pos < len; pos += rc) { + rc = write(log_fd, buf + pos, len - pos); + if (rc < 0) { + return rc; + } + } + } + return len; +} + +static void +tty_raw(int fd) +{ + struct termios tattr; + + tcgetattr(fd, &tattr); + tattr.c_lflag &= ~(ICANON|ECHO); + tattr.c_cc[VMIN] = 1; + tattr.c_cc[VTIME] = 0; + tcsetattr(fd, TCSAFLUSH, &tattr); +} + +int main(int argc, char *argv[]) +{ + const char *command; + pid_t cmdpid, pid; + int status, i, rc, pty, log; + bool cmd_exit, eof_seen; + + /* say hi */ + fprintf(stderr, "-*- qemu initramfs starting -*-\n"); + + /* mount filesystems */ + for (i = 0; i < ARRAY_SIZE(fs); i++) { + rc = mount(fs[i].type, fs[i].dest, fs[i].type, 0, NULL); + if (rc < 0) { + fprintf(stderr, "mount %s at %s: %s\n", + fs[i].type, fs[i].dest, strerror(errno)); + } + } + + /* create basic chardevs */ + for (i = 0; i < ARRAY_SIZE(cdevs); i++) { + rc = mknod(cdevs[i].path, cdevs[i].mode | S_IFCHR, + makedev(cdevs[i].major, cdevs[i].minor)); + if (rc < 0 && errno != EEXIST) { + fprintf(stderr, "mknod %s : %s\n", + cdevs[i].path, strerror(errno)); + } + } + + /* start udev, let it create devices */ + fprintf(stderr, "-*- starting udev -*-\n"); + run("/sbin/depmod", "-a", NULL); + run("/sbin/udevd", "--daemon", "--resolve-names=never", NULL); + run("/sbin/udevadm", "trigger", "--action=add", NULL); + + /* wait until virtio-serial is up'n'running */ + for (log = -1, i = 0; log == -1 && i < 32; i++) { + sleep(1); + run("/sbin/udevadm", "settle", NULL); + log = open(logdev, O_WRONLY); + } + if (log == -1) { + fprintf(stderr, "open %s: %s\n", logdev, strerror(errno)); + } + + /* setup environment */ + setenv("PATH", "/sbin:/bin", 1); + + /* run our command in a pseuto tty */ + command = getenv("QEMU_RUN"); + if (command == NULL) { + command = "/bin/bash"; + } + fprintf(stderr, "-*- running %s -*-\n", command); + + tty_raw(0); + cmdpid = forkpty(&pty, NULL, NULL, NULL); + if (cmdpid == 0) { + /* child */ + execl(command, command, NULL); + fprintf(stderr, "exec %s: %s\n", command, strerror(errno)); + exit(1); + } + + /* main loop */ + cmd_exit = false; + eof_seen = false; + do { + fd_set rd; + + /* reap children and zombies */ + for (;;) { + pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) { + break; + } + if (pid == cmdpid) { + cmd_exit = true; + } + } + + /* forward data between stdio (aka /dev/console) and pseudo tty */ + if (!eof_seen) { + FD_ZERO(&rd); + FD_SET(0, &rd); + FD_SET(pty, &rd); + rc = select(pty+1, &rd, NULL, NULL, NULL); + if (rc > 0) { + if (FD_ISSET(0, &rd)) { + /* stdin -> pseudo tty */ + if (forward(0, pty, -1) <= 0) { + eof_seen = 1; + } + } + if (FD_ISSET(pty, &rd)) { + /* pseudo tty -> stdout + log */ + if (forward(pty, 1, log) <= 0) { + eof_seen = 1; + } + } + } + } + } while (!cmd_exit || !eof_seen); + + /* powerdown vm */ + fprintf(stderr, "-*- qemu initramfs done -*-\n"); + reboot(RB_POWER_OFF); + + /* keep gcc happy ;) */ + return 0; +} diff --git a/initramfs/initramfs-boot b/initramfs/initramfs-boot new file mode 100755 index 0000000..4884a6d --- /dev/null +++ b/initramfs/initramfs-boot @@ -0,0 +1,32 @@ +#!/bin/sh + +base="$(dirname $0)" +command="$1" +shift; + +case "$(uname -m)" in + x86_64) qemu="x86_64-softmmu/qemu-system-x86_64" + ;; + i?86) qemu="i386-softmmu/qemu-system-i386" + ;; + *) echo "unknown arch: $(uname -m)" + exit 1 + ;; +esac + +kernel="$(echo /boot/vmlinu*$(uname -r)*)" +initrd="initramfs.cpio.gz" +append="console=ttyS0" +if test "$command" != ""; then + append="$append QEMU_RUN=$command" +fi + +exec ../$qemu -nographic -no-reboot \ + -machine accel=kvm:tcg \ + -kernel "$kernel" \ + -initrd "$initrd" \ + -append "$append" \ + -chardev file,id=cmdlog,path=org.qemu.initramfs.log \ + -device virtio-serial \ + -device virtserialport,name=org.qemu.initramfs.log,chardev=cmdlog \ + "$@" diff --git a/initramfs/initramfs-create b/initramfs/initramfs-create new file mode 100755 index 0000000..4525c11 --- /dev/null +++ b/initramfs/initramfs-create @@ -0,0 +1,111 @@ +#!/bin/bash +# +# Create a simple linux initramfs with the stuff found on the host. +# Intented to be used for a quick boot test with the host kernel. +# + +base="$(dirname $0)" +file="${1-initramfs.cpio.gz}"; shift +tests="$*" + +# create work dir +WORK="${TMPDIR-/tmp}/${0##*/}-$$" +mkdir "$WORK" || exit 1 +trap 'rm -rf "$WORK"' EXIT + + +############################################################################## +# helper functions + +function add_dirs() { + local dest="$WORK/fs" + local dir + for dir in dev dev/pts dev/shm etc proc sys \ + var var/tmp /var/log tmp + do + mkdir -p "$dest/$dir" + done +} + +function add_binary() { + local dest="$WORK/fs$1"; shift + local item + + mkdir -p "$dest" + for item in $*; do + cp -L "$(which $item)" "$dest" || exit 1 + done +} + +function add_symlink() { + local target="$1" + local name="$2" + ln -s "$target" "$WORK/fs$name" +} + +function add_data() { + local dest="$WORK/fs" + local item + + for item in $*; do + mkdir -p $(dirname "$dest/$item") + cp -L "$item" "$dest/$item" || exit 1 + done +} + +function add_data_to_dir() { + local dest="$WORK/fs$1"; shift + local item + + mkdir -p "$dest" + for item in $*; do + cp -L "$item" "$dest" || exit 1 + done +} + +function add_libs() { + local item dest + for item in $(ldd $WORK/fs/init $WORK/fs/bin/* $WORK/fs/sbin/* \ + $WORK/fs/tests/* $WORK/fs/lib/udev/*id); do + test -f "$item" || continue + test -f "$WORK/fs$item" && continue + dest=$(dirname "$WORK/fs$item") + mkdir -p "$dest" + cp -L "$item" "$dest" + done +} + +function add_modules() { + local item + + echo -n > "$WORK/modules" + for item in $*; do + modprobe --show-depends $item 2>/dev/null \ + | awk '{ print $2 }' >> "$WORK/modules" + done + add_data $(sort "$WORK/modules" | uniq) +} + + +############################################################################## +# main + +add_dirs +add_binary / $base/init +for t in $tests; do + add_binary /tests $base/$t; +done +add_binary /bin bash ls cat more dmesg ps uname find sort grep uniq +add_binary /sbin lspci lsusb mount umount udevd udevadm blkid +add_binary /sbin depmod insmod lsmod modinfo modprobe rmmod ip +add_symlink bash /bin/sh +add_data /usr/share/hwdata/*.ids +add_data /lib/udev/*id +add_data /lib/udev/rules.d/60-persistent-storage.rules +add_data_to_dir /lib/udev/rules.d $base/10-qemu-udev.rules +add_libs +add_modules virtio_pci virtio_blk virtio_net virtio_console \ + virtio_balloon virtio-rng 9pnet_virtio 9p \ + ata_piix ahci sd_mod sr_mod sg e1000 8139cp + +(cd $WORK/fs; find -print | cpio -o -R 0:0 -H newc) | gzip > "$file" diff --git a/initramfs/test-ehci b/initramfs/test-ehci new file mode 100755 index 0000000..bd4bb5c --- /dev/null +++ b/initramfs/test-ehci @@ -0,0 +1,3 @@ +#!/bin/sh +/sbin/lspci -s1d +/sbin/lsusb | sort diff --git a/initramfs/test-ehci.good b/initramfs/test-ehci.good new file mode 100644 index 0000000..dd9c6b8 --- /dev/null +++ b/initramfs/test-ehci.good @@ -0,0 +1,8 @@ +00:1d.0 USB Controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #1 (rev 03) +00:1d.1 USB Controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #2 (rev 03) +00:1d.2 USB Controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #3 (rev 03) +00:1d.7 USB Controller: Intel Corporation 82801I (ICH9 Family) USB2 EHCI Controller #1 (rev 03) +Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub +Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub +Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub +Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub diff --git a/initramfs/test-hello.c b/initramfs/test-hello.c new file mode 100644 index 0000000..e5ce2d2 --- /dev/null +++ b/initramfs/test-hello.c @@ -0,0 +1,7 @@ +#include + +int main(int argc, char *argv[]) +{ + printf("Hello world!\n"); + return 0; +} diff --git a/initramfs/test-hello.good b/initramfs/test-hello.good new file mode 100644 index 0000000..dfd6895 --- /dev/null +++ b/initramfs/test-hello.good @@ -0,0 +1 @@ +Hello world! diff --git a/initramfs/test-uhci b/initramfs/test-uhci new file mode 100755 index 0000000..0af70e0 --- /dev/null +++ b/initramfs/test-uhci @@ -0,0 +1,3 @@ +#!/bin/sh +/sbin/lspci -s1.2 +/sbin/lsusb | sort \ No newline at end of file diff --git a/initramfs/test-uhci.good b/initramfs/test-uhci.good new file mode 100644 index 0000000..c87271e --- /dev/null +++ b/initramfs/test-uhci.good @@ -0,0 +1,3 @@ +00:01.2 USB Controller: Intel Corporation 82371SB PIIX3 USB [Natoma/Triton II] (rev 01) +Bus 001 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub +Bus 001 Device 002: ID 0627:0001 Adomax Technology Co., Ltd -- 1.7.1