Remove guestfs___print_timestamped_argv.
[libguestfs.git] / src / launch.c
index 8074d13..234f42e 100644 (file)
@@ -1,5 +1,5 @@
 /* libguestfs
- * Copyright (C) 2009-2010 Red Hat Inc.
+ * Copyright (C) 2009-2011 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
@@ -63,6 +63,7 @@
 #include <netinet/in.h>
 
 #include "c-ctype.h"
+#include "ignore-value.h"
 #include "glthread/lock.h"
 
 #include "guestfs.h"
 #include "guestfs_protocol.h"
 
 static int launch_appliance (guestfs_h *g);
+static int64_t timeval_diff (const struct timeval *x, const struct timeval *y);
+static void print_qemu_command_line (guestfs_h *g, char **argv);
 static int connect_unix_socket (guestfs_h *g, const char *sock);
 static int qemu_supports (guestfs_h *g, const char *option);
 
+#if 0
+static int qemu_supports_re (guestfs_h *g, const pcre *option_regex);
+
+static void compile_regexps (void) __attribute__((constructor));
+static void free_regexps (void) __attribute__((destructor));
+
+static void
+compile_regexps (void)
+{
+  const char *err;
+  int offset;
+
+#define COMPILE(re,pattern,options)                                     \
+  do {                                                                  \
+    re = pcre_compile ((pattern), (options), &err, &offset, NULL);      \
+    if (re == NULL) {                                                   \
+      ignore_value (write (2, err, strlen (err)));                      \
+      abort ();                                                         \
+    }                                                                   \
+  } while (0)
+}
+
+static void
+free_regexps (void)
+{
+}
+#endif
+
 /* Add a string to the current command line. */
 static void
 incr_cmdline_size (guestfs_h *g)
@@ -103,20 +134,20 @@ add_cmdline (guestfs_h *g, const char *str)
   return 0;
 }
 
-int
+size_t
 guestfs___checkpoint_cmdline (guestfs_h *g)
 {
   return g->cmdline_size;
 }
 
 void
