From b6233d1fff5d9a6bbc61e7123a57bdd7d8cbc792 Mon Sep 17 00:00:00 2001 From: rjones Date: Tue, 3 Mar 2009 15:35:50 +0000 Subject: [PATCH] Running qemu as a subprocess. --- .cvsignore | 2 + Makefile.am | 1 + autogen.sh | 2 + configure.ac | 7 +- examples/.cvsignore | 5 + examples/LICENSE | 2 + examples/Makefile.am | 7 + examples/df.c | 36 ++++ src/.cvsignore | 6 + src/Makefile.am | 20 ++ src/guestfs.c | 508 +++++++++++++++++++++++++++++++++++++++++++++++++ src/guestfs.h | 55 ++++++ src/guestfs_protocol.x | 22 +++ 13 files changed, 672 insertions(+), 1 deletion(-) create mode 100644 examples/.cvsignore create mode 100644 examples/LICENSE create mode 100644 examples/Makefile.am create mode 100644 examples/df.c create mode 100644 src/.cvsignore create mode 100644 src/Makefile.am create mode 100644 src/guestfs.c create mode 100644 src/guestfs.h create mode 100644 src/guestfs_protocol.x diff --git a/.cvsignore b/.cvsignore index 286d115..9707adc 100644 --- a/.cvsignore +++ b/.cvsignore @@ -1,3 +1,4 @@ +.deps Makefile.in Makefile aclocal.m4 @@ -7,4 +8,5 @@ config.h.in config.log config.status configure +libtool stamp-h1 \ No newline at end of file diff --git a/Makefile.am b/Makefile.am index ae5ba41..cb1fdd2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,3 +15,4 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +SUBDIRS = src examples diff --git a/autogen.sh b/autogen.sh index 959fdce..8d86dbe 100755 --- a/autogen.sh +++ b/autogen.sh @@ -21,6 +21,8 @@ set -e set -v export AUTOMAKE='automake --foreign' +aclocal +libtoolize autoreconf cd daemon autoreconf \ No newline at end of file diff --git a/configure.ac b/configure.ac index 45f1e04..7b9db49 100644 --- a/configure.ac +++ b/configure.ac @@ -17,6 +17,7 @@ AC_INIT([libguestfs],[0.1]) AM_INIT_AUTOMAKE +AC_PROG_LIBTOOL dnl Make sure the user has created the link to nfs-utils source. if ! test -e $srcdir/daemon/nfs-utils \ @@ -35,6 +36,9 @@ test "x$U" != "x" && AC_MSG_ERROR([Compiler not ANSI compliant]) AC_PROG_CC_C_O +dnl Headers. +AC_CHECK_HEADERS([errno.h sys/types.h sys/un.h sys/wait.h sys/socket.h]) + dnl Check for rpcgen and XDR library. rpcgen is optional. AC_CHECK_PROG([RPCGEN],[rpcgen],[rpcgen],[no]) AM_CONDITIONAL([RPCGEN],[test "x$RPCGEN" != "xno"]) @@ -49,6 +53,7 @@ dnl on several factors explained in the README. AC_PATH_PROG([QEMU],[qemu],[no], [$PATH$PATH_SEPARATOR/usr/sbin$PATH_SEPARATOR/sbin]) test "x$QEMU" = "xno" && AC_MSG_ERROR([No 'qemu' program found]) +AC_DEFINE_UNQUOTED([QEMU],["$QEMU"],[Location of qemu binary.]) dnl Check for mkinitrd, cpio. AC_PATH_PROG([MKINITRD],[mkinitrd],[no], @@ -59,5 +64,5 @@ test "x$CPIO" = "xno" && AC_MSG_ERROR([No 'cpio' program found]) dnl Produce output files. AC_CONFIG_HEADERS([config.h]) -AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile examples/Makefile]) AC_OUTPUT diff --git a/examples/.cvsignore b/examples/.cvsignore new file mode 100644 index 0000000..d8d6624 --- /dev/null +++ b/examples/.cvsignore @@ -0,0 +1,5 @@ +.deps +.libs +Makefile +Makefile.in +df \ No newline at end of file diff --git a/examples/LICENSE b/examples/LICENSE new file mode 100644 index 0000000..5ba695a --- /dev/null +++ b/examples/LICENSE @@ -0,0 +1,2 @@ +All the examples in the examples/ subdirectory may be freely copied +without any restrictions. diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..66a32d7 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,7 @@ +# libguestfs examples + +noinst_PROGRAMS = df + +df_SOURCES = df.c +df_CFLAGS = -I$(top_builddir)/src +df_LDADD = $(top_builddir)/src/libguestfs.la diff --git a/examples/df.c b/examples/df.c new file mode 100644 index 0000000..818de6e --- /dev/null +++ b/examples/df.c @@ -0,0 +1,36 @@ +/* A simple "df" command for guests. */ + +#include +#include +#include +#include + +int +main (int argc, char *argv[]) +{ + guestfs_h *g; + + if (argc != 2 || access (argv[1], F_OK) != 0) { + fprintf (stderr, "Usage: df disk-image\n"); + exit (1); + } + + g = guestfs_create (); + if (!g) { + perror ("guestfs_create"); + exit (1); + } + + guestfs_set_exit_on_error (g, 1); + guestfs_set_verbose (g, 1); + + guestfs_add_drive (g, argv[1]); + + guestfs_wait_ready (g); + + + + + guestfs_free (g); + return 0; +} diff --git a/src/.cvsignore b/src/.cvsignore new file mode 100644 index 0000000..c45d1c0 --- /dev/null +++ b/src/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +libguestfs.la +*.lo \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..d900e3a --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,20 @@ +# libguestfs +# Copyright (C) 2009 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +lib_LTLIBRARIES = libguestfs.la + +libguestfs_la_SOURCES = guestfs.c guestfs.h diff --git a/src/guestfs.c b/src/guestfs.c new file mode 100644 index 0000000..e18021f --- /dev/null +++ b/src/guestfs.c @@ -0,0 +1,508 @@ +/* libguestfs + * Copyright (C) 2009 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#define _BSD_SOURCE /* for mkdtemp, usleep */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ERRNO_H +#include +#endif + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef HAVE_SYS_UN_H +#include +#endif + +#include "guestfs.h" + +static int error (guestfs_h *g, const char *fs, ...); +static int perrorf (guestfs_h *g, const char *fs, ...); +static void *safe_malloc (guestfs_h *g, int nbytes); +static void *safe_realloc (guestfs_h *g, void *ptr, int nbytes); +static char *safe_strdup (guestfs_h *g, const char *str); + +/* GuestFS handle and connection. */ +struct guestfs_h +{ + /* All these socks/pids are -1 if not connected. */ + int sock; /* Daemon communications socket. */ + int pid; /* Qemu PID. */ + + char *tmpdir; /* Temporary directory containing logfile + * and socket. Cleaned up unless there is + * an error. + */ + + char **cmdline; /* Qemu command line. */ + int cmdline_size; + + guestfs_abort_fn abort_fn; + int exit_on_error; + int verbose; +}; + +guestfs_h * +guestfs_create (void) +{ + guestfs_h *g; + + g = malloc (sizeof (*g)); + if (!g) return NULL; + + g->sock = -1; + g->pid = -1; + + g->tmpdir = NULL; + + g->abort_fn = abort; /* Have to set these before safe_malloc. */ + g->exit_on_error = 0; + g->verbose = getenv ("LIBGUESTFS_VERBOSE") != NULL; + + g->cmdline = safe_malloc (g, sizeof (char *) * 1); + g->cmdline_size = 1; + g->cmdline[0] = NULL; /* This is chosen by guestfs_launch. */ + + return g; +} + +void +guestfs_free (guestfs_h *g) +{ + int i; + char filename[256]; + + if (g->pid) guestfs_kill_subprocess (g); + + /* The assumption is that programs calling this have successfully + * used qemu, so delete the logfile and socket directory. + */ + if (g->tmpdir) { + snprintf (filename, sizeof filename, "%s/sock", g->tmpdir); + unlink (filename); + + snprintf (filename, sizeof filename, "%s/qemu.log", g->tmpdir); + unlink (filename); + + rmdir (g->tmpdir); + + free (g->tmpdir); + } + + for (i = 0; i < g->cmdline_size; ++i) + free (g->cmdline[i]); + free (g->cmdline); + + free (g); +} + +/* Cleanup fds and sockets, assuming the subprocess is dead already. */ +static void +cleanup_fds (guestfs_h *g) +{ + if (g->sock >= 0) close (g->sock); + g->sock = -1; +} + +/* Wait for subprocess to exit. */ +static void +wait_subprocess (guestfs_h *g) +{ + if (g->pid >= 0) waitpid (g->pid, NULL, 0); + g->pid = -1; +} + +static int +error (guestfs_h *g, const char *fs, ...) +{ + va_list args; + + fprintf (stderr, "libguestfs: "); + va_start (args, fs); + vfprintf (stderr, fs, args); + va_end (args); + fputc ('\n', stderr); + + if (g->exit_on_error) exit (1); + return -1; +} + +static int +perrorf (guestfs_h *g, const char *fs, ...) +{ + va_list args; + char buf[256]; + int err = errno; + + fprintf (stderr, "libguestfs: "); + va_start (args, fs); + vfprintf (stderr, fs, args); + va_end (args); + strerror_r (err, buf, sizeof buf); + fprintf (stderr, ": %s\n", buf); + + if (g->exit_on_error) exit (1); + return -1; +} + +static void * +safe_malloc (guestfs_h *g, int nbytes) +{ + void *ptr = malloc (nbytes); + if (!ptr) g->abort_fn (); + return ptr; +} + +static void * +safe_realloc (guestfs_h *g, void *ptr, int nbytes) +{ + void *p = realloc (ptr, nbytes); + if (!p) g->abort_fn (); + return p; +} + +static char * +safe_strdup (guestfs_h *g, const char *str) +{ + char *s = strdup (str); + if (!s) g->abort_fn (); + return s; +} + +void +guestfs_set_out_of_memory_handler (guestfs_h *g, guestfs_abort_fn a) +{ + g->abort_fn = a; +} + +guestfs_abort_fn +guestfs_get_out_of_memory_handler (guestfs_h *g) +{ + return g->abort_fn; +} + +void +guestfs_set_exit_on_error (guestfs_h *g, int e) +{ + g->exit_on_error = e; +} + +int +guestfs_get_exit_on_error (guestfs_h *g) +{ + return g->exit_on_error; +} + +void +guestfs_set_verbose (guestfs_h *g, int v) +{ + g->verbose = v; +} + +int +guestfs_get_verbose (guestfs_h *g) +{ + return g->verbose; +} + +/* Add an escaped string to the current command line. */ +static int +add_cmdline (guestfs_h *g, const char *str) +{ + if (g->pid >= 0) + return error (g, "command line cannot be altered after qemu subprocess launched"); + + g->cmdline_size++; + g->cmdline = safe_realloc (g, g->cmdline, sizeof (char *) * g->cmdline_size); + g->cmdline[g->cmdline_size-1] = safe_strdup (g, str); + + return 0; +} + +int +guestfs_config (guestfs_h *g, + const char *qemu_param, const char *qemu_value) +{ + if (qemu_param[0] != '-') + return error (g, "guestfs_config: parameter must begin with '-' character"); + + /* A bit fascist, but the user will probably break the extra + * parameters that we add if they try to set any of these. + */ + if (strcmp (qemu_param, "-kernel") == 0 || + strcmp (qemu_param, "-initrd") == 0 || + strcmp (qemu_param, "-nographic") == 0 || + strcmp (qemu_param, "-serial") == 0 || + strcmp (qemu_param, "-vnc") == 0 || + strcmp (qemu_param, "-full-screen") == 0 || + strcmp (qemu_param, "-std-vga") == 0 || + strcmp (qemu_param, "-vnc") == 0) + return error (g, "guestfs_config: parameter '%s' isn't allowed"); + + if (add_cmdline (g, qemu_param) != 0) return -1; + + if (qemu_value != NULL) { + if (add_cmdline (g, qemu_value) != 0) return -1; + } + + return 0; +} + +int +guestfs_add_drive (guestfs_h *g, const char *filename) +{ + int len = strlen (filename) + 64; + char buf[len]; + + if (strchr (filename, ',') != NULL) + return error (g, "filename cannot contain ',' (comma) character"); + + snprintf (buf, len, "file=%s,media=disk", filename); + + return guestfs_config (g, "-drive", buf); +} + +int +guestfs_add_cdrom (guestfs_h *g, const char *filename) +{ + int len = strlen (filename) + 64; + char buf[len]; + + if (strchr (filename, ',') != NULL) + return error (g, "filename cannot contain ',' (comma) character"); + + snprintf (buf, len, "file=%s,if=ide,index=1,media=cdrom", filename); + + return guestfs_config (g, "-drive", buf); +} + +int +guestfs_launch (guestfs_h *g) +{ + static const char *dir_template = "/tmp/libguestfsXXXXXX"; + int r, i; + const char *qemu = QEMU; /* XXX */ + const char *kernel = "/boot/vmlinuz-2.6.27.15-170.2.24.fc10.x86_64"; + const char *initrd = "/boot/initrd-2.6.27.15-170.2.24.fc10.x86_64.img"; + char unixsock[256]; + char vmchannel[256]; + char tmpfile[256]; + + /* XXX Choose which qemu to run. */ + /* XXX Choose initrd, etc. */ + + /* Make the temporary directory containing the logfile and socket. */ + if (!g->tmpdir) { + g->tmpdir = safe_strdup (g, dir_template); + if (mkdtemp (g->tmpdir) == NULL) + return perrorf (g, "%s: cannot create temporary directory", dir_template); + + snprintf (unixsock, sizeof unixsock, "%s/sock", g->tmpdir); + } + + r = fork (); + if (r == -1) + return perrorf (g, "fork"); + + if (r > 0) { /* Parent (library). */ + g->pid = r; + + /* If qemu is going to die during startup, give it a tiny amount of + * time to print the error message. + */ + usleep (10000); + } else { /* Child (qemu). */ + /* Set up the full command line. Do this in the subprocess so we + * don't need to worry about cleaning up. + */ + g->cmdline[0] = (char *) qemu; + + g->cmdline = realloc (g->cmdline, sizeof (char *) * (g->cmdline_size + 14)); + if (g->cmdline == NULL) { + perror ("realloc"); + _exit (1); + } + + snprintf (vmchannel, sizeof vmchannel, + "channel,%d:unix:%s,server,nowait", 666, unixsock); + + g->cmdline[g->cmdline_size ] = "-kernel"; + g->cmdline[g->cmdline_size+ 1] = (char *) kernel; + g->cmdline[g->cmdline_size+ 2] = "-initrd"; + g->cmdline[g->cmdline_size+ 3] = (char *) initrd; + g->cmdline[g->cmdline_size+ 4] = "-append"; + g->cmdline[g->cmdline_size+ 5] = "console=ttyS0"; + g->cmdline[g->cmdline_size+ 6] = "-nographic"; + g->cmdline[g->cmdline_size+ 7] = "-serial"; + g->cmdline[g->cmdline_size+ 8] = "stdio"; + g->cmdline[g->cmdline_size+ 9] = "-net"; + g->cmdline[g->cmdline_size+10] = vmchannel; + g->cmdline[g->cmdline_size+11] = "-net"; + g->cmdline[g->cmdline_size+12] = "user,vlan0"; + g->cmdline[g->cmdline_size+13] = NULL; + + if (g->verbose) { + fprintf (stderr, "Running %s", qemu); + for (i = 0; g->cmdline[i]; ++i) + fprintf (stderr, " %s", g->cmdline[i]); + fprintf (stderr, "\n"); + } + + /* Set up stdin, stdout. Messages should go to the logfile. */ + close (0); + close (1); + open ("/dev/null", O_RDONLY); + snprintf (tmpfile, sizeof tmpfile, "%s/qemu.log", g->tmpdir); + open (tmpfile, O_WRONLY|O_CREAT|O_APPEND, 0644); + /*dup2 (1, 2);*/ + + execv (qemu, g->cmdline); /* Run qemu. */ + perror (qemu); + _exit (1); + } + + return 0; +} + +#define UNIX_PATH_MAX 108 + +int +guestfs_wait_ready (guestfs_h *g) +{ + int r, i, lsock; + struct sockaddr_un addr; + + if (guestfs_ready (g)) return 0; + + /* Launch the subprocess, if there isn't one already. */ + if (g->pid == -1) { + if (guestfs_launch (g) != 0) + return -1; + } + + if (g->sock >= 0) { + close (g->sock); + g->sock = -1; + } + + lsock = socket (AF_UNIX, SOCK_STREAM, 0); + if (lsock == -1) + return perrorf (g, "socket"); + + addr.sun_family = AF_UNIX; + snprintf (addr.sun_path, UNIX_PATH_MAX, "%s/sock", g->tmpdir); + + if (bind (lsock, (struct sockaddr *) &addr, sizeof addr) == -1) { + perrorf (g, "bind"); + close (lsock); + return -1; + } + + if (listen (lsock, 1) == -1) { + perrorf (g, "listen"); + close (lsock); + return -1; + } + + if (fcntl (lsock, F_SETFL, O_NONBLOCK) == -1) { + perrorf (g, "set socket non-blocking"); + close (lsock); + return -1; + } + + /* Wait until the daemon running inside the guest connects to the + * Unix socket, which indicates it's alive. Qemu might exit in the + * meantime if there is a problem. More problematically qemu might + * hang, which we can only detect by timeout. + */ + for (i = 0; i < 30; ++i) { + r = waitpid (g->pid, NULL, WNOHANG); + + if (r > 0 || (r == -1 && errno == ECHILD)) { + error (g, "qemu subprocess exited unexpectedly during initialization"); + g->pid = -1; + cleanup_fds (g); + close (lsock); + return -1; + } + + r = accept (lsock, NULL, 0); + if (r >= 0) { + g->sock = r; + fcntl (g->sock, F_SETFL, O_NONBLOCK); + close (lsock); + return 0; + } + if (errno == EAGAIN) { + sleep (1); + continue; + } + perrorf (g, "accept"); + close (lsock); + guestfs_kill_subprocess (g); + return -1; + } + + close (lsock); + return error (g, "timeout waiting for guest to become ready"); +} + +int +guestfs_ready (guestfs_h *g) +{ + return + g->pid >= 0 && + kill (g->pid, 0) == 0 && + g->sock >= 0 /* && + guestfs_ping_daemon (g) >= 0 */; +} + +int +guestfs_kill_subprocess (guestfs_h *g) +{ + if (g->pid >= 0) { + if (g->verbose) + fprintf (stderr, "sending SIGINT to pid %d\n", g->pid); + + kill (g->pid, SIGINT); + wait_subprocess (g); + } + + cleanup_fds (g); + + return 0; +} diff --git a/src/guestfs.h b/src/guestfs.h new file mode 100644 index 0000000..9b84e31 --- /dev/null +++ b/src/guestfs.h @@ -0,0 +1,55 @@ +/* libguestfs + * Copyright (C) 2009 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef GUESTFS_H_ +#define GUESTFS_H_ + +/* For API documentation, please read the manual page guestfs(3). */ + +typedef struct guestfs_h guestfs_h; + +/* Create and destroy the guest handle. */ +extern guestfs_h *guestfs_create (void); +extern void guestfs_free (guestfs_h *g); + +/* Guest configuration. */ +extern int guestfs_config (guestfs_h *g, + const char *qemu_param, const char *qemu_value); +extern int guestfs_add_drive (guestfs_h *g, const char *filename); +extern int guestfs_add_cdrom (guestfs_h *g, const char *filename); + +/* Steps to start up the guest. */ +extern int guestfs_launch (guestfs_h *g); +extern int guestfs_wait_ready (guestfs_h *g); +extern int guestfs_ready (guestfs_h *g); + +/* Kill the guest subprocess. */ +extern int guestfs_kill_subprocess (guestfs_h *g); + +/* Error handling. */ +typedef void (*guestfs_abort_fn) (void); +extern void guestfs_set_out_of_memory_handler (guestfs_h *g, guestfs_abort_fn); +extern guestfs_abort_fn guestfs_get_out_of_memory_handler (guestfs_h *g); + +extern void guestfs_set_exit_on_error (guestfs_h *g, int exit_on_error); +extern int guestfs_get_exit_on_error (guestfs_h *g); + +extern void guestfs_set_verbose (guestfs_h *g, int verbose); +extern int guestfs_get_verbose (guestfs_h *g); + +#endif /* GUESTFS_H_ */ diff --git a/src/guestfs_protocol.x b/src/guestfs_protocol.x new file mode 100644 index 0000000..59bad58 --- /dev/null +++ b/src/guestfs_protocol.x @@ -0,0 +1,22 @@ +/* libguestfs + * Copyright (C) 2009 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * This file describes the client [library] to server [daemon in + * guest] protocol. As far as possible, all code in the library is + * automatically generated from this protocol description using + * rpcgen and the perl script 'generator.pl'. + */ -- 1.8.3.1