Starts up QEMU now
authorrjones <rjones>
Wed, 4 Mar 2009 13:18:48 +0000 (13:18 +0000)
committerrjones <rjones>
Wed, 4 Mar 2009 13:18:48 +0000 (13:18 +0000)
src/guestfs.c
src/guestfs.h

index e18021f..a5b3ae4 100644 (file)
@@ -27,6 +27,7 @@
 #include <ctype.h>
 #include <string.h>
 #include <fcntl.h>
+#include <time.h>
 
 #ifdef HAVE_ERRNO_H
 #include <errno.h>
@@ -56,12 +57,17 @@ 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);
 
+#define VMCHANNEL_PORT 6666
+#define VMCHANNEL_ADDR "10.0.2.4"
+
 /* 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. */
+  time_t start_t;              /* The time when we started qemu. */
+  int daemon_up;               /* Received hello message from daemon. */
 
   char *tmpdir;                        /* Temporary directory containing logfile
                                 * and socket.  Cleaned up unless there is
@@ -87,6 +93,9 @@ guestfs_create (void)
   g->sock = -1;
   g->pid = -1;
 
+  g->start_t = 0;
+  g->daemon_up = 0;
+
   g->tmpdir = NULL;
 
   g->abort_fn = abort;         /* Have to set these before safe_malloc. */
@@ -157,7 +166,10 @@ error (guestfs_h *g, const char *fs, ...)
   va_end (args);
   fputc ('\n', stderr);
 
-  if (g->exit_on_error) exit (1);
+  if (g->exit_on_error) {
+    guestfs_kill_subprocess (g);
+    exit (1);
+  }
   return -1;
 }
 
@@ -175,7 +187,10 @@ perrorf (guestfs_h *g, const char *fs, ...)
   strerror_r (err, buf, sizeof buf);
   fprintf (stderr, ": %s\n", buf);
 
-  if (g->exit_on_error) exit (1);
+  if (g->exit_on_error) {
+    guestfs_kill_subprocess (g);
+    exit (1);
+  }
   return -1;
 }
 
@@ -315,12 +330,11 @@ guestfs_launch (guestfs_h *g)
 {
   static const char *dir_template = "/tmp/libguestfsXXXXXX";
   int r, i;
-  const char *qemu = QEMU;     /* XXX */
+  /*const char *qemu = QEMU;*/ /* XXX */
+  const char *qemu = "/home/rjones/d/redhat/libguestfs/qemu";
   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";
+  const char *initrd = "/tmp/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. */
@@ -341,39 +355,57 @@ guestfs_launch (guestfs_h *g)
   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.
+    /* 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). */
+
+    /* Start the clock ... */
+    time (&g->start_t);
+  }
+  else {                       /* Child (qemu). */
+    char vmchannel[256];
+    char logfile[256];
+    char append[256];
+
     /* 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));
+    g->cmdline =
+      realloc (g->cmdline, sizeof (char *) * (g->cmdline_size + 16));
     if (g->cmdline == NULL) {
       perror ("realloc");
       _exit (1);
     }
 
+    /* Construct the -net channel parameter for qemu. */
     snprintf (vmchannel, sizeof vmchannel,
-             "channel,%d:unix:%s,server,nowait", 666, unixsock);
+             "channel,%d:unix:%s,server,nowait", VMCHANNEL_PORT, unixsock);
+
+    /* Linux kernel command line. */
+    snprintf (append, sizeof append,
+             "console=ttyS0 guestfs=%s:%d", VMCHANNEL_ADDR, VMCHANNEL_PORT);
+
+    /* XXX -m */
 
     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+ 5] = append;
     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;
+    g->cmdline[g->cmdline_size+12] = "user,vlan=0";
+    g->cmdline[g->cmdline_size+13] = "-net";
+    g->cmdline[g->cmdline_size+14] = "nic,vlan=0";
+    g->cmdline[g->cmdline_size+15] = NULL;
 
     if (g->verbose) {
       fprintf (stderr, "Running %s", qemu);
@@ -386,10 +418,15 @@ guestfs_launch (guestfs_h *g)
     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);
