Remove several unused local variables.
[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 /* 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";
47
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);
57
58 /* Locate or build the appliance.
59  *
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.
63  *
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
70  * deleted.
71  *
72  * The process is as follows:
73  *
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.
85  */
86 int
87 guestfs___build_appliance (guestfs_h *g,
88                            char **kernel, char **initrd, char **appliance)
89 {
90   int r;
91
92   /* Step (1). */
93   char *supermin_path;
94   r = find_path (g, contains_supermin_appliance, NULL, &supermin_path);
95   if (r == -1)
96     return -1;
97
98   if (r == 1) {
99     /* Step (2): calculate checksum. */
100     char *checksum = calculate_supermin_checksum (g, supermin_path);
101     if (checksum) {
102       /* Step (3): cached appliance exists? */
103       r = check_for_cached_appliance (g, supermin_path, checksum,
104                                       kernel, initrd, appliance);
105       if (r != 0) {
106         free (supermin_path);
107         free (checksum);
108         return r == 1 ? 0 : -1;
109       }
110
111       /* Step (4): build supermin appliance. */
112       r = build_supermin_appliance (g, supermin_path, checksum,
113                                     kernel, initrd, appliance);
114       free (supermin_path);
115       free (checksum);
116       return r;
117     }
118     free (supermin_path);
119   }
120
121   /* Step (5). */
122   char *path;
123   r = find_path (g, contains_ordinary_appliance, NULL, &path);
124   if (r == -1)
125     return -1;
126
127   if (r == 1) {
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);
133     *appliance = NULL;
134
135     free (path);
136     return 0;
137   }
138
139   error (g, _("cannot find any suitable libguestfs supermin or ordinary appliance on LIBGUESTFS_PATH (search path: %s)"),
140          g->path);
141   return -1;
142 }
143
144 static int
145 contains_ordinary_appliance (guestfs_h *g, const char *path, void *data)
146 {
147   return dir_contains_files (path, kernel_name, initrd_name, NULL);
148 }
149
150 static int
151 contains_supermin_appliance (guestfs_h *g, const char *path, void *data)
152 {
153   return dir_contains_files (path, "supermin.d", NULL);
154 }
155
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.
159  */
160 static char *
161 calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
162 {
163   size_t len = 2 * strlen (supermin_path) + 256;
164   char cmd[len];
165   int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
166
167   if (!pass_u_g_args)
168     snprintf (cmd, len,
169               "febootstrap-supermin-helper%s "
170               "-f checksum "
171               "'%s/supermin.d' "
172               host_cpu,
173               g->verbose ? " --verbose" : "",
174               supermin_path);
175   else
176     snprintf (cmd, len,
177               "febootstrap-supermin-helper%s "
178               "-u %i "
179               "-g %i "
180               "-f checksum "
181               "'%s/supermin.d' "
182               host_cpu,
183               g->verbose ? " --verbose" : "",
184               geteuid (), getegid (),
185               supermin_path);
186
187   if (g->verbose)
188     guestfs___print_timestamped_message (g, "%s", cmd);
189
190   /* Errors here are non-fatal, so we don't need to call error(). */
191   FILE *pp = popen (cmd, "r");
192   if (pp == NULL)
193     return NULL;
194
195   char checksum[256];
196   if (fgets (checksum, sizeof checksum, pp) == NULL) {
197     pclose (pp);
198     return NULL;
199   }
200
201   if (pclose (pp) == -1) {
202     perror ("pclose");
203     return NULL;
204   }
205
206   len = strlen (checksum);
207
208   if (len < 16) {               /* sanity check */
209     fprintf (stderr, "libguestfs: internal error: febootstrap-supermin-helper -f checksum returned a short string\n");
210     return NULL;
211   }
212
213   if (len > 0 && checksum[len-1] == '\n')
214     checksum[--len] = '\0';
215
216   return safe_strndup (g, checksum, len);
217 }
218
219 /* Check for cached appliance in $TMPDIR/$checksum.  Check it exists
220  * and passes some basic security checks.
221  *
222  * Returns:
223  * 1 = exists, and passes
224  * 0 = does not exist
225  * -1 = error which should abort the whole launch process
226  */
227 static int
228 security_check_cache_file (guestfs_h *g, const char *filename,
229                            const struct stat *statbuf)
230 {
231   uid_t uid = geteuid ();
232
233   if (statbuf->st_uid != uid) {
234     error (g, ("libguestfs cached appliance %s is not owned by UID %d\n"),
235            filename, uid);
236     return -1;
237   }
238
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);
242     return -1;
243   }
244
245   return 0;
246 }
247
248 static int
249 check_for_cached_appliance (guestfs_h *g,
250                             const char *supermin_path, const char *checksum,
251                             char **kernel, char **initrd, char **appliance)
252 {
253   const char *tmpdir = guestfs_tmpdir ();
254
255   size_t len = strlen (tmpdir) + strlen (checksum) + 10;
256   char cachedir[len];
257   snprintf (cachedir, len, "%s/guestfs.%s", tmpdir, checksum);
258
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.
262    */
263   (void) utime (cachedir, NULL);
264
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.
268    */
269   struct stat statbuf;
270   if (lstat (cachedir, &statbuf) == -1)
271     return 0;
272
273   if (security_check_cache_file (g, cachedir, &statbuf) == -1)
274     return -1;
275
276   int ret;
277
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);
284
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.
287    */
288   (void) utime (*kernel, NULL);
289
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.
293    */
294   if (stat (*kernel, &statbuf) == -1) {
295     ret = 0;
296     goto error;
297   }
298
299   (void) utime (*initrd, NULL);
300
301   if (lstat (*initrd, &statbuf) == -1) {
302     ret = 0;
303     goto error;
304   }
305
306   if (security_check_cache_file (g, *initrd, &statbuf) == -1) {
307     ret = -1;
308     goto error;
309   }
310
311   (void) utime (*appliance, NULL);
312
313   if (lstat (*appliance, &statbuf) == -1) {
314     ret = 0;
315     goto error;
316   }
317
318   if (security_check_cache_file (g, *appliance, &statbuf) == -1) {
319     ret = -1;
320     goto error;
321   }
322
323   /* Exists! */
324   return 1;
325
326  error:
327   free (*kernel);
328   free (*initrd);
329   free (*appliance);
330   return ret;
331 }
332
333 /* Build supermin appliance from supermin_path to $TMPDIR/$checksum.
334  *
335  * Returns:
336  * 0 = built
337  * -1 = error (aborts launch)
338  */
339 static int
340 build_supermin_appliance (guestfs_h *g,
341                           const char *supermin_path, const char *checksum,
342                           char **kernel, char **initrd, char **appliance)
343 {
344   if (g->verbose)
345     guestfs___print_timestamped_message (g, "begin building supermin appliance");
346
347   const char *tmpdir = guestfs_tmpdir ();
348
349   size_t tmpcdlen = strlen (tmpdir) + 16;
350   char tmpcd[tmpcdlen];
351   snprintf (tmpcd, tmpcdlen, "%s/guestfs.XXXXXX", tmpdir);
352
353   if (NULL == mkdtemp (tmpcd)) {
354     error (g, _("failed to create temporary cache directory: %m"));
355     return -1;
356   }
357
358   if (g->verbose)
359     guestfs___print_timestamped_message (g, "run febootstrap-supermin-helper");
360
361   int r = run_supermin_helper (g, supermin_path, tmpcd, tmpcdlen);
362   if (r == -1)
363     return -1;
364
365   if (g->verbose)
366     guestfs___print_timestamped_message (g, "finished building supermin appliance");
367
368   size_t cdlen = strlen (tmpdir) + strlen (checksum) + 10;
369   char cachedir[cdlen];
370   snprintf (cachedir, cdlen, "%s/guestfs.%s", tmpdir, checksum);
371
372   /* Make the temporary directory world readable */
373   if (chmod (tmpcd, 0755) == -1) {
374     error (g, "chmod %s: %m", tmpcd);
375   }
376
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);
385       if (dir == NULL) {
386         error (g, "opendir %s: %m", tmpcd);
387         return -1;
388       }
389
390       int fd = dirfd (dir);
391       if (fd == -1) {
392         error (g, "dirfd: %m");
393         closedir (dir);
394         return -1;
395       }
396
397       struct dirent *dirent;
398       for (;;) {
399         errno = 0;
400         dirent = readdir (dir);
401
402         if (dirent == NULL) {
403           break;
404         }
405
406         /* Check that dirent is a file so we don't try to delete . and .. */
407         struct stat st;
408         if (fstatat (fd, dirent->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1) {
409           error (g, "fstatat %s: %m", dirent->d_name);
410           return -1;
411         }
412
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);
416             closedir (dir);
417             return -1;
418           }
419         }
420       }
421
422       if (errno != 0) {
423         error (g, "readdir %s: %m", tmpcd);
424         closedir (dir);
425         return -1;
426       }
427
428       closedir (dir);
429
430       /* Delete the temporary cache directory itself. */
431       if (rmdir (tmpcd) == -1) {
432         error (g, "rmdir %s: %m", tmpcd);
433         return -1;
434       }
435
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);
439     }
440
441     else {
442       error (g, _("error renaming temporary cache directory: %m"));
443       return -1;
444     }
445   }
446
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);
453
454   return 0;
455 }
456
457 /* Run febootstrap-supermin-helper and tell it to generate the
458  * appliance.
459  */
460 static int
461 run_supermin_helper (guestfs_h *g, const char *supermin_path,
462                      const char *cachedir, size_t cdlen)
463 {
464   size_t pathlen = strlen (supermin_path);
465
466   const char *argv[30];
467   size_t i = 0;
468
469   char uid[32];
470   snprintf (uid, sizeof uid, "%i", geteuid ());
471   char gid[32];
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);
481
482   int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
483
484   argv[i++] = "febootstrap-supermin-helper";
485   if (g->verbose)
486     argv[i++] = "--verbose";
487   if (pass_u_g_args) {
488     argv[i++] = "-u";
489     argv[i++] = uid;
490     argv[i++] = "-g";
491     argv[i++] = gid;
492   }
493   argv[i++] = "-f";
494   argv[i++] = "ext2";
495   argv[i++] = supermin_d;
496   argv[i++] = host_cpu;
497   argv[i++] = kernel;
498   argv[i++] = initrd;
499   argv[i++] = root;
500   argv[i++] = NULL;
501
502   pid_t pid = fork ();
503   if (pid == -1) {
504     perrorf (g, "fork");
505     return -1;
506   }
507
508   if (pid > 0) {                /* Parent. */
509     if (g->verbose)
510       guestfs___print_timestamped_argv (g, argv);
511
512     int status;
513     if (waitpid (pid, &status, 0) == -1) {
514       perrorf (g, "waitpid");
515       return -1;
516     }
517     if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
518       error (g, _("external command failed, see earlier error messages"));
519       return -1;
520     }
521     return 0;
522   }
523
524   /* Child. */
525
526   /* Set a sensible umask in the subprocess, so kernel and initrd
527    * output files are world-readable (RHBZ#610880).
528    */
529   umask (0022);
530
531   execvp ("febootstrap-supermin-helper", (char * const *) argv);
532   perror ("execvp");
533   _exit (EXIT_FAILURE);
534 }
535
536 /* Search elements of g->path, returning the first path element which
537  * matches the predicate function 'pred'.
538  *
539  * Function 'pred' must return a true or false value.  If it returns
540  * -1 then the entire search is aborted.
541  *
542  * Return values:
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
547  */
548 static int
549 find_path (guestfs_h *g,
550            int (*pred) (guestfs_h *g, const char *pelem, void *data),
551            void *data,
552            char **pelem_ret)
553 {
554   size_t len;
555   int r;
556   const char *pelem = g->path;
557
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).
561    */
562   do {
563     len = strcspn (pelem, ":");
564
565     /* Empty element or "." means current directory. */
566     if (len == 0)
567       *pelem_ret = safe_strdup (g, ".");
568     else
569       *pelem_ret = safe_strndup (g, pelem, len);
570
571     r = pred (g, *pelem_ret, data);
572     if (r == -1) {
573       free (*pelem_ret);
574       return -1;
575     }
576
577     if (r != 0)                 /* predicate matched */
578       return 1;
579
580     free (*pelem_ret);
581
582     if (pelem[len] == ':')
583       pelem += len + 1;
584     else
585       pelem += len;
586   } while (*pelem);
587
588   /* Predicate didn't match on any path element. */
589   *pelem_ret = NULL;
590   return 0;
591 }
592
593 /* Returns true iff file is contained in dir. */
594 static int
595 dir_contains_file (const char *dir, const char *file)
596 {
597   size_t dirlen = strlen (dir);
598   size_t filelen = strlen (file);
599   size_t len = dirlen + filelen + 2;
600   char path[len];
601
602   snprintf (path, len, "%s/%s", dir, file);
603   return access (path, F_OK) == 0;
604 }
605
606 /* Returns true iff every listed file is contained in 'dir'. */
607 static int
608 dir_contains_files (const char *dir, ...)
609 {
610   va_list args;
611   const char *file;
612
613   va_start (args, dir);
614   while ((file = va_arg (args, const char *)) != NULL) {
615     if (!dir_contains_file (dir, file)) {
616       va_end (args);
617       return 0;
618     }
619   }
620   va_end (args);
621   return 1;
622 }