/* 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
#include <sys/stat.h>
#include <sys/select.h>
#include <dirent.h>
-#include <signal.h>
+#include <assert.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#include "c-ctype.h"
#include "glthread/lock.h"
-#include "ignore-value.h"
+#include "hash.h"
+#include "hash-pjw.h"
#include "guestfs.h"
#include "guestfs-internal.h"
#include "guestfs_protocol.h"
static void default_error_cb (guestfs_h *g, void *data, const char *msg);
+static void remove_tmpdir (guestfs_h *g);
static void close_handles (void);
gl_lock_define_initialized (static, handles_lock);
g->error_cb_data = NULL;
g->recovery_proc = 1;
+ g->autosync = 1;
str = getenv ("LIBGUESTFS_DEBUG");
g->verbose = str != NULL && STREQ (str, "1");
str = getenv ("LIBGUESTFS_MEMSIZE");
if (str) {
if (sscanf (str, "%d", &g->memsize) != 1 || g->memsize <= 256) {
- fprintf (stderr, "libguestfs: non-numeric or too small value for LIBGUESTFS_MEMSIZE\n");
+ warning (g, "non-numeric or too small value for LIBGUESTFS_MEMSIZE");
goto error;
}
} else
}
gl_lock_unlock (handles_lock);
- if (g->verbose)
- fprintf (stderr, "new guestfs handle %p\n", g);
+ debug (g, "new guestfs handle %p", g);
return g;
void
guestfs_close (guestfs_h *g)
{
- int i;
- char filename[256];
- guestfs_h *gg;
-
if (g->state == NO_HANDLE) {
- /* Not safe to call 'error' here, so ... */
+ /* Not safe to call ANY callbacks here, so ... */
fprintf (stderr, _("guestfs_close: called twice on the same handle\n"));
return;
}
- if (g->verbose)
- fprintf (stderr, "closing guestfs handle %p (state %d)\n", g, g->state);
+ if (g->trace) {
+ const char trace_msg[] = "close";
- /* Run user close callback before anything else. */
- if (g->close_cb)
- g->close_cb (g, g->close_cb_data);
+ guestfs___call_callbacks_message (g, GUESTFS_EVENT_TRACE,
+ trace_msg, strlen (trace_msg));
+ }
- guestfs___free_inspect_info (g);
+ debug (g, "closing guestfs handle %p (state %d)", g, g->state);
/* Try to sync if autosync flag is set. */
- if (g->autosync && g->state == READY) {
- guestfs_umount_all (g);
- guestfs_sync (g);
- }
-
- /* Remove any handlers that might be called back before we kill the
- * subprocess.
- */
- g->log_message_cb = NULL;
+ if (g->autosync && g->state == READY)
+ guestfs_internal_autosync (g);
+ /* Kill the qemu subprocess. */
if (g->state != CONFIG)
guestfs_kill_subprocess (g);
+ /* Run user close callbacks. */
+ guestfs___call_callbacks_void (g, GUESTFS_EVENT_CLOSE);
+
+ /* Remove all other registered callbacks. Since we've already
+ * called the close callbacks, we shouldn't call any others.
+ */
+ free (g->events);
+ g->nr_events = 0;
+ g->events = NULL;
+
+ guestfs___free_inspect_info (g);
+
/* Close sockets. */
if (g->fd[0] >= 0)
close (g->fd[0]);
g->sock = -1;
/* Wait for subprocess(es) to exit. */
- waitpid (g->pid, NULL, 0);
+ if (g->pid > 0) waitpid (g->pid, NULL, 0);
if (g->recoverypid > 0) waitpid (g->recoverypid, NULL, 0);
- /* Remove tmpfiles. */
- if (g->tmpdir) {
- snprintf (filename, sizeof filename, "%s/sock", g->tmpdir);
- unlink (filename);
-
- rmdir (g->tmpdir);
-
- free (g->tmpdir);
- }
+ /* Remove whole temporary directory. */
+ remove_tmpdir (g);
if (g->cmdline) {
+ size_t i;
+
for (i = 0; i < g->cmdline_size; ++i)
free (g->cmdline[i]);
free (g->cmdline);
if (handles == g)
handles = g->next;
else {
+ guestfs_h *gg;
+
for (gg = handles; gg->next != g; gg = gg->next)
;
gg->next = g->next;
}
gl_lock_unlock (handles_lock);
+ if (g->pda)
+ hash_free (g->pda);
free (g->last_error);
free (g->path);
free (g->qemu);
free (g);
}
+/* g->tmpdir can contain any files (but not subdirectories). Remove
+ * those and the directory itself. Note that errors in this function
+ * aren't really that important: if we end up not deleting temporary
+ * files it's only annoying.
+ */
+static void
+remove_tmpdir (guestfs_h *g)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ if (!g->tmpdir)
+ return;
+
+ dir = opendir (g->tmpdir);
+ if (dir == NULL) {
+ perror (g->tmpdir);
+ return;
+ }
+
+ while ((d = readdir (dir)) != NULL) {
+ if (STRNEQ (d->d_name, ".") && STRNEQ (d->d_name, "..")) {
+ if (unlinkat (dirfd (dir), d->d_name, 0) == -1)
+ perror (d->d_name);
+ }
+ }
+
+ if (closedir (dir) == -1)
+ perror (g->tmpdir);
+
+ if (rmdir (g->tmpdir) == -1)
+ perror (g->tmpdir);
+
+ free (g->tmpdir);
+ g->tmpdir = NULL;
+}
+
/* Close all open handles (called from atexit(3)). */
static void
close_handles (void)
return g->last_error;
}
+int
+guestfs_last_errno (guestfs_h *g)
+{
+ return g->last_errnum;
+}
+
static void
-set_last_error (guestfs_h *g, const char *msg)
+set_last_error (guestfs_h *g, int errnum, const char *msg)
{
free (g->last_error);
g->last_error = strdup (msg);
+ g->last_errnum = errnum;
+}
+
+/* Warning are printed unconditionally. We try to make these rare.
+ * Generally speaking, a warning should either be an error, or if it's
+ * not important for end users then it should be a debug message.
+ */
+void
+guestfs___warning (guestfs_h *g, const char *fs, ...)
+{
+ va_list args;
+ char *msg, *msg2;
+ int len;
+
+ va_start (args, fs);
+ len = vasprintf (&msg, fs, args);
+ va_end (args);
+
+ if (len < 0) return;
+
+ len = asprintf (&msg2, _("warning: %s"), msg);
+ free (msg);
+
+ if (len < 0) return;
+
+ guestfs___call_callbacks_message (g, GUESTFS_EVENT_LIBRARY, msg2, len);
+
+ free (msg2);
+}
+
+/* Debug messages. */
+void
+guestfs___debug (guestfs_h *g, const char *fs, ...)
+{
+ va_list args;
+ char *msg;
+ int len;
+
+ /* The cpp macro "debug" has already checked that g->verbose is true
+ * before calling this function, but we check it again just in case
+ * anyone calls this function directly.
+ */
+ if (!g->verbose)
+ return;
+
+ va_start (args, fs);
+ len = vasprintf (&msg, fs, args);
+ va_end (args);
+
+ if (len < 0) return;
+
+ guestfs___call_callbacks_message (g, GUESTFS_EVENT_LIBRARY, msg, len);
+}
+
+/* Call trace messages. These are enabled by setting g->trace, and
+ * calls to this function should only happen from the generated code
+ * in src/actions.c
+ */
+void
+guestfs___trace (guestfs_h *g, const char *fs, ...)
+{
+ va_list args;
+ char *msg;
+ int len;
+
+ va_start (args, fs);
+ len = vasprintf (&msg, fs, args);
+ va_end (args);
+
+ if (len < 0) return;
+
+ guestfs___call_callbacks_message (g, GUESTFS_EVENT_TRACE, msg, len);
+
+ free (msg);
}
static void
}
void
-guestfs_error (guestfs_h *g, const char *fs, ...)
+guestfs_error_errno (guestfs_h *g, int errnum, const char *fs, ...)
{
va_list args;
char *msg;
if (err < 0) return;
+ /* set_last_error first so that the callback can access the error
+ * message and errno through the handle if it wishes.
+ */
+ set_last_error (g, errnum, msg);
if (g->error_cb) g->error_cb (g, g->error_cb_data, msg);
- set_last_error (g, msg);
free (msg);
}
strcat (msg, ": ");
strcat (msg, buf);
+ /* set_last_error first so that the callback can access the error
+ * message and errno through the handle if it wishes.
+ */
+ set_last_error (g, errnum, msg);
if (g->error_cb) g->error_cb (g, g->error_cb_data, msg);
- set_last_error (g, msg);
free (msg);
}
return p;
}
+char *
+guestfs_safe_asprintf (guestfs_h *g, const char *fs, ...)
+{
+ va_list args;
+ char *msg;
+
+ va_start (args, fs);
+ int err = vasprintf (&msg, fs, args);
+ va_end (args);
+
+ if (err == -1)
+ g->abort_cb ();
+
+ return msg;
+}
+
void
guestfs_set_out_of_memory_handler (guestfs_h *g, guestfs_abort_cb cb)
{
return g->enable_network;
}
+int
+guestfs__set_attach_method (guestfs_h *g, const char *method)
+{
+ if (STREQ (method, "appliance")) {
+ g->attach_method = ATTACH_METHOD_APPLIANCE;
+ free (g->attach_method_arg);
+ g->attach_method_arg = NULL;
+ }
+ else if (STRPREFIX (method, "unix:") && strlen (method) > 5) {
+ g->attach_method = ATTACH_METHOD_UNIX;
+ free (g->attach_method_arg);
+ g->attach_method_arg = safe_strdup (g, method + 5);
+ /* Note that we don't check the path exists until launch is called. */
+ }
+ else {
+ error (g, "invalid attach method: %s", method);
+ return -1;
+ }
+
+ return 0;
+}
+
+char *
+guestfs__get_attach_method (guestfs_h *g)
+{
+ char *ret;
+
+ switch (g->attach_method) {
+ case ATTACH_METHOD_APPLIANCE:
+ ret = safe_strdup (g, "appliance");
+ break;
+
+ case ATTACH_METHOD_UNIX:
+ ret = safe_malloc (g, strlen (g->attach_method_arg) + 5 + 1);
+ strcpy (ret, "unix:");
+ strcat (ret, g->attach_method_arg);
+ break;
+
+ default: /* keep GCC happy - this is not reached */
+ abort ();
+ }
+
+ return ret;
+}
+
+/* Note the private data area is allocated lazily, since the vast
+ * majority of callers will never use it. This means g->pda is
+ * likely to be NULL.
+ */
+struct pda_entry {
+ char *key; /* key */
+ void *data; /* opaque user data pointer */
+};
+
+static size_t
+hasher (void const *x, size_t table_size)
+{
+ struct pda_entry const *p = x;
+ return hash_pjw (p->key, table_size);
+}
+
+static bool
+comparator (void const *x, void const *y)
+{
+ struct pda_entry const *a = x;
+ struct pda_entry const *b = y;
+ return STREQ (a->key, b->key);
+}
+
+static void
+freer (void *x)
+{
+ if (x) {
+ struct pda_entry *p = x;
+ free (p->key);
+ free (p);
+ }
+}
+
void
-guestfs_set_log_message_callback (guestfs_h *g,
- guestfs_log_message_cb cb, void *opaque)
+guestfs_set_private (guestfs_h *g, const char *key, void *data)
+{
+ if (g->pda == NULL) {
+ g->pda = hash_initialize (16, NULL, hasher, comparator, freer);
+ if (g->pda == NULL)
+ g->abort_cb ();
+ }
+
+ struct pda_entry *new_entry = safe_malloc (g, sizeof *new_entry);
+ new_entry->key = safe_strdup (g, key);
+ new_entry->data = data;
+
+ struct pda_entry *old_entry = hash_delete (g->pda, new_entry);
+ freer (old_entry);
+
+ struct pda_entry *entry = hash_insert (g->pda, new_entry);
+ if (entry == NULL)
+ g->abort_cb ();
+ assert (entry == new_entry);
+}
+
+static inline char *
+bad_cast (char const *s)
+{
+ return (char *) s;
+}
+
+void *
+guestfs_get_private (guestfs_h *g, const char *key)
+{
+ if (g->pda == NULL)
+ return NULL; /* no keys have been set */
+
+ const struct pda_entry k = { .key = bad_cast (key) };
+ struct pda_entry *entry = hash_lookup (g->pda, &k);
+ if (entry)
+ return entry->data;
+ else
+ return NULL;
+}
+
+/* Iterator. */
+void *
+guestfs_first_private (guestfs_h *g, const char **key_rtn)
+{
+ if (g->pda == NULL)
+ return NULL;
+
+ g->pda_next = hash_get_first (g->pda);
+
+ /* Ignore any keys with NULL data pointers. */
+ while (g->pda_next && g->pda_next->data == NULL)
+ g->pda_next = hash_get_next (g->pda, g->pda_next);
+
+ if (g->pda_next == NULL)
+ return NULL;
+
+ *key_rtn = g->pda_next->key;
+ return g->pda_next->data;
+}
+
+void *
+guestfs_next_private (guestfs_h *g, const char **key_rtn)
{
- g->log_message_cb = cb;
- g->log_message_cb_data = opaque;
+ if (g->pda == NULL)
+ return NULL;
+
+ if (g->pda_next == NULL)
+ return NULL;
+
+ /* Walk to the next key with a non-NULL data pointer. */
+ do {
+ g->pda_next = hash_get_next (g->pda, g->pda_next);
+ } while (g->pda_next && g->pda_next->data == NULL);
+
+ if (g->pda_next == NULL)
+ return NULL;
+
+ *key_rtn = g->pda_next->key;
+ return g->pda_next->data;
}
+/* When tracing, be careful how we print BufferIn parameters which
+ * usually contain large amounts of binary data (RHBZ#646822).
+ */
void
-guestfs_set_subprocess_quit_callback (guestfs_h *g,
- guestfs_subprocess_quit_cb cb, void *opaque)
+guestfs___print_BufferIn (FILE *out, const char *buf, size_t buf_size)
{
- g->subprocess_quit_cb = cb;
- g->subprocess_quit_cb_data = opaque;
+ size_t i;
+ size_t orig_size = buf_size;
+
+ if (buf_size > 256)
+ buf_size = 256;
+
+ fputc ('"', out);
+
+ for (i = 0; i < buf_size; ++i) {
+ if (c_isprint (buf[i]))
+ fputc (buf[i], out);
+ else
+ fprintf (out, "\\x%02x", (unsigned char) buf[i]);
+ }
+
+ fputc ('"', out);
+
+ if (orig_size > buf_size)
+ fprintf (out,
+ _("<truncated, original size %zu bytes>"), orig_size);
}
void
-guestfs_set_launch_done_callback (guestfs_h *g,
- guestfs_launch_done_cb cb, void *opaque)
+guestfs___print_BufferOut (FILE *out, const char *buf, size_t buf_size)
{
- g->launch_done_cb = cb;
- g->launch_done_cb_data = opaque;
+ guestfs___print_BufferIn (out, buf, buf_size);
}
void
-guestfs_set_close_callback (guestfs_h *g,
- guestfs_close_cb cb, void *opaque)
+guestfs___free_string_list (char **argv)
{
- g->close_cb = cb;
- g->close_cb_data = opaque;
+ size_t i;
+ for (i = 0; argv[i] != NULL; ++i)
+ free (argv[i]);
+ free (argv);
}