+    snprintf (logfile, sizeof logfile, "%s/qemu.log", g->tmpdir);
+    open (logfile, O_WRONLY|O_CREAT|O_APPEND, 0644);
     /*dup2 (1, 2);*/
 
+    /* Set up a new process group, so we can signal this process
+     * and all subprocesses (eg. if qemu is really a shell script).
+     */
+    setpgid (0, 0);
+
     execv (qemu, g->cmdline);  /* Run qemu. */
     perror (qemu);
     _exit (1);
@@ -398,15 +435,43 @@ guestfs_launch (guestfs_h *g)
   return 0;
 }
 
-#define UNIX_PATH_MAX 108
+/* A peculiarity of qemu's vmchannel implementation is that both sides
+ * connect to qemu, ie:
+ *
+ *   libguestfs  --- connect --> qemu <-- connect --- daemon
+ *    (host)                                          (guest)
+ *
+ * This has several implications: (1) qemu creates the Unix socket, so
+ * we have to wait for it to do that.  (2) we have to arrange for the
+ * daemon to send a "hello" message which we also wait for.
+ *
+ * At any time during this, the qemu subprocess might run slowly, die
+ * or hang (it's very prone to just hanging if the BIOS fails for any
+ * reason or if the kernel cannot be found to boot from).
+ *
+ * The only realistic way to handle this is, unfortunately, using
+ * timeouts, also checking if the qemu subprocess is still alive.
+ *
+ * We could do better here by monitoring the Linux kernel log messages
+ * (via the serial console, which is currently just redirected to a
+ * log file) and seeing if the Linux guest is making progress. (XXX)
+ */
+
+#define QEMU_SOCKET_TIMEOUT 5  /* How long we wait for qemu to make
+                                * the socket.  This should be very quick.
+                                */
+#define DAEMON_TIMEOUT 60      /* How long we wait for guest to boot
+                                * and start the daemon.  This could take
+                                * a potentially long time, and is very
+                                * sensitive to the overall load on the host.
+                                */
+
+static int wait_ready (guestfs_h *g);
 
 int
 guestfs_wait_ready (guestfs_h *g)
 {
-  int r, i, lsock;
-  struct sockaddr_un addr;
-
-  if (guestfs_ready (g)) return 0;
+  int r;
 
   /* Launch the subprocess, if there isn't one already. */
   if (g->pid == -1) {
@@ -414,95 +479,119 @@ guestfs_wait_ready (guestfs_h *g)
       return -1;
   }
 
-  if (g->sock >= 0) {
-    close (g->sock);
-    g->sock = -1;
+  for (;;) {
+    r = wait_ready (g);
+    if (r == -1) {             /* Error. */
+      guestfs_kill_subprocess (g);
+      return -1;
+    }
+    else if (r > 0) {          /* Keep waiting. */
+      sleep (1);
+      continue;
+    }
+    else if (r == 0)           /* Daemon is ready. */
+      break;
   }
 
-  lsock = socket (AF_UNIX, SOCK_STREAM, 0);
-  if (lsock == -1)
-    return perrorf (g, "socket");
+  return 0;
+}
+
+#define UNIX_PATH_MAX 108
 
-  addr.sun_family = AF_UNIX;
-  snprintf (addr.sun_path, UNIX_PATH_MAX, "%s/sock", g->tmpdir);
+/* This function is called repeatedly until the qemu subprocess and
+ * daemon is ready.  It returns:
+ *   -1 : error
+ *    0 : done, daemon is ready
+ *   >0 : not ready, keep waiting
+ */
+static int
+wait_ready (guestfs_h *g)
+{
+  int r, i, sock;
+  time_t now;
+  double elapsed;
+  struct sockaddr_un addr;
+  unsigned char m;
 
-  if (bind (lsock, (struct sockaddr *) &addr, sizeof addr) == -1) {
-    perrorf (g, "bind");
-    close (lsock);
-    return -1;
-  }
+  if (g->pid == -1) abort ();  /* Internal state error. */
 
-  if (listen (lsock, 1) == -1) {
-    perrorf (g, "listen");
-    close (lsock);
-    return -1;
-  }
+  /* Check the daemon is still around. */
+  r = waitpid (g->pid, NULL, WNOHANG);
 
-  if (fcntl (lsock, F_SETFL, O_NONBLOCK) == -1) {
-    perrorf (g, "set socket non-blocking");
-    close (lsock);
-    return -1;
+  if (r > 0 || (r == -1 && errno == ECHILD)) {
+    g->pid = -1;
+    return error (g,
+                 "qemu subprocess exited unexpectedly during initialization");
   }
 
-  /* 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);
+  time (&now);
+  elapsed = difftime (now, g->start_t);
+
+  if (g->sock == -1) {
+    /* Create the socket. */
+    sock = socket (AF_UNIX, SOCK_STREAM, 0);
+    if (sock == -1)
+      return perrorf (g, "socket");
+
+    addr.sun_family = AF_UNIX;
+    snprintf (addr.sun_path, UNIX_PATH_MAX, "%s/sock", g->tmpdir);
+
+    if (connect (sock, (struct sockaddr *) &addr, sizeof addr) == -1) {
+      if (elapsed <= QEMU_SOCKET_TIMEOUT) {
+       close (sock);
+       return 1;               /* Keep waiting for the socket ... */
+      }
+      perrorf (g, "qemu process hanging before making vmchannel socket");
+      close (sock);
+      return -1;
+    }
+
+    if (fcntl (sock, F_SETFL, O_NONBLOCK) == -1) {
+      perrorf (g, "set socket non-blocking");
+      close (sock);
       return -1;
     }
 
-    r = accept (lsock, NULL, 0);
-    if (r >= 0) {
-      g->sock = r;
-      fcntl (g->sock, F_SETFL, O_NONBLOCK);
-      close (lsock);
-      return 0;
+    g->sock = sock;
+  }
+
+  if (!g->daemon_up) {
+    /* Wait for the daemon to say hello. */
+    errno = 0;
+    r = read (g->sock, &m, 1);
+    if (r == 1) {
+      if (m == 0xF5) {
+       g->daemon_up = 1;
+       return 0;
+      } else {
+       error (g, "unexpected message from qemu vmchannel or daemon");
+       return -1;
+      }
     }
     if (errno == EAGAIN) {
-      sleep (1);
-      continue;
+      if (elapsed <= DAEMON_TIMEOUT)
+       return 1;               /* Keep waiting for the daemon ... */
+      error (g, "timeout waiting for guest to become ready");
+      return -1;
     }
-    perrorf (g, "accept");
-    close (lsock);
-    guestfs_kill_subprocess (g);
+
+    perrorf (g, "read");
     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 */;
+  return 0;
 }
 
-int
+void
 guestfs_kill_subprocess (guestfs_h *g)
 {
   if (g->pid >= 0) {
     if (g->verbose)
-      fprintf (stderr, "sending SIGINT to pid %d\n", g->pid);
+      fprintf (stderr, "sending SIGTERM to pgid %d\n", g->pid);
 
-    kill (g->pid, SIGINT);
+    kill (- g->pid, SIGTERM);
     wait_subprocess (g);
   }
 
   cleanup_fds (g);
-
-  return 0;
 }
index 9b84e31..49b14f5 100644 (file)
@@ -36,10 +36,9 @@ 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);
+extern void guestfs_kill_subprocess (guestfs_h *g);
 
 /* Error handling. */
 typedef void (*guestfs_abort_fn) (void);