Definition and implementation of new guestfs_read_lines API call.
[libguestfs.git] / src / guestfs.c
index d3594d3..7f0f821 100644 (file)
@@ -19,7 +19,7 @@
 #include <config.h>
 
 #define _BSD_SOURCE /* for mkdtemp, usleep */
-#define _GNU_SOURCE /* for vasprintf, GNU strerror_r */
+#define _GNU_SOURCE /* for vasprintf, GNU strerror_r, strchrnul */
 
 #include <stdio.h>
 #include <stdlib.h>
 
 static void error (guestfs_h *g, const char *fs, ...);
 static void perrorf (guestfs_h *g, const char *fs, ...);
-static void *safe_malloc (guestfs_h *g, int nbytes);
+static void *safe_malloc (guestfs_h *g, size_t nbytes);
 static void *safe_realloc (guestfs_h *g, void *ptr, int nbytes);
 static char *safe_strdup (guestfs_h *g, const char *str);
+static void *safe_memdup (guestfs_h *g, void *ptr, size_t size);
 
 static void default_error_cb (guestfs_h *g, void *data, const char *msg);
 static void stdout_event (void *data, int watch, int fd, int events);
 static void sock_read_event (void *data, int watch, int fd, int events);
 static void sock_write_event (void *data, int watch, int fd, int events);
 
+static void close_handles (void);
+
 static int select_add_handle (guestfs_h *g, int fd, int events, guestfs_handle_event_cb cb, void *data);
 static int select_remove_handle (guestfs_h *g, int watch);
 static int select_add_timeout (guestfs_h *g, int interval, guestfs_handle_timeout_cb cb, void *data);
@@ -95,6 +98,8 @@ enum state { CONFIG, LAUNCHING, READY, BUSY, NO_HANDLE };
 
 struct guestfs_h
 {
+  struct guestfs_h *next;      /* Linked list of open handles. */
+
   /* State: see the state machine diagram in the man page guestfs(3). */
   enum state state;
 
@@ -112,6 +117,9 @@ struct guestfs_h
   int cmdline_size;
 
   int verbose;
+  int autosync;
+
+  const char *path;
 
   /* Callbacks. */
   guestfs_abort_cb           abort_cb;
@@ -144,6 +152,9 @@ struct guestfs_h
   int msg_next_serial;
 };
 
+static guestfs_h *handles = NULL;
+static int atexit_handler_set = 0;
+
 guestfs_h *
 guestfs_create (void)
 {
@@ -170,11 +181,30 @@ guestfs_create (void)
   str = getenv ("LIBGUESTFS_DEBUG");
   g->verbose = str != NULL && strcmp (str, "1") == 0;
 
+  str = getenv ("LIBGUESTFS_PATH");
+  g->path = str != NULL ? str : GUESTFS_DEFAULT_PATH;
+  /* XXX We should probably make QEMU configurable as well. */
+
   /* Start with large serial numbers so they are easy to spot
    * inside the protocol.
    */
   g->msg_next_serial = 0x00123400;
 
+  /* Link the handles onto a global list.  This is the one area
+   * where the library needs to be made thread-safe. (XXX)
+   */
+  /* acquire mutex (XXX) */
+  g->next = handles;
+  handles = g;
+  if (!atexit_handler_set) {
+    atexit (close_handles);
+    atexit_handler_set = 1;
+  }
+  /* release mutex (XXX) */
+
+  if (g->verbose)
+    fprintf (stderr, "new guestfs handle %p\n", g);
+
   return g;
 }
 
@@ -183,6 +213,7 @@ guestfs_close (guestfs_h *g)
 {
   int i;
   char filename[256];
+  guestfs_h *gg;
 
   if (g->state == NO_HANDLE) {
     /* Not safe to call 'error' here, so ... */
@@ -190,6 +221,13 @@ guestfs_close (guestfs_h *g)
     return;
   }
 
+  if (g->verbose)
+    fprintf (stderr, "closing guestfs handle %p (state %d)\n", g, g->state);
+
+  /* Try to sync if autosync flag is set. */
+  if (g->autosync && g->state == READY)
+    guestfs_sync (g);
+
   /* Remove any handlers that might be called back before we kill the
    * subprocess.
    */
@@ -216,9 +254,26 @@ guestfs_close (guestfs_h *g)
   /* Mark the handle as dead before freeing it. */
   g->state = NO_HANDLE;
 
+  /* acquire mutex (XXX) */
+  if (handles == g)
+    handles = g->next;
+  else {
+    for (gg = handles; gg->next != g; gg = gg->next)
+      ;
+    gg->next = g->next;
+  }
+  /* release mutex (XXX) */
+
   free (g);
 }
 
