Guest boots, and basic select/callbacks work.
authorRichard Jones <rjones@redhat.com>
Thu, 2 Apr 2009 19:07:21 +0000 (20:07 +0100)
committerRichard Jones <rjones@redhat.com>
Thu, 2 Apr 2009 19:07:21 +0000 (20:07 +0100)
.gitignore
Makefile.am
examples/df.c
guestfs.pod
src/Makefile.am
src/guestfs.c
src/guestfs.h

index e553a6f..8d79766 100644 (file)
@@ -1,5 +1,6 @@
 .deps
 .libs
+*.bak
 *.la
 *.lo
 *.o
index 7054ea8..31cb6e3 100644 (file)
@@ -35,13 +35,12 @@ fs_DATA =  $(INITRAMFSIMG) $(VMLINUZ)
 
 $(VMLINUZ) $(INITRAMFSIMG): initramfs.timestamp
 
-initramfs.timestamp: make-initramfs.sh.in daemon/guestfsd
+#initramfs.timestamp: make-initramfs.sh.in daemon/guestfsd
+initramfs.timestamp: make-initramfs.sh.in
        rm -f $@
-       $(builddir)/make-initramfs.sh
+       bash $(builddir)/make-initramfs.sh
        touch $@
 
-.PHONY: daemon/guestfsd
-
 # Make clean.
 
 CLEANFILES = $(fs_DATA) initramfs.timestamp emptydisk
index 818de6e..91c07d7 100644 (file)
@@ -16,21 +16,16 @@ main (int argc, char *argv[])
   }
 
   g = guestfs_create ();
-  if (!g) {
-    perror ("guestfs_create");
-    exit (1);
-  }
+  if (!g) exit (1);
 
-  guestfs_set_exit_on_error (g, 1);
   guestfs_set_verbose (g, 1);
-
   guestfs_add_drive (g, argv[1]);
 
+  guestfs_launch (g);
   guestfs_wait_ready (g);
 
 
 
-
-  guestfs_free (g);
+  guestfs_close (g);
   return 0;
 }
index a3ef84b..9202fb8 100644 (file)
@@ -483,9 +483,9 @@ two are provided for you:
 
 =over 4
 
-=item libguestfs-poll
+=item libguestfs-select
 
-A simple main loop that is implemented using L<poll(2)>.
+A simple main loop that is implemented using L<select(2)>.
 
 This is the default main loop unless you call C<guestfs_set_main_loop>
 or C<guestfs_glib_set_main_loop>.
@@ -562,8 +562,8 @@ function, eg. C<g_main_loop_quit>.  In those cases, ignore this call.
 
 =head2 WRITING A CUSTOM MAIN LOOP
 
-This isn't documented.  Please see the libguestfs-poll and libguestfs-glib
-implementations.
+This isn't documented.  Please see the libguestfs-select and
+libguestfs-glib implementations.
 
 =head1 SEE ALSO
 
index d900e3a..a0066db 100644 (file)
@@ -18,3 +18,4 @@
 lib_LTLIBRARIES = libguestfs.la
 
 libguestfs_la_SOURCES = guestfs.c guestfs.h
+libguestfs_la_CFLAGS = -Wall -Werror
\ No newline at end of file
index a5b3ae4..a827157 100644 (file)
@@ -19,6 +19,7 @@
 #include <config.h>
 
 #define _BSD_SOURCE /* for mkdtemp, usleep */
+#define _GNU_SOURCE /* for vasprintf, GNU strerror_r */
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/un.h>
 #endif
 
+#include <sys/select.h>
+
 #include "guestfs.h"
 
-static int error (guestfs_h *g, const char *fs, ...);
-static int perrorf (guestfs_h *g, const char *fs, ...);
+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_realloc (guestfs_h *g, void *ptr, int nbytes);
 static char *safe_strdup (guestfs_h *g, const char *str);
 
+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 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);
+static int select_remove_timeout (guestfs_h *g, int timer);
+static void select_main_loop_run (guestfs_h *g);
+static void select_main_loop_quit (guestfs_h *g);
+
+#define UNIX_PATH_MAX 108
+
 #define VMCHANNEL_PORT 6666
 #define VMCHANNEL_ADDR "10.0.2.4"
 
+/* Current main loop. */
+static guestfs_main_loop main_loop = {
+  .add_handle = select_add_handle,
+  .remove_handle = select_remove_handle,
+  .add_timeout = select_add_timeout,
+  .remove_timeout = select_remove_timeout,
+  .main_loop_run = select_main_loop_run,
+  .main_loop_quit = select_main_loop_quit,
+};
+
 /* GuestFS handle and connection. */