-guestfs___rollback_cmdline (guestfs_h *g, int pos)
+guestfs___rollback_cmdline (guestfs_h *g, size_t pos)
 {
-  int i;
+  size_t i;
 
   assert (g->cmdline_size >= pos);
 
-  for (i = g->cmdline_size - 1; i >= pos; --i)
+  for (i = pos; i < g->cmdline_size; ++i)
     free (g->cmdline[i]);
 
   g->cmdline_size = pos;
@@ -126,7 +157,7 @@ guestfs___rollback_cmdline (guestfs_h *g, int pos)
 char **
 guestfs__debug_cmdline (guestfs_h *g)
 {
-  int i;
+  size_t i;
   char **r;
 
   if (g->cmdline == NULL) {
@@ -406,12 +437,15 @@ launch_appliance (guestfs_h *g)
 
   /* Start the clock ... */
   gettimeofday (&g->launch_t, NULL);
+  guestfs___launch_send_progress (g, 0);
 
   /* Locate and/or build the appliance. */
   char *kernel = NULL, *initrd = NULL, *appliance = NULL;
   if (guestfs___build_appliance (g, &kernel, &initrd, &appliance) == -1)
     return -1;
 
+  guestfs___launch_send_progress (g, 3);
+
   if (g->verbose)
     guestfs___print_timestamped_message (g, "begin testing qemu features");
 
@@ -483,21 +517,30 @@ launch_appliance (guestfs_h *g)
     if (qemu_supports (g, "-nodefconfig"))
       add_cmdline (g, "-nodefconfig");
 
-    /* qemu sometimes needs this option to enable hardware
-     * virtualization, but some versions of 'qemu-kvm' will use KVM
-     * regardless (even where this option appears in the help text).
-     * It is rumoured that there are versions of qemu where supplying
-     * this option when hardware virtualization is not available will
-     * cause qemu to fail, so we we have to check at least that
-     * /dev/kvm is openable.  That's not reliable, since /dev/kvm
-     * might be openable by qemu but not by us (think: SELinux) in
-     * which case the user would not get hardware virtualization,
-     * although at least shouldn't fail.  A giant clusterfuck with the
-     * qemu command line, again.
+    /* The qemu -machine option (added 2010-12) is a bit more sane
+     * since it falls back through various different acceleration
+     * modes, so try that first (thanks Markus Armbruster).
      */
-    if (qemu_supports (g, "-enable-kvm") &&
-        is_openable (g, "/dev/kvm", O_RDWR))
-      add_cmdline (g, "-enable-kvm");
+    if (qemu_supports (g, "-machine")) {
+      add_cmdline (g, "-machine");
+      add_cmdline (g, "accel=kvm:tcg");
+    } else {
+      /* qemu sometimes needs this option to enable hardware
+       * virtualization, but some versions of 'qemu-kvm' will use KVM
+       * regardless (even where this option appears in the help text).
+       * It is rumoured that there are versions of qemu where supplying
+       * this option when hardware virtualization is not available will
+       * cause qemu to fail, so we we have to check at least that
+       * /dev/kvm is openable.  That's not reliable, since /dev/kvm
+       * might be openable by qemu but not by us (think: SELinux) in
+       * which case the user would not get hardware virtualization,
+       * although at least shouldn't fail.  A giant clusterfuck with the
+       * qemu command line, again.
+       */
+      if (qemu_supports (g, "-enable-kvm") &&
+          is_openable (g, "/dev/kvm", O_RDWR))
+        add_cmdline (g, "-enable-kvm");
+    }
 
     /* Newer versions of qemu (from around 2009/12) changed the
      * behaviour of monitors so that an implicit '-monitor stdio' is
@@ -608,21 +651,28 @@ launch_appliance (guestfs_h *g)
     incr_cmdline_size (g);
     g->cmdline[g->cmdline_size-1] = NULL;
 
-    if (g->verbose)
-      guestfs___print_timestamped_argv (g, (const char **)g->cmdline);
-
     if (!g->direct) {
-      /* Set up stdin, stdout. */
+      /* Set up stdin, stdout, stderr. */
       close (0);
       close (1);
       close (wfd[1]);
       close (rfd[0]);
 
+      /* Stdin. */
       if (dup (wfd[0]) == -1) {
       dup_failed:
         perror ("dup failed");
         _exit (EXIT_FAILURE);
       }
+      /* Stdout. */
+      if (dup (rfd[1]) == -1)
+        goto dup_failed;
+
+      /* Particularly since qemu 0.15, qemu spews all sorts of debug
+       * information on stderr.  It is useful to both capture this and
+       * not confuse casual users, so send stderr to the pipe as well.
+       */
+      close (2);
       if (dup (rfd[1]) == -1)
         goto dup_failed;
 
@@ -630,12 +680,13 @@ launch_appliance (guestfs_h *g)
       close (rfd[1]);
     }
 
-#if 0
-    /* 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);
-#endif
+    /* Dump the command line (after setting up stderr above). */
+    if (g->verbose)
+      print_qemu_command_line (g, g->cmdline);
+
+    /* Put qemu in a new process group. */
+    if (g->pgroup)
+      setpgid (0, 0);
 
     setenv ("LC_ALL", "C", 1);
 
@@ -664,8 +715,16 @@ launch_appliance (guestfs_h *g)
       pid_t qemu_pid = g->pid;
       pid_t parent_pid = getppid ();
 
+      /* It would be nice to be able to put this in the same process
+       * group as qemu (ie. setpgid (0, qemu_pid)).  However this is
+       * not possible because we don't have any guarantee here that
+       * the qemu process has started yet.
+       */
+      if (g->pgroup)
+        setpgid (0, 0);
+
       /* Writing to argv is hideously complicated and error prone.  See:
-       * http://anoncvs.postgresql.org/cvsweb.cgi/pgsql/src/backend/utils/misc/ps_status.c?rev=1.33.2.1;content-type=text%2Fplain
+       * http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/misc/ps_status.c;hb=HEAD
        */
 
       /* Loop around waiting for one or both of the other processes to
@@ -760,6 +819,8 @@ launch_appliance (guestfs_h *g)
     goto cleanup1;
   }
 
+  guestfs___launch_send_progress (g, 12);
+
   return 0;
 
  cleanup1:
@@ -861,6 +922,36 @@ connect_unix_socket (guestfs_h *g, const char *sockpath)
   return -1;
 }
 
+/* launch (of the ordinary appliance) generates approximate progress
+ * messages.  Currently these are defined as follows:
+ *
+ *    0 / 12: launch clock starts
+ *    3 / 12: appliance created
+ *    6 / 12: detected that guest kernel started
+ *    9 / 12: detected that /init script is running
+ *   12 / 12: launch completed successfully
+ *
+ * Notes:
+ * (1) This is not a documented ABI and the behaviour may be changed
+ * or removed in future.
+ * (2) Messages are only sent if more than 5 seconds has elapsed
+ * since the launch clock started.
+ * (3) There is a gross hack in proto.c to make this work.
+ */
+void
+guestfs___launch_send_progress (guestfs_h *g, int perdozen)
+{
+  struct timeval tv;
+
+  gettimeofday (&tv, NULL);
+  if (timeval_diff (&g->launch_t, &tv) >= 5000) {
+    guestfs_progress progress_message =
+      { .proc = 0, .serial = 0, .position = perdozen, .total = 12 };
+
+    guestfs___progress_message_callback (g, &progress_message);
+  }
+}
+
 /* Return the location of the tmpdir (eg. "/tmp") and allow users
  * to override it at runtime using $TMPDIR.
  * http://www.pathname.com/fhs/pub/fhs-2.3.html#TMPTEMPORARYFILES
@@ -913,47 +1004,9 @@ timeval_diff (const struct timeval *x, const struct timeval *y)
   return msec;
 }
 
-void
-guestfs___print_timestamped_argv (guestfs_h *g, const char * argv[])
-{
-  int i = 0;
-  int needs_quote;
-  char *buf = NULL;
-  size_t len;
-  FILE *fp;
-
-  fp = open_memstream (&buf, &len);
-  if (fp == NULL) {
-    warning (g, "open_memstream: %m");
-    return;
-  }
-
-  struct timeval tv;
-  gettimeofday (&tv, NULL);
-  fprintf (fp, "[%05" PRIi64 "ms] ", timeval_diff (&g->launch_t, &tv));
-
-  while (argv[i]) {
-    if (argv[i][0] == '-') /* -option starts a new line */
-      fprintf (fp, " \\\n   ");
-
-    if (i > 0) fputc (' ', fp);
-
-    /* Does it need shell quoting?  This only deals with simple cases. */
-    needs_quote = strcspn (argv[i], " ") != strlen (argv[i]);
-
-    if (needs_quote) fputc ('\'', fp);
-    fprintf (fp, "%s", argv[i]);
-    if (needs_quote) fputc ('\'', fp);
-    i++;
-  }
-
-  fclose (fp);
-
-  debug (g, "%s", buf);
-
-  free (buf);
-}
-
+/* Note that since this calls 'debug' it should only be called
+ * from the parent process.
+ */
 void
 guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...)
 {
@@ -975,6 +1028,36 @@ guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...)
   free (msg);
 }
 
