launch: Add qemu_supports_re function.
[libguestfs.git] / src / launch.c
index 1e1ea8e..56aa288 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
@@ -34,6 +34,7 @@
 #include <sys/select.h>
 #include <dirent.h>
 #include <signal.h>
+#include <assert.h>
 
 #include <rpc/types.h>
 #include <rpc/xdr.h>
 #include <netinet/in.h>
 
 #include "c-ctype.h"
-#include "glthread/lock.h"
 #include "ignore-value.h"
+#include "glthread/lock.h"
 
 #include "guestfs.h"
 #include "guestfs-internal.h"
 #include "guestfs-internal-actions.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 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)
@@ -101,6 +133,49 @@ add_cmdline (guestfs_h *g, const char *str)
   return 0;
 }
 
+size_t
+guestfs___checkpoint_cmdline (guestfs_h *g)
+{
+  return g->cmdline_size;
+}
+
+void
+guestfs___rollback_cmdline (guestfs_h *g, size_t pos)
+{
+  size_t i;
+
+  assert (g->cmdline_size >= pos);
+
+  for (i = pos; i < g->cmdline_size; ++i)
+    free (g->cmdline[i]);
+
+  g->cmdline_size = pos;
+}
+
+/* Internal command to return the command line. */
+char **
+guestfs__debug_cmdline (guestfs_h *g)
+{
+  size_t i;
+  char **r;
+
+  if (g->cmdline == NULL) {
+    r = safe_malloc (g, sizeof (char *) * 1);
+    r[0] = NULL;
+    return r;
+  }
+
+  r = safe_malloc (g, sizeof (char *) * (g->cmdline_size + 1));
+  r[0] = safe_strdup (g, g->qemu); /* g->cmdline[0] is always NULL */
+
+  for (i = 1; i < g->cmdline_size; ++i)
+    r[i] = safe_strdup (g, g->cmdline[i]);
+
+  r[g->cmdline_size] = NULL;
+
+  return r;                     /* caller frees */
+}
+
 int
 guestfs__config (guestfs_h *g,
                  const char *qemu_param, const char *qemu_value)
@@ -133,64 +208,108 @@ guestfs__config (guestfs_h *g,
   return 0;
 }
 