+enum state { CONFIG, LAUNCHING, READY, BUSY, NO_HANDLE };
+
 struct guestfs_h
 {
-  /* All these socks/pids are -1 if not connected. */
+  /* State: see the state machine diagram in the man page guestfs(3). */
+  enum state state;
+
+  int fd[2];                   /* Stdin/stdout of qemu. */
   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
-                                * an error.
-                                */
+  int stdout_watch;            /* Watches qemu stdout for log messages. */
+  int sock_watch;              /* Watches daemon comm socket. */
+
+  char *tmpdir;                        /* Temporary directory containing socket. */
 
   char **cmdline;              /* Qemu command line. */
   int cmdline_size;
 
-  guestfs_abort_fn abort_fn;
-  int exit_on_error;
   int verbose;
+
+  /* Callbacks. */
+  guestfs_abort_cb           abort_cb;
+  guestfs_error_handler_cb   error_cb;
+  void *                     error_cb_data;
+  guestfs_reply_cb           reply_cb;
+  void *                     reply_cb_data;
+  guestfs_log_message_cb     log_message_cb;
+  void *                     log_message_cb_data;
+  guestfs_subprocess_quit_cb subprocess_quit_cb;
+  void *                     subprocess_quit_cb_data;
+  guestfs_launch_done_cb     launch_done_cb;
+  void *                     launch_done_cb_data;
+
+  /* These callbacks are called before reply_cb and launch_done_cb,
+   * and are used to implement the high-level API without needing to
+   * interfere with callbacks that the user might have set.
+   */
+  guestfs_reply_cb           reply_cb_internal;
+  void *                     reply_cb_internal_data;
+  guestfs_launch_done_cb     launch_done_cb_internal;
+  void *                     launch_done_cb_internal_data;
 };
 
 guestfs_h *
 guestfs_create (void)
 {
   guestfs_h *g;
+  const char *str;
 
   g = malloc (sizeof (*g));
   if (!g) return NULL;
 
-  g->sock = -1;
-  g->pid = -1;
+  memset (g, 0, sizeof (*g));
 
-  g->start_t = 0;
-  g->daemon_up = 0;
+  g->state = CONFIG;
 
-  g->tmpdir = NULL;
+  g->fd[0] = -1;
+  g->fd[1] = -1;
+  g->sock = -1;
+  g->stdout_watch = -1;
+  g->sock_watch = -1;
 
-  g->abort_fn = abort;         /* Have to set these before safe_malloc. */
-  g->exit_on_error = 0;
-  g->verbose = getenv ("LIBGUESTFS_VERBOSE") != NULL;
+  g->abort_cb = abort;
+  g->error_cb = default_error_cb;
+  g->error_cb_data = NULL;
 
-  g->cmdline = safe_malloc (g, sizeof (char *) * 1);
-  g->cmdline_size = 1;
-  g->cmdline[0] = NULL;                /* This is chosen by guestfs_launch. */
+  str = getenv ("LIBGUESTFS_DEBUG");
+  g->verbose = str != NULL && strcmp (str, "1") == 0;
 
   return g;
 }
 
 void
-guestfs_free (guestfs_h *g)
+guestfs_close (guestfs_h *g)
 {
   int i;
   char filename[256];
 
-  if (g->pid) guestfs_kill_subprocess (g);
+  if (g->state == NO_HANDLE) {
+    /* Not safe to call 'error' here, so ... */
+    fprintf (stderr, "guestfs_close: called twice on the same handle\n");
+    return;
+  }
 
-  /* The assumption is that programs calling this have successfully
-   * used qemu, so delete the logfile and socket directory.
+  /* Remove any handlers that might be called back before we kill the
+   * subprocess.
    */
+  g->log_message_cb = NULL;
+
+  if (g->state != CONFIG)
+    guestfs_kill_subprocess (g);
+
   if (g->tmpdir) {
     snprintf (filename, sizeof filename, "%s/sock", g->tmpdir);
     unlink (filename);
 
-    snprintf (filename, sizeof filename, "%s/qemu.log", g->tmpdir);
-    unlink (filename);
-
     rmdir (g->tmpdir);
 
     free (g->tmpdir);
   }
 
-  for (i = 0; i < g->cmdline_size; ++i)
-    free (g->cmdline[i]);
-  free (g->cmdline);
+  if (g->cmdline) {
+    for (i = 0; i < g->cmdline_size; ++i)
+      free (g->cmdline[i]);
+    free (g->cmdline);
+  }
+
+  /* Mark the handle as dead before freeing it. */
+  g->state = NO_HANDLE;
 
   free (g);
 }
 
-/* Cleanup fds and sockets, assuming the subprocess is dead already. */
 static void
-cleanup_fds (guestfs_h *g)
+default_error_cb (guestfs_h *g, void *data, const char *msg)
 {
-  if (g->sock >= 0) close (g->sock);
-  g->sock = -1;
+  fprintf (stderr, "libguestfs: %s\n", msg);
 }
 
-/* Wait for subprocess to exit. */
 static void
-wait_subprocess (guestfs_h *g)
-{
-  if (g->pid >= 0) waitpid (g->pid, NULL, 0);
-  g->pid = -1;
-}
-
-static int
 error (guestfs_h *g, const char *fs, ...)
 {
   va_list args;
+  char *msg;
+
+  if (!g->error_cb) return;
 
-  fprintf (stderr, "libguestfs: ");
   va_start (args, fs);
-  vfprintf (stderr, fs, args);
+  vasprintf (&msg, fs, args);
   va_end (args);
-  fputc ('\n', stderr);
 
-  if (g->exit_on_error) {
-    guestfs_kill_subprocess (g);
-    exit (1);
-  }
-  return -1;
+  g->error_cb (g, g->error_cb_data, msg);
+
+  free (msg);
 }
 
