2 * Copyright (C) 2010 Red Hat Inc.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
31 #include <sys/select.h>
35 #ifdef HAVE_SYS_TYPES_H
36 #include <sys/types.h>
40 #include "guestfs-internal.h"
41 #include "guestfs-internal-actions.h"
42 #include "guestfs_protocol.h"
44 /* Old-style appliance is going to be obsoleted. */
45 static const char *kernel_name = "vmlinuz." host_cpu;
46 static const char *initrd_name = "initramfs." host_cpu ".img";
48 static int find_path (guestfs_h *g, int (*pred) (guestfs_h *g, const char *pelem, void *data), void *data, char **pelem);
49 static int dir_contains_file (const char *dir, const char *file);
50 static int dir_contains_files (const char *dir, ...);
51 static int contains_ordinary_appliance (guestfs_h *g, const char *path, void *data);
52 static int contains_supermin_appliance (guestfs_h *g, const char *path, void *data);
53 static char *calculate_supermin_checksum (guestfs_h *g, const char *supermin_path);
54 static int check_for_cached_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, uid_t uid, char **kernel, char **initrd, char **appliance);
55 static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, uid_t uid, char **kernel, char **initrd, char **appliance);
56 static int hard_link_to_cached_appliance (guestfs_h *g, const char *cachedir, char **kernel, char **initrd, char **appliance);
57 static int run_supermin_helper (guestfs_h *g, const char *supermin_path, const char *cachedir, size_t cdlen);
59 /* Locate or build the appliance.
61 * This function locates or builds the appliance as necessary,
62 * handling the supermin appliance, caching of supermin-built
63 * appliances, or using an ordinary appliance.
65 * The return value is 0 = good, -1 = error. Returned in '*kernel'
66 * will be the name of the kernel to use, '*initrd' the name of the
67 * initrd, '*appliance' the name of the ext2 root filesystem.
68 * '*appliance' can be NULL, meaning that we are using an ordinary
69 * (non-ext2) appliance. All three strings must be freed by the
70 * caller. However the referenced files themselves must not be
73 * The process is as follows:
75 * (1) Look for the first element of g->path which contains a
76 * supermin appliance skeleton. If no element has this, skip
77 * straight to step (5).
79 * (2) Calculate the checksum of this supermin appliance.
81 * (3) Check whether a cached appliance with the checksum calculated
82 * in (2) exists and passes basic security checks. If so, return
85 * (4) Try to build the supermin appliance. If this is successful,
88 * (5) Check each element of g->path, looking for an ordinary appliance.
89 * If one is found, return it.
91 * The supermin appliance cache directory lives in
92 * $TMPDIR/.guestfs-$UID/ and consists of four files:
94 * $TMPDIR/.guestfs-$UID/checksum - the checksum
95 * $TMPDIR/.guestfs-$UID/kernel - symlink to the kernel
96 * $TMPDIR/.guestfs-$UID/initrd - the febootstrap initrd
97 * $TMPDIR/.guestfs-$UID/root - the appliance
99 * Since multiple instances of libguestfs with the same UID may be
100 * racing to create an appliance, we need to be careful when building
101 * and using the appliance.
103 * If a cached appliance with checksum exists (step (2) above) then we
104 * make a hard link to it with our current PID, so that we have a copy
105 * even if the appliance is replaced by another process building an
106 * appliance afterwards:
108 * $TMPDIR/.guestfs-$UID/kernel.$PID
109 * $TMPDIR/.guestfs-$UID/initrd.$PID
110 * $TMPDIR/.guestfs-$UID/root.$PID
112 * A lock is taken on "checksum" while we perform the link.
114 * Linked files are deleted by a garbage collection sweep which can be
115 * initiated by any libguestfs process with the same UID when the
116 * corresponding PID no longer exists. (This is safe: the parent is
117 * always around in guestfs_launch() while qemu is starting up, and
118 * after that qemu will either have finished with the files or be
119 * holding them open, so we can unlink them).
121 * When building a new appliance (step (3)), it is built into randomly
122 * named temporary files in the $TMPDIR. Then a lock is acquired on
123 * $TMPDIR/.guestfs-$UID/checksum (this file being created if
124 * necessary), the files are renamed into their final location, and
125 * the lock is released.
128 guestfs___build_appliance (guestfs_h *g,
129 char **kernel, char **initrd, char **appliance)
132 uid_t uid = geteuid ();
136 r = find_path (g, contains_supermin_appliance, NULL, &supermin_path);
141 /* Step (2): calculate checksum. */
142 char *checksum = calculate_supermin_checksum (g, supermin_path);
144 /* Step (3): cached appliance exists? */
145 r = check_for_cached_appliance (g, supermin_path, checksum, uid,
146 kernel, initrd, appliance);
148 free (supermin_path);
150 return r == 1 ? 0 : -1;
153 /* Step (4): build supermin appliance. */
154 r = build_supermin_appliance (g, supermin_path, checksum, uid,
155 kernel, initrd, appliance);
156 free (supermin_path);
160 free (supermin_path);
165 r = find_path (g, contains_ordinary_appliance, NULL, &path);
170 size_t len = strlen (path);
171 *kernel = safe_malloc (g, len + strlen (kernel_name) + 2);
172 *initrd = safe_malloc (g, len + strlen (initrd_name) + 2);
173 sprintf (*kernel, "%s/%s", path, kernel_name);
174 sprintf (*initrd, "%s/%s", path, initrd_name);
181 error (g, _("cannot find any suitable libguestfs supermin or ordinary appliance on LIBGUESTFS_PATH (search path: %s)"),
187 contains_ordinary_appliance (guestfs_h *g, const char *path, void *data)
189 return dir_contains_files (path, kernel_name, initrd_name, NULL);
193 contains_supermin_appliance (guestfs_h *g, const char *path, void *data)
195 return dir_contains_files (path, "supermin.d", NULL);
198 /* supermin_path is a path which is known to contain a supermin
199 * appliance. Using febootstrap-supermin-helper -f checksum calculate
200 * the checksum so we can see if it is cached.
203 calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
205 size_t len = 2 * strlen (supermin_path) + 256;
207 int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
211 "febootstrap-supermin-helper%s "
215 g->verbose ? " --verbose" : "",
219 "febootstrap-supermin-helper%s "
225 g->verbose ? " --verbose" : "",
226 geteuid (), getegid (),
230 guestfs___print_timestamped_message (g, "%s", cmd);
232 /* Errors here are non-fatal, so we don't need to call error(). */
233 FILE *pp = popen (cmd, "r");
238 if (fgets (checksum, sizeof checksum, pp) == NULL) {
243 if (pclose (pp) == -1) {
248 len = strlen (checksum);
250 if (len < 16) { /* sanity check */
251 fprintf (stderr, "libguestfs: internal error: febootstrap-supermin-helper -f checksum returned a short string\n");
255 if (len > 0 && checksum[len-1] == '\n')
256 checksum[--len] = '\0';
258 return safe_strndup (g, checksum, len);
262 process_exists (int pid)
264 if (kill (pid, 0) == 0)
273 /* Garbage collect appliance hard links. Files that match
274 * (kernel|initrd|root).$PID where the corresponding PID doesn't exist
275 * are deleted. Note that errors in this function don't matter.
276 * There may also be other libguestfs processes racing to do the same
280 garbage_collect_appliances (const char *cachedir)
286 dir = opendir (cachedir);
290 while ((d = readdir (dir)) != NULL) {
291 if (sscanf (d->d_name, "kernel.%d", &pid) == 1 &&
292 process_exists (pid) == 0)
293 unlinkat (dirfd (dir), d->d_name, 0);
294 else if (sscanf (d->d_name, "initrd.%d", &pid) == 1 &&
295 process_exists (pid) == 0)
296 unlinkat (dirfd (dir), d->d_name, 0);
297 else if (sscanf (d->d_name, "root.%d", &pid) == 1 &&
298 process_exists (pid) == 0)
299 unlinkat (dirfd (dir), d->d_name, 0);
306 check_for_cached_appliance (guestfs_h *g,
307 const char *supermin_path, const char *checksum,
309 char **kernel, char **initrd, char **appliance)
311 const char *tmpdir = guestfs_tmpdir ();
313 /* len must be longer than the length of any pathname we can
314 * generate in this function.
316 size_t len = strlen (tmpdir) + 128;
318 snprintf (cachedir, len, "%s/.guestfs-%d", tmpdir, uid);
320 snprintf (filename, len, "%s/checksum", cachedir);
322 (void) mkdir (cachedir, 0755);
324 /* See if the cache directory exists and passes some simple checks
325 * to make sure it has not been tampered with.
328 if (lstat (cachedir, &statbuf) == -1)
330 if (statbuf.st_uid != uid) {
331 error (g, _("security: cached appliance %s is not owned by UID %d"),
335 if (!S_ISDIR (statbuf.st_mode)) {
336 error (g, _("security: cached appliance %s is not a directory (mode %o)"),
337 filename, statbuf.st_mode);
340 if ((statbuf.st_mode & 0022) != 0) {
341 error (g, _("security: cached appliance %s is writable by group or other (mode %o)"),
342 cachedir, statbuf.st_mode);
346 (void) utime (cachedir, NULL);
348 garbage_collect_appliances (cachedir);
350 /* Try to open and acquire a lock on the checksum file. */
351 int fd = open (filename, O_RDONLY);
354 (void) futimens (fd, NULL);
357 fl.l_whence = SEEK_SET;
361 if (fcntl (fd, F_SETLKW, &fl) == -1) {
364 perrorf (g, "fcntl: F_SETLKW: %s", filename);
369 /* Read the checksum file. */
370 size_t clen = strlen (checksum);
371 char checksum_on_disk[clen];
372 ssize_t rr = read (fd, checksum_on_disk, clen);
374 perrorf (g, "read: %s", filename);
378 if ((size_t) rr != clen) {
383 if (memcmp (checksum, checksum_on_disk, clen) != 0) {
388 /* At this point, cachedir exists, and checksum matches, and we have
389 * a read lock on the checksum file. Make hard links to the files.
391 if (hard_link_to_cached_appliance (g, cachedir,
392 kernel, initrd, appliance) == -1) {
397 /* Releases the lock on checksum. */
398 if (close (fd) == -1) {
399 perrorf (g, "close");
407 /* Build supermin appliance from supermin_path to $TMPDIR/.guestfs-$UID.
411 * -1 = error (aborts launch)
414 build_supermin_appliance (guestfs_h *g,
415 const char *supermin_path, const char *checksum,
417 char **kernel, char **initrd, char **appliance)
420 guestfs___print_timestamped_message (g, "begin building supermin appliance");
422 const char *tmpdir = guestfs_tmpdir ();
424 /* len must be longer than the length of any pathname we can
425 * generate in this function.
427 size_t len = strlen (tmpdir) + 128;
429 /* Build the appliance into a temporary directory. */
431 snprintf (tmpcd, len, "%s/guestfs.XXXXXX", tmpdir);
433 if (mkdtemp (tmpcd) == NULL) {
434 perrorf (g, "mkdtemp");
439 guestfs___print_timestamped_message (g, "run febootstrap-supermin-helper");
441 int r = run_supermin_helper (g, supermin_path, tmpcd, len);
446 guestfs___print_timestamped_message (g, "finished building supermin appliance");
449 snprintf (cachedir, len, "%s/.guestfs-%d", tmpdir, uid);
452 snprintf (filename, len, "%s/checksum", cachedir);
454 /* Open and acquire write lock on checksum file. The file might
455 * not exist, in which case we want to create it.
457 int fd = open (filename, O_WRONLY|O_CREAT, 0755);
459 perrorf (g, "open: %s", filename);
464 fl.l_whence = SEEK_SET;
468 if (fcntl (fd, F_SETLKW, &fl) == -1) {
471 perrorf (g, "fcntl: F_SETLKW: %s", filename);
476 /* At this point we have acquired a write lock on the checksum
477 * file so we go ahead and replace it with the new checksum, and
478 * rename in appliance files into this directory.
480 size_t clen = strlen (checksum);
481 if (ftruncate (fd, clen) == -1) {
482 perrorf (g, "ftruncate: %s", filename);
487 ssize_t rr = write (fd, checksum, clen);
489 perrorf (g, "write: %s", filename);
493 if ((size_t) rr != clen) {
494 error (g, "partial write: %s", filename);
499 snprintf (filename, len, "%s/kernel", tmpcd);
500 snprintf (filename2, len, "%s/kernel", cachedir);
502 if (rename (filename, filename2) == -1) {
503 perrorf (g, "rename: %s %s", filename, filename2);
508 snprintf (filename, len, "%s/initrd", tmpcd);
509 snprintf (filename2, len, "%s/initrd", cachedir);
511 if (rename (filename, filename2) == -1) {
512 perrorf (g, "rename: %s %s", filename, filename2);
517 snprintf (filename, len, "%s/root", tmpcd);
518 snprintf (filename2, len, "%s/root", cachedir);
520 if (rename (filename, filename2) == -1) {
521 perrorf (g, "rename: %s %s", filename, filename2);
528 /* Now finish off by linking to the cached appliance and returning it. */
529 if (hard_link_to_cached_appliance (g, cachedir,
530 kernel, initrd, appliance) == -1) {
535 /* Releases the lock on checksum. */
536 if (close (fd) == -1) {
537 perrorf (g, "close");
544 /* NB: lock on checksum file must be held when this is called. */
546 hard_link_to_cached_appliance (guestfs_h *g,
547 const char *cachedir,
548 char **kernel, char **initrd, char **appliance)
550 pid_t pid = getpid ();
551 size_t len = strlen (cachedir) + 32;
553 *kernel = safe_malloc (g, len);
554 *initrd = safe_malloc (g, len);
555 *appliance = safe_malloc (g, len);
556 snprintf (*kernel, len, "%s/kernel.%d", cachedir, pid);
557 snprintf (*initrd, len, "%s/initrd.%d", cachedir, pid);
558 snprintf (*appliance, len, "%s/root.%d", cachedir, pid);
561 snprintf (filename, len, "%s/kernel", cachedir);
562 (void) unlink (*kernel);
563 if (link (filename, *kernel) == -1) {
564 perrorf (g, "link: %s %s", filename, *kernel);
567 (void) lutimes (filename, NULL); /* lutimes because it's a symlink */
569 snprintf (filename, len, "%s/initrd", cachedir);
570 (void) unlink (*initrd);
571 if (link (filename, *initrd) == -1) {
572 perrorf (g, "link: %s %s", filename, *initrd);
575 (void) utime (filename, NULL);
577 snprintf (filename, len, "%s/root", cachedir);
578 (void) unlink (*appliance);
579 if (link (filename, *appliance) == -1) {
580 perrorf (g, "link: %s %s", filename, *appliance);
583 (void) utime (filename, NULL);
594 /* Run febootstrap-supermin-helper and tell it to generate the
598 run_supermin_helper (guestfs_h *g, const char *supermin_path,
599 const char *cachedir, size_t cdlen)
601 size_t pathlen = strlen (supermin_path);
603 const char *argv[30];
607 snprintf (uid, sizeof uid, "%i", geteuid ());
609 snprintf (gid, sizeof gid, "%i", getegid ());
610 char supermin_d[pathlen + 32];
611 snprintf (supermin_d, pathlen + 32, "%s/supermin.d", supermin_path);
612 char kernel[cdlen + 32];
613 snprintf (kernel, cdlen + 32, "%s/kernel", cachedir);
614 char initrd[cdlen + 32];
615 snprintf (initrd, cdlen + 32, "%s/initrd", cachedir);
616 char root[cdlen + 32];
617 snprintf (root, cdlen + 32, "%s/root", cachedir);
619 int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
621 argv[i++] = "febootstrap-supermin-helper";
623 argv[i++] = "--verbose";
632 argv[i++] = supermin_d;
633 argv[i++] = host_cpu;
645 if (pid > 0) { /* Parent. */
647 guestfs___print_timestamped_argv (g, argv);
650 if (waitpid (pid, &status, 0) == -1) {
651 perrorf (g, "waitpid");
654 if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
655 error (g, _("external command failed, see earlier error messages"));
663 /* Set a sensible umask in the subprocess, so kernel and initrd
664 * output files are world-readable (RHBZ#610880).
668 execvp ("febootstrap-supermin-helper", (char * const *) argv);
670 _exit (EXIT_FAILURE);
673 /* Search elements of g->path, returning the first path element which
674 * matches the predicate function 'pred'.
676 * Function 'pred' must return a true or false value. If it returns
677 * -1 then the entire search is aborted.
680 * 1 = a path element matched, it is returned in *pelem_ret and must be
681 * freed by the caller,
682 * 0 = no path element matched, *pelem_ret is set to NULL, or
683 * -1 = error which aborts the launch process
686 find_path (guestfs_h *g,
687 int (*pred) (guestfs_h *g, const char *pelem, void *data),
693 const char *pelem = g->path;
695 /* Note that if g->path is an empty string, we want to check the
696 * current directory (for backwards compatibility with
697 * libguestfs < 1.5.4).
700 len = strcspn (pelem, ":");
702 /* Empty element or "." means current directory. */
704 *pelem_ret = safe_strdup (g, ".");
706 *pelem_ret = safe_strndup (g, pelem, len);
708 r = pred (g, *pelem_ret, data);
714 if (r != 0) /* predicate matched */
719 if (pelem[len] == ':')
725 /* Predicate didn't match on any path element. */
730 /* Returns true iff file is contained in dir. */
732 dir_contains_file (const char *dir, const char *file)
734 size_t dirlen = strlen (dir);
735 size_t filelen = strlen (file);
736 size_t len = dirlen + filelen + 2;
739 snprintf (path, len, "%s/%s", dir, file);
740 return access (path, F_OK) == 0;
743 /* Returns true iff every listed file is contained in 'dir'. */
745 dir_contains_files (const char *dir, ...)
750 va_start (args, dir);
751 while ((file = va_arg (args, const char *)) != NULL) {
752 if (!dir_contains_file (dir, file)) {