-int
-guestfs__add_drive_with_if (guestfs_h *g, const char *filename,
-                            const char *drive_if)
+/* cache=off improves reliability in the event of a host crash.
+ *
+ * However this option causes qemu to try to open the file with
+ * O_DIRECT.  This fails on some filesystem types (notably tmpfs).
+ * So we check if we can open the file with or without O_DIRECT,
+ * and use cache=off (or not) accordingly.
+ */
+static int
+test_cache_off (guestfs_h *g, const char *filename)
 {
-  size_t len = strlen (filename) + 64;
-  char buf[len];
-
-  if (strchr (filename, ',') != NULL) {
-    error (g, _("filename cannot contain ',' (comma) character"));
-    return -1;
+  int fd = open (filename, O_RDONLY|O_DIRECT);
+  if (fd >= 0) {
+    close (fd);
+    return 1;
   }
 
-  /* cache=off improves reliability in the event of a host crash.
-   *
-   * However this option causes qemu to try to open the file with
-   * O_DIRECT.  This fails on some filesystem types (notably tmpfs).
-   * So we check if we can open the file with or without O_DIRECT,
-   * and use cache=off (or not) accordingly.
-   *
-   * This test also checks for the presence of the file, which
-   * is a documented semantic of this interface.
-   */
-  int fd = open (filename, O_RDONLY|O_DIRECT);
+  fd = open (filename, O_RDONLY);
   if (fd >= 0) {
     close (fd);
-    snprintf (buf, len, "file=%s,cache=off,if=%s", filename, drive_if);
-  } else {
-    fd = open (filename, O_RDONLY);
-    if (fd >= 0) {
-      close (fd);
-      snprintf (buf, len, "file=%s,if=%s", filename, drive_if);
-    } else {
-      perrorf (g, "%s", filename);
-      return -1;
-    }
+    return 0;
   }
 
-  return guestfs__config (g, "-drive", buf);
+  perrorf (g, "%s", filename);
+  return -1;
+}
+
+/* Check string parameter matches ^[-_[:alnum:]]+$ (in C locale). */
+static int
+valid_format_iface (const char *str)
+{
+  size_t len = strlen (str);
+
+  if (len == 0)
+    return 0;
+
+  while (len > 0) {
+    char c = *str++;
+    len--;
+    if (c != '-' && c != '_' && !c_isalnum (c))
+      return 0;
+  }
+  return 1;
 }
 
 int
-guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename,
-                               const char *drive_if)
+guestfs__add_drive_opts (guestfs_h *g, const char *filename,
+                         const struct guestfs_add_drive_opts_argv *optargs)
 {
+  int readonly;
+  const char *format;
+  const char *iface;
+
   if (strchr (filename, ',') != NULL) {
     error (g, _("filename cannot contain ',' (comma) character"));
     return -1;
   }
 
-  if (access (filename, F_OK) == -1) {
-    perrorf (g, "%s", filename);
+  readonly = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK
+             ? optargs->readonly : 0;
+  format = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK
+           ? optargs->format : NULL;
+  iface = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK
+          ? optargs->iface : DRIVE_IF;
+
+  if (format && !valid_format_iface (format)) {
+    error (g, _("%s parameter is empty or contains disallowed characters"),
+           "format");
+    return -1;
+  }
+  if (!valid_format_iface (iface)) {
+    error (g, _("%s parameter is empty or contains disallowed characters"),
+           "iface");
     return -1;
   }
 
-  size_t len = strlen (filename) + 64;
+  /* For writable files, see if we can use cache=off.  This also
+   * checks for the existence of the file.  For readonly we have
+   * to do the check explicitly.
+   */
+  int use_cache_off = readonly ? 0 : test_cache_off (g, filename);
+  if (use_cache_off == -1)
+    return -1;
+
+  if (readonly) {
+    if (access (filename, F_OK) == -1) {
+      perrorf (g, "%s", filename);
+      return -1;
+    }
+  }
+
+  /* Construct the final -drive parameter. */
+  size_t len = 64 + strlen (filename) + strlen (iface);
+  if (format) len += strlen (format);
   char buf[len];
 
-  snprintf (buf, len, "file=%s,snapshot=on,if=%s", filename, drive_if);
+  snprintf (buf, len, "file=%s%s%s%s%s,if=%s",
+            filename,
+            readonly ? ",snapshot=on" : "",
+            use_cache_off ? ",cache=off" : "",
+            format ? ",format=" : "",
+            format ? format : "",
+            iface);
 
   return guestfs__config (g, "-drive", buf);
 }
@@ -198,13 +317,48 @@ guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename,
 int
 guestfs__add_drive (guestfs_h *g, const char *filename)
 {
-  return guestfs__add_drive_with_if (g, filename, DRIVE_IF);
+  struct guestfs_add_drive_opts_argv optargs = {
+    .bitmask = 0,
+  };
+
+  return guestfs__add_drive_opts (g, filename, &optargs);
 }
 
 int
 guestfs__add_drive_ro (guestfs_h *g, const char *filename)
 {
-  return guestfs__add_drive_ro_with_if (g, filename, DRIVE_IF);
+  struct guestfs_add_drive_opts_argv optargs = {
+    .bitmask = GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK,
+    .readonly = 1,
+  };
+
+  return guestfs__add_drive_opts (g, filename, &optargs);
+}
+
+int
+guestfs__add_drive_with_if (guestfs_h *g, const char *filename,
+                            const char *iface)
+{
+  struct guestfs_add_drive_opts_argv optargs = {
+    .bitmask = GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK,
+    .iface = iface,
+  };
+
+  return guestfs__add_drive_opts (g, filename, &optargs);
+}
+
+int
+guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename,
+                               const char *iface)
+{
+  struct guestfs_add_drive_opts_argv optargs = {
+    .bitmask = GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK
+             | GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK,
+    .iface = iface,
+    .readonly = 1,
+  };
+
+  return guestfs__add_drive_opts (g, filename, &optargs);
 }
 
 int