+/* Close all open handles (called from atexit(3)). */
+static void
+close_handles (void)
+{
+  while (handles) guestfs_close (handles);
+}
+
 static void
 default_error_cb (guestfs_h *g, void *data, const char *msg)
 {
@@ -274,7 +329,7 @@ perrorf (guestfs_h *g, const char *fs, ...)
 }
 
 static void *
-safe_malloc (guestfs_h *g, int nbytes)
+safe_malloc (guestfs_h *g, size_t nbytes)
 {
   void *ptr = malloc (nbytes);
   if (!ptr) g->abort_cb ();
@@ -297,6 +352,15 @@ safe_strdup (guestfs_h *g, const char *str)
   return s;
 }
 
+static void *
+safe_memdup (guestfs_h *g, void *ptr, size_t size)
+{
+  void *p = malloc (size);
+  if (!p) g->abort_cb ();
+  memcpy (p, ptr, size);
+  return p;
+}
+
 void
 guestfs_set_out_of_memory_handler (guestfs_h *g, guestfs_abort_cb cb)
 {
@@ -323,10 +387,11 @@ guestfs_get_error_handler (guestfs_h *g, void **data_rtn)
   return g->error_cb;
 }
 
-void
+int
 guestfs_set_verbose (guestfs_h *g, int v)
 {
-  g->verbose = v;
+  g->verbose = !!v;
+  return 0;
 }
 
 int
@@ -335,6 +400,35 @@ guestfs_get_verbose (guestfs_h *g)
   return g->verbose;
 }
 
+int
+guestfs_set_autosync (guestfs_h *g, int a)
+{
+  g->autosync = !!a;
+  return 0;
+}
+
+int
+guestfs_get_autosync (guestfs_h *g)
+{
+  return g->autosync;
+}
+
+int
+guestfs_set_path (guestfs_h *g, const char *path)
+{
+  if (path == NULL)
+    g->path = GUESTFS_DEFAULT_PATH;
+  else
+    g->path = path;
+  return 0;
+}
+
+const char *
+guestfs_get_path (guestfs_h *g)
+{
+  return g->path;
+}
+
 /* Add a string to the current command line. */
 static void
 incr_cmdline_size (guestfs_h *g)
@@ -407,6 +501,11 @@ guestfs_add_drive (guestfs_h *g, const char *filename)
     return -1;
   }
 
+  if (access (filename, F_OK) == -1) {
+    perrorf (g, "%s", filename);
+    return -1;
+  }
+
   snprintf (buf, len, "file=%s", filename);
 
   return guestfs_config (g, "-drive", buf);
@@ -420,6 +519,11 @@ guestfs_add_cdrom (guestfs_h *g, const char *filename)
     return -1;
   }
 
+  if (access (filename, F_OK) == -1) {
+    perrorf (g, "%s", filename);
+    return -1;
+  }
+
   return guestfs_config (g, "-cdrom", filename);
 }
 