-static int
+static void
 perrorf (guestfs_h *g, const char *fs, ...)
 {
   va_list args;
-  char buf[256];
+  char *msg;
   int err = errno;
 
-  fprintf (stderr, "libguestfs: ");
+  if (!g->error_cb) return;
+
   va_start (args, fs);
-  vfprintf (stderr, fs, args);
+  vasprintf (&msg, fs, args);
   va_end (args);
+
+#ifndef _GNU_SOURCE
+  char buf[256];
   strerror_r (err, buf, sizeof buf);
-  fprintf (stderr, ": %s\n", buf);
+#else
+  char _buf[256];
+  char *buf;
+  buf = strerror_r (err, _buf, sizeof _buf);
+#endif
 
-  if (g->exit_on_error) {
-    guestfs_kill_subprocess (g);
-    exit (1);
-  }
-  return -1;
+  msg = safe_realloc (g, msg, strlen (msg) + 2 + strlen (buf) + 1);
+  strcat (msg, ": ");
+  strcat (msg, buf);
+
+  g->error_cb (g, g->error_cb_data, msg);
+
+  free (msg);
 }
 
 static void *
 safe_malloc (guestfs_h *g, int nbytes)
 {
   void *ptr = malloc (nbytes);
-  if (!ptr) g->abort_fn ();
+  if (!ptr) g->abort_cb ();
   return ptr;
 }
 
@@ -206,7 +269,7 @@ static void *
 safe_realloc (guestfs_h *g, void *ptr, int nbytes)
 {
   void *p = realloc (ptr, nbytes);
-  if (!p) g->abort_fn ();
+  if (!p) g->abort_cb ();
   return p;
 }
 
@@ -214,32 +277,34 @@ static char *
 safe_strdup (guestfs_h *g, const char *str)
 {
   char *s = strdup (str);
-  if (!s) g->abort_fn ();
+  if (!s) g->abort_cb ();
   return s;
 }
 
 void
-guestfs_set_out_of_memory_handler (guestfs_h *g, guestfs_abort_fn a)
+guestfs_set_out_of_memory_handler (guestfs_h *g, guestfs_abort_cb cb)
 {
-  g->abort_fn = a;
+  g->abort_cb = cb;
 }
 
-guestfs_abort_fn
+guestfs_abort_cb
 guestfs_get_out_of_memory_handler (guestfs_h *g)
 {
-  return g->abort_fn;
+  return g->abort_cb;
 }
 
 void
-guestfs_set_exit_on_error (guestfs_h *g, int e)
+guestfs_set_error_handler (guestfs_h *g, guestfs_error_handler_cb cb, void *data)
 {
-  g->exit_on_error = e;
+  g->error_cb = cb;
+  g->error_cb_data = data;
 }
 
-int
-guestfs_get_exit_on_error (guestfs_h *g)
+guestfs_error_handler_cb
+guestfs_get_error_handler (guestfs_h *g, void **data_rtn)
 {
-  return g->exit_on_error;
+  if (data_rtn) *data_rtn = g->error_cb_data;
+  return g->error_cb;
 }
 
 void
@@ -254,17 +319,31 @@ guestfs_get_verbose (guestfs_h *g)
   return g->verbose;
 }
 
-/* Add an escaped string to the current command line. */
-static int
-add_cmdline (guestfs_h *g, const char *str)
+/* Add a string to the current command line. */
+static void
+incr_cmdline_size (guestfs_h *g)
 {
-  if (g->pid >= 0)
-    return error (g, "command line cannot be altered after qemu subprocess launched");
+  if (g->cmdline == NULL) {
+    /* g->cmdline[0] is reserved for argv[0], set in guestfs_launch. */
+    g->cmdline_size = 1;
+    g->cmdline = safe_malloc (g, sizeof (char *));
+    g->cmdline[0] = NULL;
+  }
 
   g->cmdline_size++;
   g->cmdline = safe_realloc (g, g->cmdline, sizeof (char *) * g->cmdline_size);
-  g->cmdline[g->cmdline_size-1] = safe_strdup (g, str);
+}
 
+static int
+add_cmdline (guestfs_h *g, const char *str)
+{
+  if (g->state != CONFIG) {
+    error (g, "command line cannot be altered after qemu subprocess launched");
+    return -1;
+  }
+
+  incr_cmdline_size (g);
+  g->cmdline[g->cmdline_size-1] = safe_strdup (g, str);
   return 0;
 }
 
