--- /dev/null
+/* libguestfs
+ * Copyright (C) 2010 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
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <utime.h>
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+#include "guestfs-internal-actions.h"
+#include "guestfs_protocol.h"
+
+static const char *kernel_name = "vmlinuz." REPO "." host_cpu;
+static const char *initrd_name = "initramfs." REPO "." host_cpu ".img";
+
+static int find_path (guestfs_h *g, int (*pred) (guestfs_h *g, const char *pelem, void *data), void *data, char **pelem);
+static int dir_contains_file (const char *dir, const char *file);
+static int dir_contains_files (const char *dir, ...);
+static int contains_supermin_appliance (guestfs_h *g, const char *path, void *data);
+static int contains_ordinary_appliance (guestfs_h *g, const char *path, void *data);
+static char *calculate_supermin_checksum (guestfs_h *g, const char *supermin_path);
+static int check_for_cached_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance);
+static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance);
+
+/* Locate or build the appliance.
+ *
+ * This function locates or builds the appliance as necessary,
+ * handling the supermin appliance, caching of supermin-built
+ * appliances, or using an ordinary appliance.
+ *
+ * The return value is 0 = good, -1 = error. Returned in '*kernel'
+ * will be the name of the kernel to use, '*initrd' the name of the
+ * initrd, '*appliance' the name of the ext2 root filesystem.
+ * '*appliance' can be NULL, meaning that we are using an ordinary
+ * (non-ext2) appliance. All three strings must be freed by the
+ * caller. However the referenced files themselves must not be
+ * deleted.
+ *
+ * The process is as follows:
+ *
+ * (1) Look for the first element of g->path which contains a
+ * supermin appliance skeleton. If no element has this, skip
+ * straight to step (5).
+ * (2) Calculate the checksum of this supermin appliance.
+ * (3) Check whether $TMPDIR/$checksum/ directory exists, contains
+ * a cached appliance, and passes basic security checks. If so,
+ * return this appliance.
+ * (4) Try to build the supermin appliance into $TMPDIR/$checksum/.
+ * If this is successful, return it.
+ * (5) Check each element of g->path, looking for an ordinary appliance.
+ * If one is found, return it.
+ */
+int
+guestfs___build_appliance (guestfs_h *g,
+ char **kernel, char **initrd, char **appliance)
+{
+ int r;
+
+ /* Step (1). */
+ char *supermin_path;
+ r = find_path (g, contains_supermin_appliance, NULL, &supermin_path);
+ if (r == -1)
+ return -1;
+
+ if (r == 1) {
+ /* Step (2): calculate checksum. */
+ char *checksum = calculate_supermin_checksum (g, supermin_path);
+ if (checksum) {
+ /* Step (3): cached appliance exists? */
+ r = check_for_cached_appliance (g, supermin_path, checksum,
+ kernel, initrd, appliance);
+ if (r != 0) {
+ free (supermin_path);
+ free (checksum);
+ return r == 1 ? 0 : -1;
+ }
+
+ /* Step (4): build supermin appliance. */
+ r = build_supermin_appliance (g, supermin_path, checksum,
+ kernel, initrd, appliance);
+ free (supermin_path);
+ free (checksum);
+ return r;
+ }
+ free (supermin_path);
+ }
+
+ /* Step (5). */
+ char *path;
+ r = find_path (g, contains_ordinary_appliance, NULL, &path);
+ if (r == -1)
+ return -1;
+
+ if (r == 1) {
+ size_t len = strlen (path);
+ *kernel = safe_malloc (g, len + strlen (kernel_name) + 2);
+ *initrd = safe_malloc (g, len + strlen (initrd_name) + 2);
+ sprintf (*kernel, "%s/%s", path, kernel_name);
+ sprintf (*initrd, "%s/%s", path, initrd_name);
+ *appliance = NULL;
+
+ free (path);
+ return 0;
+ }
+
+ error (g, _("cannot find any suitable libguestfs supermin or ordinary appliance on LIBGUESTFS_PATH (search path: %s)"),
+ g->path);
+ return -1;
+}
+
+static int
+contains_supermin_appliance (guestfs_h *g, const char *path, void *data)
+{
+ return dir_contains_files (path, "supermin.d", "kmod.whitelist", NULL);
+}
+
+static int
+contains_ordinary_appliance (guestfs_h *g, const char *path, void *data)
+{
+ return dir_contains_files (path, kernel_name, initrd_name, NULL);
+}
+
+/* supermin_path is a path which is known to contain a supermin
+ * appliance. Using febootstrap-supermin-helper -f checksum calculate
+ * the checksum so we can see if it is cached.
+ */
+static char *
+calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
+{
+ size_t len = 2 * strlen (supermin_path) + 256;
+ char cmd[len];
+ snprintf (cmd, len,
+ "febootstrap-supermin-helper%s "
+ "-f checksum "
+ "-k '%s/kmod.whitelist' "
+ "'%s/supermin.d' "
+ host_cpu,
+ g->verbose ? " --verbose" : "",
+ supermin_path,
+ supermin_path);
+
+ if (g->verbose)
+ guestfs___print_timestamped_message (g, "%s", cmd);
+
+ /* Errors here are non-fatal, so we don't need to call error(). */
+ FILE *pp = popen (cmd, "r");
+ if (pp == NULL)
+ return NULL;
+
+ char checksum[256];
+ if (fgets (checksum, sizeof checksum, pp) == NULL) {
+ pclose (pp);
+ return NULL;
+ }
+
+ if (pclose (pp) == -1) {
+ perror ("pclose");
+ return NULL;
+ }
+
+ len = strlen (checksum);
+
+ if (len < 16) { /* sanity check */
+ fprintf (stderr, "libguestfs: internal error: febootstrap-supermin-helper -f checksum returned a short string\n");
+ return NULL;
+ }
+
+ if (len > 0 && checksum[len-1] == '\n')
+ checksum[--len] = '\0';
+
+ return safe_strndup (g, checksum, len);
+}
+
+/* Check for cached appliance in $TMPDIR/$checksum. Check it exists
+ * and passes some basic security checks.
+ *
+ * Returns:
+ * 1 = exists, and passes
+ * 0 = does not exist
+ * -1 = error which should abort the whole launch process
+ */
+static int
+security_check_cache_file (guestfs_h *g, const char *filename,
+ const struct stat *statbuf)
+{
+ uid_t uid = geteuid ();
+
+ if (statbuf->st_uid != uid) {
+ error (g, ("libguestfs cached appliance %s is not owned by UID %d\n"),
+ filename, uid);
+ return -1;
+ }
+
+ if ((statbuf->st_mode & 0022) != 0) {
+ error (g, ("libguestfs cached appliance %s is writable by group or other (mode %o)\n"),
+ filename, statbuf->st_mode);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+check_for_cached_appliance (guestfs_h *g,
+ const char *supermin_path, const char *checksum,
+ char **kernel, char **initrd, char **appliance)
+{
+ const char *tmpdir = guestfs___tmpdir ();
+
+ size_t len = strlen (tmpdir) + strlen (checksum) + 2;
+ char cachedir[len];
+ snprintf (cachedir, len, "%s/%s", tmpdir, checksum);
+
+ /* Touch the directory to prevent it being deleting in a rare race
+ * between us doing the checks and a tmp cleaner running. Note this
+ * doesn't create the directory, and we ignore any error.
+ */
+ (void) utime (cachedir, NULL);
+
+ /* See if the cache directory exists and passes some simple checks
+ * to make sure it has not been tampered with. Note that geteuid()
+ * forms a part of the checksum.
+ */
+ struct stat statbuf;
+ if (lstat (cachedir, &statbuf) == -1)
+ return 0;
+
+ if (security_check_cache_file (g, cachedir, &statbuf) == -1)
+ return -1;
+
+ int ret;
+
+ *kernel = safe_malloc (g, len + 8 /* / + "kernel" + \0 */);
+ *initrd = safe_malloc (g, len + 8 /* / + "initrd" + \0 */);
+ *appliance = safe_malloc (g, len + 6 /* / + "root" + \0 */);
+ sprintf (*kernel, "%s/kernel", cachedir);
+ sprintf (*initrd, "%s/initrd", cachedir);
+ sprintf (*appliance, "%s/root", cachedir);
+
+ /* Touch the files to prevent them being deleted, and to bring the
+ * cache up to date. Note this doesn't create the files.
+ */
+ (void) utime (*kernel, NULL);
+
+ /* NB. *kernel is a symlink, so we want to check the kernel, not the
+ * link (stat, not lstat). We don't do a security check on the
+ * kernel since it's always under /boot.
+ */
+ if (stat (*kernel, &statbuf) == -1) {
+ ret = 0;
+ goto error;
+ }
+
+ (void) utime (*initrd, NULL);
+
+ if (lstat (*initrd, &statbuf) == -1) {
+ ret = 0;
+ goto error;
+ }
+
+ if (security_check_cache_file (g, *initrd, &statbuf) == -1) {
+ ret = -1;
+ goto error;
+ }
+
+ (void) utime (*appliance, NULL);
+
+ if (lstat (*appliance, &statbuf) == -1) {
+ ret = 0;
+ goto error;
+ }
+
+ if (security_check_cache_file (g, *appliance, &statbuf) == -1) {
+ ret = -1;
+ goto error;
+ }
+
+ /* Exists! */
+ return 1;
+
+ error:
+ free (*kernel);
+ free (*initrd);
+ free (*appliance);
+ return ret;
+}
+
+/* Build supermin appliance from supermin_path to $TMPDIR/$checksum.
+ *
+ * Returns:
+ * 0 = built
+ * -1 = error (aborts launch)
+ */
+static int
+build_supermin_appliance (guestfs_h *g,
+ const char *supermin_path, const char *checksum,
+ char **kernel, char **initrd, char **appliance)
+{
+ if (g->verbose)
+ guestfs___print_timestamped_message (g, "begin building supermin appliance");
+
+ const char *tmpdir = guestfs___tmpdir ();
+ size_t cdlen = strlen (tmpdir) + strlen (checksum) + 2;
+ char cachedir[cdlen];
+ snprintf (cachedir, cdlen, "%s/%s", tmpdir, checksum);
+
+ /* Don't worry about this failing, because the command below will
+ * fail if the directory doesn't exist. Note the directory might
+ * already exist, eg. if a tmp cleaner has removed the existing
+ * appliance but not the directory itself.
+ */
+ (void) mkdir (cachedir, 0755);
+
+ /* Set a sensible umask in the subprocess, so kernel and initrd
+ * output files are world-readable (RHBZ#610880).
+ */
+ size_t cmdlen = 2 * strlen (supermin_path) + 3 * (cdlen + 16) + 256;
+ char cmd[cmdlen];
+ snprintf (cmd, cmdlen,
+ "umask 0022; "
+ "febootstrap-supermin-helper%s "
+ "-f ext2 "
+ "-k '%s/kmod.whitelist' "
+ "'%s/supermin.d' "
+ host_cpu " "
+ "%s/kernel %s/initrd %s/root",
+ g->verbose ? " --verbose" : "",
+ supermin_path, supermin_path,
+ cachedir, cachedir, cachedir);
+ if (g->verbose)
+ guestfs___print_timestamped_message (g, "%s", cmd);
+ int r = system (cmd);
+ if (r == -1 || WEXITSTATUS (r) != 0) {
+ error (g, _("external command failed: %s"), cmd);
+ return -1;
+ }
+
+ if (g->verbose)
+ guestfs___print_timestamped_message (g, "finished building supermin appliance");
+
+ *kernel = safe_malloc (g, cdlen + 8 /* / + "kernel" + \0 */);
+ *initrd = safe_malloc (g, cdlen + 8 /* / + "initrd" + \0 */);
+ *appliance = safe_malloc (g, cdlen + 6 /* / + "root" + \0 */);
+ sprintf (*kernel, "%s/kernel", cachedir);
+ sprintf (*initrd, "%s/initrd", cachedir);
+ sprintf (*appliance, "%s/root", cachedir);
+
+ return 0;
+}
+
+/* Search elements of g->path, returning the first path element which
+ * matches the predicate function 'pred'.
+ *
+ * Function 'pred' must return a true or false value. If it returns
+ * -1 then the entire search is aborted.
+ *
+ * Return values:
+ * 1 = a path element matched, it is returned in *pelem_ret and must be
+ * freed by the caller,
+ * 0 = no path element matched, *pelem_ret is set to NULL, or
+ * -1 = error which aborts the launch process
+ */
+static int
+find_path (guestfs_h *g,
+ int (*pred) (guestfs_h *g, const char *pelem, void *data),
+ void *data,
+ char **pelem_ret)
+{
+ size_t len;
+ int r;
+ const char *pelem = g->path;
+
+ /* Note that if g->path is an empty string, we want to check the
+ * current directory (for backwards compatibility with
+ * libguestfs < 1.5.4).
+ */
+ do {
+ len = strcspn (pelem, ":");
+
+ /* Empty element or "." means current directory. */
+ if (len == 0)
+ *pelem_ret = safe_strdup (g, ".");
+ else
+ *pelem_ret = safe_strndup (g, pelem, len);
+
+ r = pred (g, *pelem_ret, data);
+ if (r == -1) {
+ free (*pelem_ret);
+ return -1;
+ }
+
+ if (r != 0) /* predicate matched */
+ return 1;
+
+ free (*pelem_ret);
+
+ if (pelem[len] == ':')
+ pelem += len + 1;
+ else
+ pelem += len;
+ } while (*pelem);
+
+ /* Predicate didn't match on any path element. */
+ *pelem_ret = NULL;
+ return 0;
+}
+
+/* Returns true iff file is contained in dir. */
+static int
+dir_contains_file (const char *dir, const char *file)
+{
+ size_t dirlen = strlen (dir);
+ size_t filelen = strlen (file);
+ size_t len = dirlen + filelen + 2;
+ char path[len];
+
+ snprintf (path, len, "%s/%s", dir, file);
+ return access (path, F_OK) == 0;
+}
+
+/* Returns true iff every listed file is contained in 'dir'. */
+static int
+dir_contains_files (const char *dir, ...)
+{
+ va_list args;
+ const char *file;
+
+ va_start (args, dir);
+ while ((file = va_arg (args, const char *)) != NULL) {
+ if (!dir_contains_file (dir, file)) {
+ va_end (args);
+ return 0;
+ }
+ }
+ va_end (args);
+ return 1;
+}
return guestfs__config (g, "-cdrom", filename);
}
-/* Returns true iff file is contained in dir. */
-static int
-dir_contains_file (const char *dir, const char *file)
-{
- int dirlen = strlen (dir);
- int filelen = strlen (file);
- int len = dirlen+filelen+2;
- char path[len];
-
- snprintf (path, len, "%s/%s", dir, file);
- return access (path, F_OK) == 0;
-}
-
-/* Returns true iff every listed file is contained in 'dir'. */
-static int
-dir_contains_files (const char *dir, ...)
-{
- va_list args;
- const char *file;
-
- va_start (args, dir);
- while ((file = va_arg (args, const char *)) != NULL) {
- if (!dir_contains_file (dir, file)) {
- va_end (args);
- return 0;
- }
- }
- va_end (args);
- return 1;
-}
-
-static int build_supermin_appliance (guestfs_h *g, const char *path, char **kernel, char **initrd);
static int is_openable (guestfs_h *g, const char *path, int flags);
static void print_cmdline (guestfs_h *g);
-static const char *kernel_name = "vmlinuz." REPO "." host_cpu;
-static const char *initrd_name = "initramfs." REPO "." host_cpu ".img";
-
int
guestfs__launch (guestfs_h *g)
{
- int r, pmore;
- size_t len;
+ int r;
int wfd[2], rfd[2];
int tries;
- char *path, *pelem, *pend;
- char *kernel = NULL, *initrd = NULL;
int null_vmchannel_sock;
char unixsock[256];
struct sockaddr_un addr;
}
}
- /* Allow anyone to read the temporary directory. There are no
- * secrets in the kernel or initrd files. The socket in this
+ /* Allow anyone to read the temporary directory. The socket in this
* directory won't be readable but anyone can see it exists if they
* want. (RHBZ#610880).
*/
if (chmod (g->tmpdir, 0755) == -1)
fprintf (stderr, "chmod: %s: %m (ignored)\n", g->tmpdir);
- /* First search g->path for the supermin appliance, and try to
- * synthesize a kernel and initrd from that. If it fails, we
- * try the path search again looking for a backup ordinary
- * appliance.
- */
- pelem = path = safe_strdup (g, g->path);
- do {
- pend = strchrnul (pelem, ':');
- pmore = *pend == ':';
- *pend = '\0';
- len = pend - pelem;
-
- /* Empty element of "." means cwd. */
- if (len == 0 || (len == 1 && *pelem == '.')) {
- if (g->verbose)
- fprintf (stderr,
- "looking for supermin appliance in current directory\n");
- if (dir_contains_files (".",
- "supermin.d", "kmod.whitelist", NULL)) {
- if (build_supermin_appliance (g, ".", &kernel, &initrd) == -1)
- return -1;
- break;
- }
- }
- /* Look at <path>/supermin* etc. */
- else {
- if (g->verbose)
- fprintf (stderr, "looking for supermin appliance in %s\n", pelem);
-
- if (dir_contains_files (pelem,
- "supermin.d", "kmod.whitelist", NULL)) {
- if (build_supermin_appliance (g, pelem, &kernel, &initrd) == -1)
- return -1;
- break;
- }
- }
-
- pelem = pend + 1;
- } while (pmore);
-
- free (path);
-
- if (kernel == NULL || initrd == NULL) {
- /* 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 appliance in current directory\n");
- if (dir_contains_files (".", kernel_name, initrd_name, NULL)) {
- kernel = safe_strdup (g, kernel_name);
- initrd = safe_strdup (g, initrd_name);
- break;
- }
- }
- /* Look at <path>/kernel etc. */
- else {
- if (g->verbose)
- fprintf (stderr, "looking for appliance in %s\n", pelem);
-
- if (dir_contains_files (pelem, kernel_name, initrd_name, NULL)) {
- 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);
- break;
- }
- }
-
- 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;
- }
+ /* Locate and/or build the appliance. */
+ char *kernel = NULL, *initrd = NULL, *appliance = NULL;
+ if (guestfs___build_appliance (g, &kernel, &initrd, &appliance) == -1)
+ return -1;
if (g->verbose)
guestfs___print_timestamped_message (g, "begin testing qemu features");
g->append ? g->append : "");
add_cmdline (g, "-kernel");
- add_cmdline (g, (char *) kernel);
+ add_cmdline (g, kernel);
add_cmdline (g, "-initrd");
- add_cmdline (g, (char *) initrd);
+ add_cmdline (g, initrd);
add_cmdline (g, "-append");
add_cmdline (g, buf);
+ /* Add the ext2 appliance drive (last of all). */
+ if (appliance) {
+ const char *cachemode = "";
+ if (qemu_supports (g, "cache=")) {
+ if (qemu_supports (g, "unsafe"))
+ cachemode = ",cache=unsafe";
+ else if (qemu_supports (g, "writeback"))
+ cachemode = ",cache=writeback";
+ }
+
+ char buf2[PATH_MAX + 64];
+ add_cmdline (g, "-drive");
+ snprintf (buf2, sizeof buf2, "file=%s,snapshot=on,if=" DRIVE_IF "%s",
+ appliance, cachemode);
+ add_cmdline (g, buf2);
+ }
+
/* Finish off the command line. */
incr_cmdline_size (g);
g->cmdline[g->cmdline_size-1] = NULL;
g->state = CONFIG;
free (kernel);
free (initrd);
+ free (appliance);
return -1;
}
fputc ('\n', stderr);
}
-/* This function does the hard work of building the supermin appliance
- * on the fly. 'path' is the directory containing the control files.
- * 'kernel' and 'initrd' are where we will return the names of the
- * kernel and initrd (only initrd is built). The work is done by
- * an external script. We just tell it where to put the result.
- */
-static int
-build_supermin_appliance (guestfs_h *g, const char *path,
- char **kernel, char **initrd)
-{
- char cmd[4096];
- int r, len;
-
- if (g->verbose)
- print_timestamped_message (g, "begin building supermin appliance");
-
- len = strlen (g->tmpdir);
- *kernel = safe_malloc (g, len + 8);
- snprintf (*kernel, len+8, "%s/kernel", g->tmpdir);
- *initrd = safe_malloc (g, len + 8);
- snprintf (*initrd, len+8, "%s/initrd", g->tmpdir);
-
- /* Set a sensible umask in the subprocess, so kernel and initrd
- * output files are world-readable (RHBZ#610880).
- */
- snprintf (cmd, sizeof cmd,
- "umask 0002; "
- "febootstrap-supermin-helper%s "
- "-k '%s/kmod.whitelist' "
- "'%s/supermin.d' "
- host_cpu " "
- "%s %s",
- g->verbose ? " --verbose" : "",
- path,
- path,
- *kernel, *initrd);
- if (g->verbose)
- print_timestamped_message (g, "%s", cmd);
-
- r = system (cmd);
- if (r == -1 || WEXITSTATUS(r) != 0) {
- error (g, _("external command failed: %s"), cmd);
- free (*kernel);
- free (*initrd);
- *kernel = *initrd = NULL;
- return -1;
- }
-
- if (g->verbose)
- print_timestamped_message (g, "finished building supermin appliance");
-
- return 0;
-}
-
/* Compute Y - X and return the result in milliseconds.
* Approximately the same as this code:
* http://www.mpp.mpg.de/~huber/util/timevaldiff.c