@@ -224,41 +378,23 @@ guestfs__add_cdrom (guestfs_h *g, const char *filename)
 }
 
 static int is_openable (guestfs_h *g, const char *path, int flags);
-static void print_cmdline (guestfs_h *g);
 
 int
 guestfs__launch (guestfs_h *g)
 {
-  int r;
-  int wfd[2], rfd[2];
-  int tries;
-  char unixsock[256];
-  struct sockaddr_un addr;
-
   /* Configured? */
-  if (!g->cmdline) {
-    error (g, _("you must call guestfs_add_drive before guestfs_launch"));
-    return -1;
-  }
-
   if (g->state != CONFIG) {
     error (g, _("the libguestfs handle has already been launched"));
     return -1;
   }
 
-  /* Start the clock ... */
-  gettimeofday (&g->launch_t, NULL);
-
   /* Make the temporary directory. */
   if (!g->tmpdir) {
-    const char *tmpdir = guestfs___tmpdir ();
-    char dir_template[strlen (tmpdir) + 32];
-    sprintf (dir_template, "%s/libguestfsXXXXXX", tmpdir);
-
+    TMP_TEMPLATE_ON_STACK (dir_template);
     g->tmpdir = safe_strdup (g, dir_template);
     if (mkdtemp (g->tmpdir) == NULL) {
       perrorf (g, _("%s: cannot create temporary directory"), dir_template);
-      goto cleanup0;
+      return -1;
     }
   }
 
@@ -267,13 +403,48 @@ guestfs__launch (guestfs_h *g)
    * want. (RHBZ#610880).
    */
   if (chmod (g->tmpdir, 0755) == -1)
-    fprintf (stderr, "chmod: %s: %m (ignored)\n", g->tmpdir);
+    warning (g, "chmod: %s: %m (ignored)", g->tmpdir);
+
+  /* Launch the appliance or attach to an existing daemon. */
+  switch (g->attach_method) {
+  case ATTACH_METHOD_APPLIANCE:
+    return launch_appliance (g);
+
+  case ATTACH_METHOD_UNIX:
+    return connect_unix_socket (g, g->attach_method_arg);
+
+  default:
+    abort ();
+  }
+}
+
+static int
+launch_appliance (guestfs_h *g)
+{
+  int r;
+  int wfd[2], rfd[2];
+  char guestfsd_sock[256];
+  struct sockaddr_un addr;
+
+  /* At present you must add drives before starting the appliance.  In
+   * future when we enable hotplugging you won't need to do this.
+   */
+  if (!g->cmdline) {
+    error (g, _("you must call guestfs_add_drive before guestfs_launch"));
+    return -1;
+  }
+
+  /* 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");
 
@@ -284,8 +455,8 @@ guestfs__launch (guestfs_h *g)
   /* Using virtio-serial, we need to create a local Unix domain socket
    * for qemu to connect to.
    */
-  snprintf (unixsock, sizeof unixsock, "%s/sock", g->tmpdir);
-  unlink (unixsock);
+  snprintf (guestfsd_sock, sizeof guestfsd_sock, "%s/guestfsd.sock", g->tmpdir);
+  unlink (guestfsd_sock);
 
   g->sock = socket (AF_UNIX, SOCK_STREAM, 0);
   if (g->sock == -1) {
@@ -299,7 +470,7 @@ guestfs__launch (guestfs_h *g)
   }
 
   addr.sun_family = AF_UNIX;
-  strncpy (addr.sun_path, unixsock, UNIX_PATH_MAX);
+  strncpy (addr.sun_path, guestfsd_sock, UNIX_PATH_MAX);
   addr.sun_path[UNIX_PATH_MAX-1] = '\0';
 
   if (bind (g->sock, &addr, sizeof addr) == -1) {
@@ -342,21 +513,33 @@ guestfs__launch (guestfs_h *g)
      */
     g->cmdline[0] = g->qemu;
 
-    /* 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, "-nodefconfig"))
+      add_cmdline (g, "-nodefconfig");
+
+    /* 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
@@ -405,11 +588,19 @@ guestfs__launch (guestfs_h *g)
 
     /* Set up virtio-serial for the communications channel. */
     add_cmdline (g, "-chardev");
-    snprintf (buf, sizeof buf, "socket,path=%s,id=channel0", unixsock);
+    snprintf (buf, sizeof buf, "socket,path=%s,id=channel0", guestfsd_sock);
     add_cmdline (g, buf);
     add_cmdline (g, "-device");
     add_cmdline (g, "virtserialport,chardev=channel0,name=org.libguestfs.channel.0");
 
+    /* Enable user networking. */
+    if (g->enable_network) {
+      add_cmdline (g, "-netdev");
+      add_cmdline (g, "user,id=usernet,net=169.254.0.0/16");
+      add_cmdline (g, "-device");
+      add_cmdline (g, NET_IF ",netdev=usernet");
+    }
+
 #define LINUX_CMDLINE                                                  \
     "panic=1 "         /* force kernel to panic if daemon exits */     \
     "console=ttyS0 "   /* serial console */                            \
@@ -460,20 +651,30 @@ guestfs__launch (guestfs_h *g)
     g->cmdline[g->cmdline_size-1] = NULL;
 
     if (g->verbose)
-      print_cmdline (g);
+      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;
 
@@ -481,12 +682,9 @@ guestfs__launch (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
+    /* Put qemu in a new process group. */
+    if (g->pgroup)
+      setpgid (0, 0);
 
     setenv ("LC_ALL", "C", 1);
 
@@ -502,6 +700,8 @@ guestfs__launch (guestfs_h *g)
   kernel = NULL;
   free (initrd);
   initrd = NULL;
+  free (appliance);
+  appliance = NULL;
 
   /* Fork the recovery process off which will kill qemu if the parent
    * process fails to do so (eg. if the parent segfaults).
@@ -513,6 +713,14 @@ guestfs__launch (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
        */
@@ -609,6 +817,8 @@ guestfs__launch (guestfs_h *g)
     goto cleanup1;
   }
 
+  guestfs___launch_send_progress (g, 12);
+
   return 0;
 
  cleanup1:
@@ -618,7 +828,7 @@ guestfs__launch (guestfs_h *g)
   }
   if (g->pid > 0) kill (g->pid, 9);
   if (g->recoverypid > 0) kill (g->recoverypid, 9);
-  waitpid (g->pid, NULL, 0);
+  if (g->pid > 0) waitpid (g->pid, NULL, 0);
   if (g->recoverypid > 0) waitpid (g->recoverypid, NULL, 0);
   g->fd[0] = -1;
   g->fd[1] = -1;
@@ -638,8 +848,114 @@ guestfs__launch (guestfs_h *g)
   return -1;
 }
 
+/* Alternate attach method: instead of launching the appliance,
+ * connect to an existing unix socket.
+ */
+static int
+connect_unix_socket (guestfs_h *g, const char *sockpath)
+{
+  int r;
+  struct sockaddr_un addr;
+
+  /* Start the clock ... */
+  gettimeofday (&g->launch_t, NULL);
+
+  /* Set these to nothing so we don't try to kill random processes or
+   * read from random file descriptors.
+   */
+  g->pid = 0;
+  g->recoverypid = 0;
+  g->fd[0] = -1;
+  g->fd[1] = -1;
+
+  if (g->verbose)
+    guestfs___print_timestamped_message (g, "connecting to %s", sockpath);
+
+  g->sock = socket (AF_UNIX, SOCK_STREAM, 0);
+  if (g->sock == -1) {
+    perrorf (g, "socket");
+    return -1;
+  }
+
+  addr.sun_family = AF_UNIX;
+  strncpy (addr.sun_path, sockpath, UNIX_PATH_MAX);
+  addr.sun_path[UNIX_PATH_MAX-1] = '\0';
+
+  g->state = LAUNCHING;
+
+  if (connect (g->sock, &addr, sizeof addr) == -1) {
+    perrorf (g, "bind");
+    goto cleanup;
+  }
+
+  if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) {
+    perrorf (g, "fcntl");
+    goto cleanup;
+  }
+
+  uint32_t size;
+  void *buf = NULL;
+  r = guestfs___recv_from_daemon (g, &size, &buf);
+  free (buf);
+
+  if (r == -1) return -1;
+
+  if (size != GUESTFS_LAUNCH_FLAG) {
+    error (g, _("guestfs_launch failed, unexpected initial message from guestfsd"));
+    goto cleanup;
+  }
+
+  if (g->verbose)
+    guestfs___print_timestamped_message (g, "connected");
+
+  if (g->state != READY) {
+    error (g, _("contacted guestfsd, but state != READY"));
+    goto cleanup;
+  }
+
+  return 0;
+
+ cleanup:
+  close (g->sock);
+  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
+ */
 const char *
-guestfs___tmpdir (void)
+guestfs_tmpdir (void)
 {
   const char *tmpdir;
 
@@ -655,31 +971,21 @@ guestfs___tmpdir (void)
   return tmpdir;
 }
 
-/* This function is used to print the qemu command line before it gets
- * executed, when in verbose mode.
+/* Return the location of the persistent tmpdir (eg. "/var/tmp") and
+ * allow users to override it at runtime using $TMPDIR.
+ * http://www.pathname.com/fhs/pub/fhs-2.3.html#VARTMPTEMPORARYFILESPRESERVEDBETWEE
  */
-static void
-print_cmdline (guestfs_h *g)
+const char *
+guestfs___persistent_tmpdir (void)
 {
-  int i = 0;
-  int needs_quote;
-
-  while (g->cmdline[i]) {
-    if (g->cmdline[i][0] == '-') /* -option starts a new line */
-      fprintf (stderr, " \\\n   ");
-
-    if (i > 0) fputc (' ', stderr);
+  const char *tmpdir;
 
-    /* Does it need shell quoting?  This only deals with simple cases. */
-    needs_quote = strcspn (g->cmdline[i], " ") != strlen (g->cmdline[i]);
+  tmpdir = "/var/tmp";
 
-    if (needs_quote) fputc ('\'', stderr);
-    fprintf (stderr, "%s", g->cmdline[i]);
-    if (needs_quote) fputc ('\'', stderr);
-    i++;
-  }
+  const char *t = getenv ("TMPDIR");
+  if (t) tmpdir = t;
 
-  fputc ('\n', stderr);
+  return tmpdir;
 }
 
 /* Compute Y - X and return the result in milliseconds.
@@ -697,6 +1003,47 @@ timeval_diff (const struct timeval *x, const struct timeval *y)
 }
 
 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);
+}
+
+void
 guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...)
 {
   va_list args;
@@ -712,8 +1059,7 @@ guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...)
 
   gettimeofday (&tv, NULL);
 
-  fprintf (stderr, "[%05" PRIi64 "ms] %s\n",
-           timeval_diff (&g->launch_t, &tv), msg);
+  debug (g, "[%05" PRIi64 "ms] %s", timeval_diff (&g->launch_t, &tv), msg);
 
   free (msg);
 }
@@ -753,7 +1099,7 @@ test_qemu (guestfs_h *g)
     goto error;
 
   snprintf (cmd, sizeof cmd, "LC_ALL=C '%s' -nographic -version 2>/dev/null",
-           g->qemu);
+            g->qemu);
 
   fp = popen (cmd, "r");
   if (fp) {
@@ -813,14 +1159,27 @@ 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)
 {
   int fd = open (path, flags);
   if (fd == -1) {
-    if (g->verbose)
-      perror (path);
+    debug (g, "is_openable: %s: %m", path);
     return 0;
   }
   close (fd);
@@ -849,8 +1208,7 @@ guestfs__kill_subprocess (guestfs_h *g)
     return -1;
   }
 
-  if (g->verbose)
-    fprintf (stderr, "sending SIGTERM to process %d\n", g->pid);
+  debug (g, "sending SIGTERM to process %d", g->pid);
 
   if (g->pid > 0) kill (g->pid, SIGTERM);
   if (g->recoverypid > 0) kill (g->recoverypid, 9);