@@ -272,8 +351,10 @@ int
 guestfs_config (guestfs_h *g,
                const char *qemu_param, const char *qemu_value)
 {
-  if (qemu_param[0] != '-')
-    return error (g, "guestfs_config: parameter must begin with '-' character");
+  if (qemu_param[0] != '-') {
+    error (g, "guestfs_config: parameter must begin with '-' character");
+    return -1;
+  }
 
   /* A bit fascist, but the user will probably break the extra
    * parameters that we add if they try to set any of these.
@@ -285,8 +366,10 @@ guestfs_config (guestfs_h *g,
       strcmp (qemu_param, "-vnc") == 0 ||
       strcmp (qemu_param, "-full-screen") == 0 ||
       strcmp (qemu_param, "-std-vga") == 0 ||
-      strcmp (qemu_param, "-vnc") == 0)
-    return error (g, "guestfs_config: parameter '%s' isn't allowed");
+      strcmp (qemu_param, "-vnc") == 0) {
+    error (g, "guestfs_config: parameter '%s' isn't allowed", qemu_param);
+    return -1;
+  }
 
   if (add_cmdline (g, qemu_param) != 0) return -1;
 
@@ -303,10 +386,12 @@ guestfs_add_drive (guestfs_h *g, const char *filename)
   int len = strlen (filename) + 64;
   char buf[len];
 
-  if (strchr (filename, ',') != NULL)
-    return error (g, "filename cannot contain ',' (comma) character");
+  if (strchr (filename, ',') != NULL) {
+    error (g, "filename cannot contain ',' (comma) character");
+    return -1;
+  }
 
-  snprintf (buf, len, "file=%s,media=disk", filename);
+  snprintf (buf, len, "file=%s", filename);
 
   return guestfs_config (g, "-drive", buf);
 }
@@ -314,15 +399,12 @@ guestfs_add_drive (guestfs_h *g, const char *filename)
 int
 guestfs_add_cdrom (guestfs_h *g, const char *filename)
 {
-  int len = strlen (filename) + 64;
-  char buf[len];
-
-  if (strchr (filename, ',') != NULL)
-    return error (g, "filename cannot contain ',' (comma) character");
-
-  snprintf (buf, len, "file=%s,if=ide,index=1,media=cdrom", filename);
+  if (strchr (filename, ',') != NULL) {
+    error (g, "filename cannot contain ',' (comma) character");
+    return -1;
+  }
 
-  return guestfs_config (g, "-drive", buf);
+  return guestfs_config (g, "-cdrom", filename);
 }
 
 int
@@ -330,42 +412,57 @@ guestfs_launch (guestfs_h *g)
 {
   static const char *dir_template = "/tmp/libguestfsXXXXXX";
   int r, i;
+  int wfd[2], rfd[2];
+  int tries;
   /*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 = "/tmp/initrd-2.6.27.15-170.2.24.fc10.x86_64.img";
+  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";
   char unixsock[256];
+  struct sockaddr_un addr;
 
   /* XXX Choose which qemu to run. */
   /* XXX Choose initrd, etc. */
 
-  /* Make the temporary directory containing the logfile and socket. */
-  if (!g->tmpdir) {
-    g->tmpdir = safe_strdup (g, dir_template);
-    if (mkdtemp (g->tmpdir) == NULL)
-      return perrorf (g, "%s: cannot create temporary directory", dir_template);
+  /* Configured? */
+  if (!g->cmdline) {
+    error (g, "you must call guestfs_add_drive before guestfs_launch");
+    return -1;
+  }
 
-    snprintf (unixsock, sizeof unixsock, "%s/sock", g->tmpdir);
+  if (g->state != CONFIG) {
+    error (g, "qemu has already been launched");
+    return -1;
   }
 
-  r = fork ();
-  if (r == -1)
-    return perrorf (g, "fork");
+  /* 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;
+    }
+  }
 
-  if (r > 0) {                 /* Parent (library). */
-    g->pid = r;
+  snprintf (unixsock, sizeof unixsock, "%s/sock", g->tmpdir);
 
-    /* If qemu is going to die during startup, give it a tiny amount
-     * of time to print the error message.
-     */
-    usleep (10000);
+  if (pipe (wfd) == -1 || pipe (rfd) == -1) {
+    perrorf (g, "pipe");
+    return -1;
+  }
 
-    /* Start the clock ... */
-    time (&g->start_t);
+  r = fork ();
+  if (r == -1) {
+    perrorf (g, "fork");
+    close (wfd[0]);
+    close (wfd[1]);
+    close (rfd[0]);
+    close (rfd[1]);
+    return -1;
   }
-  else {                       /* Child (qemu). */
+
+  if (r == 0) {                        /* Child (qemu). */
     char vmchannel[256];
-    char logfile[256];
     char append[256];
 
     /* Set up the full command line.  Do this in the subprocess so we
@@ -373,225 +470,488 @@ guestfs_launch (guestfs_h *g)
      */
     g->cmdline[0] = (char *) qemu;
 
-    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", VMCHANNEL_PORT, 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] = 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,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;
+    add_cmdline (g, "-m");
+    add_cmdline (g, "384");    /* XXX Choose best size. */
+    add_cmdline (g, "-kernel");
+    add_cmdline (g, (char *) kernel);
+    add_cmdline (g, "-initrd");
+    add_cmdline (g, (char *) initrd);
+    add_cmdline (g, "-append");
+    add_cmdline (g, append);
+    add_cmdline (g, "-nographic");
+    add_cmdline (g, "-serial");
+    add_cmdline (g, "stdio");
+    add_cmdline (g, "-net");
+    add_cmdline (g, vmchannel);
+    add_cmdline (g, "-net");
+    add_cmdline (g, "user,vlan=0");
+    add_cmdline (g, "-net");
+    add_cmdline (g, "nic,vlan=0");
+    incr_cmdline_size (g);
+    g->cmdline[g->cmdline_size-1] = NULL;
 
     if (g->verbose) {
-      fprintf (stderr, "Running %s", qemu);
+      fprintf (stderr, "%s", qemu);
       for (i = 0; g->cmdline[i]; ++i)
        fprintf (stderr, " %s", g->cmdline[i]);
       fprintf (stderr, "\n");
     }
 
-    /* Set up stdin, stdout.  Messages should go to the logfile. */
+    /* Set up stdin, stdout. */
     close (0);
     close (1);
-    open ("/dev/null", O_RDONLY);
-    snprintf (logfile, sizeof logfile, "%s/qemu.log", g->tmpdir);
-    open (logfile, O_WRONLY|O_CREAT|O_APPEND, 0644);
-    /*dup2 (1, 2);*/
+    close (wfd[1]);
+    close (rfd[0]);
+    dup (wfd[0]);
+    dup (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
 
     execv (qemu, g->cmdline);  /* Run qemu. */
     perror (qemu);
     _exit (1);
   }
 
-  return 0;
-}
+  /* Parent (library). */
+  g->pid = r;
 
-/* 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)
- */
+  /* Start the clock ... */
+  time (&g->start_t);
 
-#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.
-                                */
+  /* Close the other ends of the pipe. */
+  close (wfd[0]);
+  close (rfd[1]);
+
+  if (fcntl (wfd[1], F_SETFL, O_NONBLOCK) == -1 ||
+      fcntl (rfd[0], F_SETFL, O_NONBLOCK) == -1) {
+    perrorf (g, "fcntl");
+    goto cleanup1;
+  }
+
+  g->fd[0] = wfd[1];           /* stdin of child */
+  g->fd[1] = rfd[0];           /* stdout of child */
+
+  /* Open the Unix socket.  The vmchannel implementation that got
+   * merged with qemu sucks in a number of ways.  Both ends do
+   * connect(2), which means that no one knows what, if anything, is
+   * connected to the other end, or if it becomes disconnected.  Even
+   * worse, we have to wait some indeterminate time for qemu to create
+   * the socket and connect to it (which happens very early in qemu's
+   * start-up), so any code that uses vmchannel is inherently racy.
+   * Hence this silly loop.
+   */
+  g->sock = socket (AF_UNIX, SOCK_STREAM, 0);
+  if (g->sock == -1) {
+    perrorf (g, "socket");
+    goto cleanup1;
+  }
+
+  if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) {
+    perrorf (g, "fcntl");
+    goto cleanup2;
+  }
 
-static int wait_ready (guestfs_h *g);
+  addr.sun_family = AF_UNIX;
+  strncpy (addr.sun_path, unixsock, UNIX_PATH_MAX);
+  addr.sun_path[UNIX_PATH_MAX-1] = '\0';
+
+  tries = 100;
+  while (tries > 0) {
+    /* Always sleep at least once to give qemu a small chance to start up. */
+    usleep (10000);
+
+    r = connect (g->sock, (struct sockaddr *) &addr, sizeof addr);
+    if ((r == -1 && errno == EINPROGRESS) || r == 0)
+      goto connected;
+
+    perrorf (g, "connect");
+    tries--;
+  }
+
+  error (g, "failed to connect to vmchannel socket");
+  goto cleanup2;
+
+ connected:
+  /* Watch the file descriptors. */
+  g->stdout_watch =
+    main_loop.add_handle (g, g->fd[1],
+                         GUESTFS_HANDLE_READABLE,
+                         stdout_event, g);
+  if (g->stdout_watch == -1) {
+    error (g, "could not watch qemu stdout");
+    goto cleanup3;
+  }
+
+  g->sock_watch =
+    main_loop.add_handle (g, g->sock,
+                         GUESTFS_HANDLE_READABLE |
+                         GUESTFS_HANDLE_HANGUP |
+                         GUESTFS_HANDLE_ERROR,
+                         sock_read_event, g);
+  if (g->sock_watch == -1) {
+    error (g, "could not watch daemon communications socket");
+    goto cleanup3;
+  }
+
+  g->state = LAUNCHING;
+  return 0;
+
+ cleanup3:
+  if (g->stdout_watch >= 0)
+    main_loop.remove_handle (g, g->stdout_watch);
+  if (g->sock_watch >= 0)
+    main_loop.remove_handle (g, g->sock_watch);
+
+ cleanup2:
+  close (g->sock);
+
+ cleanup1:
+  close (wfd[1]);
+  close (rfd[0]);
+  kill (g->pid, 9);
+  waitpid (g->pid, NULL, 0);
+  g->fd[0] = -1;
+  g->fd[1] = -1;
+  g->sock = -1;
+  g->pid = 0;
+  g->start_t = 0;
+  g->stdout_watch = -1;
+  g->sock_watch = -1;
+  return -1;
+}
+
+static void
+finish_wait_ready (guestfs_h *g, void *vp)
+{
+  *((int *)vp) = 1;
+  main_loop.main_loop_quit (g);
+}
 
 int
 guestfs_wait_ready (guestfs_h *g)
 {
-  int r;
+  int r = 0;
 
-  /* Launch the subprocess, if there isn't one already. */
-  if (g->pid == -1) {
-    if (guestfs_launch (g) != 0)
-      return -1;
+  if (g->state == READY) return 0;
+
+  if (g->state == BUSY) {
+    error (g, "qemu has finished launching already");
+    return -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;
+  if (g->state != LAUNCHING) {
+    error (g, "qemu has not been launched yet");
+    return -1;
+  }
+
+  g->launch_done_cb_internal = finish_wait_ready;
+  g->launch_done_cb_internal_data = &r;
+  main_loop.main_loop_run (g);
+  g->launch_done_cb_internal = NULL;
+  g->launch_done_cb_internal_data = NULL;
+
+  if (r != 1) {
+    error (g, "guestfs_wait_ready failed, see earlier error messages");
+    return -1;
+  }
+
+  /* This is possible in some really strange situations, such as
+   * guestfsd starts up OK but then qemu immediately exits.  Check for
+   * it because the caller is probably expecting to be able to send
+   * commands after this function returns.
+   */
+  if (g->state != READY) {
+    error (g, "qemu launched and contacted daemon, but state != READY");
+    return -1;
   }
 
   return 0;
 }
 
-#define UNIX_PATH_MAX 108
+int
+guestfs_kill_subprocess (guestfs_h *g)
+{
+  if (g->state == CONFIG) {
+    error (g, "no subprocess to kill");
+    return -1;
+  }
+
+  if (g->verbose)
+    fprintf (stderr, "sending SIGTERM to process group %d\n", g->pid);
+
+  kill (g->pid, SIGTERM);
+
+  return 0;
+}
 
-/* 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
+/* This function is called whenever qemu prints something on stdout.
+ * Qemu's stdout is also connected to the guest's serial console, so
+ * we see kernel messages here too.
  */
-static int
-wait_ready (guestfs_h *g)
+static void
+stdout_event (void *data, int watch, int fd, int events)
 {
-  int r, i, sock;
-  time_t now;
-  double elapsed;
-  struct sockaddr_un addr;
-  unsigned char m;
+  guestfs_h *g = (guestfs_h *) data;
+  char buf[4096];
+  int n;
+
+#if 0
+  if (g->verbose)
+    fprintf (stderr,
+            "stdout_event: %p g->state = %d, fd = %d, events = 0x%x\n",
+            g, g->state, fd, events);
+#endif
 
-  if (g->pid == -1) abort ();  /* Internal state error. */
+  if (g->fd[1] != fd) {
+    error (g, "stdout_event: internal error: %d != %d", g->fd[1], fd);
+    return;
+  }
 
-  /* Check the daemon is still around. */
-  r = waitpid (g->pid, NULL, WNOHANG);
+  n = read (fd, buf, sizeof buf);
+  if (n == 0) {
+    /* Hopefully this indicates the qemu child process has died. */
+    if (g->verbose)
+      fprintf (stderr, "stdout_event: %p: child process died\n", g);
+    /*kill (g->pid, SIGTERM);*/
+    waitpid (g->pid, NULL, 0);
+    if (g->stdout_watch >= 0)
+      main_loop.remove_handle (g, g->stdout_watch);
+    if (g->sock_watch >= 0)
+      main_loop.remove_handle (g, g->sock_watch);
+    close (g->fd[0]);
+    close (g->fd[1]);
+    close (g->sock);
+    g->fd[0] = -1;
+    g->fd[1] = -1;
+    g->sock = -1;
+    g->pid = 0;
+    g->start_t = 0;
+    g->stdout_watch = -1;
+    g->sock_watch = -1;
+    g->state = CONFIG;
+    if (g->subprocess_quit_cb)
+      g->subprocess_quit_cb (g, g->subprocess_quit_cb_data);
+    return;
+  }
 
-  if (r > 0 || (r == -1 && errno == ECHILD)) {
-    g->pid = -1;
-    return error (g,
-                 "qemu subprocess exited unexpectedly during initialization");
+  if (n == -1) {
+    if (errno != EAGAIN)
+      perrorf (g, "read");
+    return;
   }
 
-  time (&now);
-  elapsed = difftime (now, g->start_t);
+  /* In verbose mode, copy all log messages to stderr. */
+  if (g->verbose)
+    write (2, buf, n);
 
-  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;
-    }
+  /* It's an actual log message, send it upwards if anyone is listening. */
+  if (g->log_message_cb)
+    g->log_message_cb (g, g->log_message_cb_data, buf, n);
+}
 
-    if (fcntl (sock, F_SETFL, O_NONBLOCK) == -1) {
-      perrorf (g, "set socket non-blocking");
-      close (sock);
-      return -1;
-    }
+/* The function is called whenever we can read something on the
+ * guestfsd (daemon inside the guest) communication socket.
+ */
+static void
+sock_read_event (void *data, int watch, int fd, int events)
+{
+  /*guestfs_h *g = (guestfs_h *) data;*/
+
+
+
+
+
+
+
+}
+
+/* This is the default main loop implementation, using select(2). */
+
+struct handle_cb_data {
+  guestfs_handle_event_cb cb;
+  void *data;
+};
+
+static fd_set rset;
+static fd_set wset;
+static fd_set xset;
+static int select_init_done = 0;
+static int max_fd = -1;
+static struct handle_cb_data *handle_cb_data = NULL;
+
+static void
+select_init (void)
+{
+  if (!select_init_done) {
+    FD_ZERO (&rset);
+    FD_ZERO (&wset);
+    FD_ZERO (&xset);
 
-    g->sock = sock;
+    select_init_done = 1;
   }
+}
 
-  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) {
-      if (elapsed <= DAEMON_TIMEOUT)
-       return 1;               /* Keep waiting for the daemon ... */
-      error (g, "timeout waiting for guest to become ready");
-      return -1;
-    }
+static int
+select_add_handle (guestfs_h *g, int fd, int events,
+                  guestfs_handle_event_cb cb, void *data)
+{
+  select_init ();
+
+  if (fd < 0 || fd >= FD_SETSIZE) {
+    error (g, "fd %d is out of range", fd);
+    return -1;
+  }
+
+  if ((events & ~(GUESTFS_HANDLE_READABLE |
+                 GUESTFS_HANDLE_WRITABLE |
+                 GUESTFS_HANDLE_HANGUP |
+                 GUESTFS_HANDLE_ERROR)) != 0) {
+    error (g, "set of events (0x%x) contains unknown events", events);
+    return -1;
+  }
+
+  if (events == 0) {
+    error (g, "set of events is empty");
+    return -1;
+  }
+
+  if (FD_ISSET (fd, &rset) || FD_ISSET (fd, &wset) || FD_ISSET (fd, &xset)) {
+    error (g, "fd %d is already registered", fd);
+    return -1;
+  }
+
+  if (cb == NULL) {
+    error (g, "callback is NULL");
+    return -1;
+  }
+
+  if ((events & GUESTFS_HANDLE_READABLE))
+    FD_SET (fd, &rset);
+  if ((events & GUESTFS_HANDLE_WRITABLE))
+    FD_SET (fd, &wset);
+  if ((events & GUESTFS_HANDLE_HANGUP) || (events & GUESTFS_HANDLE_ERROR))
+    FD_SET (fd, &xset);
+
+  if (fd > max_fd) {
+    max_fd = fd;
+    handle_cb_data = safe_realloc (g, handle_cb_data,
+                                  sizeof (struct handle_cb_data) * (max_fd+1));
+  }
+  handle_cb_data[fd].cb = cb;
+  handle_cb_data[fd].data = data;
+
+  /* Any integer >= 0 can be the handle, and this is as good as any ... */
+  return fd;
+}
+
+static int
+select_remove_handle (guestfs_h *g, int fd)
+{
+  select_init ();
 
-    perrorf (g, "read");
+  if (fd < 0 || fd >= FD_SETSIZE) {
+    error (g, "fd %d is out of range", fd);
     return -1;
   }
 
+  if (!FD_ISSET (fd, &rset) && !FD_ISSET (fd, &wset) && !FD_ISSET (fd, &xset)) {
+    error (g, "fd %d was not registered", fd);
+    return -1;
+  }
+
+  FD_CLR (fd, &rset);
+  FD_CLR (fd, &wset);
+  FD_CLR (fd, &xset);
+
+  if (fd == max_fd) {
+    max_fd--;
+    handle_cb_data = safe_realloc (g, handle_cb_data,
+                                  sizeof (struct handle_cb_data) * (max_fd+1));
+  }
+
   return 0;
 }
 
-void
-guestfs_kill_subprocess (guestfs_h *g)
+static int
+select_add_timeout (guestfs_h *g, int interval,
+                   guestfs_handle_timeout_cb cb, void *data)
 {
-  if (g->pid >= 0) {
-    if (g->verbose)
-      fprintf (stderr, "sending SIGTERM to pgid %d\n", g->pid);
+  select_init ();
+
+  abort ();                    /* XXX not implemented yet */
+}
+
+static int
+select_remove_timeout (guestfs_h *g, int timer)
+{
+  select_init ();
+
+  abort ();                    /* XXX not implemented yet */
+}
+
+/* Note that main loops can be nested. */
+static int level = 0;
+
+static void
+select_main_loop_run (guestfs_h *g)
+{
+  int old_level, fd, r, events;
+  fd_set rset2, wset2, xset2;
+
+  select_init ();
+
+  old_level = level++;
+  while (level > old_level) {
+    rset2 = rset;
+    wset2 = wset;
+    xset2 = xset;
+    r = select (max_fd+1, &rset2, &wset2, &xset2, NULL);
+    if (r == -1) {
+      perrorf (g, "select");
+      level = old_level;
+      break;
+    }
+
+    for (fd = 0; r > 0 && fd <= max_fd; ++fd) {
+      events = 0;
+      if (FD_ISSET (fd, &rset2))
+       events |= GUESTFS_HANDLE_READABLE;
+      if (FD_ISSET (fd, &wset2))
+       events |= GUESTFS_HANDLE_WRITABLE;
+      if (FD_ISSET (fd, &xset2))
+       events |= GUESTFS_HANDLE_ERROR | GUESTFS_HANDLE_HANGUP;
+      if (events) {
+       r--;
+       handle_cb_data[fd].cb (handle_cb_data[fd].data,
+                              fd, fd, events);
+      }
+    }
+  }
+}
+
+static void
+select_main_loop_quit (guestfs_h *g)
+{
+  select_init ();
 
-    kill (- g->pid, SIGTERM);
-    wait_subprocess (g);
+  if (level == 0) {
+    error (g, "cannot quit, we are not in a main loop");
+    return;
   }
 
-  cleanup_fds (g);
+  level--;
 }
index 49b14f5..ead5307 100644 (file)
 #ifndef GUESTFS_H_
 #define GUESTFS_H_
 
-/* For API documentation, please read the manual page guestfs(3). */
+/* IMPORTANT NOTE!
+ * All API documentation is in the manual page --> guestfs(3) <--
+ * Go and read it now, I'll wait.
+ */
 
 typedef struct guestfs_h guestfs_h;
 
-/* Create and destroy the guest handle. */
+/* Connection management. */
 extern guestfs_h *guestfs_create (void);
-extern void guestfs_free (guestfs_h *g);
+extern void guestfs_close (guestfs_h *g);
+extern int guestfs_launch (guestfs_h *g);
+extern int guestfs_wait_ready (guestfs_h *g);
+extern int guestfs_kill_subprocess (guestfs_h *g);
 
-/* Guest configuration. */
+/* Configuration management. */
 extern int guestfs_config (guestfs_h *g,
                           const char *qemu_param, const char *qemu_value);
 extern int guestfs_add_drive (guestfs_h *g, const char *filename);
 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);
-
-/* Kill the guest subprocess. */
-extern void guestfs_kill_subprocess (guestfs_h *g);
-
 /* Error handling. */
-typedef void (*guestfs_abort_fn) (void);
-extern void guestfs_set_out_of_memory_handler (guestfs_h *g, guestfs_abort_fn);
-extern guestfs_abort_fn guestfs_get_out_of_memory_handler (guestfs_h *g);
+typedef void (*guestfs_error_handler_cb) (guestfs_h *g, void *data, const char *msg);
+typedef void (*guestfs_abort_cb) (void);
+
+extern void guestfs_set_error_handler (guestfs_h *g, guestfs_error_handler_cb cb, void *data);
+extern guestfs_error_handler_cb guestfs_get_error_handler (guestfs_h *g, void **data_rtn);
 
-extern void guestfs_set_exit_on_error (guestfs_h *g, int exit_on_error);
-extern int guestfs_get_exit_on_error (guestfs_h *g);
+extern void guestfs_set_out_of_memory_handler (guestfs_h *g, guestfs_abort_cb);
+extern guestfs_abort_cb guestfs_get_out_of_memory_handler (guestfs_h *g);
 
 extern void guestfs_set_verbose (guestfs_h *g, int verbose);
 extern int guestfs_get_verbose (guestfs_h *g);
 
+/* Actions. XXX Will be auto-generated */
+extern int guestfs_sync (guestfs_h *g);
+
+/* Low-level event API. */
+typedef void (*guestfs_reply_cb) (guestfs_h *g, void *data /* , ... */);
+typedef void (*guestfs_log_message_cb) (guestfs_h *g, void *data, char *buf, int len);
+typedef void (*guestfs_subprocess_quit_cb) (guestfs_h *g, void *data);
+typedef void (*guestfs_launch_done_cb) (guestfs_h *g, void *data /* , ... */);
+
+extern void guestfs_set_reply_callback (guestfs_h *g, guestfs_reply_cb cb, void *opaque);
+extern void guestfs_set_log_message_callback (guestfs_h *g, guestfs_log_message_cb cb, void *opaque);
+extern void guestfs_set_subprocess_quit_callback (guestfs_h *g, guestfs_subprocess_quit_cb cb, void *opaque);
+extern void guestfs_set_launch_done_callback (guestfs_h *g, guestfs_launch_done_cb cb, void *opaque);
+
+/* Main loop. */
+#define GUESTFS_HANDLE_READABLE 0x1
+#define GUESTFS_HANDLE_WRITABLE 0x2
+#define GUESTFS_HANDLE_HANGUP   0x4
+#define GUESTFS_HANDLE_ERROR    0x8
+
+typedef void (*guestfs_handle_event_cb) (void *data, int watch, int fd, int events);
+typedef int (*guestfs_add_handle_cb) (guestfs_h *g, int fd, int events, guestfs_handle_event_cb cb, void *data);
+typedef int (*guestfs_remove_handle_cb) (guestfs_h *g, int watch);
+typedef void (*guestfs_handle_timeout_cb) (void *data, int timer);
+typedef int (*guestfs_add_timeout_cb) (guestfs_h *g, int interval, guestfs_handle_timeout_cb cb, void *data);
+typedef int (*guestfs_remove_timeout_cb) (guestfs_h *g, int timer);
+typedef void (*guestfs_main_loop_run_cb) (guestfs_h *g);
+typedef void (*guestfs_main_loop_quit_cb) (guestfs_h *g);
+
+struct guestfs_main_loop {
+  guestfs_add_handle_cb add_handle;
+  guestfs_remove_handle_cb remove_handle;
+  guestfs_add_timeout_cb add_timeout;
+  guestfs_remove_timeout_cb remove_timeout;
+  guestfs_main_loop_run_cb main_loop_run;
+  guestfs_main_loop_quit_cb main_loop_quit;
+};
+typedef struct guestfs_main_loop guestfs_main_loop;
+
+extern void guestfs_set_main_loop (guestfs_main_loop *);
+extern void guestfs_main_loop_run (void);
+extern void guestfs_main_loop_quit (void);
+
 #endif /* GUESTFS_H_ */