mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
Starts up QEMU now
This commit is contained in:
275
src/guestfs.c
275
src/guestfs.c
@@ -27,6 +27,7 @@
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef HAVE_ERRNO_H
|
||||
#include <errno.h>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
lsock = socket (AF_UNIX, SOCK_STREAM, 0);
|
||||
if (lsock == -1)
|
||||
return perrorf (g, "socket");
|
||||
|
||||
addr.sun_family = AF_UNIX;
|
||||
snprintf (addr.sun_path, UNIX_PATH_MAX, "%s/sock", g->tmpdir);
|
||||
|
||||
if (bind (lsock, (struct sockaddr *) &addr, sizeof addr) == -1) {
|
||||
perrorf (g, "bind");
|
||||
close (lsock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (listen (lsock, 1) == -1) {
|
||||
perrorf (g, "listen");
|
||||
close (lsock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fcntl (lsock, F_SETFL, O_NONBLOCK) == -1) {
|
||||
perrorf (g, "set socket non-blocking");
|
||||
close (lsock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
for (;;) {
|
||||
r = wait_ready (g);
|
||||
if (r == -1) { /* Error. */
|
||||
guestfs_kill_subprocess (g);
|
||||
return -1;
|
||||
}
|
||||
|
||||
r = accept (lsock, NULL, 0);
|
||||
if (r >= 0) {
|
||||
g->sock = r;
|
||||
fcntl (g->sock, F_SETFL, O_NONBLOCK);
|
||||
close (lsock);
|
||||
return 0;
|
||||
}
|
||||
if (errno == EAGAIN) {
|
||||
else if (r > 0) { /* Keep waiting. */
|
||||
sleep (1);
|
||||
continue;
|
||||
}
|
||||
perrorf (g, "accept");
|
||||
close (lsock);
|
||||
guestfs_kill_subprocess (g);
|
||||
else if (r == 0) /* Daemon is ready. */
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define UNIX_PATH_MAX 108
|
||||
|
||||
/* 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 (g->pid == -1) abort (); /* Internal state error. */
|
||||
|
||||
/* Check the daemon is still around. */
|
||||
r = waitpid (g->pid, NULL, WNOHANG);
|
||||
|
||||
if (r > 0 || (r == -1 && errno == ECHILD)) {
|
||||
g->pid = -1;
|
||||
return error (g,
|
||||
"qemu subprocess exited unexpectedly during initialization");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (elapsed <= DAEMON_TIMEOUT)
|
||||
return 1; /* Keep waiting for the daemon ... */
|
||||
error (g, "timeout waiting for guest to become ready");
|
||||
return -1;
|
||||
}
|
||||
|
||||
perrorf (g, "read");
|
||||
return -1;
|
||||
}
|
||||
|
||||
close (lsock);
|
||||
return error (g, "timeout waiting for guest to become ready");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
guestfs_ready (guestfs_h *g)
|
||||
{
|
||||
return
|
||||
g->pid >= 0 &&
|
||||
kill (g->pid, 0) == 0 &&
|
||||
g->sock >= 0 /* &&
|
||||
guestfs_ping_daemon (g) >= 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user