a4f5f6f44252e58e5336f67b8f73a686b55e6ec3
[libguestfs.git] / src / appliance.c
1 /* libguestfs
2  * Copyright (C) 2010 Red Hat Inc.
3  *
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.
8  *
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.
13  *
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
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <fcntl.h>
27 #include <time.h>
28 #include <sys/stat.h>
29 #include <sys/select.h>
30 #include <sys/wait.h>
31 #include <utime.h>
32
33 #ifdef HAVE_SYS_TYPES_H
34 #include <sys/types.h>
35 #endif
36
37 #include "guestfs.h"
38 #include "guestfs-internal.h"
39 #include "guestfs-internal-actions.h"
40 #include "guestfs_protocol.h"
41
42 static const char *kernel_name = "vmlinuz." host_cpu;
43 static const char *initrd_name = "initramfs." host_cpu ".img";
44
45 static int find_path (guestfs_h *g, int (*pred) (guestfs_h *g, const char *pelem, void *data), void *data, char **pelem);
46 static int dir_contains_file (const char *dir, const char *file);
47 static int dir_contains_files (const char *dir, ...);
48 static int contains_supermin_appliance (guestfs_h *g, const char *path, void *data);
49 static int contains_ordinary_appliance (guestfs_h *g, const char *path, void *data);
50 static char *calculate_supermin_checksum (guestfs_h *g, const char *supermin_path);
51 static int check_for_cached_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance);
52 static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance);
53 static int run_supermin_helper (guestfs_h *g, const char *supermin_path, const char *cachedir, size_t cdlen);
54
55 /* Locate or build the appliance.
56  *
57  * This function locates or builds the appliance as necessary,
58  * handling the supermin appliance, caching of supermin-built
59  * appliances, or using an ordinary appliance.
60  *
61  * The return value is 0 = good, -1 = error.  Returned in '*kernel'
62  * will be the name of the kernel to use, '*initrd' the name of the
63  * initrd, '*appliance' the name of the ext2 root filesystem.
64  * '*appliance' can be NULL, meaning that we are using an ordinary
65  * (non-ext2) appliance.  All three strings must be freed by the
66  * caller.  However the referenced files themselves must not be
67  * deleted.
68  *
69  * The process is as follows:
70  *
71  * (1) Look for the first element of g->path which contains a
72  * supermin appliance skeleton.  If no element has this, skip
73  * straight to step (5).
74  * (2) Calculate the checksum of this supermin appliance.
75  * (3) Check whether $TMPDIR/$checksum/ directory exists, contains
76  * a cached appliance, and passes basic security checks.  If so,
77  * return this appliance.
78  * (4) Try to build the supermin appliance into $TMPDIR/$checksum/.
79  * If this is successful, return it.
80  * (5) Check each element of g->path, looking for an ordinary appliance.
81  * If one is found, return it.
82  */
83 int
84 guestfs___build_appliance (guestfs_h *g,
85                            char **kernel, char **initrd, char **appliance)
86 {
87   int r;
88
89   /* Step (1). */
90   char *supermin_path;
91   r = find_path (g, contains_supermin_appliance, NULL, &supermin_path);
92   if (r == -1)
93     return -1;
94
95   if (r == 1) {
96     /* Step (2): calculate checksum. */
97     char *checksum = calculate_supermin_checksum (g, supermin_path);
98     if (checksum) {
99       /* Step (3): cached appliance exists? */
100       r = check_for_cached_appliance (g, supermin_path, checksum,
101                                       kernel, initrd, appliance);
102       if (r != 0) {
103         free (supermin_path);
104         free (checksum);
105         return r == 1 ? 0 : -1;
106       }
107
108       /* Step (4): build supermin appliance. */
109       r = build_supermin_appliance (g, supermin_path, checksum,
110                                     kernel, initrd, appliance);
111       free (supermin_path);
112       free (checksum);
113       return r;
114     }
115     free (supermin_path);
116   }
117
118   /* Step (5). */
119   char *path;
120   r = find_path (g, contains_ordinary_appliance, NULL, &path);
121   if (r == -1)
122     return -1;
123
124   if (r == 1) {
125     size_t len = strlen (path);
126     *kernel = safe_malloc (g, len + strlen (kernel_name) + 2);
127     *initrd = safe_malloc (g, len + strlen (initrd_name) + 2);
128     sprintf (*kernel, "%s/%s", path, kernel_name);
129     sprintf (*initrd, "%s/%s", path, initrd_name);
130     *appliance = NULL;
131
132     free (path);
133     return 0;
134   }
135
136   error (g, _("cannot find any suitable libguestfs supermin or ordinary appliance on LIBGUESTFS_PATH (search path: %s)"),
137          g->path);
138   return -1;
139 }
140
141 static int
142 contains_supermin_appliance (guestfs_h *g, const char *path, void *data)
143 {
144   return dir_contains_files (path, "supermin.d", "kmod.whitelist", NULL);
145 }
146
147 static int
148 contains_ordinary_appliance (guestfs_h *g, const char *path, void *data)
149 {
150   return dir_contains_files (path, kernel_name, initrd_name, NULL);
151 }
152
153 /* supermin_path is a path which is known to contain a supermin
154  * appliance.  Using febootstrap-supermin-helper -f checksum calculate
155  * the checksum so we can see if it is cached.
156  */
157 static char *
158 calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
159 {
160   size_t len = 2 * strlen (supermin_path) + 256;
161   char cmd[len];
162   snprintf (cmd, len,
163             "febootstrap-supermin-helper%s "
164             "-f checksum "
165             "'%s/supermin.d' "
166             host_cpu,
167             g->verbose ? " --verbose" : "",
168             supermin_path);
169
170   if (g->verbose)
171     guestfs___print_timestamped_message (g, "%s", cmd);
172
173   /* Errors here are non-fatal, so we don't need to call error(). */
174   FILE *pp = popen (cmd, "r");
175   if (pp == NULL)
176     return NULL;
177
178   char checksum[256];
179   if (fgets (checksum, sizeof checksum, pp) == NULL) {
180     pclose (pp);
181     return NULL;
182   }
183
184   if (pclose (pp) == -1) {
185     perror ("pclose");
186     return NULL;
187   }
188
189   len = strlen (checksum);
190
191   if (len < 16) {               /* sanity check */
192     fprintf (stderr, "libguestfs: internal error: febootstrap-supermin-helper -f checksum returned a short string\n");
193     return NULL;
194   }
195
196   if (len > 0 && checksum[len-1] == '\n')
197     checksum[--len] = '\0';
198
199   return safe_strndup (g, checksum, len);
200 }
201
202 /* Check for cached appliance in $TMPDIR/$checksum.  Check it exists
203  * and passes some basic security checks.
204  *
205  * Returns:
206  * 1 = exists, and passes
207  * 0 = does not exist
208  * -1 = error which should abort the whole launch process
209  */
210 static int
211 security_check_cache_file (guestfs_h *g, const char *filename,
212                            const struct stat *statbuf)
213 {
214   uid_t uid = geteuid ();
215
216   if (statbuf->st_uid != uid) {
217     error (g, ("libguestfs cached appliance %s is not owned by UID %d\n"),
218            filename, uid);
219     return -1;
220   }
221
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);
225     return -1;
226   }
227
228   return 0;
229 }
230
231 static int
232 check_for_cached_appliance (guestfs_h *g,
233                             const char *supermin_path, const char *checksum,
234                             char **kernel, char **initrd, char **appliance)
235 {
236   const char *tmpdir = guestfs_tmpdir ();
237
238   size_t len = strlen (tmpdir) + strlen (checksum) + 2;
239   char cachedir[len];
240   snprintf (cachedir, len, "%s/%s", tmpdir, checksum);
241
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.
245    */
246   (void) utime (cachedir, NULL);
247
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.
251    */
252   struct stat statbuf;
253   if (lstat (cachedir, &statbuf) == -1)
254     return 0;
255
256   if (security_check_cache_file (g, cachedir, &statbuf) == -1)
257     return -1;
258
259   int ret;
260
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);
267
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.
270    */
271   (void) utime (*kernel, NULL);
272
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.
276    */
277   if (stat (*kernel, &statbuf) == -1) {
278     ret = 0;
279     goto error;
280   }
281
282   (void) utime (*initrd, NULL);
283
284   if (lstat (*initrd, &statbuf) == -1) {
285     ret = 0;
286     goto error;
287   }
288
289   if (security_check_cache_file (g, *initrd, &statbuf) == -1) {
290     ret = -1;
291     goto error;
292   }
293
294   (void) utime (*appliance, NULL);
295
296   if (lstat (*appliance, &statbuf) == -1) {
297     ret = 0;
298     goto error;
299   }
300
301   if (security_check_cache_file (g, *appliance, &statbuf) == -1) {
302     ret = -1;
303     goto error;
304   }
305
306   /* Exists! */
307   return 1;
308
309  error:
310   free (*kernel);
311   free (*initrd);
312   free (*appliance);
313   return ret;
314 }
315
316 /* Build supermin appliance from supermin_path to $TMPDIR/$checksum.
317  *
318  * Returns:
319  * 0 = built
320  * -1 = error (aborts launch)
321  */
322 static int
323 build_supermin_appliance (guestfs_h *g,
324                           const char *supermin_path, const char *checksum,
325                           char **kernel, char **initrd, char **appliance)
326 {
327   if (g->verbose)
328     guestfs___print_timestamped_message (g, "begin building supermin appliance");
329
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);
334
335   /* Don't worry about this failing, because the
336    * febootstrap-supermin-helper command will fail if the directory
337    * doesn't exist.  Note the directory might already exist, eg. if a
338    * tmp cleaner has removed the existing appliance but not the
339    * directory itself.
340    */
341   (void) mkdir (cachedir, 0755);
342
343   if (g->verbose)
344     guestfs___print_timestamped_message (g, "run febootstrap-supermin-helper");
345
346   int r = run_supermin_helper (g, supermin_path, cachedir, cdlen);
347   if (r == -1)
348     return -1;
349
350   if (g->verbose)
351     guestfs___print_timestamped_message (g, "finished building supermin appliance");
352
353   *kernel = safe_malloc (g, cdlen + 8 /* / + "kernel" + \0 */);
354   *initrd = safe_malloc (g, cdlen + 8 /* / + "initrd" + \0 */);
355   *appliance = safe_malloc (g, cdlen + 6 /* / + "root" + \0 */);
356   sprintf (*kernel, "%s/kernel", cachedir);
357   sprintf (*initrd, "%s/initrd", cachedir);
358   sprintf (*appliance, "%s/root", cachedir);
359
360   return 0;
361 }
362
363 /* Run febootstrap-supermin-helper and tell it to generate the
364  * appliance.  Note that we have to do an explicit fork/exec here.
365  * 'system' goes via the shell, and on systems that have bash, bash
366  * has a misfeature where it resets the euid to uid which breaks
367  * virt-v2v.  'posix_spawn' was also considered but that doesn't allow
368  * us to reset the umask.
369  */
370 static int
371 run_supermin_helper (guestfs_h *g, const char *supermin_path,
372                      const char *cachedir, size_t cdlen)
373 {
374   size_t pathlen = strlen (supermin_path);
375
376   const char *argv[30];
377   size_t i = 0;
378
379   char supermin_d[pathlen + 32];
380   snprintf (supermin_d, pathlen + 32, "%s/supermin.d", supermin_path);
381   char kernel[cdlen + 32];
382   snprintf (kernel, cdlen + 32, "%s/kernel", cachedir);
383   char initrd[cdlen + 32];
384   snprintf (initrd, cdlen + 32, "%s/initrd", cachedir);
385   char root[cdlen + 32];
386   snprintf (root, cdlen + 32, "%s/root", cachedir);
387
388   argv[i++] = "febootstrap-supermin-helper";
389   if (g->verbose)
390     argv[i++] = "--verbose";
391   argv[i++] = "-f";
392   argv[i++] = "ext2";
393   argv[i++] = supermin_d;
394   argv[i++] = host_cpu;
395   argv[i++] = kernel;
396   argv[i++] = initrd;
397   argv[i++] = root;
398   argv[i++] = NULL;
399
400   pid_t pid = fork ();
401   if (pid == -1) {
402     perrorf (g, "fork");
403     return -1;
404   }
405
406   if (pid > 0) {                /* Parent. */
407     if (g->verbose)
408       guestfs___print_timestamped_argv (g, argv);
409
410     int status;
411     if (waitpid (pid, &status, 0) == -1) {
412       perrorf (g, "waitpid");
413       return -1;
414     }
415     if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
416       error (g, _("external command failed, see earlier error messages"));
417       return -1;
418     }
419     return 0;
420   }
421
422   /* Child. */
423
424   /* Set a sensible umask in the subprocess, so kernel and initrd
425    * output files are world-readable (RHBZ#610880).
426    */
427   umask (0022);
428
429   /* Set uid/gid in the child.  This is a workaround for a misfeature
430    * in bash which breaks virt-v2v - see the comment at the top of
431    * this function.
432    */
433   if (getuid () == 0) {
434     int egid = getegid ();
435     int euid = geteuid ();
436
437     if (egid != 0 || euid != 0) {
438       if (seteuid (0) == -1) {
439         perror ("seteuid");
440         _exit (EXIT_FAILURE);
441       }
442
443       if (setgid (egid) == -1) {
444         perror ("setgid");
445         _exit (EXIT_FAILURE);
446       }
447
448       if (setuid (euid) == -1) {
449         perror ("setuid");
450         _exit (EXIT_FAILURE);
451       }
452     }
453   }
454   execvp ("febootstrap-supermin-helper", (char * const *) argv);
455   perror ("execvp");
456   _exit (EXIT_FAILURE);
457 }
458
459 /* Search elements of g->path, returning the first path element which
460  * matches the predicate function 'pred'.
461  *
462  * Function 'pred' must return a true or false value.  If it returns
463  * -1 then the entire search is aborted.
464  *
465  * Return values:
466  * 1 = a path element matched, it is returned in *pelem_ret and must be
467  *     freed by the caller,
468  * 0 = no path element matched, *pelem_ret is set to NULL, or
469  * -1 = error which aborts the launch process
470  */
471 static int
472 find_path (guestfs_h *g,
473            int (*pred) (guestfs_h *g, const char *pelem, void *data),
474            void *data,
475            char **pelem_ret)
476 {
477   size_t len;
478   int r;
479   const char *pelem = g->path;
480
481   /* Note that if g->path is an empty string, we want to check the
482    * current directory (for backwards compatibility with
483    * libguestfs < 1.5.4).
484    */
485   do {
486     len = strcspn (pelem, ":");
487
488     /* Empty element or "." means current directory. */
489     if (len == 0)
490       *pelem_ret = safe_strdup (g, ".");
491     else
492       *pelem_ret = safe_strndup (g, pelem, len);
493
494     r = pred (g, *pelem_ret, data);
495     if (r == -1) {
496       free (*pelem_ret);
497       return -1;
498     }
499
500     if (r != 0)                 /* predicate matched */
501       return 1;
502
503     free (*pelem_ret);
504
505     if (pelem[len] == ':')
506       pelem += len + 1;
507     else
508       pelem += len;
509   } while (*pelem);
510
511   /* Predicate didn't match on any path element. */
512   *pelem_ret = NULL;
513   return 0;
514 }
515
516 /* Returns true iff file is contained in dir. */
517 static int
518 dir_contains_file (const char *dir, const char *file)
519 {
520   size_t dirlen = strlen (dir);
521   size_t filelen = strlen (file);
522   size_t len = dirlen + filelen + 2;
523   char path[len];
524
525   snprintf (path, len, "%s/%s", dir, file);
526   return access (path, F_OK) == 0;
527 }
528
529 /* Returns true iff every listed file is contained in 'dir'. */
530 static int
531 dir_contains_files (const char *dir, ...)
532 {
533   va_list args;
534   const char *file;
535
536   va_start (args, dir);
537   while ((file = va_arg (args, const char *)) != NULL) {
538     if (!dir_contains_file (dir, file)) {
539       va_end (args);
540       return 0;
541     }
542   }
543   va_end (args);
544   return 1;
545 }