X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Fguestfs.c;h=fb214c535de1a63b2042d22912d6cf2b53664d3d;hp=0b3142c77523e24959c3090712f798d0ce3a17ac;hb=d70248333edf8a5b5f509609cf2c8f7fd77d5e05;hpb=936108f54cbf8e81132a0a0a8c093a4056f0e11c diff --git a/src/guestfs.c b/src/guestfs.c index 0b3142c..fb214c5 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -29,7 +29,9 @@ #include #include #include +#include #include + #include #include @@ -56,6 +58,15 @@ #include "guestfs.h" #include "guestfs_protocol.h" +#ifdef HAVE_GETTEXT +#include "gettext.h" +#define _(str) dgettext(PACKAGE, (str)) +#define N_(str) dgettext(PACKAGE, (str)) +#else +#define _(str) str +#define N_(str) str +#endif + #define error guestfs_error #define perrorf guestfs_perrorf #define safe_malloc guestfs_safe_malloc @@ -141,7 +152,8 @@ struct guestfs_h int fd[2]; /* Stdin/stdout of qemu. */ int sock; /* Daemon communications socket. */ - int pid; /* Qemu PID. */ + pid_t pid; /* Qemu PID. */ + pid_t recoverypid; /* Recovery process PID. */ time_t start_t; /* The time when we started qemu. */ int stdout_watch; /* Watches qemu stdout for log messages. */ @@ -155,7 +167,9 @@ struct guestfs_h int verbose; int autosync; - const char *path; + char *path; /* Path to kernel, initrd. */ + char *qemu; /* Qemu binary. */ + char *append; /* Append to kernel command line. */ char *last_error; @@ -216,8 +230,18 @@ guestfs_create (void) 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->path = str != NULL ? strdup (str) : strdup (GUESTFS_DEFAULT_PATH); + if (!g->path) goto error; + + str = getenv ("LIBGUESTFS_QEMU"); + g->qemu = str != NULL ? strdup (str) : strdup (QEMU); + if (!g->qemu) goto error; + + str = getenv ("LIBGUESTFS_APPEND"); + if (str) { + g->append = strdup (str); + if (!g->append) goto error; + } g->main_loop = guestfs_get_default_main_loop (); @@ -242,6 +266,13 @@ guestfs_create (void) fprintf (stderr, "new guestfs handle %p\n", g); return g; + + error: + free (g->path); + free (g->qemu); + free (g->append); + free (g); + return NULL; } void @@ -253,7 +284,7 @@ guestfs_close (guestfs_h *g) if (g->state == NO_HANDLE) { /* Not safe to call 'error' here, so ... */ - fprintf (stderr, "guestfs_close: called twice on the same handle\n"); + fprintf (stderr, _("guestfs_close: called twice on the same handle\n")); return; } @@ -261,8 +292,10 @@ guestfs_close (guestfs_h *g) 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) + if (g->autosync && g->state == READY) { + guestfs_umount_all (g); guestfs_sync (g); + } /* Remove any handlers that might be called back before we kill the * subprocess. @@ -300,7 +333,12 @@ guestfs_close (guestfs_h *g) } /* release mutex (XXX) */ + free (g->msg_in); + free (g->msg_out); free (g->last_error); + free (g->path); + free (g->qemu); + free (g->append); free (g); } @@ -327,7 +365,7 @@ set_last_error (guestfs_h *g, const char *msg) static void default_error_cb (guestfs_h *g, void *data, const char *msg) { - fprintf (stderr, "libguestfs: error: %s\n", msg); + fprintf (stderr, _("libguestfs: error: %s\n"), msg); } void @@ -433,8 +471,11 @@ xread (int fd, void *buf, size_t len) while (len > 0) { r = read (fd, buf, len); - if (r == -1) + if (r == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; return -1; + } buf += r; len -= r; @@ -498,10 +539,12 @@ guestfs_get_autosync (guestfs_h *g) int guestfs_set_path (guestfs_h *g, const char *path) { - if (path == NULL) - g->path = GUESTFS_DEFAULT_PATH; - else - g->path = path; + free (g->path); + g->path = NULL; + + g->path = + path == NULL ? + safe_strdup (g, GUESTFS_DEFAULT_PATH) : safe_strdup (g, path); return 0; } @@ -511,6 +554,38 @@ guestfs_get_path (guestfs_h *g) return g->path; } +int +guestfs_set_qemu (guestfs_h *g, const char *qemu) +{ + free (g->qemu); + g->qemu = NULL; + + g->qemu = qemu == NULL ? safe_strdup (g, QEMU) : safe_strdup (g, qemu); + return 0; +} + +const char * +guestfs_get_qemu (guestfs_h *g) +{ + return g->qemu; +} + +int +guestfs_set_append (guestfs_h *g, const char *append) +{ + free (g->append); + g->append = NULL; + + g->append = append ? safe_strdup (g, append) : NULL; + return 0; +} + +const char * +guestfs_get_append (guestfs_h *g) +{ + return g->append; +} + /* Add a string to the current command line. */ static void incr_cmdline_size (guestfs_h *g) @@ -530,7 +605,8 @@ 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"); + error (g, + _("command line cannot be altered after qemu subprocess launched")); return -1; } @@ -544,7 +620,7 @@ 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"); + error (g, _("guestfs_config: parameter must begin with '-' character")); return -1; } @@ -559,7 +635,7 @@ guestfs_config (guestfs_h *g, strcmp (qemu_param, "-full-screen") == 0 || strcmp (qemu_param, "-std-vga") == 0 || strcmp (qemu_param, "-vnc") == 0) { - error (g, "guestfs_config: parameter '%s' isn't allowed", qemu_param); + error (g, _("guestfs_config: parameter '%s' isn't allowed"), qemu_param); return -1; } @@ -575,11 +651,11 @@ guestfs_config (guestfs_h *g, int guestfs_add_drive (guestfs_h *g, const char *filename) { - int len = strlen (filename) + 64; + size_t len = strlen (filename) + 64; char buf[len]; if (strchr (filename, ',') != NULL) { - error (g, "filename cannot contain ',' (comma) character"); + error (g, _("filename cannot contain ',' (comma) character")); return -1; } @@ -597,7 +673,7 @@ int guestfs_add_cdrom (guestfs_h *g, const char *filename) { if (strchr (filename, ',') != NULL) { - error (g, "filename cannot contain ',' (comma) character"); + error (g, _("filename cannot contain ',' (comma) character")); return -1; } @@ -613,7 +689,8 @@ int guestfs_launch (guestfs_h *g) { static const char *dir_template = "/tmp/libguestfsXXXXXX"; - int r, i, len, pmore; + int r, i, pmore, memsize; + size_t len; int wfd[2], rfd[2]; int tries; const char *kernel_name = "vmlinuz." REPO "." host_cpu; @@ -625,12 +702,12 @@ guestfs_launch (guestfs_h *g) /* Configured? */ if (!g->cmdline) { - error (g, "you must call guestfs_add_drive before guestfs_launch"); + error (g, _("you must call guestfs_add_drive before guestfs_launch")); return -1; } if (g->state != CONFIG) { - error (g, "qemu has already been launched"); + error (g, _("qemu has already been launched")); return -1; } @@ -676,16 +753,25 @@ guestfs_launch (guestfs_h *g) free (path); if (kernel == NULL || initrd == NULL) { - error (g, "cannot find %s or %s on LIBGUESTFS_PATH (current path = %s)", + error (g, _("cannot find %s or %s on LIBGUESTFS_PATH (current path = %s)"), kernel_name, initrd_name, g->path); goto cleanup0; } + /* Choose a suitable memory size. Previously we tried to choose + * a minimal memory size, but this isn't really necessary since + * recent QEMU and KVM don't do anything nasty like locking + * memory into core any more. This we can safely choose a + * large, generous amount of memory, and it'll just get swapped + * on smaller systems. + */ + memsize = 384; + /* 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); + perrorf (g, _("%s: cannot create temporary directory"), dir_template); goto cleanup0; } } @@ -711,11 +797,12 @@ guestfs_launch (guestfs_h *g) if (r == 0) { /* Child (qemu). */ char vmchannel[256]; char append[256]; + char memsize_str[256]; /* 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] = g->qemu; /* Construct the -net channel parameter for qemu. */ snprintf (vmchannel, sizeof vmchannel, @@ -724,15 +811,19 @@ guestfs_launch (guestfs_h *g) /* Linux kernel command line. */ snprintf (append, sizeof append, - "console=ttyS0 guestfs=%s:%d%s", + "panic=1 console=ttyS0 guestfs=%s:%d%s%s%s", VMCHANNEL_ADDR, VMCHANNEL_PORT, - g->verbose ? " guestfs_verbose=1" : ""); + g->verbose ? " guestfs_verbose=1" : "", + g->append ? " " : "", g->append ? g->append : ""); + + snprintf (memsize_str, sizeof memsize_str, "%d", memsize); add_cmdline (g, "-m"); - add_cmdline (g, "384"); /* XXX Choose best size. */ -#if defined(__i386__) || defined(__x86_64) + add_cmdline (g, memsize_str); +#if 0 add_cmdline (g, "-no-kqemu"); /* Avoids a warning. */ #endif + add_cmdline (g, "-no-reboot"); /* Force exit instead of reboot on panic */ add_cmdline (g, "-kernel"); add_cmdline (g, (char *) kernel); add_cmdline (g, "-initrd"); @@ -747,12 +838,12 @@ guestfs_launch (guestfs_h *g) add_cmdline (g, "-net"); add_cmdline (g, "user,vlan=0"); add_cmdline (g, "-net"); - add_cmdline (g, "nic,vlan=0"); + add_cmdline (g, "nic,model=virtio,vlan=0"); incr_cmdline_size (g); g->cmdline[g->cmdline_size-1] = NULL; if (g->verbose) { - fprintf (stderr, "%s", QEMU); + fprintf (stderr, "%s", g->qemu); for (i = 0; g->cmdline[i]; ++i) fprintf (stderr, " %s", g->cmdline[i]); fprintf (stderr, "\n"); @@ -775,14 +866,53 @@ guestfs_launch (guestfs_h *g) setpgid (0, 0); #endif - execv (QEMU, g->cmdline); /* Run qemu. */ - perror (QEMU); + execv (g->qemu, g->cmdline); /* Run qemu. */ + perror (g->qemu); _exit (1); } /* Parent (library). */ g->pid = r; + free (kernel); + kernel = NULL; + free (initrd); + initrd = NULL; + + /* Fork the recovery process off which will kill qemu if the parent + * process fails to do so (eg. if the parent segfaults). + */ + r = fork (); + if (r == 0) { + pid_t qemu_pid = g->pid; + pid_t parent_pid = getppid (); + + /* Writing to argv is hideously complicated and error prone. See: + * http://anoncvs.postgresql.org/cvsweb.cgi/pgsql/src/backend/utils/misc/ps_status.c?rev=1.33.2.1;content-type=text%2Fplain + */ + + /* Loop around waiting for one or both of the other processes to + * disappear. It's fair to say this is very hairy. The PIDs that + * we are looking at might be reused by another process. We are + * effectively polling. Is the cure worse than the disease? + */ + for (;;) { + if (kill (qemu_pid, 0) == -1) /* qemu's gone away, we aren't needed */ + _exit (0); + if (kill (parent_pid, 0) == -1) { + /* Parent's gone away, qemu still around, so kill qemu. */ + kill (qemu_pid, 9); + _exit (0); + } + sleep (2); + } + } + + /* Don't worry, if the fork failed, this will be -1. The recovery + * process isn't essential. + */ + g->recoverypid = r; + /* Start the clock ... */ time (&g->start_t); @@ -824,10 +954,9 @@ guestfs_launch (guestfs_h *g) addr.sun_path[UNIX_PATH_MAX-1] = '\0'; tries = 100; + /* Always sleep at least once to give qemu a small chance to start up. */ + usleep (10000); 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; @@ -835,9 +964,10 @@ guestfs_launch (guestfs_h *g) if (errno != ENOENT) perrorf (g, "connect"); tries--; + usleep (100000); } - error (g, "failed to connect to vmchannel socket"); + error (g, _("failed to connect to vmchannel socket")); goto cleanup2; connected: @@ -856,7 +986,7 @@ guestfs_launch (guestfs_h *g) GUESTFS_HANDLE_READABLE, stdout_event, NULL); if (g->stdout_watch == -1) { - error (g, "could not watch qemu stdout"); + error (g, _("could not watch qemu stdout")); goto cleanup3; } @@ -879,11 +1009,14 @@ guestfs_launch (guestfs_h *g) close (wfd[1]); close (rfd[0]); kill (g->pid, 9); + if (g->recoverypid > 0) kill (g->recoverypid, 9); waitpid (g->pid, NULL, 0); + if (g->recoverypid > 0) waitpid (g->recoverypid, NULL, 0); g->fd[0] = -1; g->fd[1] = -1; g->sock = -1; g->pid = 0; + g->recoverypid = 0; g->start_t = 0; g->stdout_watch = -1; g->sock_watch = -1; @@ -912,12 +1045,12 @@ guestfs_wait_ready (guestfs_h *g) if (g->state == READY) return 0; if (g->state == BUSY) { - error (g, "qemu has finished launching already"); + error (g, _("qemu has finished launching already")); return -1; } if (g->state != LAUNCHING) { - error (g, "qemu has not been launched yet"); + error (g, _("qemu has not been launched yet")); return -1; } @@ -930,7 +1063,7 @@ guestfs_wait_ready (guestfs_h *g) if (r == -1) return -1; if (finished != 1) { - error (g, "guestfs_wait_ready failed, see earlier error messages"); + error (g, _("guestfs_wait_ready failed, see earlier error messages")); return -1; } @@ -940,7 +1073,7 @@ guestfs_wait_ready (guestfs_h *g) * commands after this function returns. */ if (g->state != READY) { - error (g, "qemu launched and contacted daemon, but state != READY"); + error (g, _("qemu launched and contacted daemon, but state != READY")); return -1; } @@ -951,7 +1084,7 @@ int guestfs_kill_subprocess (guestfs_h *g) { if (g->state == CONFIG) { - error (g, "no subprocess to kill"); + error (g, _("no subprocess to kill")); return -1; } @@ -959,6 +1092,7 @@ guestfs_kill_subprocess (guestfs_h *g) fprintf (stderr, "sending SIGTERM to process %d\n", g->pid); kill (g->pid, SIGTERM); + if (g->recoverypid > 0) kill (g->recoverypid, 9); return 0; } @@ -998,7 +1132,8 @@ int guestfs_set_ready (guestfs_h *g) { if (g->state != BUSY) { - error (g, "guestfs_set_ready: called when in state %d != BUSY", g->state); + error (g, _("guestfs_set_ready: called when in state %d != BUSY"), + g->state); return -1; } g->state = READY; @@ -1009,13 +1144,33 @@ int guestfs_set_busy (guestfs_h *g) { if (g->state != READY) { - error (g, "guestfs_set_busy: called when in state %d != READY", g->state); + error (g, _("guestfs_set_busy: called when in state %d != READY"), + g->state); return -1; } g->state = BUSY; return 0; } +int +guestfs_end_busy (guestfs_h *g) +{ + switch (g->state) + { + case BUSY: + g->state = READY; + break; + case CONFIG: + case READY: + break; + case LAUNCHING: + case NO_HANDLE: + error (g, _("guestfs_end_busy: called when in state %d"), g->state); + return -1; + } + return 0; +} + /* Structure-freeing functions. These rely on the fact that the * structure format is identical to the XDR format. See note in * generator.ml. @@ -1047,6 +1202,39 @@ guestfs_free_lvm_lv_list (struct guestfs_lvm_lv_list *x) free (x); } +/* We don't know if stdout_event or sock_read_event will be the + * first to receive EOF if the qemu process dies. This function + * has the common cleanup code for both. + */ +static void +child_cleanup (guestfs_h *g) +{ + if (g->verbose) + fprintf (stderr, "stdout_event: %p: child process died\n", g); + /*kill (g->pid, SIGTERM);*/ + if (g->recoverypid > 0) kill (g->recoverypid, 9); + waitpid (g->pid, NULL, 0); + if (g->recoverypid > 0) waitpid (g->recoverypid, NULL, 0); + if (g->stdout_watch >= 0) + g->main_loop->remove_handle (g->main_loop, g, g->stdout_watch); + if (g->sock_watch >= 0) + g->main_loop->remove_handle (g->main_loop, 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->recoverypid = 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); +} + /* 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. @@ -1066,39 +1254,19 @@ stdout_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, #endif if (g->fd[1] != fd) { - error (g, "stdout_event: internal error: %d != %d", g->fd[1], fd); + error (g, _("stdout_event: internal error: %d != %d"), g->fd[1], fd); return; } 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) - g->main_loop->remove_handle (g->main_loop, g, g->stdout_watch); - if (g->sock_watch >= 0) - g->main_loop->remove_handle (g->main_loop, 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); + child_cleanup (g); return; } if (n == -1) { - if (errno != EAGAIN) + if (errno != EINTR && errno != EAGAIN) perrorf (g, "read"); return; } @@ -1120,7 +1288,7 @@ sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, int watch, int fd, int events) { XDR xdr; - unsigned len; + u_int32_t len; int n; if (g->verbose) @@ -1129,7 +1297,7 @@ sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, g, g->state, fd, events); if (g->sock != fd) { - error (g, "sock_read_event: internal error: %d != %d", g->sock, fd); + error (g, _("sock_read_event: internal error: %d != %d"), g->sock, fd); return; } @@ -1139,14 +1307,14 @@ sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, } n = read (g->sock, g->msg_in + g->msg_in_size, g->msg_in_allocated - g->msg_in_size); - if (n == 0) - /* Disconnected? Ignore it because stdout_watch will get called - * and will do the cleanup. - */ + if (n == 0) { + /* Disconnected. */ + child_cleanup (g); return; + } if (n == -1) { - if (errno != EAGAIN) + if (errno != EINTR && errno != EAGAIN) perrorf (g, "read"); return; } @@ -1159,7 +1327,7 @@ sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, xdrmem_create (&xdr, g->msg_in, g->msg_in_size, XDR_DECODE); if (!xdr_uint32_t (&xdr, &len)) { - error (g, "can't decode length word"); + error (g, _("can't decode length word")); goto cleanup; } @@ -1169,10 +1337,10 @@ sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, */ if (len == GUESTFS_LAUNCH_FLAG) { if (g->state != LAUNCHING) - error (g, "received magic signature from guestfsd, but in state %d", + error (g, _("received magic signature from guestfsd, but in state %d"), g->state); else if (g->msg_in_size != 4) - error (g, "received magic signature from guestfsd, but msg size is %d", + error (g, _("received magic signature from guestfsd, but msg size is %d"), g->msg_in_size); else { g->state = READY; @@ -1197,7 +1365,7 @@ sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, * synchronization. */ if (len > GUESTFS_MESSAGE_MAX) { - error (g, "message length (%u) > maximum possible size (%d)", + error (g, _("message length (%u) > maximum possible size (%d)"), len, GUESTFS_MESSAGE_MAX); goto cleanup; } @@ -1205,6 +1373,7 @@ sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, if (g->msg_in_size-4 < len) return; /* Need more of this message. */ /* Got the full message, begin processing it. */ +#if 0 if (g->verbose) { int i, j; @@ -1225,14 +1394,18 @@ sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, printf ("|\n"); } } +#endif /* Not in the expected state. */ if (g->state != BUSY) - error (g, "state %d != BUSY", g->state); + error (g, _("state %d != BUSY"), g->state); /* Push the message up to the higher layer. */ if (g->reply_cb) g->reply_cb (g, g->reply_cb_data, &xdr); + else + /* This message (probably) should never be printed. */ + fprintf (stderr, "libguesfs: sock_read_event: !!! dropped message !!!\n"); g->msg_in_size -= len + 4; memmove (g->msg_in, g->msg_in+len+4, g->msg_in_size); @@ -1265,12 +1438,12 @@ sock_write_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, g, g->state, fd, events); if (g->sock != fd) { - error (g, "sock_write_event: internal error: %d != %d", 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); + error (g, _("sock_write_event: state %d != BUSY"), g->state); return; } @@ -1374,7 +1547,7 @@ guestfs__switch_to_sending (guestfs_h *g) { if (g->sock_watch >= 0) { if (g->main_loop->remove_handle (g->main_loop, g, g->sock_watch) == -1) { - error (g, "remove_handle failed"); + error (g, _("remove_handle failed")); g->sock_watch = -1; return -1; } @@ -1385,7 +1558,7 @@ guestfs__switch_to_sending (guestfs_h *g) GUESTFS_HANDLE_WRITABLE, sock_write_event, NULL); if (g->sock_watch == -1) { - error (g, "add_handle failed"); + error (g, _("add_handle failed")); return -1; } @@ -1397,7 +1570,7 @@ guestfs__switch_to_receiving (guestfs_h *g) { if (g->sock_watch >= 0) { if (g->main_loop->remove_handle (g->main_loop, g, g->sock_watch) == -1) { - error (g, "remove_handle failed"); + error (g, _("remove_handle failed")); g->sock_watch = -1; return -1; } @@ -1408,7 +1581,7 @@ guestfs__switch_to_receiving (guestfs_h *g) GUESTFS_HANDLE_READABLE, sock_read_event, NULL); if (g->sock_watch == -1) { - error (g, "add_handle failed"); + error (g, _("add_handle failed")); return -1; } @@ -1433,19 +1606,35 @@ int guestfs__send_sync (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; + u_int32_t len; int serial = g->msg_next_serial++; int sent; guestfs_main_loop *ml = guestfs_get_main_loop (g); if (g->state != BUSY) { - error (g, "guestfs__send_sync: state %d != BUSY", g->state); + error (g, _("guestfs__send_sync: state %d != BUSY"), g->state); return -1; } + /* This is probably an internal error. Or perhaps we should just + * free the buffer anyway? + */ + if (g->msg_out != NULL) { + error (g, _("guestfs__send_sync: msg_out should be NULL")); + return -1; + } + + /* We have to allocate this message buffer on the heap because + * it is quite large (although will be mostly unused). We + * can't allocate it on the stack because in some environments + * we have quite limited stack space available, notably when + * running in the JVM. + */ + g->msg_out = safe_malloc (g, GUESTFS_MESSAGE_MAX + 4); + xdrmem_create (&xdr, g->msg_out + 4, GUESTFS_MESSAGE_MAX, XDR_ENCODE); + /* Serialize the header. */ hdr.prog = GUESTFS_PROGRAM; hdr.vers = GUESTFS_PROTOCOL_VERSION; @@ -1454,10 +1643,9 @@ guestfs__send_sync (guestfs_h *g, int proc_nr, 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; + error (g, _("xdr_guestfs_message_header failed")); + goto cleanup1; } /* Serialize the args. If any, because some message types @@ -1465,25 +1653,24 @@ guestfs__send_sync (guestfs_h *g, int proc_nr, */ if (xdrp) { if (!(*xdrp) (&xdr, args)) { - error (g, "dispatch failed to marshal args"); - return -1; + error (g, _("dispatch failed to marshal args")); + goto cleanup1; } } + /* Get the actual length of the message, resize the buffer to match + * the actual length, and write the length word at the beginning. + */ len = xdr_getpos (&xdr); xdr_destroy (&xdr); - /* Allocate the outgoing message buffer. */ - g->msg_out = safe_malloc (g, len + 4); - + g->msg_out = safe_realloc (g, g->msg_out, len + 4); g->msg_out_size = len + 4; g->msg_out_pos = 0; xdrmem_create (&xdr, g->msg_out, 4, XDR_ENCODE); xdr_uint32_t (&xdr, &len); - memcpy (g->msg_out + 4, buffer, len); - if (guestfs__switch_to_sending (g) == -1) goto cleanup1; @@ -1492,7 +1679,7 @@ guestfs__send_sync (guestfs_h *g, int proc_nr, if (ml->main_loop_run (ml, g) == -1) goto cleanup1; if (sent != 1) { - error (g, "send failed, see earlier error messages"); + error (g, _("send failed, see earlier error messages")); goto cleanup1; } @@ -1534,7 +1721,11 @@ guestfs__send_file_sync (guestfs_h *g, const char *filename) } /* Send file in chunked encoding. */ - while (!cancel && (r = read (fd, buf, sizeof buf)) > 0) { + while (!cancel) { + r = read (fd, buf, sizeof buf); + if (r == -1 && (errno == EINTR || errno == EAGAIN)) + continue; + if (r <= 0) break; err = send_file_data_sync (g, buf, r); if (err < 0) { if (err == -2) /* daemon sent cancellation */ @@ -1594,17 +1785,24 @@ send_file_complete_sync (guestfs_h *g) static int check_for_daemon_cancellation (guestfs_h *g); static int -send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t len) +send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t buflen) { - char data[GUESTFS_MAX_CHUNK_SIZE + 48]; - unsigned datalen; + u_int32_t len; int sent; guestfs_chunk chunk; XDR xdr; guestfs_main_loop *ml = guestfs_get_main_loop (g); if (g->state != BUSY) { - error (g, "send_file_chunk_sync: state %d != READY", g->state); + error (g, _("send_file_chunk_sync: state %d != READY"), g->state); + return -1; + } + + /* This is probably an internal error. Or perhaps we should just + * free the buffer anyway? + */ + if (g->msg_out != NULL) { + error (g, _("guestfs__send_sync: msg_out should be NULL")); return -1; } @@ -1615,35 +1813,34 @@ send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t len) return -2; } + /* Allocate the chunk buffer. Don't use the stack to avoid + * excessive stack usage and unnecessary copies. + */ + g->msg_out = safe_malloc (g, GUESTFS_MAX_CHUNK_SIZE + 4 + 48); + xdrmem_create (&xdr, g->msg_out + 4, GUESTFS_MAX_CHUNK_SIZE + 48, XDR_ENCODE); + /* Serialize the chunk. */ chunk.cancel = cancel; - chunk.data.data_len = len; + chunk.data.data_len = buflen; chunk.data.data_val = (char *) buf; - if (g->verbose) - fprintf (stderr, - "library sending chunk cancel = %d, len = %zu, buf = %p\n", - cancel, len, buf); - - xdrmem_create (&xdr, data, sizeof data, XDR_ENCODE); if (!xdr_guestfs_chunk (&xdr, &chunk)) { - error (g, "xdr_guestfs_chunk failed (buf = %p, len = %zu)", buf, len); + error (g, _("xdr_guestfs_chunk failed (buf = %p, buflen = %zu)"), + buf, buflen); xdr_destroy (&xdr); - return -1; + goto cleanup1; } - datalen = xdr_getpos (&xdr); + len = xdr_getpos (&xdr); xdr_destroy (&xdr); - /* Allocate outgoing message buffer. */ - g->msg_out = safe_malloc (g, datalen + 4); - g->msg_out_size = datalen + 4; + /* Reduce the size of the outgoing message buffer to the real length. */ + g->msg_out = safe_realloc (g, g->msg_out, len + 4); + g->msg_out_size = len + 4; g->msg_out_pos = 0; xdrmem_create (&xdr, g->msg_out, 4, XDR_ENCODE); - xdr_uint32_t (&xdr, &datalen); - - memcpy (g->msg_out + 4, data, datalen); + xdr_uint32_t (&xdr, &len); if (guestfs__switch_to_sending (g) == -1) goto cleanup1; @@ -1653,7 +1850,7 @@ send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t len) if (ml->main_loop_run (ml, g) == -1) goto cleanup1; if (sent != 1) { - error (g, "send file chunk failed, see earlier error messages"); + error (g, _("send file chunk failed, see earlier error messages")); goto cleanup1; } @@ -1704,7 +1901,7 @@ check_for_daemon_cancellation (guestfs_h *g) xdr_destroy (&xdr); if (flag != GUESTFS_CANCEL_FLAG) { - error (g, "check_for_daemon_cancellation: read 0x%x from daemon, expected 0x%x\n", + error (g, _("check_for_daemon_cancellation: read 0x%x from daemon, expected 0x%x\n"), flag, GUESTFS_CANCEL_FLAG); return 0; } @@ -1714,13 +1911,15 @@ check_for_daemon_cancellation (guestfs_h *g) /* Synchronously receive a file. */ -static int receive_file_data_sync (guestfs_h *g, void **buf); +/* Returns -1 = error, 0 = EOF, 1 = more data */ +static int receive_file_data_sync (guestfs_h *g, void **buf, size_t *len); int guestfs__receive_file_sync (guestfs_h *g, const char *filename) { void *buf; int fd, r; + size_t len; fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666); if (fd == -1) { @@ -1729,17 +1928,18 @@ guestfs__receive_file_sync (guestfs_h *g, const char *filename) } /* Receive the file in chunked encoding. */ - while ((r = receive_file_data_sync (g, &buf)) > 0) { - if (xwrite (fd, buf, r) == -1) { + while ((r = receive_file_data_sync (g, &buf, &len)) >= 0) { + if (xwrite (fd, buf, len) == -1) { perrorf (g, "%s: write", filename); free (buf); goto cancel; } free (buf); + if (r == 0) break; /* End of file. */ } if (r == -1) { - error (g, "%s: error in chunked encoding", filename); + error (g, _("%s: error in chunked encoding"), filename); return -1; } @@ -1763,79 +1963,128 @@ guestfs__receive_file_sync (guestfs_h *g, const char *filename) xdr_destroy (&xdr); if (xwrite (g->sock, fbuf, sizeof fbuf) == -1) { - perrorf (g, "write to daemon socket"); + perrorf (g, _("write to daemon socket")); return -1; } - while ((r = receive_file_data_sync (g, &buf)) > 0) - free (buf); /* just discard it */ + while ((r = receive_file_data_sync (g, NULL, NULL)) > 0) + ; /* just discard it */ return -1; } +/* Note that the reply callback can be called multiple times before + * the main loop quits and we get back to the synchronous code. So + * we have to be prepared to save multiple chunks on a list here. + */ struct receive_file_ctx { - int code; - void **buf; + int count; /* 0 if receive_file_cb not called, or + * else count number of chunks. + */ + guestfs_chunk *chunks; /* Array of chunks. */ }; static void +free_chunks (struct receive_file_ctx *ctx) +{ + int i; + + for (i = 0; i < ctx->count; ++i) + free (ctx->chunks[i].data.data_val); + + free (ctx->chunks); +} + +static void receive_file_cb (guestfs_h *g, void *data, XDR *xdr) { guestfs_main_loop *ml = guestfs_get_main_loop (g); struct receive_file_ctx *ctx = (struct receive_file_ctx *) data; guestfs_chunk chunk; + if (ctx->count == -1) /* Parse error occurred previously. */ + return; + ml->main_loop_quit (ml, g); memset (&chunk, 0, sizeof chunk); if (!xdr_guestfs_chunk (xdr, &chunk)) { - error (g, "failed to parse file chunk"); - ctx->code = -1; - return; - } - if (chunk.cancel) { - error (g, "file receive cancelled by daemon"); - ctx->code = -2; - return; - } - if (chunk.data.data_len == 0) { /* end of transfer */ - ctx->code = 0; + error (g, _("failed to parse file chunk")); + free_chunks (ctx); + ctx->chunks = NULL; + ctx->count = -1; return; } - ctx->code = chunk.data.data_len; - *ctx->buf = chunk.data.data_val; /* caller frees */ + /* Copy the chunk to the list. */ + ctx->chunks = safe_realloc (g, ctx->chunks, + sizeof (guestfs_chunk) * (ctx->count+1)); + ctx->chunks[ctx->count] = chunk; + ctx->count++; } /* Receive a chunk of file data. */ +/* Returns -1 = error, 0 = EOF, 1 = more data */ static int -receive_file_data_sync (guestfs_h *g, void **buf) +receive_file_data_sync (guestfs_h *g, void **buf, size_t *len_r) { struct receive_file_ctx ctx; guestfs_main_loop *ml = guestfs_get_main_loop (g); + int i; + size_t len; - ctx.code = -3; - ctx.buf = buf; + ctx.count = 0; + ctx.chunks = NULL; guestfs_set_reply_callback (g, receive_file_cb, &ctx); (void) ml->main_loop_run (ml, g); guestfs_set_reply_callback (g, NULL, NULL); - if (g->verbose) - fprintf (stderr, "receive_file_data_sync: code %d\n", ctx.code); - - switch (ctx.code) { - case 0: /* end of file */ - return 0; - case -1: case -2: + if (ctx.count == 0) { + error (g, _("receive_file_data_sync: reply callback not called\n")); return -1; - case -3: - error (g, "failed to call receive_file_cb"); + } + + if (ctx.count == -1) { + error (g, _("receive_file_data_sync: parse error in reply callback\n")); + /* callback already freed the chunks */ return -1; - default: /* received n bytes of data */ - return ctx.code; } + + if (g->verbose) + fprintf (stderr, "receive_file_data_sync: got %d chunks\n", ctx.count); + + /* Process each chunk in the list. */ + if (buf) *buf = NULL; /* Accumulate data in this buffer. */ + len = 0; + + for (i = 0; i < ctx.count; ++i) { + if (ctx.chunks[i].cancel) { + error (g, _("file receive cancelled by daemon")); + free_chunks (&ctx); + if (buf) free (*buf); + if (len_r) *len_r = 0; + return -1; + } + + if (ctx.chunks[i].data.data_len == 0) { /* end of transfer */ + free_chunks (&ctx); + if (len_r) *len_r = len; + return 0; + } + + if (buf) { + *buf = safe_realloc (g, *buf, len + ctx.chunks[i].data.data_len); + memcpy (*buf+len, ctx.chunks[i].data.data_val, + ctx.chunks[i].data.data_len); + } + len += ctx.chunks[i].data.data_len; + } + + if (len_r) *len_r = len; + free_chunks (&ctx); + return 1; } /* This is the default main loop implementation, using select(2). */ @@ -1847,7 +2096,7 @@ select_add_handle (guestfs_main_loop *mlv, guestfs_h *g, int fd, int events, 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); + error (g, _("fd %d is out of range"), fd); return -1; } @@ -1855,24 +2104,24 @@ select_add_handle (guestfs_main_loop *mlv, guestfs_h *g, int fd, int events, GUESTFS_HANDLE_WRITABLE | GUESTFS_HANDLE_HANGUP | GUESTFS_HANDLE_ERROR)) != 0) { - error (g, "set of events (0x%x) contains unknown events", events); + error (g, _("set of events (0x%x) contains unknown events"), events); return -1; } if (events == 0) { - error (g, "set of events is empty"); + error (g, _("set of events is empty")); return -1; } if (FD_ISSET (fd, &ml->rset) || FD_ISSET (fd, &ml->wset) || FD_ISSET (fd, &ml->xset)) { - error (g, "fd %d is already registered", fd); + error (g, _("fd %d is already registered"), fd); return -1; } if (cb == NULL) { - error (g, "callback is NULL"); + error (g, _("callback is NULL")); return -1; } @@ -1905,14 +2154,14 @@ select_remove_handle (guestfs_main_loop *mlv, guestfs_h *g, int fd) 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); + error (g, _("fd %d is out of range"), fd); return -1; } if (!FD_ISSET (fd, &ml->rset) && !FD_ISSET (fd, &ml->wset) && !FD_ISSET (fd, &ml->xset)) { - error (g, "fd %d was not registered", fd); + error (g, _("fd %d was not registered"), fd); return -1; } @@ -1961,7 +2210,7 @@ select_main_loop_run (guestfs_main_loop *mlv, guestfs_h *g) fd_set rset2, wset2, xset2; if (ml->is_running) { - error (g, "select_main_loop_run: this cannot be called recursively"); + error (g, _("select_main_loop_run: this cannot be called recursively")); return -1; } @@ -1976,6 +2225,8 @@ select_main_loop_run (guestfs_main_loop *mlv, guestfs_h *g) xset2 = ml->xset; r = select (ml->max_fd+1, &rset2, &wset2, &xset2, NULL); if (r == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; perrorf (g, "select"); ml->is_running = 0; return -1;