From: rjones Date: Wed, 4 Mar 2009 13:18:48 +0000 (+0000) Subject: Starts up QEMU now X-Git-Tag: 0.4~60 X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=commitdiff_plain;h=09c16b5da0ec982bfffe654a55abd637a3335c95 Starts up QEMU now --- diff --git a/src/guestfs.c b/src/guestfs.c index e18021f..a5b3ae4 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -27,6 +27,7 @@ #include #include #include +#include #ifdef HAVE_ERRNO_H #include @@ -56,12 +57,17 @@ static void *safe_malloc (guestfs_h *g, int nbytes); static void *safe_realloc (guestfs_h *g, void *ptr, int nbytes); static char *safe_strdup (guestfs_h *g, const char *str); +#define VMCHANNEL_PORT 6666 +#define VMCHANNEL_ADDR "10.0.2.4" + /* GuestFS handle and connection. */ struct guestfs_h { /* All these socks/pids are -1 if not connected. */ int sock; /* Daemon communications socket. */ int pid; /* Qemu PID. */ + time_t start_t; /* The time when we started qemu. */ + int daemon_up; /* Received hello message from daemon. */ char *tmpdir; /* Temporary directory containing logfile * and socket. Cleaned up unless there is @@ -87,6 +93,9 @@ guestfs_create (void) g->sock = -1; g->pid = -1; + g->start_t = 0; + g->daemon_up = 0; + g->tmpdir = NULL; g->abort_fn = abort; /* Have to set these before safe_malloc. */ @@ -157,7 +166,10 @@ error (guestfs_h *g, const char *fs, ...) va_end (args); fputc ('\n', stderr); - if (g->exit_on_error) exit (1); + if (g->exit_on_error) { + guestfs_kill_subprocess (g); + exit (1); + } return -1; } @@ -175,7 +187,10 @@ perrorf (guestfs_h *g, const char *fs, ...) strerror_r (err, buf, sizeof buf); fprintf (stderr, ": %s\n", buf); - if (g->exit_on_error) exit (1); + if (g->exit_on_error) { + guestfs_kill_subprocess (g); + exit (1); + } return -1; } @@ -315,12 +330,11 @@ guestfs_launch (guestfs_h *g) { static const char *dir_template = "/tmp/libguestfsXXXXXX"; int r, i; - const char *qemu = QEMU; /* XXX */ + /*const char *qemu = QEMU;*/ /* XXX */ + const char *qemu = "/home/rjones/d/redhat/libguestfs/qemu"; const char *kernel = "/boot/vmlinuz-2.6.27.15-170.2.24.fc10.x86_64"; - const char *initrd = "/boot/initrd-2.6.27.15-170.2.24.fc10.x86_64.img"; + const char *initrd = "/tmp/initrd-2.6.27.15-170.2.24.fc10.x86_64.img"; char unixsock[256]; - char vmchannel[256]; - char tmpfile[256]; /* XXX Choose which qemu to run. */ /* XXX Choose initrd, etc. */ @@ -341,39 +355,57 @@ guestfs_launch (guestfs_h *g) if (r > 0) { /* Parent (library). */ g->pid = r; - /* If qemu is going to die during startup, give it a tiny amount of - * time to print the error message. + /* If qemu is going to die during startup, give it a tiny amount + * of time to print the error message. */ usleep (10000); - } else { /* Child (qemu). */ + + /* Start the clock ... */ + time (&g->start_t); + } + else { /* Child (qemu). */ + char vmchannel[256]; + char logfile[256]; + char append[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 = realloc (g->cmdline, sizeof (char *) * (g->cmdline_size + 14)); + g->cmdline = + realloc (g->cmdline, sizeof (char *) * (g->cmdline_size + 16)); if (g->cmdline == NULL) { perror ("realloc"); _exit (1); } + /* Construct the -net channel parameter for qemu. */ snprintf (vmchannel, sizeof vmchannel, - "channel,%d:unix:%s,server,nowait", 666, unixsock); + "channel,%d:unix:%s,server,nowait", VMCHANNEL_PORT, unixsock); + + /* Linux kernel command line. */ + snprintf (append, sizeof append, + "console=ttyS0 guestfs=%s:%d", VMCHANNEL_ADDR, VMCHANNEL_PORT); + + /* XXX -m */ g->cmdline[g->cmdline_size ] = "-kernel"; g->cmdline[g->cmdline_size+ 1] = (char *) kernel; g->cmdline[g->cmdline_size+ 2] = "-initrd"; g->cmdline[g->cmdline_size+ 3] = (char *) initrd; g->cmdline[g->cmdline_size+ 4] = "-append"; - g->cmdline[g->cmdline_size+ 5] = "console=ttyS0"; + g->cmdline[g->cmdline_size+ 5] = append; g->cmdline[g->cmdline_size+ 6] = "-nographic"; g->cmdline[g->cmdline_size+ 7] = "-serial"; g->cmdline[g->cmdline_size+ 8] = "stdio"; g->cmdline[g->cmdline_size+ 9] = "-net"; g->cmdline[g->cmdline_size+10] = vmchannel; g->cmdline[g->cmdline_size+11] = "-net"; - g->cmdline[g->cmdline_size+12] = "user,vlan0"; - g->cmdline[g->cmdline_size+13] = NULL; + g->cmdline[g->cmdline_size+12] = "user,vlan=0"; + g->cmdline[g->cmdline_size+13] = "-net"; + g->cmdline[g->cmdline_size+14] = "nic,vlan=0"; + g->cmdline[g->cmdline_size+15] = NULL; if (g->verbose) { fprintf (stderr, "Running %s", qemu); @@ -386,10 +418,15 @@ guestfs_launch (guestfs_h *g) close (0); close (1); open ("/dev/null", O_RDONLY); - snprintf (tmpfile, sizeof tmpfile, "%s/qemu.log", g->tmpdir); - open (tmpfile, O_WRONLY|O_CREAT|O_APPEND, 0644); + snprintf (logfile, sizeof logfile, "%s/qemu.log", g->tmpdir); + open (logfile, O_WRONLY|O_CREAT|O_APPEND, 0644); /*dup2 (1, 2);*/ + /* 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); + execv (qemu, g->cmdline); /* Run qemu. */ perror (qemu); _exit (1); @@ -398,15 +435,43 @@ guestfs_launch (guestfs_h *g) return 0; } -#define UNIX_PATH_MAX 108 +/* A peculiarity of qemu's vmchannel implementation is that both sides + * connect to qemu, ie: + * + * libguestfs --- connect --> qemu <-- connect --- daemon + * (host) (guest) + * + * This has several implications: (1) qemu creates the Unix socket, so + * we have to wait for it to do that. (2) we have to arrange for the + * daemon to send a "hello" message which we also wait for. + * + * At any time during this, the qemu subprocess might run slowly, die + * or hang (it's very prone to just hanging if the BIOS fails for any + * reason or if the kernel cannot be found to boot from). + * + * The only realistic way to handle this is, unfortunately, using + * timeouts, also checking if the qemu subprocess is still alive. + * + * We could do better here by monitoring the Linux kernel log messages + * (via the serial console, which is currently just redirected to a + * log file) and seeing if the Linux guest is making progress. (XXX) + */ + +#define QEMU_SOCKET_TIMEOUT 5 /* How long we wait for qemu to make + * the socket. This should be very quick. + */ +#define DAEMON_TIMEOUT 60 /* How long we wait for guest to boot + * and start the daemon. This could take + * a potentially long time, and is very + * sensitive to the overall load on the host. + */ + +static int wait_ready (guestfs_h *g); int guestfs_wait_ready (guestfs_h *g) { - int r, i, lsock; - struct sockaddr_un addr; - - if (guestfs_ready (g)) return 0; + int r; /* Launch the subprocess, if there isn't one already. */ if (g->pid == -1) { @@ -414,95 +479,119 @@ guestfs_wait_ready (guestfs_h *g) return -1; } - if (g->sock >= 0) { - close (g->sock); - g->sock = -1; + for (;;) { + r = wait_ready (g); + if (r == -1) { /* Error. */ + guestfs_kill_subprocess (g); + return -1; + } + else if (r > 0) { /* Keep waiting. */ + sleep (1); + continue; + } + else if (r == 0) /* Daemon is ready. */ + break; } - lsock = socket (AF_UNIX, SOCK_STREAM, 0); - if (lsock == -1) - return perrorf (g, "socket"); + return 0; +} + +#define UNIX_PATH_MAX 108 - addr.sun_family = AF_UNIX; - snprintf (addr.sun_path, UNIX_PATH_MAX, "%s/sock", g->tmpdir); +/* This function is called repeatedly until the qemu subprocess and + * daemon is ready. It returns: + * -1 : error + * 0 : done, daemon is ready + * >0 : not ready, keep waiting + */ +static int +wait_ready (guestfs_h *g) +{ + int r, i, sock; + time_t now; + double elapsed; + struct sockaddr_un addr; + unsigned char m; - if (bind (lsock, (struct sockaddr *) &addr, sizeof addr) == -1) { - perrorf (g, "bind"); - close (lsock); - return -1; - } + if (g->pid == -1) abort (); /* Internal state error. */ - if (listen (lsock, 1) == -1) { - perrorf (g, "listen"); - close (lsock); - return -1; - } + /* Check the daemon is still around. */ + r = waitpid (g->pid, NULL, WNOHANG); - if (fcntl (lsock, F_SETFL, O_NONBLOCK) == -1) { - perrorf (g, "set socket non-blocking"); - close (lsock); - return -1; + if (r > 0 || (r == -1 && errno == ECHILD)) { + g->pid = -1; + return error (g, + "qemu subprocess exited unexpectedly during initialization"); } - /* Wait until the daemon running inside the guest connects to the - * Unix socket, which indicates it's alive. Qemu might exit in the - * meantime if there is a problem. More problematically qemu might - * hang, which we can only detect by timeout. - */ - for (i = 0; i < 30; ++i) { - r = waitpid (g->pid, NULL, WNOHANG); - - if (r > 0 || (r == -1 && errno == ECHILD)) { - error (g, "qemu subprocess exited unexpectedly during initialization"); - g->pid = -1; - cleanup_fds (g); - close (lsock); + time (&now); + elapsed = difftime (now, g->start_t); + + if (g->sock == -1) { + /* Create the socket. */ + sock = socket (AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) + return perrorf (g, "socket"); + + addr.sun_family = AF_UNIX; + snprintf (addr.sun_path, UNIX_PATH_MAX, "%s/sock", g->tmpdir); + + if (connect (sock, (struct sockaddr *) &addr, sizeof addr) == -1) { + if (elapsed <= QEMU_SOCKET_TIMEOUT) { + close (sock); + return 1; /* Keep waiting for the socket ... */ + } + perrorf (g, "qemu process hanging before making vmchannel socket"); + close (sock); + return -1; + } + + if (fcntl (sock, F_SETFL, O_NONBLOCK) == -1) { + perrorf (g, "set socket non-blocking"); + close (sock); return -1; } - r = accept (lsock, NULL, 0); - if (r >= 0) { - g->sock = r; - fcntl (g->sock, F_SETFL, O_NONBLOCK); - close (lsock); - return 0; + g->sock = sock; + } + + if (!g->daemon_up) { + /* Wait for the daemon to say hello. */ + errno = 0; + r = read (g->sock, &m, 1); + if (r == 1) { + if (m == 0xF5) { + g->daemon_up = 1; + return 0; + } else { + error (g, "unexpected message from qemu vmchannel or daemon"); + return -1; + } } if (errno == EAGAIN) { - sleep (1); - continue; + if (elapsed <= DAEMON_TIMEOUT) + return 1; /* Keep waiting for the daemon ... */ + error (g, "timeout waiting for guest to become ready"); + return -1; } - perrorf (g, "accept"); - close (lsock); - guestfs_kill_subprocess (g); + + perrorf (g, "read"); return -1; } - close (lsock); - return error (g, "timeout waiting for guest to become ready"); -} - -int -guestfs_ready (guestfs_h *g) -{ - return - g->pid >= 0 && - kill (g->pid, 0) == 0 && - g->sock >= 0 /* && - guestfs_ping_daemon (g) >= 0 */; + return 0; } -int +void guestfs_kill_subprocess (guestfs_h *g) { if (g->pid >= 0) { if (g->verbose) - fprintf (stderr, "sending SIGINT to pid %d\n", g->pid); + fprintf (stderr, "sending SIGTERM to pgid %d\n", g->pid); - kill (g->pid, SIGINT); + kill (- g->pid, SIGTERM); wait_subprocess (g); } cleanup_fds (g); - - return 0; } diff --git a/src/guestfs.h b/src/guestfs.h index 9b84e31..49b14f5 100644 --- a/src/guestfs.h +++ b/src/guestfs.h @@ -36,10 +36,9 @@ extern int guestfs_add_cdrom (guestfs_h *g, const char *filename); /* Steps to start up the guest. */ extern int guestfs_launch (guestfs_h *g); extern int guestfs_wait_ready (guestfs_h *g); -extern int guestfs_ready (guestfs_h *g); /* Kill the guest subprocess. */ -extern int guestfs_kill_subprocess (guestfs_h *g); +extern void guestfs_kill_subprocess (guestfs_h *g); /* Error handling. */ typedef void (*guestfs_abort_fn) (void);