Add command trace functionality.
[libguestfs.git] / src / guestfs.c
index 186c570..98d99b8 100644 (file)
@@ -19,7 +19,6 @@
 #include <config.h>
 
 #define _BSD_SOURCE /* for mkdtemp, usleep */
-#define _GNU_SOURCE /* for vasprintf, GNU strerror_r, strchrnul */
 
 #include <stdio.h>
 #include <stdlib.h>
 #endif
 
 #include "guestfs.h"
+#include "guestfs-internal-actions.h"
 #include "guestfs_protocol.h"
+#include "ignore-value.h"
 
 #ifdef HAVE_GETTEXT
 #include "gettext.h"
 #define _(str) dgettext(PACKAGE, (str))
-#define N_(str) dgettext(PACKAGE, (str))
+//#define N_(str) dgettext(PACKAGE, (str))
 #else
 #define _(str) str
-#define N_(str) str
+//#define N_(str) str
 #endif
 
 #define error guestfs_error
@@ -74,7 +75,7 @@
 #define safe_malloc guestfs_safe_malloc
 #define safe_realloc guestfs_safe_realloc
 #define safe_strdup guestfs_safe_strdup
-#define safe_memdup guestfs_safe_memdup
+//#define safe_memdup guestfs_safe_memdup
 
 static void default_error_cb (guestfs_h *g, void *data, const char *msg);
 static void stdout_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, int watch, int fd, int events);
@@ -169,6 +170,7 @@ struct guestfs_h
   int cmdline_size;
 
   int verbose;
+  int trace;
   int autosync;
 
   char *path;                  /* Path to kernel, initrd. */
@@ -177,6 +179,8 @@ struct guestfs_h
 
   int memsize;                 /* Size of RAM (megabytes). */
 
+  int selinux;                  /* selinux enabled? */
+
   char *last_error;
 
   /* Callbacks. */
@@ -199,9 +203,9 @@ struct guestfs_h
 
   /* Messages sent and received from the daemon. */
   char *msg_in;
-  int msg_in_size, msg_in_allocated;
+  unsigned int msg_in_size, msg_in_allocated;
   char *msg_out;
-  int msg_out_size, msg_out_pos;
+  unsigned int msg_out_size, msg_out_pos;
 
   int msg_next_serial;
 };
@@ -235,6 +239,9 @@ guestfs_create (void)
   str = getenv ("LIBGUESTFS_DEBUG");
   g->verbose = str != NULL && strcmp (str, "1") == 0;
 
+  str = getenv ("LIBGUESTFS_TRACE");
+  g->trace = str != NULL && strcmp (str, "1") == 0;
+
   str = getenv ("LIBGUESTFS_PATH");
   g->path = str != NULL ? strdup (str) : strdup (GUESTFS_DEFAULT_PATH);
   if (!g->path) goto error;
@@ -345,6 +352,10 @@ guestfs_close (guestfs_h *g)
   g->fd[1] = -1;
   g->sock = -1;
 
+  /* Wait for subprocess(es) to exit. */
+  waitpid (g->pid, NULL, 0);
+  if (g->recoverypid > 0) waitpid (g->recoverypid, NULL, 0);
+
   /* Remove tmpfiles. */
   if (g->tmpdir) {
     snprintf (filename, sizeof filename, "%s/sock", g->tmpdir);
@@ -537,8 +548,9 @@ guestfs_safe_memdup (guestfs_h *g, void *ptr, size_t size)
 }
 
 static int
