X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Fguestfs.c;h=6c49e9091887e6da643079be0fb556cb265040aa;hp=ea86f1ba9779299267291de9ce3eaa7c8b85d29e;hb=1765330e07a48dc6f7bdef7007f69ebe606fa731;hpb=56465eb694ba2077de37e66600c7bef96061cbe4 diff --git a/src/guestfs.c b/src/guestfs.c index ea86f1b..6c49e90 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -19,7 +19,7 @@ #include #define _BSD_SOURCE /* for mkdtemp, usleep */ -#define _GNU_SOURCE /* for vasprintf, GNU strerror_r */ +#define _GNU_SOURCE /* for vasprintf, GNU strerror_r, strchrnul */ #include #include @@ -54,45 +54,88 @@ #endif #include "guestfs.h" +#include "guestfs_protocol.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 stdout_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, int watch, int fd, int events); +static void sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, int watch, int fd, int events); +static void sock_write_event (struct guestfs_main_loop *ml, guestfs_h *g, 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); +static void close_handles (void); -#define UNIX_PATH_MAX 108 +static int select_add_handle (guestfs_main_loop *ml, guestfs_h *g, int fd, int events, guestfs_handle_event_cb cb, void *data); +static int select_remove_handle (guestfs_main_loop *ml, guestfs_h *g, int watch); +static int select_add_timeout (guestfs_main_loop *ml, guestfs_h *g, int interval, guestfs_handle_timeout_cb cb, void *data); +static int select_remove_timeout (guestfs_main_loop *ml, guestfs_h *g, int timer); +static int select_main_loop_run (guestfs_main_loop *ml, guestfs_h *g); +static int select_main_loop_quit (guestfs_main_loop *ml, guestfs_h *g); -#define VMCHANNEL_PORT 6666 -#define VMCHANNEL_ADDR "10.0.2.4" +/* Default select-based main loop. */ +struct select_handle_cb_data { + guestfs_handle_event_cb cb; + guestfs_h *g; + void *data; +}; -/* Current main loop. */ -static guestfs_main_loop main_loop = { +struct select_main_loop { + /* NB. These fields must be the same as in 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; + + /* Additional private data: */ + int is_running; + + fd_set rset; + fd_set wset; + fd_set xset; + + int max_fd; + int nr_fds; + struct select_handle_cb_data *handle_cb_data; +}; + +/* Default main loop. */ +static struct select_main_loop default_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, + + /* XXX hopefully .rset, .wset, .xset are initialized to the empty + * set by the normal action of everything being initialized to zero. + */ + .is_running = 0, + .max_fd = -1, + .nr_fds = 0, + .handle_cb_data = NULL, }; +#define UNIX_PATH_MAX 108 + +/* Also in guestfsd.c */ +#define VMCHANNEL_PORT 6666 +#define VMCHANNEL_ADDR "10.0.2.4" + /* GuestFS handle and connection. */ 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; @@ -110,11 +153,18 @@ struct guestfs_h int cmdline_size; int verbose; + int autosync; + + const char *path; + + char *last_error; /* Callbacks. */ guestfs_abort_cb abort_cb; guestfs_error_handler_cb error_cb; void * error_cb_data; + guestfs_send_cb send_cb; + void * send_cb_data; guestfs_reply_cb reply_cb; void * reply_cb_data; guestfs_log_message_cb log_message_cb; @@ -124,22 +174,33 @@ struct guestfs_h 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. + /* These callbacks are called before send_cb, 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_send_cb send_cb_internal; + void * send_cb_internal_data; 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; + /* Main loop used by this handle. */ + guestfs_main_loop *main_loop; + /* Messages sent and received from the daemon. */ char *msg_in; int msg_in_size, msg_in_allocated; char *msg_out; - int msg_out_size; + int msg_out_size, msg_out_pos; + + int msg_next_serial; }; +static guestfs_h *handles = NULL; +static int atexit_handler_set = 0; + guestfs_h * guestfs_create (void) { @@ -166,6 +227,32 @@ 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. */ + + g->main_loop = (guestfs_main_loop *) &default_main_loop; + + /* 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; } @@ -174,6 +261,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 ... */ @@ -181,6 +269,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. */ @@ -207,9 +302,40 @@ 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->last_error); free (g); } +/* Close all open handles (called from atexit(3)). */ +static void +close_handles (void) +{ + while (handles) guestfs_close (handles); +} + +const char * +guestfs_last_error (guestfs_h *g) +{ + return g->last_error; +} + +static void +set_last_error (guestfs_h *g, const char *msg) +{ + free (g->last_error); + g->last_error = strdup (msg); +} + static void default_error_cb (guestfs_h *g, void *data, const char *msg) { @@ -222,13 +348,12 @@ error (guestfs_h *g, const char *fs, ...) va_list args; char *msg; - if (!g->error_cb) return; - va_start (args, fs); vasprintf (&msg, fs, args); va_end (args); - g->error_cb (g, g->error_cb_data, msg); + if (g->error_cb) g->error_cb (g, g->error_cb_data, msg); + set_last_error (g, msg); free (msg); } @@ -240,8 +365,6 @@ perrorf (guestfs_h *g, const char *fs, ...) char *msg; int err = errno; - if (!g->error_cb) return; - va_start (args, fs); vasprintf (&msg, fs, args); va_end (args); @@ -259,13 +382,14 @@ perrorf (guestfs_h *g, const char *fs, ...) strcat (msg, ": "); strcat (msg, buf); - g->error_cb (g, g->error_cb_data, msg); + if (g->error_cb) g->error_cb (g, g->error_cb_data, msg); + set_last_error (g, msg); free (msg); } 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 (); @@ -288,6 +412,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) { @@ -314,10 +447,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 @@ -326,6 +460,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) @@ -398,6 +561,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); @@ -411,6 +579,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); } @@ -418,19 +591,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"); @@ -442,12 +612,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 /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; } } @@ -456,7 +673,7 @@ guestfs_launch (guestfs_h *g) if (pipe (wfd) == -1 || pipe (rfd) == -1) { perrorf (g, "pipe"); - return -1; + goto cleanup0; } r = fork (); @@ -466,7 +683,7 @@ guestfs_launch (guestfs_h *g) close (wfd[1]); close (rfd[0]); close (rfd[1]); - return -1; + goto cleanup0; } if (r == 0) { /* Child (qemu). */ @@ -476,7 +693,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, @@ -488,7 +705,8 @@ guestfs_launch (guestfs_h *g) "console=ttyS0 guestfs=%s:%d", VMCHANNEL_ADDR, VMCHANNEL_PORT); add_cmdline (g, "-m"); - add_cmdline (g, "384"); /* XXX Choose best size. */ + add_cmdline (g, "384"); /* XXX Choose best size. */ + add_cmdline (g, "-no-kqemu"); /* Avoids a warning. */ add_cmdline (g, "-kernel"); add_cmdline (g, (char *) kernel); add_cmdline (g, "-initrd"); @@ -508,7 +726,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"); @@ -521,6 +739,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 @@ -529,8 +749,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); } @@ -586,7 +806,8 @@ guestfs_launch (guestfs_h *g) if ((r == -1 && errno == EINPROGRESS) || r == 0) goto connected; - perrorf (g, "connect"); + if (errno != ENOENT) + perrorf (g, "connect"); tries--; } @@ -602,22 +823,21 @@ guestfs_launch (guestfs_h *g) free (g->msg_out); g->msg_out = NULL; g->msg_out_size = 0; + g->msg_out_pos = 0; g->stdout_watch = - main_loop.add_handle (g, g->fd[1], - GUESTFS_HANDLE_READABLE, - stdout_event, g); + g->main_loop->add_handle (g->main_loop, g, g->fd[1], + GUESTFS_HANDLE_READABLE, + stdout_event, NULL); 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); + g->main_loop->add_handle (g->main_loop, g, g->sock, + GUESTFS_HANDLE_READABLE, + sock_read_event, NULL); if (g->sock_watch == -1) { error (g, "could not watch daemon communications socket"); goto cleanup3; @@ -628,9 +848,9 @@ guestfs_launch (guestfs_h *g) cleanup3: if (g->stdout_watch >= 0) - main_loop.remove_handle (g, g->stdout_watch); + g->main_loop->remove_handle (g->main_loop, g, g->stdout_watch); if (g->sock_watch >= 0) - main_loop.remove_handle (g, g->sock_watch); + g->main_loop->remove_handle (g->main_loop, g, g->sock_watch); cleanup2: close (g->sock); @@ -647,20 +867,27 @@ guestfs_launch (guestfs_h *g) g->start_t = 0; g->stdout_watch = -1; g->sock_watch = -1; + + cleanup0: + free (kernel); + free (initrd); return -1; } static void finish_wait_ready (guestfs_h *g, void *vp) { + if (g->verbose) + fprintf (stderr, "finish_wait_ready called, %p, vp = %p\n", g, vp); + *((int *)vp) = 1; - main_loop.main_loop_quit (g); + g->main_loop->main_loop_quit (g->main_loop, g); } int guestfs_wait_ready (guestfs_h *g) { - int r = 0; + int finished = 0, r; if (g->state == READY) return 0; @@ -675,12 +902,14 @@ guestfs_wait_ready (guestfs_h *g) } 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_data = &finished; + r = g->main_loop->main_loop_run (g->main_loop, g); g->launch_done_cb_internal = NULL; g->launch_done_cb_internal_data = NULL; - if (r != 1) { + if (r == -1) return -1; + + if (finished != 1) { error (g, "guestfs_wait_ready failed, see earlier error messages"); return -1; } @@ -707,7 +936,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); @@ -719,9 +948,9 @@ guestfs_kill_subprocess (guestfs_h *g) * we see kernel messages here too. */ static void -stdout_event (void *data, int watch, int fd, int events) +stdout_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, + int watch, int fd, int events) { - guestfs_h *g = (guestfs_h *) data; char buf[4096]; int n; @@ -745,9 +974,9 @@ stdout_event (void *data, int watch, int fd, int events) /*kill (g->pid, SIGTERM);*/ waitpid (g->pid, NULL, 0); if (g->stdout_watch >= 0) - main_loop.remove_handle (g, g->stdout_watch); + g->main_loop->remove_handle (g->main_loop, g, g->stdout_watch); if (g->sock_watch >= 0) - main_loop.remove_handle (g, g->sock_watch); + g->main_loop->remove_handle (g->main_loop, g, g->sock_watch); close (g->fd[0]); close (g->fd[1]); close (g->sock); @@ -783,16 +1012,16 @@ stdout_event (void *data, int watch, int fd, int events) * guestfsd (daemon inside the guest) communication socket. */ static void -sock_read_event (void *data, int watch, int fd, int events) +sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, + int watch, int fd, int events) { - guestfs_h *g = (guestfs_h *) data; XDR xdr; unsigned len; int n; if (g->verbose) fprintf (stderr, - "sock_event: %p g->state = %d, fd = %d, events = 0x%x\n", + "sock_read_event: %p g->state = %d, fd = %d, events = 0x%x\n", g, g->state, fd, events); if (g->sock != fd) { @@ -833,7 +1062,7 @@ sock_read_event (void *data, int watch, int fd, int events) * starts up it sends a "magic" value (longer than any possible * message). Check for this. */ - if (len == 0xf5f5f5f5) { + if (len == 0xf5f55ff5) { if (g->state != LAUNCHING) error (g, "received magic signature from guestfsd, but in state %d", g->state); @@ -851,16 +1080,45 @@ sock_read_event (void *data, int watch, int fd, int events) goto cleanup; } - if (g->msg_in_size < len) return; /* Need more of this message. */ + /* If this happens, it's pretty bad and we've probably lost synchronization.*/ + if (len > GUESTFS_MESSAGE_MAX) { + error (g, "message length (%u) > maximum possible size (%d)", + len, GUESTFS_MESSAGE_MAX); + goto cleanup; + } + + if (g->msg_in_size-4 < len) return; /* Need more of this message. */ /* This should not happen, and if it does it probably means we've * lost all hope of synchronization. */ - if (g->msg_in_size > len) { - error (g, "len = %d, but msg_in_size = %d", len, g->msg_in_size); + if (g->msg_in_size-4 > len) { + error (g, "len = %d, but msg_in_size-4 = %d", len, g->msg_in_size-4); 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); @@ -887,38 +1145,363 @@ sock_read_event (void *data, int watch, int fd, int events) xdr_destroy (&xdr); } -/* This is the default main loop implementation, using select(2). */ +/* The function is called whenever we can write something on the + * guestfsd (daemon inside the guest) communication socket. + */ +static void +sock_write_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, + int watch, int fd, int events) +{ + int n; -struct handle_cb_data { - guestfs_handle_event_cb cb; + if (g->verbose) + fprintf (stderr, + "sock_write_event: %p g->state = %d, fd = %d, events = 0x%x\n", + g, g->state, fd, events); + + if (g->sock != fd) { + error (g, "sock_write_event: internal error: %d != %d", g->sock, fd); + return; + } + + if (g->state != BUSY) { + error (g, "sock_write_event: state %d != BUSY", g->state); + return; + } + + if (g->verbose) + fprintf (stderr, "sock_write_event: writing %d bytes ...\n", + g->msg_out_size - g->msg_out_pos); + + n = write (g->sock, g->msg_out + g->msg_out_pos, + g->msg_out_size - g->msg_out_pos); + if (n == -1) { + if (errno != EAGAIN) + perrorf (g, "write"); + return; + } + + if (g->verbose) + fprintf (stderr, "sock_write_event: wrote %d bytes\n", n); + + g->msg_out_pos += n; + + /* More to write? */ + if (g->msg_out_pos < g->msg_out_size) + return; + + if (g->verbose) + fprintf (stderr, "sock_write_event: done writing, switching back to reading events\n"); + + free (g->msg_out); + g->msg_out_pos = g->msg_out_size = 0; + + if (g->main_loop->remove_handle (g->main_loop, g, g->sock_watch) == -1) { + error (g, "remove_handle failed in sock_write_event"); + return; + } + g->sock_watch = + g->main_loop->add_handle (g->main_loop, g, g->sock, + GUESTFS_HANDLE_READABLE, + sock_read_event, NULL); + if (g->sock_watch == -1) { + error (g, "add_handle failed in sock_write_event"); + return; + } +} + +/* Dispatch a call to the remote daemon. This function just queues + * the call in msg_out, to be sent when we next enter the main loop. + * Returns -1 for error, or the message serial number. + */ +static int +dispatch (guestfs_h *g, int proc_nr, xdrproc_t xdrp, char *args) +{ + char buffer[GUESTFS_MESSAGE_MAX]; + struct guestfs_message_header hdr; + XDR xdr; + unsigned len; + int serial = g->msg_next_serial++; + + if (g->state != READY) { + error (g, "dispatch: state %d != READY", g->state); + return -1; + } + + /* Serialize the header. */ + hdr.prog = GUESTFS_PROGRAM; + hdr.vers = GUESTFS_PROTOCOL_VERSION; + hdr.proc = proc_nr; + hdr.direction = GUESTFS_DIRECTION_CALL; + hdr.serial = serial; + hdr.status = GUESTFS_STATUS_OK; + + xdrmem_create (&xdr, buffer, sizeof buffer, XDR_ENCODE); + if (!xdr_guestfs_message_header (&xdr, &hdr)) { + error (g, "xdr_guestfs_message_header failed"); + return -1; + } + + /* Serialize the args. If any, because some message types + * have no parameters. + */ + if (xdrp) { + if (!(*xdrp) (&xdr, args)) { + error (g, "dispatch failed to marshal args"); + return -1; + } + } + + len = xdr_getpos (&xdr); + xdr_destroy (&xdr); + + /* Allocate the outgoing message buffer. */ + g->msg_out = safe_malloc (g, len + 4); + + g->msg_out_size = len + 4; + g->msg_out_pos = 0; + g->state = BUSY; + + xdrmem_create (&xdr, g->msg_out, 4, XDR_ENCODE); + if (!xdr_uint32_t (&xdr, &len)) { + error (g, "xdr_uint32_t failed in dispatch"); + goto cleanup1; + } + + memcpy (g->msg_out + 4, buffer, len); + + /* Change the handle to sock_write_event. */ + if (g->main_loop->remove_handle (g->main_loop, g, g->sock_watch) == -1) { + error (g, "remove_handle failed in dispatch"); + goto cleanup1; + } + g->sock_watch = + g->main_loop->add_handle (g->main_loop, g, g->sock, + GUESTFS_HANDLE_WRITABLE, + sock_write_event, NULL); + if (g->sock_watch == -1) { + error (g, "add_handle failed in dispatch"); + goto cleanup1; + } + + return serial; + + cleanup1: + free (g->msg_out); + g->msg_out = NULL; + g->msg_out_size = 0; + g->state = READY; + return -1; +} + +#if 0 +static int cancel = 0; /* XXX Implement file cancellation. */ + +static int +send_file (guestfs_h *g, const char *filename) +{ + char buf[GUESTFS_MAX_CHUNK_SIZE]; + int fd, r; + + fd = open (filename, O_RDONLY); + if (fd == -1) { + perrorf (g, "open: %s", filename); + send_file_cancellation (g); + /* Daemon sees cancellation and won't reply, so caller can + * just return here. + */ + return -1; + } + + /* Send file in chunked encoding. */ + while (!cancel && (r = read (fd, buf, sizeof buf)) > 0) { + if (send_file_data (g, buf, r) == -1) + return -1; + } + + if (cancel) { + send_file_cancellation (g); + return -1; + } + + if (r == -1) { + perrorf (g, "read: %s", filename); + send_file_cancellation (g); + return -1; + } + + /* End of file, but before we send that, we need to close + * the file and check for errors. + */ + if (close (fd) == -1) { + perrorf (g, "close: %s", filename); + send_file_cancellation (g); + return -1; + } + + return send_file_complete (g); +} + +/* Send a chunk, cancellation or end of file, wait for it to go. */ +static int +send_file_chunk (guestfs_h *g, int cancel, const char *buf, size_t len) +{ void *data; -}; + guestfs_chunk chunk; + XDR xdr; -static fd_set rset; -static fd_set wset; -static fd_set xset; -static int select_init_done = 0; -static int max_fd = -1; -static int nr_fds = 0; -static struct handle_cb_data *handle_cb_data = NULL; + if (g->state != BUSY) { + error (g, "send_file_chunk: state %d != READY", g->state); + return -1; + } -static void -select_init (void) + /* Serialize the chunk. */ + chunk.cancel = cancel; + chunk.data.data_len = len; + chunk.data.data_val = (char *) buf; + + data = safe_malloc (g, GUESTFS_MAX_CHUNK_SIZE + 48); + xdrmem_create (&xdr, data, GUESTFS_MAX_CHUNK_SIZE + 48, XDR_ENCODE); + if (xdr_guestfs_chunk (&xdr, &chunk)) { + error (g, "xdr_guestfs_chunk failed"); + free (data); + return -1; + } + + chunkdatalen = xdr_getpos (&xdr); + xdr_destroy (&xdr); + + len = xdr_getpos (&xdr); + xdr_destroy (&xdr); + + data = safe_realloc (g, data, len); + g->msg_out = data; + g->msg_out_size = len; + g->msg_out_pos = 0; + + /* Change the handle to sock_write_event. */ + if (g->main_loop->remove_handle (g->main_loop, g, g->sock_watch) == -1) { + error (g, "remove_handle failed in dispatch"); + goto cleanup1; + } + g->sock_watch = + g->main_loop->add_handle (g->main_loop, g, g->sock, + GUESTFS_HANDLE_WRITABLE, + sock_write_event, NULL); + if (g->sock_watch == -1) { + error (g, "add_handle failed in dispatch"); + goto cleanup1; + } + + return 0; + + cleanup1: + free (g->msg_out); + g->msg_out = NULL; + g->msg_out_size = 0; + g->state = READY; + return -1; +} + +/* Send a chunk of file data. */ +static int +send_file_data (guestfs_h *g, const char *buf, size_t len) { - if (!select_init_done) { - FD_ZERO (&rset); - FD_ZERO (&wset); - FD_ZERO (&xset); + return send_file_chunk (g, 0, buf, len); +} - select_init_done = 1; +/* Send a cancellation message. */ +static int +send_file_cancellation (guestfs_h *g) +{ + char buf[1]; + return send_file_chunk (g, 1, buf, 0); +} + +/* Send a file complete chunk. */ +static int +send_file_complete (guestfs_h *g) +{ + char buf[0]; + return send_file_chunk (g, 0, buf, 0); +} +#endif + +/* 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_int_bool (struct guestfs_int_bool *x) +{ + free (x); +} + +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). */ + static int -select_add_handle (guestfs_h *g, int fd, int events, +select_add_handle (guestfs_main_loop *mlv, guestfs_h *g, int fd, int events, guestfs_handle_event_cb cb, void *data) { - select_init (); + struct select_main_loop *ml = (struct select_main_loop *) mlv; if (fd < 0 || fd >= FD_SETSIZE) { error (g, "fd %d is out of range", fd); @@ -938,7 +1521,9 @@ select_add_handle (guestfs_h *g, int fd, int events, return -1; } - if (FD_ISSET (fd, &rset) || FD_ISSET (fd, &wset) || FD_ISSET (fd, &xset)) { + if (FD_ISSET (fd, &ml->rset) || + FD_ISSET (fd, &ml->wset) || + FD_ISSET (fd, &ml->xset)) { error (g, "fd %d is already registered", fd); return -1; } @@ -949,102 +1534,111 @@ select_add_handle (guestfs_h *g, int fd, int events, } if ((events & GUESTFS_HANDLE_READABLE)) - FD_SET (fd, &rset); + FD_SET (fd, &ml->rset); if ((events & GUESTFS_HANDLE_WRITABLE)) - FD_SET (fd, &wset); + FD_SET (fd, &ml->wset); if ((events & GUESTFS_HANDLE_HANGUP) || (events & GUESTFS_HANDLE_ERROR)) - FD_SET (fd, &xset); + FD_SET (fd, &ml->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)); + if (fd > ml->max_fd) { + ml->max_fd = fd; + ml->handle_cb_data = + safe_realloc (g, ml->handle_cb_data, + sizeof (struct select_handle_cb_data) * (ml->max_fd+1)); } - handle_cb_data[fd].cb = cb; - handle_cb_data[fd].data = data; + ml->handle_cb_data[fd].cb = cb; + ml->handle_cb_data[fd].g = g; + ml->handle_cb_data[fd].data = data; - nr_fds++; + ml->nr_fds++; /* 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_remove_handle (guestfs_main_loop *mlv, guestfs_h *g, int fd) { - select_init (); + struct select_main_loop *ml = (struct select_main_loop *) mlv; 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)) { + if (!FD_ISSET (fd, &ml->rset) && + !FD_ISSET (fd, &ml->wset) && + !FD_ISSET (fd, &ml->xset)) { error (g, "fd %d was not registered", fd); return -1; } - FD_CLR (fd, &rset); - FD_CLR (fd, &wset); - FD_CLR (fd, &xset); + FD_CLR (fd, &ml->rset); + FD_CLR (fd, &ml->wset); + FD_CLR (fd, &ml->xset); - if (fd == max_fd) { - max_fd--; - handle_cb_data = safe_realloc (g, handle_cb_data, - sizeof (struct handle_cb_data) * (max_fd+1)); + if (fd == ml->max_fd) { + ml->max_fd--; + ml->handle_cb_data = + safe_realloc (g, ml->handle_cb_data, + sizeof (struct select_handle_cb_data) * (ml->max_fd+1)); } - nr_fds--; + ml->nr_fds--; return 0; } static int -select_add_timeout (guestfs_h *g, int interval, +select_add_timeout (guestfs_main_loop *mlv, guestfs_h *g, int interval, guestfs_handle_timeout_cb cb, void *data) { - select_init (); + //struct select_main_loop *ml = (struct select_main_loop *) mlv; abort (); /* XXX not implemented yet */ } static int -select_remove_timeout (guestfs_h *g, int timer) +select_remove_timeout (guestfs_main_loop *mlv, guestfs_h *g, int timer) { - select_init (); + //struct select_main_loop *ml = (struct select_main_loop *) mlv; 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) +/* The 'g' parameter is just used for error reporting. Events + * for multiple handles can be dispatched by running the main + * loop. + */ +static int +select_main_loop_run (guestfs_main_loop *mlv, guestfs_h *g) { - int old_level, fd, r, events; + struct select_main_loop *ml = (struct select_main_loop *) mlv; + int fd, r, events; fd_set rset2, wset2, xset2; - select_init (); + if (ml->is_running) { + error (g, "select_main_loop_run: this cannot be called recursively"); + return -1; + } - old_level = level++; - while (level > old_level) { - if (nr_fds == 0) { - level = old_level; + ml->is_running = 1; + + while (ml->is_running) { + if (ml->nr_fds == 0) break; - } - rset2 = rset; - wset2 = wset; - xset2 = xset; - r = select (max_fd+1, &rset2, &wset2, &xset2, NULL); + rset2 = ml->rset; + wset2 = ml->wset; + xset2 = ml->xset; + r = select (ml->max_fd+1, &rset2, &wset2, &xset2, NULL); if (r == -1) { perrorf (g, "select"); - level = old_level; - break; + ml->is_running = 0; + return -1; } - for (fd = 0; r > 0 && fd <= max_fd; ++fd) { + for (fd = 0; r > 0 && fd <= ml->max_fd; ++fd) { events = 0; if (FD_ISSET (fd, &rset2)) events |= GUESTFS_HANDLE_READABLE; @@ -1054,22 +1648,28 @@ select_main_loop_run (guestfs_h *g) events |= GUESTFS_HANDLE_ERROR | GUESTFS_HANDLE_HANGUP; if (events) { r--; - handle_cb_data[fd].cb (handle_cb_data[fd].data, - fd, fd, events); + ml->handle_cb_data[fd].cb ((guestfs_main_loop *) ml, + ml->handle_cb_data[fd].g, + ml->handle_cb_data[fd].data, + fd, fd, events); } } } + + ml->is_running = 0; + return 0; } -static void -select_main_loop_quit (guestfs_h *g) +static int +select_main_loop_quit (guestfs_main_loop *mlv, guestfs_h *g) { - select_init (); + struct select_main_loop *ml = (struct select_main_loop *) mlv; - if (level == 0) { - error (g, "cannot quit, we are not in a main loop"); - return; + if (!ml->is_running) { + error (g, "cannot quit, we are not running in a main loop"); + return -1; } - level--; + ml->is_running = 0; + return 0; }