X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Flaunch.c;h=e58add54362b7933ab28e023bff492462d8ceee0;hp=775f7a68a2ae69561a4411d9613e5fcfbf953746;hb=3cf31c2fe0b356ea5c04117c5235b0a3cfe34971;hpb=99f0d8859fdc8f6c81873d4cdd1c8c780ac25e84 diff --git a/src/launch.c b/src/launch.c index 775f7a6..e58add5 100644 --- a/src/launch.c +++ b/src/launch.c @@ -1,5 +1,5 @@ /* libguestfs - * Copyright (C) 2009-2010 Red Hat Inc. + * Copyright (C) 2009-2011 Red Hat Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -63,6 +63,7 @@ #include #include "c-ctype.h" +#include "ignore-value.h" #include "glthread/lock.h" #include "guestfs.h" @@ -71,8 +72,38 @@ #include "guestfs_protocol.h" static int launch_appliance (guestfs_h *g); +static int64_t timeval_diff (const struct timeval *x, const struct timeval *y); +static int connect_unix_socket (guestfs_h *g, const char *sock); static int qemu_supports (guestfs_h *g, const char *option); +#if 0 +static int qemu_supports_re (guestfs_h *g, const pcre *option_regex); + +static void compile_regexps (void) __attribute__((constructor)); +static void free_regexps (void) __attribute__((destructor)); + +static void +compile_regexps (void) +{ + const char *err; + int offset; + +#define COMPILE(re,pattern,options) \ + do { \ + re = pcre_compile ((pattern), (options), &err, &offset, NULL); \ + if (re == NULL) { \ + ignore_value (write (2, err, strlen (err))); \ + abort (); \ + } \ + } while (0) +} + +static void +free_regexps (void) +{ +} +#endif + /* Add a string to the current command line. */ static void incr_cmdline_size (guestfs_h *g) @@ -102,20 +133,20 @@ add_cmdline (guestfs_h *g, const char *str) return 0; } -int +size_t guestfs___checkpoint_cmdline (guestfs_h *g) { return g->cmdline_size; } void -guestfs___rollback_cmdline (guestfs_h *g, int pos) +guestfs___rollback_cmdline (guestfs_h *g, size_t pos) { - int i; + size_t i; assert (g->cmdline_size >= pos); - for (i = g->cmdline_size - 1; i >= pos; --i) + for (i = pos; i < g->cmdline_size; ++i) free (g->cmdline[i]); g->cmdline_size = pos; @@ -125,7 +156,7 @@ guestfs___rollback_cmdline (guestfs_h *g, int pos) char ** guestfs__debug_cmdline (guestfs_h *g) { - int i; + size_t i; char **r; if (g->cmdline == NULL) { @@ -372,9 +403,19 @@ guestfs__launch (guestfs_h *g) * want. (RHBZ#610880). */ if (chmod (g->tmpdir, 0755) == -1) - fprintf (stderr, "chmod: %s: %m (ignored)\n", g->tmpdir); + warning (g, "chmod: %s: %m (ignored)", g->tmpdir); + + /* Launch the appliance or attach to an existing daemon. */ + switch (g->attach_method) { + case ATTACH_METHOD_APPLIANCE: + return launch_appliance (g); + + case ATTACH_METHOD_UNIX: + return connect_unix_socket (g, g->attach_method_arg); - return launch_appliance (g); + default: + abort (); + } } static int @@ -382,7 +423,7 @@ launch_appliance (guestfs_h *g) { int r; int wfd[2], rfd[2]; - char unixsock[256]; + char guestfsd_sock[256]; struct sockaddr_un addr; /* At present you must add drives before starting the appliance. In @@ -395,12 +436,15 @@ launch_appliance (guestfs_h *g) /* Start the clock ... */ gettimeofday (&g->launch_t, NULL); + guestfs___launch_send_progress (g, 0); /* Locate and/or build the appliance. */ char *kernel = NULL, *initrd = NULL, *appliance = NULL; if (guestfs___build_appliance (g, &kernel, &initrd, &appliance) == -1) return -1; + guestfs___launch_send_progress (g, 3); + if (g->verbose) guestfs___print_timestamped_message (g, "begin testing qemu features"); @@ -411,8 +455,8 @@ launch_appliance (guestfs_h *g) /* Using virtio-serial, we need to create a local Unix domain socket * for qemu to connect to. */ - snprintf (unixsock, sizeof unixsock, "%s/sock", g->tmpdir); - unlink (unixsock); + snprintf (guestfsd_sock, sizeof guestfsd_sock, "%s/guestfsd.sock", g->tmpdir); + unlink (guestfsd_sock); g->sock = socket (AF_UNIX, SOCK_STREAM, 0); if (g->sock == -1) { @@ -426,7 +470,7 @@ launch_appliance (guestfs_h *g) } addr.sun_family = AF_UNIX; - strncpy (addr.sun_path, unixsock, UNIX_PATH_MAX); + strncpy (addr.sun_path, guestfsd_sock, UNIX_PATH_MAX); addr.sun_path[UNIX_PATH_MAX-1] = '\0'; if (bind (g->sock, &addr, sizeof addr) == -1) { @@ -472,21 +516,30 @@ launch_appliance (guestfs_h *g) if (qemu_supports (g, "-nodefconfig")) add_cmdline (g, "-nodefconfig"); - /* qemu sometimes needs this option to enable hardware - * virtualization, but some versions of 'qemu-kvm' will use KVM - * regardless (even where this option appears in the help text). - * It is rumoured that there are versions of qemu where supplying - * this option when hardware virtualization is not available will - * cause qemu to fail, so we we have to check at least that - * /dev/kvm is openable. That's not reliable, since /dev/kvm - * might be openable by qemu but not by us (think: SELinux) in - * which case the user would not get hardware virtualization, - * although at least shouldn't fail. A giant clusterfuck with the - * qemu command line, again. + /* The qemu -machine option (added 2010-12) is a bit more sane + * since it falls back through various different acceleration + * modes, so try that first (thanks Markus Armbruster). */ - if (qemu_supports (g, "-enable-kvm") && - is_openable (g, "/dev/kvm", O_RDWR)) - add_cmdline (g, "-enable-kvm"); + if (qemu_supports (g, "-machine")) { + add_cmdline (g, "-machine"); + add_cmdline (g, "accel=kvm:tcg"); + } else { + /* qemu sometimes needs this option to enable hardware + * virtualization, but some versions of 'qemu-kvm' will use KVM + * regardless (even where this option appears in the help text). + * It is rumoured that there are versions of qemu where supplying + * this option when hardware virtualization is not available will + * cause qemu to fail, so we we have to check at least that + * /dev/kvm is openable. That's not reliable, since /dev/kvm + * might be openable by qemu but not by us (think: SELinux) in + * which case the user would not get hardware virtualization, + * although at least shouldn't fail. A giant clusterfuck with the + * qemu command line, again. + */ + if (qemu_supports (g, "-enable-kvm") && + is_openable (g, "/dev/kvm", O_RDWR)) + add_cmdline (g, "-enable-kvm"); + } /* Newer versions of qemu (from around 2009/12) changed the * behaviour of monitors so that an implicit '-monitor stdio' is @@ -535,7 +588,7 @@ launch_appliance (guestfs_h *g) /* Set up virtio-serial for the communications channel. */ add_cmdline (g, "-chardev"); - snprintf (buf, sizeof buf, "socket,path=%s,id=channel0", unixsock); + snprintf (buf, sizeof buf, "socket,path=%s,id=channel0", guestfsd_sock); add_cmdline (g, buf); add_cmdline (g, "-device"); add_cmdline (g, "virtserialport,chardev=channel0,name=org.libguestfs.channel.0"); @@ -601,17 +654,27 @@ launch_appliance (guestfs_h *g) guestfs___print_timestamped_argv (g, (const char **)g->cmdline); if (!g->direct) { - /* Set up stdin, stdout. */ + /* Set up stdin, stdout, stderr. */ close (0); close (1); close (wfd[1]); close (rfd[0]); + /* Stdin. */ if (dup (wfd[0]) == -1) { dup_failed: perror ("dup failed"); _exit (EXIT_FAILURE); } + /* Stdout. */ + if (dup (rfd[1]) == -1) + goto dup_failed; + + /* Particularly since qemu 0.15, qemu spews all sorts of debug + * information on stderr. It is useful to both capture this and + * not confuse casual users, so send stderr to the pipe as well. + */ + close (2); if (dup (rfd[1]) == -1) goto dup_failed; @@ -619,12 +682,9 @@ launch_appliance (guestfs_h *g) close (rfd[1]); } -#if 0 - /* Set up a new process group, so we can signal this process - * and all subprocesses (eg. if qemu is really a shell script). - */ - setpgid (0, 0); -#endif + /* Put qemu in a new process group. */ + if (g->pgroup) + setpgid (0, 0); setenv ("LC_ALL", "C", 1); @@ -653,8 +713,16 @@ launch_appliance (guestfs_h *g) pid_t qemu_pid = g->pid; pid_t parent_pid = getppid (); + /* It would be nice to be able to put this in the same process + * group as qemu (ie. setpgid (0, qemu_pid)). However this is + * not possible because we don't have any guarantee here that + * the qemu process has started yet. + */ + if (g->pgroup) + setpgid (0, 0); + /* 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 + * http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/misc/ps_status.c;hb=HEAD */ /* Loop around waiting for one or both of the other processes to @@ -749,6 +817,8 @@ launch_appliance (guestfs_h *g) goto cleanup1; } + guestfs___launch_send_progress (g, 12); + return 0; cleanup1: @@ -758,7 +828,7 @@ launch_appliance (guestfs_h *g) } if (g->pid > 0) kill (g->pid, 9); if (g->recoverypid > 0) kill (g->recoverypid, 9); - waitpid (g->pid, NULL, 0); + if (g->pid > 0) waitpid (g->pid, NULL, 0); if (g->recoverypid > 0) waitpid (g->recoverypid, NULL, 0); g->fd[0] = -1; g->fd[1] = -1; @@ -778,6 +848,108 @@ launch_appliance (guestfs_h *g) return -1; } +/* Alternate attach method: instead of launching the appliance, + * connect to an existing unix socket. + */ +static int +connect_unix_socket (guestfs_h *g, const char *sockpath) +{ + int r; + struct sockaddr_un addr; + + /* Start the clock ... */ + gettimeofday (&g->launch_t, NULL); + + /* Set these to nothing so we don't try to kill random processes or + * read from random file descriptors. + */ + g->pid = 0; + g->recoverypid = 0; + g->fd[0] = -1; + g->fd[1] = -1; + + if (g->verbose) + guestfs___print_timestamped_message (g, "connecting to %s", sockpath); + + g->sock = socket (AF_UNIX, SOCK_STREAM, 0); + if (g->sock == -1) { + perrorf (g, "socket"); + return -1; + } + + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockpath, UNIX_PATH_MAX); + addr.sun_path[UNIX_PATH_MAX-1] = '\0'; + + g->state = LAUNCHING; + + if (connect (g->sock, &addr, sizeof addr) == -1) { + perrorf (g, "bind"); + goto cleanup; + } + + if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) { + perrorf (g, "fcntl"); + goto cleanup; + } + + uint32_t size; + void *buf = NULL; + r = guestfs___recv_from_daemon (g, &size, &buf); + free (buf); + + if (r == -1) return -1; + + if (size != GUESTFS_LAUNCH_FLAG) { + error (g, _("guestfs_launch failed, unexpected initial message from guestfsd")); + goto cleanup; + } + + if (g->verbose) + guestfs___print_timestamped_message (g, "connected"); + + if (g->state != READY) { + error (g, _("contacted guestfsd, but state != READY")); + goto cleanup; + } + + return 0; + + cleanup: + close (g->sock); + return -1; +} + +/* launch (of the ordinary appliance) generates approximate progress + * messages. Currently these are defined as follows: + * + * 0 / 12: launch clock starts + * 3 / 12: appliance created + * 6 / 12: detected that guest kernel started + * 9 / 12: detected that /init script is running + * 12 / 12: launch completed successfully + * + * Notes: + * (1) This is not a documented ABI and the behaviour may be changed + * or removed in future. + * (2) Messages are only sent if more than 5 seconds has elapsed + * since the launch clock started. + * (3) There is a gross hack in proto.c to make this work. + */ +void +guestfs___launch_send_progress (guestfs_h *g, int perdozen) +{ + struct timeval tv; + + gettimeofday (&tv, NULL); + if (timeval_diff (&g->launch_t, &tv) >= 5000) { + guestfs_progress progress_message = + { .proc = 0, .serial = 0, .position = perdozen, .total = 12 }; + + guestfs___progress_message_callback (g, &progress_message); + } +} + /* Return the location of the tmpdir (eg. "/tmp") and allow users * to override it at runtime using $TMPDIR. * http://www.pathname.com/fhs/pub/fhs-2.3.html#TMPTEMPORARYFILES @@ -835,27 +1007,40 @@ guestfs___print_timestamped_argv (guestfs_h *g, const char * argv[]) { int i = 0; int needs_quote; + char *buf = NULL; + size_t len; + FILE *fp; + + fp = open_memstream (&buf, &len); + if (fp == NULL) { + warning (g, "open_memstream: %m"); + return; + } struct timeval tv; gettimeofday (&tv, NULL); - fprintf (stderr, "[%05" PRIi64 "ms] ", timeval_diff (&g->launch_t, &tv)); + fprintf (fp, "[%05" PRIi64 "ms] ", timeval_diff (&g->launch_t, &tv)); while (argv[i]) { if (argv[i][0] == '-') /* -option starts a new line */ - fprintf (stderr, " \\\n "); + fprintf (fp, " \\\n "); - if (i > 0) fputc (' ', stderr); + if (i > 0) fputc (' ', fp); /* Does it need shell quoting? This only deals with simple cases. */ needs_quote = strcspn (argv[i], " ") != strlen (argv[i]); - if (needs_quote) fputc ('\'', stderr); - fprintf (stderr, "%s", argv[i]); - if (needs_quote) fputc ('\'', stderr); + if (needs_quote) fputc ('\'', fp); + fprintf (fp, "%s", argv[i]); + if (needs_quote) fputc ('\'', fp); i++; } - fputc ('\n', stderr); + fclose (fp); + + debug (g, "%s", buf); + + free (buf); } void @@ -874,8 +1059,7 @@ guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...) gettimeofday (&tv, NULL); - fprintf (stderr, "[%05" PRIi64 "ms] %s\n", - timeval_diff (&g->launch_t, &tv), msg); + debug (g, "[%05" PRIi64 "ms] %s", timeval_diff (&g->launch_t, &tv), msg); free (msg); } @@ -975,14 +1159,27 @@ qemu_supports (guestfs_h *g, const char *option) return strstr (g->qemu_help, option) != NULL; } +#if 0 +/* As above but using a regex instead of a fixed string. */ +static int +qemu_supports_re (guestfs_h *g, const pcre *option_regex) +{ + if (!g->qemu_help) { + if (test_qemu (g) == -1) + return -1; + } + + return match (g, g->qemu_help, option_regex); +} +#endif + /* Check if a file can be opened. */ static int is_openable (guestfs_h *g, const char *path, int flags) { int fd = open (path, flags); if (fd == -1) { - if (g->verbose) - perror (path); + debug (g, "is_openable: %s: %m", path); return 0; } close (fd); @@ -1011,8 +1208,7 @@ guestfs__kill_subprocess (guestfs_h *g) return -1; } - if (g->verbose) - fprintf (stderr, "sending SIGTERM to process %d\n", g->pid); + debug (g, "sending SIGTERM to process %d", g->pid); if (g->pid > 0) kill (g->pid, SIGTERM); if (g->recoverypid > 0) kill (g->recoverypid, 9);