+/* This is called from the forked subprocess just before qemu runs, so
+ * it can just print the message straight to stderr, where it will be
+ * picked up and funnelled through the usual appliance event API.
+ */
+static void
+print_qemu_command_line (guestfs_h *g, char **argv)
+{
+  int i = 0;
+  int needs_quote;
+
+  struct timeval tv;
+  gettimeofday (&tv, NULL);
+  fprintf (stderr, "[%05" PRIi64 "ms] ", timeval_diff (&g->launch_t, &tv));
+
+  while (argv[i]) {
+    if (argv[i][0] == '-') /* -option starts a new line */
+      fprintf (stderr, " \\\n   ");
+
+    if (i > 0) fputc (' ', stderr);
+
+    /* Does it need shell quoting?  This only deals with simple cases. */
+    needs_quote = strcspn (argv[i], " ") != strlen (argv[i]);
+
+    if (needs_quote) fputc ('\'', stderr);
+    fprintf (stderr, "%s", argv[i]);
+    if (needs_quote) fputc ('\'', stderr);
+    i++;
+  }
+}
+
 static int read_all (guestfs_h *g, FILE *fp, char **ret);
 
 /* Test qemu binary (or wrapper) runs, and do 'qemu -help' and
@@ -1070,6 +1153,20 @@ qemu_supports (guestfs_h *g, const char *option)
   return strstr (g->qemu_help, option) != NULL;
 }
 
+#if 0
+/* As above but using a regex instead of a fixed string. */
+static int
+qemu_supports_re (guestfs_h *g, const pcre *option_regex)
+{
+  if (!g->qemu_help) {
+    if (test_qemu (g) == -1)
+      return -1;
+  }
+
+  return match (g, g->qemu_help, option_regex);
+}
+#endif
+
 /* Check if a file can be opened. */
 static int
 is_openable (guestfs_h *g, const char *path, int flags)