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, char **kernel, char **initrd, char **appliance);
55 static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance);
56 static int run_supermin_helper (guestfs_h *g, const char *supermin_path, const char *cachedir, size_t cdlen);
58 /* Locate or build the appliance.
60 * This function locates or builds the appliance as necessary,
61 * handling the supermin appliance, caching of supermin-built
62 * appliances, or using an ordinary appliance.
64 * The return value is 0 = good, -1 = error. Returned in '*kernel'
65 * will be the name of the kernel to use, '*initrd' the name of the
66 * initrd, '*appliance' the name of the ext2 root filesystem.
67 * '*appliance' can be NULL, meaning that we are using an ordinary
68 * (non-ext2) appliance. All three strings must be freed by the
69 * caller. However the referenced files themselves must not be
72 * The process is as follows:
74 * (1) Look for the first element of g->path which contains a
75 * supermin appliance skeleton. If no element has this, skip
76 * straight to step (5).
77 * (2) Calculate the checksum of this supermin appliance.
78 * (3) Check whether $TMPDIR/$checksum/ directory exists, contains
79 * a cached appliance, and passes basic security checks. If so,
80 * return this appliance.
81 * (4) Try to build the supermin appliance into $TMPDIR/$checksum/.
82 * If this is successful, return it.
83 * (5) Check each element of g->path, looking for an ordinary appliance.
84 * If one is found, return it.
87 guestfs___build_appliance (guestfs_h *g,
88 char **kernel, char **initrd, char **appliance)
94 r = find_path (g, contains_supermin_appliance, NULL, &supermin_path);
99 /* Step (2): calculate checksum. */
100 char *checksum = calculate_supermin_checksum (g, supermin_path);
102 /* Step (3): cached appliance exists? */
103 r = check_for_cached_appliance (g, supermin_path, checksum,
104 kernel, initrd, appliance);
106 free (supermin_path);
108 return r == 1 ? 0 : -1;
111 /* Step (4): build supermin appliance. */
112 r = build_supermin_appliance (g, supermin_path, checksum,
113 kernel, initrd, appliance);
114 free (supermin_path);
118 free (supermin_path);
123 r = find_path (g, contains_ordinary_appliance, NULL, &path);
128 size_t len = strlen (path);
129 *kernel = safe_malloc (g, len + strlen (kernel_name) + 2);
130 *initrd = safe_malloc (g, len + strlen (initrd_name) + 2);
131 sprintf (*kernel, "%s/%s", path, kernel_name);
132 sprintf (*initrd, "%s/%s", path, initrd_name);
139 error (g, _("cannot find any suitable libguestfs supermin or ordinary appliance on LIBGUESTFS_PATH (search path: %s)"),
145 contains_ordinary_appliance (guestfs_h *g, const char *path, void *data)
147 return dir_contains_files (path, kernel_name, initrd_name, NULL);
151 contains_supermin_appliance (guestfs_h *g, const char *path, void *data)
153 return dir_contains_files (path, "supermin.d", NULL);
156 /* supermin_path is a path which is known to contain a supermin
157 * appliance. Using febootstrap-supermin-helper -f checksum calculate
158 * the checksum so we can see if it is cached.
161 calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
163 size_t len = 2 * strlen (supermin_path) + 256;
165 int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
169 "febootstrap-supermin-helper%s "
173 g->verbose ? " --verbose" : "",
177 "febootstrap-supermin-helper%s "
183 g->verbose ? " --verbose" : "",
184 geteuid (), getegid (),
188 guestfs___print_timestamped_message (g, "%s", cmd);
190 /* Errors here are non-fatal, so we don't need to call error(). */
191 FILE *pp = popen (cmd, "r");
196 if (fgets (checksum, sizeof checksum, pp) == NULL) {
201 if (pclose (pp) == -1) {
206 len = strlen (checksum);
208 if (len < 16) { /* sanity check */
209 fprintf (stderr, "libguestfs: internal error: febootstrap-supermin-helper -f checksum returned a short string\n");
213 if (len > 0 && checksum[len-1] == '\n')
214 checksum[--len] = '\0';
216 return safe_strndup (g, checksum, len);
219 /* Check for cached appliance in $TMPDIR/$checksum. Check it exists
220 * and passes some basic security checks.
223 * 1 = exists, and passes
225 * -1 = error which should abort the whole launch process
228 security_check_cache_file (guestfs_h *g, const char *filename,
229 const struct stat *statbuf)
231 uid_t uid = geteuid ();
233 if (statbuf->st_uid != uid) {
234 error (g, ("libguestfs cached appliance %s is not owned by UID %d\n"),
239 if ((statbuf->st_mode & 0022) != 0) {
240 error (g, ("libguestfs cached appliance %s is writable by group or other (mode %o)\n"),
241 filename, statbuf->st_mode);
249 check_for_cached_appliance (guestfs_h *g,
250 const char *supermin_path, const char *checksum,
251 char **kernel, char **initrd, char **appliance)
253 const char *tmpdir = guestfs_tmpdir ();
255 size_t len = strlen (tmpdir) + strlen (checksum) + 10;
257 snprintf (cachedir, len, "%s/guestfs.%s", tmpdir, checksum);
259 /* Touch the directory to prevent it being deleting in a rare race
260 * between us doing the checks and a tmp cleaner running. Note this
261 * doesn't create the directory, and we ignore any error.
263 (void) utime (cachedir, NULL);
265 /* See if the cache directory exists and passes some simple checks
266 * to make sure it has not been tampered with. Note that geteuid()
267 * forms a part of the checksum.
270 if (lstat (cachedir, &statbuf) == -1)
273 if (security_check_cache_file (g, cachedir, &statbuf) == -1)
278 *kernel = safe_malloc (g, len + 8 /* / + "kernel" + \0 */);
279 *initrd = safe_malloc (g, len + 8 /* / + "initrd" + \0 */);
280 *appliance = safe_malloc (g, len + 6 /* / + "root" + \0 */);
281 sprintf (*kernel, "%s/kernel", cachedir);
282 sprintf (*initrd, "%s/initrd", cachedir);
283 sprintf (*appliance, "%s/root", cachedir);
285 /* Touch the files to prevent them being deleted, and to bring the
286 * cache up to date. Note this doesn't create the files.
288 (void) utime (*kernel, NULL);
290 /* NB. *kernel is a symlink, so we want to check the kernel, not the
291 * link (stat, not lstat). We don't do a security check on the
292 * kernel since it's always under /boot.
294 if (stat (*kernel, &statbuf) == -1) {
299 (void) utime (*initrd, NULL);
301 if (lstat (*initrd, &statbuf) == -1) {
306 if (security_check_cache_file (g, *initrd, &statbuf) == -1) {
311 (void) utime (*appliance, NULL);
313 if (lstat (*appliance, &statbuf) == -1) {
318 if (security_check_cache_file (g, *appliance, &statbuf) == -1) {
333 /* Build supermin appliance from supermin_path to $TMPDIR/$checksum.
337 * -1 = error (aborts launch)
340 build_supermin_appliance (guestfs_h *g,
341 const char *supermin_path, const char *checksum,
342 char **kernel, char **initrd, char **appliance)
345 guestfs___print_timestamped_message (g, "begin building supermin appliance");
347 const char *tmpdir = guestfs_tmpdir ();
349 size_t tmpcdlen = strlen (tmpdir) + 16;
350 char tmpcd[tmpcdlen];
351 snprintf (tmpcd, tmpcdlen, "%s/guestfs.XXXXXX", tmpdir);
353 if (NULL == mkdtemp (tmpcd)) {
354 error (g, _("failed to create temporary cache directory: %m"));
359 guestfs___print_timestamped_message (g, "run febootstrap-supermin-helper");
361 int r = run_supermin_helper (g, supermin_path, tmpcd, tmpcdlen);
366 guestfs___print_timestamped_message (g, "finished building supermin appliance");
368 size_t cdlen = strlen (tmpdir) + strlen (checksum) + 10;
369 char cachedir[cdlen];
370 snprintf (cachedir, cdlen, "%s/guestfs.%s", tmpdir, checksum);
372 /* Make the temporary directory world readable */
373 if (chmod (tmpcd, 0755) == -1) {
374 error (g, "chmod %s: %m", tmpcd);
377 /* Try to rename the temporary directory to its non-temporary name */
378 if (rename (tmpcd, cachedir) == -1) {
379 /* If the cache directory now exists, we may have been racing with another
380 * libguestfs process. Check the new directory and use it if it's valid. */
381 if (errno == ENOTEMPTY || errno == EEXIST) {
382 /* Appliance cache consists of 2 files and a symlink in the cache
383 * directory. Delete them first. */
384 DIR *dir = opendir (tmpcd);
386 error (g, "opendir %s: %m", tmpcd);
390 int fd = dirfd (dir);
392 error (g, "dirfd: %m");
397 struct dirent *dirent;
400 dirent = readdir (dir);
402 if (dirent == NULL) {
406 /* Check that dirent is a file so we don't try to delete . and .. */
408 if (fstatat (fd, dirent->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1) {
409 error (g, "fstatat %s: %m", dirent->d_name);
413 if (!S_ISDIR(st.st_mode)) {
414 if (unlinkat (fd, dirent->d_name, 0) == -1) {
415 error (g, "unlinkat %s: %m", dirent->d_name);
423 error (g, "readdir %s: %m", tmpcd);
430 /* Delete the temporary cache directory itself. */
431 if (rmdir (tmpcd) == -1) {
432 error (g, "rmdir %s: %m", tmpcd);
436 /* Check the new cache directory, and return it if valid */
437 return check_for_cached_appliance (g, supermin_path, checksum,
438 kernel, initrd, appliance);
442 error (g, _("error renaming temporary cache directory: %m"));
447 *kernel = safe_malloc (g, cdlen + 8 /* / + "kernel" + \0 */);
448 *initrd = safe_malloc (g, cdlen + 8 /* / + "initrd" + \0 */);
449 *appliance = safe_malloc (g, cdlen + 6 /* / + "root" + \0 */);
450 sprintf (*kernel, "%s/kernel", cachedir);
451 sprintf (*initrd, "%s/initrd", cachedir);
452 sprintf (*appliance, "%s/root", cachedir);
457 /* Run febootstrap-supermin-helper and tell it to generate the
461 run_supermin_helper (guestfs_h *g, const char *supermin_path,
462 const char *cachedir, size_t cdlen)
464 size_t pathlen = strlen (supermin_path);
466 const char *argv[30];
470 snprintf (uid, sizeof uid, "%i", geteuid ());
472 snprintf (gid, sizeof gid, "%i", getegid ());
473 char supermin_d[pathlen + 32];
474 snprintf (supermin_d, pathlen + 32, "%s/supermin.d", supermin_path);
475 char kernel[cdlen + 32];
476 snprintf (kernel, cdlen + 32, "%s/kernel", cachedir);
477 char initrd[cdlen + 32];
478 snprintf (initrd, cdlen + 32, "%s/initrd", cachedir);
479 char root[cdlen + 32];
480 snprintf (root, cdlen + 32, "%s/root", cachedir);
482 int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
484 argv[i++] = "febootstrap-supermin-helper";
486 argv[i++] = "--verbose";
495 argv[i++] = supermin_d;
496 argv[i++] = host_cpu;
508 if (pid > 0) { /* Parent. */
510 guestfs___print_timestamped_argv (g, argv);
513 if (waitpid (pid, &status, 0) == -1) {
514 perrorf (g, "waitpid");
517 if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
518 error (g, _("external command failed, see earlier error messages"));
526 /* Set a sensible umask in the subprocess, so kernel and initrd
527 * output files are world-readable (RHBZ#610880).
531 execvp ("febootstrap-supermin-helper", (char * const *) argv);
533 _exit (EXIT_FAILURE);
536 /* Search elements of g->path, returning the first path element which
537 * matches the predicate function 'pred'.
539 * Function 'pred' must return a true or false value. If it returns
540 * -1 then the entire search is aborted.
543 * 1 = a path element matched, it is returned in *pelem_ret and must be
544 * freed by the caller,
545 * 0 = no path element matched, *pelem_ret is set to NULL, or
546 * -1 = error which aborts the launch process
549 find_path (guestfs_h *g,
550 int (*pred) (guestfs_h *g, const char *pelem, void *data),
556 const char *pelem = g->path;
558 /* Note that if g->path is an empty string, we want to check the
559 * current directory (for backwards compatibility with
560 * libguestfs < 1.5.4).
563 len = strcspn (pelem, ":");
565 /* Empty element or "." means current directory. */
567 *pelem_ret = safe_strdup (g, ".");
569 *pelem_ret = safe_strndup (g, pelem, len);
571 r = pred (g, *pelem_ret, data);
577 if (r != 0) /* predicate matched */
582 if (pelem[len] == ':')
588 /* Predicate didn't match on any path element. */
593 /* Returns true iff file is contained in dir. */
595 dir_contains_file (const char *dir, const char *file)
597 size_t dirlen = strlen (dir);
598 size_t filelen = strlen (file);
599 size_t len = dirlen + filelen + 2;
602 snprintf (path, len, "%s/%s", dir, file);
603 return access (path, F_OK) == 0;
606 /* Returns true iff every listed file is contained in 'dir'. */
608 dir_contains_files (const char *dir, ...)
613 va_start (args, dir);
614 while ((file = va_arg (args, const char *)) != NULL) {
615 if (!dir_contains_file (dir, file)) {