#include <ctype.h>
#include <string.h>
#include <fcntl.h>
+#include <time.h>
#ifdef HAVE_ERRNO_H
#include <errno.h>
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
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. */
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;
}
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;
}
{
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. */
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);
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);
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) {
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;
}