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
29 #include <sys/select.h>
32 #ifdef HAVE_SYS_TYPES_H
33 #include <sys/types.h>
37 #include "guestfs-internal.h"
38 #include "guestfs-internal-actions.h"
39 #include "guestfs_protocol.h"
41 static const char *kernel_name = "vmlinuz." REPO "." host_cpu;
42 static const char *initrd_name = "initramfs." REPO "." host_cpu ".img";
44 static int find_path (guestfs_h *g, int (*pred) (guestfs_h *g, const char *pelem, void *data), void *data, char **pelem);
45 static int dir_contains_file (const char *dir, const char *file);
46 static int dir_contains_files (const char *dir, ...);
47 static int contains_supermin_appliance (guestfs_h *g, const char *path, void *data);
48 static int contains_ordinary_appliance (guestfs_h *g, const char *path, void *data);
49 static char *calculate_supermin_checksum (guestfs_h *g, const char *supermin_path);
50 static int check_for_cached_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance);
51 static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance);
53 /* Locate or build the appliance.
55 * This function locates or builds the appliance as necessary,
56 * handling the supermin appliance, caching of supermin-built
57 * appliances, or using an ordinary appliance.
59 * The return value is 0 = good, -1 = error. Returned in '*kernel'
60 * will be the name of the kernel to use, '*initrd' the name of the
61 * initrd, '*appliance' the name of the ext2 root filesystem.
62 * '*appliance' can be NULL, meaning that we are using an ordinary
63 * (non-ext2) appliance. All three strings must be freed by the
64 * caller. However the referenced files themselves must not be
67 * The process is as follows:
69 * (1) Look for the first element of g->path which contains a
70 * supermin appliance skeleton. If no element has this, skip
71 * straight to step (5).
72 * (2) Calculate the checksum of this supermin appliance.
73 * (3) Check whether $TMPDIR/$checksum/ directory exists, contains
74 * a cached appliance, and passes basic security checks. If so,
75 * return this appliance.
76 * (4) Try to build the supermin appliance into $TMPDIR/$checksum/.
77 * If this is successful, return it.
78 * (5) Check each element of g->path, looking for an ordinary appliance.
79 * If one is found, return it.
82 guestfs___build_appliance (guestfs_h *g,
83 char **kernel, char **initrd, char **appliance)
89 r = find_path (g, contains_supermin_appliance, NULL, &supermin_path);
94 /* Step (2): calculate checksum. */
95 char *checksum = calculate_supermin_checksum (g, supermin_path);
97 /* Step (3): cached appliance exists? */
98 r = check_for_cached_appliance (g, supermin_path, checksum,
99 kernel, initrd, appliance);
101 free (supermin_path);
103 return r == 1 ? 0 : -1;
106 /* Step (4): build supermin appliance. */
107 r = build_supermin_appliance (g, supermin_path, checksum,
108 kernel, initrd, appliance);
109 free (supermin_path);
113 free (supermin_path);
118 r = find_path (g, contains_ordinary_appliance, NULL, &path);
123 size_t len = strlen (path);
124 *kernel = safe_malloc (g, len + strlen (kernel_name) + 2);
125 *initrd = safe_malloc (g, len + strlen (initrd_name) + 2);
126 sprintf (*kernel, "%s/%s", path, kernel_name);
127 sprintf (*initrd, "%s/%s", path, initrd_name);
134 error (g, _("cannot find any suitable libguestfs supermin or ordinary appliance on LIBGUESTFS_PATH (search path: %s)"),
140 contains_supermin_appliance (guestfs_h *g, const char *path, void *data)
142 return dir_contains_files (path, "supermin.d", "kmod.whitelist", NULL);
146 contains_ordinary_appliance (guestfs_h *g, const char *path, void *data)
148 return dir_contains_files (path, kernel_name, initrd_name, NULL);
151 /* supermin_path is a path which is known to contain a supermin
152 * appliance. Using febootstrap-supermin-helper -f checksum calculate
153 * the checksum so we can see if it is cached.
156 calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
158 size_t len = 2 * strlen (supermin_path) + 256;
161 "febootstrap-supermin-helper%s "
163 "-k '%s/kmod.whitelist' "
166 g->verbose ? " --verbose" : "",
171 guestfs___print_timestamped_message (g, "%s", cmd);
173 /* Errors here are non-fatal, so we don't need to call error(). */
174 FILE *pp = popen (cmd, "r");
179 if (fgets (checksum, sizeof checksum, pp) == NULL) {
184 if (pclose (pp) == -1) {
189 len = strlen (checksum);
191 if (len < 16) { /* sanity check */
192 fprintf (stderr, "libguestfs: internal error: febootstrap-supermin-helper -f checksum returned a short string\n");
196 if (len > 0 && checksum[len-1] == '\n')
197 checksum[--len] = '\0';
199 return safe_strndup (g, checksum, len);
202 /* Check for cached appliance in $TMPDIR/$checksum. Check it exists
203 * and passes some basic security checks.
206 * 1 = exists, and passes
208 * -1 = error which should abort the whole launch process
211 security_check_cache_file (guestfs_h *g, const char *filename,
212 const struct stat *statbuf)
214 uid_t uid = geteuid ();
216 if (statbuf->st_uid != uid) {
217 error (g, ("libguestfs cached appliance %s is not owned by UID %d\n"),
222 if ((statbuf->st_mode & 0022) != 0) {
223 error (g, ("libguestfs cached appliance %s is writable by group or other (mode %o)\n"),
224 filename, statbuf->st_mode);
232 check_for_cached_appliance (guestfs_h *g,
233 const char *supermin_path, const char *checksum,
234 char **kernel, char **initrd, char **appliance)
236 const char *tmpdir = guestfs___tmpdir ();
238 size_t len = strlen (tmpdir) + strlen (checksum) + 2;
240 snprintf (cachedir, len, "%s/%s", tmpdir, checksum);
242 /* Touch the directory to prevent it being deleting in a rare race
243 * between us doing the checks and a tmp cleaner running. Note this
244 * doesn't create the directory, and we ignore any error.
246 (void) utime (cachedir, NULL);
248 /* See if the cache directory exists and passes some simple checks
249 * to make sure it has not been tampered with. Note that geteuid()
250 * forms a part of the checksum.
253 if (lstat (cachedir, &statbuf) == -1)
256 if (security_check_cache_file (g, cachedir, &statbuf) == -1)
261 *kernel = safe_malloc (g, len + 8 /* / + "kernel" + \0 */);
262 *initrd = safe_malloc (g, len + 8 /* / + "initrd" + \0 */);
263 *appliance = safe_malloc (g, len + 6 /* / + "root" + \0 */);
264 sprintf (*kernel, "%s/kernel", cachedir);
265 sprintf (*initrd, "%s/initrd", cachedir);
266 sprintf (*appliance, "%s/root", cachedir);
268 /* Touch the files to prevent them being deleted, and to bring the
269 * cache up to date. Note this doesn't create the files.
271 (void) utime (*kernel, NULL);
273 /* NB. *kernel is a symlink, so we want to check the kernel, not the
274 * link (stat, not lstat). We don't do a security check on the
275 * kernel since it's always under /boot.
277 if (stat (*kernel, &statbuf) == -1) {
282 (void) utime (*initrd, NULL);
284 if (lstat (*initrd, &statbuf) == -1) {
289 if (security_check_cache_file (g, *initrd, &statbuf) == -1) {
294 (void) utime (*appliance, NULL);
296 if (lstat (*appliance, &statbuf) == -1) {
301 if (security_check_cache_file (g, *appliance, &statbuf) == -1) {
316 /* Build supermin appliance from supermin_path to $TMPDIR/$checksum.
320 * -1 = error (aborts launch)
323 build_supermin_appliance (guestfs_h *g,
324 const char *supermin_path, const char *checksum,
325 char **kernel, char **initrd, char **appliance)
328 guestfs___print_timestamped_message (g, "begin building supermin appliance");
330 const char *tmpdir = guestfs___tmpdir ();
331 size_t cdlen = strlen (tmpdir) + strlen (checksum) + 2;
332 char cachedir[cdlen];
333 snprintf (cachedir, cdlen, "%s/%s", tmpdir, checksum);
335 /* Don't worry about this failing, because the command below will
336 * fail if the directory doesn't exist. Note the directory might
337 * already exist, eg. if a tmp cleaner has removed the existing
338 * appliance but not the directory itself.
340 (void) mkdir (cachedir, 0755);
342 /* Set a sensible umask in the subprocess, so kernel and initrd
343 * output files are world-readable (RHBZ#610880).
345 size_t cmdlen = 2 * strlen (supermin_path) + 3 * (cdlen + 16) + 256;
347 snprintf (cmd, cmdlen,
349 "febootstrap-supermin-helper%s "
351 "-k '%s/kmod.whitelist' "
354 "%s/kernel %s/initrd %s/root",
355 g->verbose ? " --verbose" : "",
356 supermin_path, supermin_path,
357 cachedir, cachedir, cachedir);
359 guestfs___print_timestamped_message (g, "%s", cmd);
360 int r = system (cmd);
361 if (r == -1 || WEXITSTATUS (r) != 0) {
362 error (g, _("external command failed: %s"), cmd);
367 guestfs___print_timestamped_message (g, "finished building supermin appliance");
369 *kernel = safe_malloc (g, cdlen + 8 /* / + "kernel" + \0 */);
370 *initrd = safe_malloc (g, cdlen + 8 /* / + "initrd" + \0 */);
371 *appliance = safe_malloc (g, cdlen + 6 /* / + "root" + \0 */);
372 sprintf (*kernel, "%s/kernel", cachedir);
373 sprintf (*initrd, "%s/initrd", cachedir);
374 sprintf (*appliance, "%s/root", cachedir);
379 /* Search elements of g->path, returning the first path element which
380 * matches the predicate function 'pred'.
382 * Function 'pred' must return a true or false value. If it returns
383 * -1 then the entire search is aborted.
386 * 1 = a path element matched, it is returned in *pelem_ret and must be
387 * freed by the caller,
388 * 0 = no path element matched, *pelem_ret is set to NULL, or
389 * -1 = error which aborts the launch process
392 find_path (guestfs_h *g,
393 int (*pred) (guestfs_h *g, const char *pelem, void *data),
399 const char *pelem = g->path;
401 /* Note that if g->path is an empty string, we want to check the
402 * current directory (for backwards compatibility with
403 * libguestfs < 1.5.4).
406 len = strcspn (pelem, ":");
408 /* Empty element or "." means current directory. */
410 *pelem_ret = safe_strdup (g, ".");
412 *pelem_ret = safe_strndup (g, pelem, len);
414 r = pred (g, *pelem_ret, data);
420 if (r != 0) /* predicate matched */
425 if (pelem[len] == ':')
431 /* Predicate didn't match on any path element. */
436 /* Returns true iff file is contained in dir. */
438 dir_contains_file (const char *dir, const char *file)
440 size_t dirlen = strlen (dir);
441 size_t filelen = strlen (file);
442 size_t len = dirlen + filelen + 2;
445 snprintf (path, len, "%s/%s", dir, file);
446 return access (path, F_OK) == 0;
449 /* Returns true iff every listed file is contained in 'dir'. */
451 dir_contains_files (const char *dir, ...)
456 va_start (args, dir);
457 while ((file = va_arg (args, const char *)) != NULL) {
458 if (!dir_contains_file (dir, file)) {