inspect: Ignore Pardus "/.swap" swapfile.
[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 <errno.h>
22 #include <dirent.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <stdarg.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include <fcntl.h>
29 #include <time.h>
30 #include <sys/stat.h>
31 #include <sys/select.h>
32 #include <sys/wait.h>
33 #include <utime.h>
34
35 #ifdef HAVE_SYS_TYPES_H
36 #include <sys/types.h>
37 #endif
38
39 #include "guestfs.h"
40 #include "guestfs-internal.h"
41 #include "guestfs-internal-actions.h"
42 #include "guestfs_protocol.h"
43
44 static const char *kernel_name = "vmlinuz." host_cpu;
45 static const char *initrd_name = "initramfs." host_cpu ".img";
46
47 static int find_path (guestfs_h *g, int (*pred) (guestfs_h *g, const char *pelem, void *data), void *data, char **pelem);
48 static int dir_contains_file (const char *dir, const char *file);
49 static int dir_contains_files (const char *dir, ...);
50 static int contains_supermin_appliance (guestfs_h *g, const char *path, void *data);
51 static int contains_ordinary_appliance (guestfs_h *g, const char *path, void *data);
52 static char *calculate_supermin_checksum (guestfs_h *g, const char *supermin_path);
53 static int check_for_cached_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance);
54 static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance);
55 static int run_supermin_helper (guestfs_h *g, const char *supermin_path, const char *cachedir, size_t cdlen);
56
57 /* Locate or build the appliance.
58  *
59  * This function locates or builds the appliance as necessary,
60  * handling the supermin appliance, caching of supermin-built
61  * appliances, or using an ordinary appliance.
62  *
63  * The return value is 0 = good, -1 = error.  Returned in '*kernel'
64  * will be the name of the kernel to use, '*initrd' the name of the
65  * initrd, '*appliance' the name of the ext2 root filesystem.
66  * '*appliance' can be NULL, meaning that we are using an ordinary
67  * (non-ext2) appliance.  All three strings must be freed by the
68  * caller.  However the referenced files themselves must not be
69  * deleted.
70  *
71  * The process is as follows:
72  *
73  * (1) Look for the first element of g->path which contains a
74  * supermin appliance skeleton.  If no element has this, skip
75  * straight to step (5).
76  * (2) Calculate the checksum of this supermin appliance.
77  * (3) Check whether $TMPDIR/$checksum/ directory exists, contains
78  * a cached appliance, and passes basic security checks.  If so,
79  * return this appliance.
80  * (4) Try to build the supermin appliance into $TMPDIR/$checksum/.
81  * If this is successful, return it.
82  * (5) Check each element of g->path, looking for an ordinary appliance.
83  * If one is found, return it.
84  */
85 int
86 guestfs___build_appliance (guestfs_h *g,
87                            char **kernel, char **initrd, char **appliance)
88 {
89   int r;
90
91   /* Step (1). */
92   char *supermin_path;
93   r = find_path (g, contains_supermin_appliance, NULL, &supermin_path);
94   if (r == -1)
95     return -1;
96
97   if (r == 1) {
98     /* Step (2): calculate checksum. */
99     char *checksum = calculate_supermin_checksum (g, supermin_path);
100     if (checksum) {
101       /* Step (3): cached appliance exists? */
102       r = check_for_cached_appliance (g, supermin_path, checksum,
103                                       kernel, initrd, appliance);
104       if (r != 0) {
105         free (supermin_path);
106         free (checksum);
107         return r == 1 ? 0 : -1;
108       }
109
110       /* Step (4): build supermin appliance. */
111       r = build_supermin_appliance (g, supermin_path, checksum,
112                                     kernel, initrd, appliance);
113       free (supermin_path);
114       free (checksum);
115       return r;
116     }
117     free (supermin_path);
118   }
119
120   /* Step (5). */
121   char *path;
122   r = find_path (g, contains_ordinary_appliance, NULL, &path);
123   if (r == -1)
124     return -1;
125
126   if (r == 1) {
127     size_t len = strlen (path);
128     *kernel = safe_malloc (g, len + strlen (kernel_name) + 2);
129     *initrd = safe_malloc (g, len + strlen (initrd_name) + 2);
130     sprintf (*kernel, "%s/%s", path, kernel_name);
131     sprintf (*initrd, "%s/%s", path, initrd_name);
132     *appliance = NULL;
133
134     free (path);
135     return 0;
136   }
137
138   error (g, _("cannot find any suitable libguestfs supermin or ordinary appliance on LIBGUESTFS_PATH (search path: %s)"),
139          g->path);
140   return -1;
141 }
142
143 static int
144 contains_supermin_appliance (guestfs_h *g, const char *path, void *data)
145 {
146   return dir_contains_files (path, "supermin.d", "kmod.whitelist", NULL);
147 }
148
149 static int
150 contains_ordinary_appliance (guestfs_h *g, const char *path, void *data)
151 {
152   return dir_contains_files (path, kernel_name, initrd_name, NULL);
153 }
154
155 /* supermin_path is a path which is known to contain a supermin
156  * appliance.  Using febootstrap-supermin-helper -f checksum calculate
157  * the checksum so we can see if it is cached.
158  */
159 static char *
160 calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
161 {
162   size_t len = 2 * strlen (supermin_path) + 256;
163   char cmd[len];
164   int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
165
166   if (!pass_u_g_args)
167     snprintf (cmd, len,
168               "febootstrap-supermin-helper%s "
169               "-f checksum "
170               "'%s/supermin.d' "
171               host_cpu,
172               g->verbose ? " --verbose" : "",
173               supermin_path);
174   else
175     snprintf (cmd, len,
176               "febootstrap-supermin-helper%s "
177               "-u %i "
178               "-g %i "
179               "-f checksum "
180               "'%s/supermin.d' "
181               host_cpu,
182               g->verbose ? " --verbose" : "",
183               geteuid (), getegid (),
184               supermin_path);
185
186   if (g->verbose)
187     guestfs___print_timestamped_message (g, "%s", cmd);
188
189   /* Errors here are non-fatal, so we don't need to call error(). */
190   FILE *pp = popen (cmd, "r");
191   if (pp == NULL)
192     return NULL;
193
194   char checksum[256];
195   if (fgets (checksum, sizeof checksum, pp) == NULL) {
196     pclose (pp);
197     return NULL;
198   }
199
200   if (pclose (pp) == -1) {
201     perror ("pclose");
202     return NULL;
203   }
204
205   len = strlen (checksum);
206
207   if (len < 16) {               /* sanity check */
208     fprintf (stderr, "libguestfs: internal error: febootstrap-supermin-helper -f checksum returned a short string\n");
209     return NULL;
210   }
211
212   if (len > 0 && checksum[len-1] == '\n')
213     checksum[--len] = '\0';
214
215   return safe_strndup (g, checksum, len);
216 }
217
218 /* Check for cached appliance in $TMPDIR/$checksum.  Check it exists
219  * and passes some basic security checks.
220  *
221  * Returns:
222  * 1 = exists, and passes
223  * 0 = does not exist
224  * -1 = error which should abort the whole launch process
225  */
226 static int
227 security_check_cache_file (guestfs_h *g, const char *filename,
228                            const struct stat *statbuf)
229 {
230   uid_t uid = geteuid ();
231
232   if (statbuf->st_uid != uid) {
233     error (g, ("libguestfs cached appliance %s is not owned by UID %d\n"),
234            filename, uid);
235     return -1;
236   }
237
238   if ((statbuf->st_mode & 0022) != 0) {
239     error (g, ("libguestfs cached appliance %s is writable by group or other (mode %o)\n"),
240            filename, statbuf->st_mode);
241     return -1;
242   }
243
244   return 0;
245 }
246
247 static int
248 check_for_cached_appliance (guestfs_h *g,
249                             const char *supermin_path, const char *checksum,
250                             char **kernel, char **initrd, char **appliance)
251 {
252   const char *tmpdir = guestfs_tmpdir ();
253
254   size_t len = strlen (tmpdir) + strlen (checksum) + 10;
255   char cachedir[len];
256   snprintf (cachedir, len, "%s/guestfs.%s", tmpdir, checksum);
257
258   /* Touch the directory to prevent it being deleting in a rare race
259    * between us doing the checks and a tmp cleaner running.  Note this
260    * doesn't create the directory, and we ignore any error.
261    */
262   (void) utime (cachedir, NULL);
263
264   /* See if the cache directory exists and passes some simple checks
265    * to make sure it has not been tampered with.  Note that geteuid()
266    * forms a part of the checksum.
267    */
268   struct stat statbuf;
269   if (lstat (cachedir, &statbuf) == -1)
270     return 0;
271
272   if (security_check_cache_file (g, cachedir, &statbuf) == -1)
273     return -1;
274
275   int ret;
276
277   *kernel = safe_malloc (g, len + 8 /* / + "kernel" + \0 */);
278   *initrd = safe_malloc (g, len + 8 /* / + "initrd" + \0 */);
279   *appliance = safe_malloc (g, len + 6 /* / + "root" + \0 */);
280   sprintf (*kernel, "%s/kernel", cachedir);
281   sprintf (*initrd, "%s/initrd", cachedir);
282   sprintf (*appliance, "%s/root", cachedir);
283
284   /* Touch the files to prevent them being deleted, and to bring the
285    * cache up to date.  Note this doesn't create the files.
286    */
287   (void) utime (*kernel, NULL);
288
289   /* NB. *kernel is a symlink, so we want to check the kernel, not the
290    * link (stat, not lstat).  We don't do a security check on the
291    * kernel since it's always under /boot.
292    */
293   if (stat (*kernel, &statbuf) == -1) {
294     ret = 0;
295     goto error;
296   }
297
298   (void) utime (*initrd, NULL);
299
300   if (lstat (*initrd, &statbuf) == -1) {
301     ret = 0;
302     goto error;
303   }
304
305   if (security_check_cache_file (g, *initrd, &statbuf) == -1) {
306     ret = -1;
307     goto error;
308   }
309
310   (void) utime (*appliance, NULL);
311
312   if (lstat (*appliance, &statbuf) == -1) {
313     ret = 0;
314     goto error;
315   }
316
317   if (security_check_cache_file (g, *appliance, &statbuf) == -1) {
318     ret = -1;
319     goto error;
320   }
321
322   /* Exists! */
323   return 1;
324
325  error:
326   free (*kernel);
327   free (*initrd);
328   free (*appliance);
329   return ret;
330 }
331
332 /* Build supermin appliance from supermin_path to $TMPDIR/$checksum.
333  *
334  * Returns:
335  * 0 = built
336  * -1 = error (aborts launch)
337  */
338 static int
339 build_supermin_appliance (guestfs_h *g,
340                           const char *supermin_path, const char *checksum,
341                           char **kernel, char **initrd, char **appliance)
342 {
343   if (g->verbose)
344     guestfs___print_timestamped_message (g, "begin building supermin appliance");
345
346   const char *tmpdir = guestfs_tmpdir ();
347
348   size_t tmpcdlen = strlen (tmpdir) + 16;
349   char tmpcd[tmpcdlen];
350   snprintf (tmpcd, tmpcdlen, "%s/guestfs.XXXXXX", tmpdir);
351
352   if (NULL == mkdtemp (tmpcd)) {
353     error (g, _("failed to create temporary cache directory: %m"));
354     return -1;
355   }
356
357   if (g->verbose)
358     guestfs___print_timestamped_message (g, "run febootstrap-supermin-helper");
359
360   int r = run_supermin_helper (g, supermin_path, tmpcd, tmpcdlen);
361   if (r == -1)
362     return -1;
363
364   if (g->verbose)
365     guestfs___print_timestamped_message (g, "finished building supermin appliance");
366
367   size_t cdlen = strlen (tmpdir) + strlen (checksum) + 10;
368   char cachedir[cdlen];
369   snprintf (cachedir, cdlen, "%s/guestfs.%s", tmpdir, checksum);
370
371   /* Make the temporary directory world readable */
372   if (chmod (tmpcd, 0755) == -1) {
373     error (g, "chmod %s: %m", tmpcd);
374   }
375
376   /* Try to rename the temporary directory to its non-temporary name */
377   if (rename (tmpcd, cachedir) == -1) {
378     /* If the cache directory now exists, we may have been racing with another
379      * libguestfs process. Check the new directory and use it if it's valid. */
380     if (errno == ENOTEMPTY || errno == EEXIST) {
381       /* Appliance cache consists of 2 files and a symlink in the cache
382        * directory. Delete them first. */
383       DIR *dir = opendir (tmpcd);
384       if (dir == NULL) {
385         error (g, "opendir %s: %m", tmpcd);
386         return -1;
387       }
388
389       int fd = dirfd (dir);
390       if (fd == -1) {
391         error (g, "dirfd: %m");
392         closedir (dir);
393         return -1;
394       }
395
396       struct dirent *dirent;
397       for (;;) {
398         errno = 0;
399         dirent = readdir (dir);
400
401         if (dirent == NULL) {
402           break;
403         }
404
405         /* Check that dirent is a file so we don't try to delete . and .. */
406         struct stat st;
407         if (fstatat (fd, dirent->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1) {
408           error (g, "fstatat %s: %m", dirent->d_name);
409           return -1;
410         }
411
412         if (!S_ISDIR(st.st_mode)) {
413           if (unlinkat (fd, dirent->d_name, 0) == -1) {
414             error (g, "unlinkat %s: %m", dirent->d_name);
415             closedir (dir);
416             return -1;
417           }
418         }
419       }
420
421       if (errno != 0) {
422         error (g, "readdir %s: %m", tmpcd);
423         closedir (dir);
424         return -1;
425       }
426
427       closedir (dir);
428
429       /* Delete the temporary cache directory itself. */
430       if (rmdir (tmpcd) == -1) {
431         error (g, "rmdir %s: %m", tmpcd);
432         return -1;
433       }
434
435       /* Check the new cache directory, and return it if valid */
436       return check_for_cached_appliance (g, supermin_path, checksum,
437                                          kernel, initrd, appliance);
438     }
439
440     else {
441       error (g, _("error renaming temporary cache directory: %m"));
442       return -1;
443     }
444   }
445
446   *kernel = safe_malloc (g, cdlen + 8 /* / + "kernel" + \0 */);
447   *initrd = safe_malloc (g, cdlen + 8 /* / + "initrd" + \0 */);
448   *appliance = safe_malloc (g, cdlen + 6 /* / + "root" + \0 */);
449   sprintf (*kernel, "%s/kernel", cachedir);
450   sprintf (*initrd, "%s/initrd", cachedir);
451   sprintf (*appliance, "%s/root", cachedir);
452
453   return 0;
454 }
455
456 /* Run febootstrap-supermin-helper and tell it to generate the
457  * appliance.
458  */
459 static int
460 run_supermin_helper (guestfs_h *g, const char *supermin_path,
461                      const char *cachedir, size_t cdlen)
462 {
463   size_t pathlen = strlen (supermin_path);
464
465   const char *argv[30];
466   size_t i = 0;
467
468   char uid[32];
469   snprintf (uid, sizeof uid, "%i", geteuid ());
470   char gid[32];
471   snprintf (gid, sizeof gid, "%i", getegid ());
472   char supermin_d[pathlen + 32];
473   snprintf (supermin_d, pathlen + 32, "%s/supermin.d", supermin_path);
474   char kernel[cdlen + 32];
475   snprintf (kernel, cdlen + 32, "%s/kernel", cachedir);
476   char initrd[cdlen + 32];
477   snprintf (initrd, cdlen + 32, "%s/initrd", cachedir);
478   char root[cdlen + 32];
479   snprintf (root, cdlen + 32, "%s/root", cachedir);
480
481   int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
482
483   argv[i++] = "febootstrap-supermin-helper";
484   if (g->verbose)
485     argv[i++] = "--verbose";
486   if (pass_u_g_args) {
487     argv[i++] = "-u";
488     argv[i++] = uid;
489     argv[i++] = "-g";
490     argv[i++] = gid;
491   }
492   argv[i++] = "-f";
493   argv[i++] = "ext2";
494   argv[i++] = supermin_d;
495   argv[i++] = host_cpu;
496   argv[i++] = kernel;
497   argv[i++] = initrd;
498   argv[i++] = root;
499   argv[i++] = NULL;
500
501   pid_t pid = fork ();
502   if (pid == -1) {
503     perrorf (g, "fork");
504     return -1;
505   }
506
507   if (pid > 0) {                /* Parent. */
508     if (g->verbose)
509       guestfs___print_timestamped_argv (g, argv);
510
511     int status;
512     if (waitpid (pid, &status, 0) == -1) {
513       perrorf (g, "waitpid");
514       return -1;
515     }
516     if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
517       error (g, _("external command failed, see earlier error messages"));
518       return -1;
519     }
520     return 0;
521   }
522
523   /* Child. */
524
525   /* Set a sensible umask in the subprocess, so kernel and initrd
526    * output files are world-readable (RHBZ#610880).
527    */
528   umask (0022);
529
530   execvp ("febootstrap-supermin-helper", (char * const *) argv);
531   perror ("execvp");
532   _exit (EXIT_FAILURE);
533 }
534
535 /* Search elements of g->path, returning the first path element which
536  * matches the predicate function 'pred'.
537  *
538  * Function 'pred' must return a true or false value.  If it returns
539  * -1 then the entire search is aborted.
540  *
541  * Return values:
542  * 1 = a path element matched, it is returned in *pelem_ret and must be
543  *     freed by the caller,
544  * 0 = no path element matched, *pelem_ret is set to NULL, or
545  * -1 = error which aborts the launch process
546  */
547 static int
548 find_path (guestfs_h *g,
549            int (*pred) (guestfs_h *g, const char *pelem, void *data),
550            void *data,
551            char **pelem_ret)
552 {
553   size_t len;
554   int r;
555   const char *pelem = g->path;
556
557   /* Note that if g->path is an empty string, we want to check the
558    * current directory (for backwards compatibility with
559    * libguestfs < 1.5.4).
560    */
561   do {
562     len = strcspn (pelem, ":");
563
564     /* Empty element or "." means current directory. */
565     if (len == 0)
566       *pelem_ret = safe_strdup (g, ".");
567     else
568       *pelem_ret = safe_strndup (g, pelem, len);
569
570     r = pred (g, *pelem_ret, data);
571     if (r == -1) {
572       free (*pelem_ret);
573       return -1;
574     }
575
576     if (r != 0)                 /* predicate matched */
577       return 1;
578
579     free (*pelem_ret);
580
581     if (pelem[len] == ':')
582       pelem += len + 1;
583     else
584       pelem += len;
585   } while (*pelem);
586
587   /* Predicate didn't match on any path element. */
588   *pelem_ret = NULL;
589   return 0;
590 }
591
592 /* Returns true iff file is contained in dir. */
593 static int
594 dir_contains_file (const char *dir, const char *file)
595 {
596   size_t dirlen = strlen (dir);
597   size_t filelen = strlen (file);
598   size_t len = dirlen + filelen + 2;
599   char path[len];
600
601   snprintf (path, len, "%s/%s", dir, file);
602   return access (path, F_OK) == 0;
603 }
604
605 /* Returns true iff every listed file is contained in 'dir'. */
606 static int
607 dir_contains_files (const char *dir, ...)
608 {
609   va_list args;
610   const char *file;
611
612   va_start (args, dir);
613   while ((file = va_arg (args, const char *)) != NULL) {
614     if (!dir_contains_file (dir, file)) {
615       va_end (args);
616       return 0;
617     }
618   }
619   va_end (args);
620   return 1;
621 }