@@ -427,19 +531,16 @@ int
 guestfs_launch (guestfs_h *g)
 {
   static const char *dir_template = "/tmp/libguestfsXXXXXX";
-  int r, i;
+  int r, i, len, pmore;
   int wfd[2], rfd[2];
   int tries;
-  /*const char *qemu = QEMU;*/ /* XXX */
-  const char *qemu = "/usr/bin/qemu-system-x86_64";
-  const char *kernel = "vmlinuz.fedora-10.x86_64";
-  const char *initrd = "initramfs.fedora-10.x86_64.img";
+  const char *kernel_name = "vmlinuz." REPO "." host_cpu;
+  const char *initrd_name = "initramfs." REPO "." host_cpu ".img";
+  char *path, *pelem, *pend;
+  char *kernel = NULL, *initrd = NULL;
   char unixsock[256];
   struct sockaddr_un addr;
 
-  /* XXX Choose which qemu to run. */
-  /* XXX Choose initrd, etc. */
-
   /* Configured? */
   if (!g->cmdline) {
     error (g, "you must call guestfs_add_drive before guestfs_launch");
@@ -451,12 +552,59 @@ guestfs_launch (guestfs_h *g)
     return -1;
   }
 
+  /* Search g->path for the kernel and initrd. */
+  pelem = path = safe_strdup (g, g->path);
+  do {
+    pend = strchrnul (pelem, ':');
+    pmore = *pend == ':';
+    *pend = '\0';
+    len = pend - pelem;
+
+    /* Empty element or "." means cwd. */
+    if (len == 0 || (len == 1 && *pelem == '.')) {
+      if (g->verbose)
+       fprintf (stderr,
+                "looking for kernel and initrd in current directory\n");
+      if (access (kernel_name, F_OK) == 0 && access (initrd_name, F_OK) == 0) {
+       kernel = safe_strdup (g, kernel_name);
+       initrd = safe_strdup (g, initrd_name);
+       break;
+      }
+    }
+    /* Look at <path>/kernel etc. */
+    else {
+      kernel = safe_malloc (g, len + strlen (kernel_name) + 2);
+      initrd = safe_malloc (g, len + strlen (initrd_name) + 2);
+      sprintf (kernel, "%s/%s", pelem, kernel_name);
+      sprintf (initrd, "%s/%s", pelem, initrd_name);
+
+      if (g->verbose)
+       fprintf (stderr, "looking for %s and %s\n", kernel, initrd);
+
+      if (access (kernel, F_OK) == 0 && access (initrd, F_OK) == 0)
+       break;
+      free (kernel);
+      free (initrd);
+      kernel = initrd = NULL;
+    }
+
+    pelem = pend + 1;
+  } while (pmore);
+
+  free (path);
+
+  if (kernel == NULL || initrd == NULL) {
+    error (g, "cannot find %s or %s on LIBGUESTFS_PATH (current path = %s)",
+          kernel_name, initrd_name, g->path);
+    goto cleanup0;
+  }
+
   /* Make the temporary directory containing the socket. */
   if (!g->tmpdir) {
     g->tmpdir = safe_strdup (g, dir_template);
     if (mkdtemp (g->tmpdir) == NULL) {
       perrorf (g, "%s: cannot create temporary directory", dir_template);
-      return -1;
+      goto cleanup0;
     }
   }
 
@@ -465,7 +613,7 @@ guestfs_launch (guestfs_h *g)
 
   if (pipe (wfd) == -1 || pipe (rfd) == -1) {
     perrorf (g, "pipe");
-    return -1;
+    goto cleanup0;
   }
 
   r = fork ();
@@ -475,7 +623,7 @@ guestfs_launch (guestfs_h *g)
     close (wfd[1]);
     close (rfd[0]);
     close (rfd[1]);
-    return -1;
+    goto cleanup0;
   }
 
   if (r == 0) {                        /* Child (qemu). */
@@ -485,7 +633,7 @@ guestfs_launch (guestfs_h *g)
     /* 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[0] = (char *) QEMU;
 
     /* Construct the -net channel parameter for qemu. */
     snprintf (vmchannel, sizeof vmchannel,
@@ -518,7 +666,7 @@ guestfs_launch (guestfs_h *g)
     g->cmdline[g->cmdline_size-1] = NULL;
 
     if (g->verbose) {
-      fprintf (stderr, "%s", qemu);
+      fprintf (stderr, "%s", QEMU);
       for (i = 0; g->cmdline[i]; ++i)
        fprintf (stderr, " %s", g->cmdline[i]);
       fprintf (stderr, "\n");
@@ -531,6 +679,8 @@ guestfs_launch (guestfs_h *g)
     close (rfd[0]);
     dup (wfd[0]);
     dup (rfd[1]);
+    close (wfd[0]);
+    close (rfd[1]);
 
 #if 0
     /* Set up a new process group, so we can signal this process
@@ -539,8 +689,8 @@ guestfs_launch (guestfs_h *g)
     setpgid (0, 0);
 #endif
 
-    execv (qemu, g->cmdline);  /* Run qemu. */
-    perror (qemu);
+    execv (QEMU, g->cmdline);  /* Run qemu. */
+    perror (QEMU);
     _exit (1);
   }
 
@@ -657,6 +807,10 @@ guestfs_launch (guestfs_h *g)
   g->start_t = 0;
   g->stdout_watch = -1;
   g->sock_watch = -1;
+
+ cleanup0:
+  free (kernel);
+  free (initrd);
   return -1;
 }
 
@@ -717,7 +871,7 @@ guestfs_kill_subprocess (guestfs_h *g)
   }
 
   if (g->verbose)