-xwrite (int fd, const void *buf, size_t len)
+xwrite (int fd, const void *v_buf, size_t len)
 {
+  const char *buf = v_buf;
   int r;
 
   while (len > 0) {
@@ -554,8 +566,9 @@ xwrite (int fd, const void *buf, size_t len)
 }
 
 static int
-xread (int fd, void *buf, size_t len)
+xread (int fd, void *v_buf, size_t len)
 {
+  char *buf = v_buf;
   int r;
 
   while (len > 0) {
@@ -600,33 +613,33 @@ guestfs_get_error_handler (guestfs_h *g, void **data_rtn)
 }
 
 int
-guestfs_set_verbose (guestfs_h *g, int v)
+guestfs__set_verbose (guestfs_h *g, int v)
 {
   g->verbose = !!v;
   return 0;
 }
 
 int
-guestfs_get_verbose (guestfs_h *g)
+guestfs__get_verbose (guestfs_h *g)
 {
   return g->verbose;
 }
 
 int
-guestfs_set_autosync (guestfs_h *g, int a)
+guestfs__set_autosync (guestfs_h *g, int a)
 {
   g->autosync = !!a;
   return 0;
 }
 
 int
-guestfs_get_autosync (guestfs_h *g)
+guestfs__get_autosync (guestfs_h *g)
 {
   return g->autosync;
 }
 
 int
-guestfs_set_path (guestfs_h *g, const char *path)
+guestfs__set_path (guestfs_h *g, const char *path)
 {
   free (g->path);
   g->path = NULL;
@@ -638,13 +651,13 @@ guestfs_set_path (guestfs_h *g, const char *path)
 }
 
 const char *
-guestfs_get_path (guestfs_h *g)
+guestfs__get_path (guestfs_h *g)
 {
   return g->path;
 }
 
 int
-guestfs_set_qemu (guestfs_h *g, const char *qemu)
+guestfs__set_qemu (guestfs_h *g, const char *qemu)
 {
   free (g->qemu);
   g->qemu = NULL;
@@ -654,13 +667,13 @@ guestfs_set_qemu (guestfs_h *g, const char *qemu)
 }
 
 const char *
-guestfs_get_qemu (guestfs_h *g)
+guestfs__get_qemu (guestfs_h *g)
 {
   return g->qemu;
 }
 
 int
-guestfs_set_append (guestfs_h *g, const char *append)
+guestfs__set_append (guestfs_h *g, const char *append)
 {
   free (g->append);
   g->append = NULL;
@@ -670,26 +683,39 @@ guestfs_set_append (guestfs_h *g, const char *append)
 }
 
 const char *
-guestfs_get_append (guestfs_h *g)
+guestfs__get_append (guestfs_h *g)
 {
   return g->append;
 }
 
 int
-guestfs_set_memsize (guestfs_h *g, int memsize)
+guestfs__set_memsize (guestfs_h *g, int memsize)
 {
   g->memsize = memsize;
   return 0;
 }
 
 int
-guestfs_get_memsize (guestfs_h *g)
+guestfs__get_memsize (guestfs_h *g)
 {
   return g->memsize;
 }
 
 int
-guestfs_get_pid (guestfs_h *g)
+guestfs__set_selinux (guestfs_h *g, int selinux)
+{
+  g->selinux = selinux;
+  return 0;
+}
+
+int
+guestfs__get_selinux (guestfs_h *g)
+{
+  return g->selinux;
+}
+
+int
+guestfs__get_pid (guestfs_h *g)
 {
   if (g->pid > 0)
     return g->pid;
@@ -700,7 +726,7 @@ guestfs_get_pid (guestfs_h *g)
 }
 
 struct guestfs_version *
-guestfs_version (guestfs_h *g)
+guestfs__version (guestfs_h *g)
 {
   struct guestfs_version *r;
 
@@ -712,6 +738,19 @@ guestfs_version (guestfs_h *g)
   return r;
 }
 
+int
+guestfs__set_trace (guestfs_h *g, int t)
+{
+  g->trace = !!t;
+  return 0;
+}
+
+int
+guestfs__get_trace (guestfs_h *g)
+{
+  return g->trace;
+}
+
 /* Add a string to the current command line. */
 static void
 incr_cmdline_size (guestfs_h *g)
@@ -742,8 +781,8 @@ add_cmdline (guestfs_h *g, const char *str)
 }
 
 int
-guestfs_config (guestfs_h *g,
-                const char *qemu_param, const char *qemu_value)
+guestfs__config (guestfs_h *g,
+                 const char *qemu_param, const char *qemu_value)
 {
   if (qemu_param[0] != '-') {
     error (g, _("guestfs_config: parameter must begin with '-' character"));
@@ -774,7 +813,7 @@ guestfs_config (guestfs_h *g,
 }
 
 int
-guestfs_add_drive (guestfs_h *g, const char *filename)
+guestfs__add_drive (guestfs_h *g, const char *filename)
 {
   size_t len = strlen (filename) + 64;
   char buf[len];
@@ -784,19 +823,36 @@ guestfs_add_drive (guestfs_h *g, const char *filename)
     return -1;
   }
 
-  if (access (filename, F_OK) == -1) {
-    perrorf (g, "%s", filename);
-    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);
+  if (fd >= 0) {
+    close (fd);
+    snprintf (buf, len, "file=%s,cache=off,if=" DRIVE_IF, filename);
+  } else {
+    fd = open (filename, O_RDONLY);
+    if (fd >= 0) {
+      close (fd);
+      snprintf (buf, len, "file=%s,if=" DRIVE_IF, filename);
+    } else {
+      perrorf (g, "%s", filename);
+      return -1;
+    }
   }
 
-  /* cache=off improves reliability in the event of a host crash. */
-  snprintf (buf, len, "file=%s,cache=off,if=%s", filename, DRIVE_IF);
-
   return guestfs_config (g, "-drive", buf);
 }
 
 int
-guestfs_add_drive_ro (guestfs_h *g, const char *filename)
+guestfs__add_drive_ro (guestfs_h *g, const char *filename)
 {
   size_t len = strlen (filename) + 64;
   char buf[len];
@@ -817,7 +873,7 @@ guestfs_add_drive_ro (guestfs_h *g, const char *filename)
 }
 
 int
-guestfs_add_cdrom (guestfs_h *g, const char *filename)
+guestfs__add_cdrom (guestfs_h *g, const char *filename)
 {
   if (strchr (filename, ',') != NULL) {
     error (g, _("filename cannot contain ',' (comma) character"));
@@ -866,6 +922,7 @@ dir_contains_files (const char *dir, ...)
 static int build_supermin_appliance (guestfs_h *g, const char *path, char **kernel, char **initrd);
 static int test_qemu (guestfs_h *g);
 static int qemu_supports (guestfs_h *g, const char *option);
+static void print_cmdline (guestfs_h *g);
 
 static const char *kernel_name = "vmlinuz." REPO "." host_cpu;
 static const char *initrd_name = "initramfs." REPO "." host_cpu ".img";
@@ -875,11 +932,11 @@ static const char *supermin_hostfiles_name =
   "initramfs." REPO "." host_cpu ".supermin.hostfiles";
 
 int
-guestfs_launch (guestfs_h *g)
+guestfs__launch (guestfs_h *g)
 {
   const char *tmpdir;
   char dir_template[PATH_MAX];
-  int r, i, pmore;
+  int r, pmore;
   size_t len;
   int wfd[2], rfd[2];
   int tries;
@@ -1046,15 +1103,19 @@ guestfs_launch (guestfs_h *g)
     "udevtimeout=300 " /* good for very slow systems (RHBZ#480319) */  \
     "noapic "          /* workaround for RHBZ#502058 - ok if not SMP */ \
     "acpi=off "        /* we don't need ACPI, turn it off */           \
-    "cgroup_disable=memory " /* saves us about 5 MB of RAM */           \
-    "selinux=0 "       /* SELinux is messed up if there's no policy */
+    "cgroup_disable=memory " /* saves us about 5 MB of RAM */
 
     /* Linux kernel command line. */
     snprintf (append, sizeof append,
-              LINUX_CMDLINE "guestfs=%s:%d%s%s%s",
+              LINUX_CMDLINE
+              "guestfs=%s:%d "
+              "%s"              /* (selinux) */
+              "%s"              /* (verbose) */
+              "%s",             /* (append) */
               VMCHANNEL_ADDR, VMCHANNEL_PORT,
-              g->verbose ? " guestfs_verbose=1" : "",
-              g->append ? " " : "", g->append ? g->append : "");
+              g->selinux ? "selinux=1 enforcing=0 " : "selinux=0 ",
+              g->verbose ? "guestfs_verbose=1 " : " ",
+              g->append ? g->append : "");
 
     snprintf (memsize_str, sizeof memsize_str, "%d", g->memsize);
 
@@ -1116,20 +1177,23 @@ guestfs_launch (guestfs_h *g)
     incr_cmdline_size (g);
     g->cmdline[g->cmdline_size-1] = NULL;
 
-    if (g->verbose) {
-      fprintf (stderr, "%s", g->qemu);
-      for (i = 0; g->cmdline[i]; ++i)
-        fprintf (stderr, " %s", g->cmdline[i]);
-      fprintf (stderr, "\n");
-    }
+    if (g->verbose)
+      print_cmdline (g);
 
     /* Set up stdin, stdout. */
     close (0);
     close (1);
     close (wfd[1]);
     close (rfd[0]);
-    dup (wfd[0]);
-    dup (rfd[1]);
+
+    if (dup (wfd[0]) == -1) {
+    dup_failed:
+      perror ("dup failed");
+      _exit (1);
+    }
+    if (dup (rfd[1]) == -1)
+      goto dup_failed;
+
     close (wfd[0]);
     close (rfd[1]);
 
@@ -1301,6 +1365,33 @@ guestfs_launch (guestfs_h *g)
   return -1;
 }
 
+/* This function is used to print the qemu command line before it gets
+ * executed, when in verbose mode.
+ */
+static void
+print_cmdline (guestfs_h *g)
+{
+  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);
+
+    /* Does it need shell quoting?  This only deals with simple cases. */
+    needs_quote = strcspn (g->cmdline[i], " ") != strlen (g->cmdline[i]);
+
+    if (needs_quote) fputc ('\'', stderr);
+    fprintf (stderr, "%s", g->cmdline[i]);
+    if (needs_quote) fputc ('\'', stderr);
+    i++;
+  }
+
+  fputc ('\n', stderr);
+}
+
 /* This function does the hard work of building the supermin appliance
  * on the fly.  'path' is the directory containing the control files.
  * 'kernel' and 'initrd' are where we will return the names of the
@@ -1433,7 +1524,7 @@ finish_wait_ready (guestfs_h *g, void *vp)
 }
 
 int
-guestfs_wait_ready (guestfs_h *g)
+guestfs__wait_ready (guestfs_h *g)
 {
   int finished = 0, r;
 
@@ -1476,7 +1567,7 @@ guestfs_wait_ready (guestfs_h *g)
 }
 
 int
-guestfs_kill_subprocess (guestfs_h *g)
+guestfs__kill_subprocess (guestfs_h *g)
 {
   if (g->state == CONFIG) {
     error (g, _("no subprocess to kill"));
@@ -1494,37 +1585,37 @@ guestfs_kill_subprocess (guestfs_h *g)
 
 /* Access current state. */
 int
-guestfs_is_config (guestfs_h *g)
+guestfs__is_config (guestfs_h *g)
 {
   return g->state == CONFIG;
 }
 
 int
-guestfs_is_launching (guestfs_h *g)
+guestfs__is_launching (guestfs_h *g)
 {
   return g->state == LAUNCHING;
 }
 
 int
-guestfs_is_ready (guestfs_h *g)
+guestfs__is_ready (guestfs_h *g)
 {
   return g->state == READY;
 }
 
 int
-guestfs_is_busy (guestfs_h *g)
+guestfs__is_busy (guestfs_h *g)
 {
   return g->state == BUSY;
 }
 
 int
-guestfs_get_state (guestfs_h *g)
+guestfs__get_state (guestfs_h *g)
 {
   return g->state;
 }
 
 int
-guestfs_set_ready (guestfs_h *g)
+guestfs__set_ready (guestfs_h *g)
 {
   if (g->state != BUSY) {
     error (g, _("guestfs_set_ready: called when in state %d != BUSY"),
@@ -1536,7 +1627,7 @@ guestfs_set_ready (guestfs_h *g)
 }
 
 int
-guestfs_set_busy (guestfs_h *g)
+guestfs__set_busy (guestfs_h *g)
 {
   if (g->state != READY) {
     error (g, _("guestfs_set_busy: called when in state %d != READY"),
@@ -1548,7 +1639,7 @@ guestfs_set_busy (guestfs_h *g)
 }
 
 int
-guestfs_end_busy (guestfs_h *g)
+guestfs__end_busy (guestfs_h *g)
 {
   switch (g->state)
     {
@@ -1558,8 +1649,10 @@ guestfs_end_busy (guestfs_h *g)
     case CONFIG:
     case READY:
       break;
+
     case LAUNCHING:
     case NO_HANDLE:
+    default:
       error (g, _("guestfs_end_busy: called when in state %d"), g->state);
       return -1;
     }
@@ -1637,7 +1730,7 @@ stdout_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data,
 
   /* In verbose mode, copy all log messages to stderr. */
   if (g->verbose)
-    write (2, buf, n);
+    ignore_value (write (STDERR_FILENO, buf, n));
 
   /* It's an actual log message, send it upwards if anyone is listening. */
   if (g->log_message_cb)
@@ -2338,7 +2431,7 @@ guestfs__receive_file_sync (guestfs_h *g, const char *filename)
     return -1;
   }
 
-  while ((r = receive_file_data_sync (g, NULL, NULL)) > 0)
+  while (receive_file_data_sync (g, NULL, NULL) > 0)
     ;                          /* just discard it */
 
   return -1;
@@ -2447,7 +2540,7 @@ receive_file_data_sync (guestfs_h *g, void **buf, size_t *len_r)
 
     if (buf) {
       *buf = safe_realloc (g, *buf, len + ctx.chunks[i].data.data_len);
-      memcpy (*buf+len, ctx.chunks[i].data.data_val,
+      memcpy (((char *)*buf)+len, ctx.chunks[i].data.data_val,
               ctx.chunks[i].data.data_len);
     }
     len += ctx.chunks[i].data.data_len;
@@ -2553,6 +2646,7 @@ select_remove_handle (guestfs_main_loop *mlv, guestfs_h *g, int fd)
 }
 
 static int
+__attribute__((noreturn))
 select_add_timeout (guestfs_main_loop *mlv, guestfs_h *g, int interval,
                     guestfs_handle_timeout_cb cb, void *data)
 {
@@ -2562,6 +2656,7 @@ select_add_timeout (guestfs_main_loop *mlv, guestfs_h *g, int interval,
 }
 
 static int
+__attribute__((noreturn))
 select_remove_timeout (guestfs_main_loop *mlv, guestfs_h *g, int timer)
 {
   //struct select_main_loop *ml = (struct select_main_loop *) mlv;