#include <config.h>
#define _BSD_SOURCE /* for mkdtemp, usleep */
-#define _GNU_SOURCE /* for vasprintf, GNU strerror_r */
+#define _GNU_SOURCE /* for vasprintf, GNU strerror_r, strchrnul */
#include <stdio.h>
#include <stdlib.h>
#endif
#include "guestfs.h"
+#include "guestfs_protocol.h"
static void error (guestfs_h *g, const char *fs, ...);
static void perrorf (guestfs_h *g, const char *fs, ...);
static void default_error_cb (guestfs_h *g, void *data, const char *msg);
static void stdout_event (void *data, int watch, int fd, int events);
static void sock_read_event (void *data, int watch, int fd, int events);
-//static void sock_write_event (void *data, int watch, int fd, int events);
+static void sock_write_event (void *data, int watch, int fd, int events);
+
+static void close_handles (void);
static int select_add_handle (guestfs_h *g, int fd, int events, guestfs_handle_event_cb cb, void *data);
static int select_remove_handle (guestfs_h *g, int watch);
#define UNIX_PATH_MAX 108
+/* Also in guestfsd.c */
#define VMCHANNEL_PORT 6666
#define VMCHANNEL_ADDR "10.0.2.4"
struct guestfs_h
{
+ struct guestfs_h *next; /* Linked list of open handles. */
+
/* State: see the state machine diagram in the man page guestfs(3). */
enum state state;
int cmdline_size;
int verbose;
+ int autosync;
+
+ const char *path;
/* Callbacks. */
guestfs_abort_cb abort_cb;
char *msg_in;
int msg_in_size, msg_in_allocated;
char *msg_out;
- int msg_out_size;
+ int msg_out_size, msg_out_pos;
+
+ int msg_next_serial;
};
+static guestfs_h *handles = NULL;
+static int atexit_handler_set = 0;
+
guestfs_h *
guestfs_create (void)
{
str = getenv ("LIBGUESTFS_DEBUG");
g->verbose = str != NULL && strcmp (str, "1") == 0;
+ str = getenv ("LIBGUESTFS_PATH");
+ g->path = str != NULL ? str : GUESTFS_DEFAULT_PATH;
+ /* XXX We should probably make QEMU configurable as well. */
+
+ /* Start with large serial numbers so they are easy to spot
+ * inside the protocol.
+ */
+ g->msg_next_serial = 0x00123400;
+
+ /* Link the handles onto a global list. This is the one area
+ * where the library needs to be made thread-safe. (XXX)
+ */
+ /* acquire mutex (XXX) */
+ g->next = handles;
+ handles = g;
+ if (!atexit_handler_set) {
+ atexit (close_handles);
+ atexit_handler_set = 1;
+ }
+ /* release mutex (XXX) */
+
+ if (g->verbose)
+ fprintf (stderr, "new guestfs handle %p\n", g);
+
return g;
}
{
int i;
char filename[256];
+ guestfs_h *gg;
if (g->state == NO_HANDLE) {
/* Not safe to call 'error' here, so ... */
return;
}
+ if (g->verbose)
+ 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)
+ guestfs_sync (g);
+
/* Remove any handlers that might be called back before we kill the
* subprocess.
*/
/* Mark the handle as dead before freeing it. */
g->state = NO_HANDLE;
+ /* acquire mutex (XXX) */
+ if (handles == g)
+ handles = g->next;
+ else {
+ for (gg = handles; gg->next != g; gg = gg->next)
+ ;
+ gg->next = g->next;
+ }
+ /* release mutex (XXX) */
+
free (g);
}
+/* Close all open handles (called from atexit(3)). */
+static void
+close_handles (void)
+{
+ while (handles) guestfs_close (handles);
+}
+
static void
default_error_cb (guestfs_h *g, void *data, const char *msg)
{
return g->verbose;
}
+void
+guestfs_set_autosync (guestfs_h *g, int a)
+{
+ g->autosync = a;
+}
+
+int
+guestfs_get_autosync (guestfs_h *g)
+{
+ return g->autosync;
+}
+
+void
+guestfs_set_path (guestfs_h *g, const char *path)
+{
+ if (path == NULL)
+ g->path = GUESTFS_DEFAULT_PATH;
+ else
+ g->path = path;
+}
+
+const char *
+guestfs_get_path (guestfs_h *g)
+{
+ return g->path;
+}
+
/* Add a string to the current command line. */
static void
incr_cmdline_size (guestfs_h *g)
return -1;
}
+ if (access (filename, F_OK) == -1) {
+ perrorf (g, "%s", filename);
+ return -1;
+ }
+
snprintf (buf, len, "file=%s", filename);
return guestfs_config (g, "-drive", buf);
return -1;
}
+ if (access (filename, F_OK) == -1) {
+ perrorf (g, "%s", filename);
+ return -1;
+ }
+
return guestfs_config (g, "-cdrom", filename);
}
guestfs_launch (guestfs_h *g)
{
static const char *dir_template = "/tmp/libguestfsXXXXXX";
- int r, i;
+ int r, i, len, pmore;
int wfd[2], rfd[2];
int tries;
- /*const char *qemu = QEMU;*/ /* XXX */
- const char *qemu = "/usr/bin/qemu-system-x86_64";
- const char *kernel = "vmlinuz.fedora-10.x86_64";
- const char *initrd = "initramfs.fedora-10.x86_64.img";
+ const char *kernel_name = "vmlinuz." REPO "." host_cpu;
+ const char *initrd_name = "initramfs." REPO "." host_cpu ".img";
+ char *path, *pelem, *pend;
+ char *kernel = NULL, *initrd = NULL;
char unixsock[256];
struct sockaddr_un addr;
- /* XXX Choose which qemu to run. */
- /* XXX Choose initrd, etc. */
-
/* Configured? */
if (!g->cmdline) {
error (g, "you must call guestfs_add_drive before guestfs_launch");
return -1;
}
+ /* Search g->path for the kernel and initrd. */
+ pelem = path = safe_strdup (g, g->path);
+ do {
+ pend = strchrnul (pelem, ':');
+ pmore = *pend == ':';
+ *pend = '\0';
+ len = pend - pelem;
+
+ /* Empty element or "." means cwd. */
+ if (len == 0 || (len == 1 && *pelem == '.')) {
+ if (g->verbose)
+ fprintf (stderr,
+ "looking for kernel and initrd in current directory\n");
+ if (access (kernel_name, F_OK) == 0 && access (initrd_name, F_OK) == 0) {
+ kernel = safe_strdup (g, kernel_name);
+ initrd = safe_strdup (g, initrd_name);
+ break;
+ }
+ }
+ /* Look at <path>/kernel etc. */
+ else {
+ kernel = safe_malloc (g, len + strlen (kernel_name) + 2);
+ initrd = safe_malloc (g, len + strlen (initrd_name) + 2);
+ sprintf (kernel, "%s/%s", pelem, kernel_name);
+ sprintf (initrd, "%s/%s", pelem, initrd_name);
+
+ if (g->verbose)
+ fprintf (stderr, "looking for %s and %s\n", kernel, initrd);
+
+ if (access (kernel, F_OK) == 0 && access (initrd, F_OK) == 0)
+ break;
+ free (kernel);
+ free (initrd);
+ kernel = initrd = NULL;
+ }
+
+ pelem = pend + 1;
+ } while (pmore);
+
+ free (path);
+
+ if (kernel == NULL || initrd == NULL) {
+ error (g, "cannot find %s or %s on LIBGUESTFS_PATH (current path = %s)",
+ kernel_name, initrd_name, g->path);
+ goto cleanup0;
+ }
+
/* 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);
- return -1;
+ goto cleanup0;
}
}
if (pipe (wfd) == -1 || pipe (rfd) == -1) {
perrorf (g, "pipe");
- return -1;
+ goto cleanup0;
}
r = fork ();
close (wfd[1]);
close (rfd[0]);
close (rfd[1]);
- return -1;
+ goto cleanup0;
}
if (r == 0) { /* Child (qemu). */
/* 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[0] = (char *) QEMU;
/* Construct the -net channel parameter for qemu. */
snprintf (vmchannel, sizeof vmchannel,
"console=ttyS0 guestfs=%s:%d", VMCHANNEL_ADDR, VMCHANNEL_PORT);
add_cmdline (g, "-m");
- add_cmdline (g, "384"); /* XXX Choose best size. */
+ add_cmdline (g, "384"); /* XXX Choose best size. */
+ add_cmdline (g, "-no-kqemu"); /* Avoids a warning. */
add_cmdline (g, "-kernel");
add_cmdline (g, (char *) kernel);
add_cmdline (g, "-initrd");
g->cmdline[g->cmdline_size-1] = NULL;
if (g->verbose) {
- fprintf (stderr, "%s", qemu);
+ fprintf (stderr, "%s", QEMU);
for (i = 0; g->cmdline[i]; ++i)
fprintf (stderr, " %s", g->cmdline[i]);
fprintf (stderr, "\n");
close (rfd[0]);
dup (wfd[0]);
dup (rfd[1]);
+ close (wfd[0]);
+ close (rfd[1]);
#if 0
/* Set up a new process group, so we can signal this process
setpgid (0, 0);
#endif
- execv (qemu, g->cmdline); /* Run qemu. */
- perror (qemu);
+ execv (QEMU, g->cmdline); /* Run qemu. */
+ perror (QEMU);
_exit (1);
}
if ((r == -1 && errno == EINPROGRESS) || r == 0)
goto connected;
- perrorf (g, "connect");
+ if (errno != ENOENT)
+ perrorf (g, "connect");
tries--;
}
free (g->msg_out);
g->msg_out = NULL;
g->msg_out_size = 0;
+ g->msg_out_pos = 0;
g->stdout_watch =
main_loop.add_handle (g, g->fd[1],
g->sock_watch =
main_loop.add_handle (g, g->sock,
- GUESTFS_HANDLE_READABLE |
- GUESTFS_HANDLE_HANGUP |
- GUESTFS_HANDLE_ERROR,
+ GUESTFS_HANDLE_READABLE,
sock_read_event, g);
if (g->sock_watch == -1) {
error (g, "could not watch daemon communications socket");
g->start_t = 0;
g->stdout_watch = -1;
g->sock_watch = -1;
+
+ cleanup0:
+ free (kernel);
+ free (initrd);
return -1;
}
}
if (g->verbose)
- fprintf (stderr, "sending SIGTERM to process group %d\n", g->pid);
+ fprintf (stderr, "sending SIGTERM to process %d\n", g->pid);
kill (g->pid, SIGTERM);
if (g->verbose)
fprintf (stderr,
- "sock_event: %p g->state = %d, fd = %d, events = 0x%x\n",
+ "sock_read_event: %p g->state = %d, fd = %d, events = 0x%x\n",
g, g->state, fd, events);
if (g->sock != fd) {
* starts up it sends a "magic" value (longer than any possible
* message). Check for this.
*/
- if (len == 0xf5f5f5f5) {
+ if (len == 0xf5f55ff5) {
if (g->state != LAUNCHING)
error (g, "received magic signature from guestfsd, but in state %d",
g->state);
goto cleanup;
}
- if (g->msg_in_size < len) return; /* Need more of this message. */
+ /* If this happens, it's pretty bad and we've probably lost synchronization.*/
+ if (len > GUESTFS_MESSAGE_MAX) {
+ error (g, "message length (%u) > maximum possible size (%d)",
+ len, GUESTFS_MESSAGE_MAX);
+ goto cleanup;
+ }
+
+ if (g->msg_in_size-4 < len) return; /* Need more of this message. */
/* This should not happen, and if it does it probably means we've
* lost all hope of synchronization.
*/
- if (g->msg_in_size > len) {
- error (g, "len = %d, but msg_in_size = %d", len, g->msg_in_size);
+ if (g->msg_in_size-4 > len) {
+ error (g, "len = %d, but msg_in_size-4 = %d", len, g->msg_in_size-4);
goto cleanup;
}
xdr_destroy (&xdr);
}
+/* The function is called whenever we can write something on the
+ * guestfsd (daemon inside the guest) communication socket.
+ */
+static void
+sock_write_event (void *data, int watch, int fd, int events)
+{
+ guestfs_h *g = (guestfs_h *) data;
+ int n;
+
+ if (g->verbose)
+ fprintf (stderr,
+ "sock_write_event: %p g->state = %d, fd = %d, events = 0x%x\n",
+ g, g->state, fd, events);
+
+ if (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);
+ return;
+ }
+
+ if (g->verbose)
+ fprintf (stderr, "sock_write_event: writing %d bytes ...\n",
+ g->msg_out_size - g->msg_out_pos);
+
+ n = write (g->sock, g->msg_out + g->msg_out_pos,
+ g->msg_out_size - g->msg_out_pos);
+ if (n == -1) {
+ if (errno != EAGAIN)
+ perrorf (g, "write");
+ return;
+ }
+
+ if (g->verbose)
+ fprintf (stderr, "sock_write_event: wrote %d bytes\n", n);
+
+ g->msg_out_pos += n;
+
+ /* More to write? */
+ if (g->msg_out_pos < g->msg_out_size)
+ return;
+
+ if (g->verbose)
+ fprintf (stderr, "sock_write_event: done writing, switching back to reading events\n");
+
+ free (g->msg_out);
+ g->msg_out_pos = g->msg_out_size = 0;
+
+ if (main_loop.remove_handle (g, g->sock_watch) == -1) {
+ error (g, "remove_handle failed in sock_write_event");
+ return;
+ }
+ g->sock_watch =
+ main_loop.add_handle (g, g->sock,
+ GUESTFS_HANDLE_READABLE,
+ sock_read_event, g);
+ if (g->sock_watch == -1) {
+ error (g, "add_handle failed in sock_write_event");
+ return;
+ }
+}
+
+/* Dispatch a call to the remote daemon. This function just queues
+ * the call in msg_out, to be sent when we next enter the main loop.
+ * Returns -1 for error, or the message serial number.
+ */
+static int
+dispatch (guestfs_h *g, int proc_nr, xdrproc_t xdrp, char *args)
+{
+ char buffer[GUESTFS_MESSAGE_MAX];
+ struct guestfs_message_header hdr;
+ XDR xdr;
+ unsigned len;
+ int serial = g->msg_next_serial++;
+
+ if (g->state != READY) {
+ error (g, "dispatch: state %d != READY", g->state);
+ return -1;
+ }
+
+ /* Serialize the header. */
+ hdr.prog = GUESTFS_PROGRAM;
+ hdr.vers = GUESTFS_PROTOCOL_VERSION;
+ hdr.proc = proc_nr;
+ hdr.direction = GUESTFS_DIRECTION_CALL;
+ hdr.serial = serial;
+ hdr.status = GUESTFS_STATUS_OK;
+
+ xdrmem_create (&xdr, buffer, sizeof buffer, XDR_ENCODE);
+ if (!xdr_guestfs_message_header (&xdr, &hdr)) {
+ error (g, "xdr_guestfs_message_header failed");
+ return -1;
+ }
+
+ /* Serialize the args. If any, because some message types
+ * have no parameters.
+ */
+ if (xdrp) {
+ if (!(*xdrp) (&xdr, args)) {
+ error (g, "dispatch failed to marshal args");
+ return -1;
+ }
+ }
+
+ len = xdr_getpos (&xdr);
+ xdr_destroy (&xdr);
+
+ /* Allocate the outgoing message buffer. */
+ g->msg_out = safe_malloc (g, len + 4);
+
+ g->msg_out_size = len + 4;
+ g->msg_out_pos = 0;
+ g->state = BUSY;
+
+ xdrmem_create (&xdr, g->msg_out, 4, XDR_ENCODE);
+ if (!xdr_uint32_t (&xdr, &len)) {
+ error (g, "xdr_uint32_t failed in dispatch");
+ goto cleanup1;
+ }
+
+ memcpy (g->msg_out + 4, buffer, len);
+
+ /* Change the handle to sock_write_event. */
+ if (main_loop.remove_handle (g, g->sock_watch) == -1) {
+ error (g, "remove_handle failed in dispatch");
+ goto cleanup1;
+ }
+ g->sock_watch =
+ main_loop.add_handle (g, g->sock,
+ GUESTFS_HANDLE_WRITABLE,
+ sock_write_event, g);
+ if (g->sock_watch == -1) {
+ error (g, "add_handle failed in dispatch");
+ goto cleanup1;
+ }
+
+ return serial;
+
+ cleanup1:
+ free (g->msg_out);
+ g->msg_out = NULL;
+ g->msg_out_size = 0;
+ g->state = READY;
+ return -1;
+}
+
+/* Check the return message from a call for validity. */
+static int
+check_reply_header (guestfs_h *g,
+ const struct guestfs_message_header *hdr,
+ int proc_nr, int serial)
+{
+ if (hdr->prog != GUESTFS_PROGRAM) {
+ error (g, "wrong program (%d/%d)", hdr->prog, GUESTFS_PROGRAM);
+ return -1;
+ }
+ if (hdr->vers != GUESTFS_PROTOCOL_VERSION) {
+ error (g, "wrong protocol version (%d/%d)",
+ hdr->vers, GUESTFS_PROTOCOL_VERSION);
+ return -1;
+ }
+ if (hdr->direction != GUESTFS_DIRECTION_REPLY) {
+ error (g, "unexpected message direction (%d/%d)",
+ hdr->direction, GUESTFS_DIRECTION_REPLY);
+ return -1;
+ }
+ if (hdr->proc != proc_nr) {
+ error (g, "unexpected procedure number (%d/%d)", hdr->proc, proc_nr);
+ return -1;
+ }
+ if (hdr->serial != serial) {
+ error (g, "unexpected serial (%d/%d)", hdr->serial, serial);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* The high-level actions are autogenerated by generator.ml. Include
+ * them here.
+ */
+#include "guestfs-actions.c"
+
/* This is the default main loop implementation, using select(2). */
struct handle_cb_data {
static fd_set xset;
static int select_init_done = 0;
static int max_fd = -1;
+static int nr_fds = 0;
static struct handle_cb_data *handle_cb_data = NULL;
static void
handle_cb_data[fd].cb = cb;
handle_cb_data[fd].data = data;
+ nr_fds++;
+
/* Any integer >= 0 can be the handle, and this is as good as any ... */
return fd;
}
sizeof (struct handle_cb_data) * (max_fd+1));
}
+ nr_fds--;
+
return 0;
}
old_level = level++;
while (level > old_level) {
+ if (nr_fds == 0) {
+ level = old_level;
+ break;
+ }
+
rset2 = rset;
wset2 = wset;
xset2 = xset;