-    fprintf (stderr, "sending SIGTERM to process group %d\n", g->pid);
+    fprintf (stderr, "sending SIGTERM to process %d\n", g->pid);
 
   kill (g->pid, SIGTERM);
 
@@ -878,6 +1032,28 @@ sock_read_event (void *data, int watch, int fd, int events)
     goto cleanup;
   }
 
+  /* Got the full message, begin processing it. */
+  if (g->verbose) {
+    int i, j;
+
+    for (i = 0; i < g->msg_in_size; i += 16) {
+      printf ("%04x: ", i);
+      for (j = i; j < MIN (i+16, g->msg_in_size); ++j)
+       printf ("%02x ", (unsigned char) g->msg_in[j]);
+      for (; j < i+16; ++j)
+       printf ("   ");
+      printf ("|");
+      for (j = i; j < MIN (i+16, g->msg_in_size); ++j)
+       if (isprint (g->msg_in[j]))
+         printf ("%c", g->msg_in[j]);
+       else
+         printf (".");
+      for (; j < i+16; ++j)
+       printf (" ");
+      printf ("|\n");
+    }
+  }
+
   /* Not in the expected state. */
   if (g->state != BUSY)
     error (g, "state %d != BUSY", g->state);
@@ -1053,11 +1229,68 @@ dispatch (guestfs_h *g, int proc_nr, xdrproc_t xdrp, char *args)
   return -1;
 }
 
+/* Check the return message from a call for validity. */
+static int
+check_reply_header (guestfs_h *g,
+                   const struct guestfs_message_header *hdr,
+                   int proc_nr, int serial)
+{
+  if (hdr->prog != GUESTFS_PROGRAM) {
+    error (g, "wrong program (%d/%d)", hdr->prog, GUESTFS_PROGRAM);
+    return -1;
+  }
+  if (hdr->vers != GUESTFS_PROTOCOL_VERSION) {
+    error (g, "wrong protocol version (%d/%d)",
+          hdr->vers, GUESTFS_PROTOCOL_VERSION);
+    return -1;
+  }
+  if (hdr->direction != GUESTFS_DIRECTION_REPLY) {
+    error (g, "unexpected message direction (%d/%d)",
+          hdr->direction, GUESTFS_DIRECTION_REPLY);
+    return -1;
+  }
+  if (hdr->proc != proc_nr) {
+    error (g, "unexpected procedure number (%d/%d)", hdr->proc, proc_nr);
+    return -1;
+  }
+  if (hdr->serial != serial) {
+    error (g, "unexpected serial (%d/%d)", hdr->serial, serial);
+    return -1;
+  }
+
+  return 0;
+}
+
 /* The high-level actions are autogenerated by generator.ml.  Include
  * them here.
  */
 #include "guestfs-actions.c"
 
+/* Structure-freeing functions.  These rely on the fact that the
+ * structure format is identical to the XDR format.  See note in
+ * generator.ml.
+ */
+void
+guestfs_free_lvm_pv_list (struct guestfs_lvm_pv_list *x)
+{
+  xdr_free ((xdrproc_t) xdr_guestfs_lvm_int_pv_list, (char *) x);
+  free (x);
+}
+
+void
+guestfs_free_lvm_vg_list (struct guestfs_lvm_vg_list *x)
+{
+  xdr_free ((xdrproc_t) xdr_guestfs_lvm_int_vg_list, (char *) x);
+  free (x);
+}
+
+void
+guestfs_free_lvm_lv_list (struct guestfs_lvm_lv_list *x)
+{
+  xdr_free ((xdrproc_t) xdr_guestfs_lvm_int_lv_list, (char *) x);
+  free (x);
+}
+
 /* This is the default main loop implementation, using select(2). */
 
 struct handle_cb_data {