X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Fguestfs.c;h=fb214c535de1a63b2042d22912d6cf2b53664d3d;hp=73419a64b85d1c210309dbd5498dccb5e770e813;hb=d70248333edf8a5b5f509609cf2c8f7fd77d5e05;hpb=851b9a18d24de8fa81d4ca3063df59668484e884 diff --git a/src/guestfs.c b/src/guestfs.c index 73419a6..fb214c5 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -58,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 @@ -143,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. */ @@ -157,8 +167,9 @@ struct guestfs_h int verbose; int autosync; - const char *path; - const char *qemu; + char *path; /* Path to kernel, initrd. */ + char *qemu; /* Qemu binary. */ + char *append; /* Append to kernel command line. */ char *last_error; @@ -219,10 +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; + g->path = str != NULL ? strdup (str) : strdup (GUESTFS_DEFAULT_PATH); + if (!g->path) goto error; str = getenv ("LIBGUESTFS_QEMU"); - g->qemu = str != NULL ? str : 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 (); @@ -247,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 @@ -258,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; } @@ -266,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. @@ -305,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); } @@ -332,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 @@ -438,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; @@ -503,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; } @@ -519,10 +557,10 @@ guestfs_get_path (guestfs_h *g) int guestfs_set_qemu (guestfs_h *g, const char *qemu) { - if (qemu == NULL) - g->qemu = QEMU; - else - g->qemu = qemu; + free (g->qemu); + g->qemu = NULL; + + g->qemu = qemu == NULL ? safe_strdup (g, QEMU) : safe_strdup (g, qemu); return 0; } @@ -532,6 +570,22 @@ 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) @@ -551,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; } @@ -565,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; } @@ -580,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; } @@ -596,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; } @@ -618,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; } @@ -634,7 +689,8 @@ int guestfs_launch (guestfs_h *g) { static const char *dir_template = "/tmp/libguestfsXXXXXX"; - int r, i, len, pmore, memsize; + int r, i, pmore, memsize; + size_t len; int wfd[2], rfd[2]; int tries; const char *kernel_name = "vmlinuz." REPO "." host_cpu; @@ -643,16 +699,15 @@ guestfs_launch (guestfs_h *g) char *kernel = NULL, *initrd = NULL; char unixsock[256]; struct sockaddr_un addr; - struct stat statbuf; /* 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; } @@ -698,50 +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 (in MB). This is more art - * than science, but you can help by doing - * ./configure --enable-debug-command - * and then running: - * debug sh free - * debug mem '' - * and seeing how much free memory is left for particular - * configurations. - * - * It's also helpful to report both the compressed and uncompressed - * size of the initramfs (ls -lh initramfs*.img; du -sh initramfs). - * - * XXX KVM virtio balloon driver? + /* 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. */ - if (stat (initrd, &statbuf) != -1) { - /* Approximate size of the initramfs after it is decompressed - * in kernel memory. The compression factor is ~2.5-3. - */ - memsize = 3 * statbuf.st_size / 1024 / 1024; - - /* Approximate size used by the kernel. */ - memsize += 10; - - /* Want to give userspace some room, so: */ - memsize += 128; - -#if SIZEOF_LONG == 8 - /* On 64 bit, assume some overhead. */ - memsize += 64; -#endif - } else - memsize = 512; - + 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; } } @@ -772,7 +802,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 *) g->qemu; + g->cmdline[0] = g->qemu; /* Construct the -net channel parameter for qemu. */ snprintf (vmchannel, sizeof vmchannel, @@ -781,9 +811,10 @@ 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); @@ -792,6 +823,7 @@ guestfs_launch (guestfs_h *g) #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"); @@ -806,7 +838,7 @@ guestfs_launch (guestfs_h *g) add_cmdline (g, "-net"); add_cmdline (g, "user,vlan=0"); add_cmdline (g, "-net"); - add_cmdline (g, "nic,model=rtl8139,vlan=0"); + add_cmdline (g, "nic,model=virtio,vlan=0"); incr_cmdline_size (g); g->cmdline[g->cmdline_size-1] = NULL; @@ -842,6 +874,45 @@ guestfs_launch (guestfs_h *g) /* 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); @@ -883,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; @@ -894,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: @@ -915,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; } @@ -938,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; @@ -971,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; } @@ -989,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; } @@ -999,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; } @@ -1010,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; } @@ -1018,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; } @@ -1057,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; @@ -1068,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. @@ -1106,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. @@ -1125,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; } @@ -1179,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) @@ -1188,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; } @@ -1198,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; } @@ -1218,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; } @@ -1228,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; @@ -1256,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; } @@ -1264,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; @@ -1284,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); @@ -1324,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; } @@ -1433,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; } @@ -1444,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; } @@ -1456,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; } @@ -1467,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; } @@ -1494,13 +1608,13 @@ guestfs__send_sync (guestfs_h *g, int proc_nr, { 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; } @@ -1508,7 +1622,7 @@ guestfs__send_sync (guestfs_h *g, int proc_nr, * free the buffer anyway? */ if (g->msg_out != NULL) { - error (g, "guestfs__send_sync: msg_out should be NULL"); + error (g, _("guestfs__send_sync: msg_out should be NULL")); return -1; } @@ -1530,7 +1644,7 @@ guestfs__send_sync (guestfs_h *g, int proc_nr, hdr.status = GUESTFS_STATUS_OK; if (!xdr_guestfs_message_header (&xdr, &hdr)) { - error (g, "xdr_guestfs_message_header failed"); + error (g, _("xdr_guestfs_message_header failed")); goto cleanup1; } @@ -1539,7 +1653,7 @@ guestfs__send_sync (guestfs_h *g, int proc_nr, */ if (xdrp) { if (!(*xdrp) (&xdr, args)) { - error (g, "dispatch failed to marshal args"); + error (g, _("dispatch failed to marshal args")); goto cleanup1; } } @@ -1565,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; } @@ -1607,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 */ @@ -1669,14 +1787,14 @@ 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 buflen) { - unsigned len; + 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; } @@ -1684,7 +1802,7 @@ send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t buflen) * free the buffer anyway? */ if (g->msg_out != NULL) { - error (g, "guestfs__send_sync: msg_out should be NULL"); + error (g, _("guestfs__send_sync: msg_out should be NULL")); return -1; } @@ -1707,7 +1825,7 @@ send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t buflen) chunk.data.data_val = (char *) buf; if (!xdr_guestfs_chunk (&xdr, &chunk)) { - error (g, "xdr_guestfs_chunk failed (buf = %p, buflen = %zu)", + error (g, _("xdr_guestfs_chunk failed (buf = %p, buflen = %zu)"), buf, buflen); xdr_destroy (&xdr); goto cleanup1; @@ -1732,7 +1850,7 @@ send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t buflen) 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; } @@ -1783,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; } @@ -1793,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) { @@ -1808,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; } @@ -1842,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). */ @@ -1926,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; } @@ -1934,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; } @@ -1984,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; } @@ -2040,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; } @@ -2055,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;