Add progress messages to zero-device command.
[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 <utime.h>
31
32 #ifdef HAVE_SYS_TYPES_H
33 #include <sys/types.h>
34 #endif
35
36 #include "guestfs.h"
37 #include "guestfs-internal.h"
38 #include "guestfs-internal-actions.h"
39 #include "guestfs_protocol.h"
40
41 static const char *kernel_name = "vmlinuz." REPO "." host_cpu;
42 static const char *initrd_name = "initramfs." REPO "." host_cpu ".img";
43
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);
52
53 /* Locate or build the appliance.
54  *
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.
58  *
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
65  * deleted.
66  *
67  * The process is as follows:
68  *
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.
80  */
81 int
82 guestfs___build_appliance (guestfs_h *g,
83                            char **kernel, char **initrd, char **appliance)
84 {
85   int r;
86
87   /* Step (1). */
88   char *supermin_path;
89   r = find_path (g, contains_supermin_appliance, NULL, &supermin_path);
90   if (r == -1)
91     return -1;
92
93   if (r == 1) {
94     /* Step (2): calculate checksum. */
95     char *checksum = calculate_supermin_checksum (g, supermin_path);
96     if (checksum) {
97       /* Step (3): cached appliance exists? */
98       r = check_for_cached_appliance (g, supermin_path, checksum,
99                                       kernel, initrd, appliance);
100       if (r != 0) {
101         free (supermin_path);
102         free (checksum);
103         return r == 1 ? 0 : -1;
104       }
105
106       /* Step (4): build supermin appliance. */
107       r = build_supermin_appliance (g, supermin_path, checksum,
108                                     kernel, initrd, appliance);
109       free (supermin_path);
110       free (checksum);
111       return r;
112     }
113     free (supermin_path);
114   }
115
116   /* Step (5). */
117   char *path;
118   r = find_path (g, contains_ordinary_appliance, NULL, &path);
119   if (r == -1)
120     return -1;
121
122   if (r == 1) {
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);
128     *appliance = NULL;
129
130     free (path);
131     return 0;
132   }
133
134   error (g, _("cannot find any suitable libguestfs supermin or ordinary appliance on LIBGUESTFS_PATH (search path: %s)"),
135          g->path);
136   return -1;
137 }
138
139 static int
140 contains_supermin_appliance (guestfs_h *g, const char *path, void *data)
141 {
142   return dir_contains_files (path, "supermin.d", "kmod.whitelist", NULL);
143 }
144
145 static int
146 contains_ordinary_appliance (guestfs_h *g, const char *path, void *data)
147 {
148   return dir_contains_files (path, kernel_name, initrd_name, NULL);
149 }
150
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.
154  */
155 static char *
156 calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
157 {
158   size_t len = 2 * strlen (supermin_path) + 256;
159   char cmd[len];
160   snprintf (cmd, len,
161             "febootstrap-supermin-helper%s "
162             "-f checksum "
163             "-k '%s/kmod.whitelist' "
164             "'%s/supermin.d' "
165             host_cpu,
166             g->verbose ? " --verbose" : "",
167             supermin_path,
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 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.
339    */
340   (void) mkdir (cachedir, 0755);
341
342   /* Set a sensible umask in the subprocess, so kernel and initrd
343    * output files are world-readable (RHBZ#610880).
344    */
345   size_t cmdlen = 2 * strlen (supermin_path) + 3 * (cdlen + 16) + 256;
346   char cmd[cmdlen];
347   snprintf (cmd, cmdlen,
348             "umask 0022; "
349             "febootstrap-supermin-helper%s "
350             "-f ext2 "
351             "-k '%s/kmod.whitelist' "
352             "'%s/supermin.d' "
353             host_cpu " "
354             "%s/kernel %s/initrd %s/root",
355             g->verbose ? " --verbose" : "",
356             supermin_path, supermin_path,
357             cachedir, cachedir, cachedir);
358   if (g->verbose)
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);
363     return -1;
364   }
365
366   if (g->verbose)
367     guestfs___print_timestamped_message (g, "finished building supermin appliance");
368
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);
375
376   return 0;
377 }
378
379 /* Search elements of g->path, returning the first path element which
380  * matches the predicate function 'pred'.
381  *
382  * Function 'pred' must return a true or false value.  If it returns
383  * -1 then the entire search is aborted.
384  *
385  * Return values:
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
390  */
391 static int
392 find_path (guestfs_h *g,
393            int (*pred) (guestfs_h *g, const char *pelem, void *data),
394            void *data,
395            char **pelem_ret)
396 {
397   size_t len;
398   int r;
399   const char *pelem = g->path;
400
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).
404    */
405   do {
406     len = strcspn (pelem, ":");
407
408     /* Empty element or "." means current directory. */
409     if (len == 0)
410       *pelem_ret = safe_strdup (g, ".");
411     else
412       *pelem_ret = safe_strndup (g, pelem, len);
413
414     r = pred (g, *pelem_ret, data);
415     if (r == -1) {
416       free (*pelem_ret);
417       return -1;
418     }
419
420     if (r != 0)                 /* predicate matched */
421       return 1;
422
423     free (*pelem_ret);
424
425     if (pelem[len] == ':')
426       pelem += len + 1;
427     else
428       pelem += len;
429   } while (*pelem);
430
431   /* Predicate didn't match on any path element. */
432   *pelem_ret = NULL;
433   return 0;
434 }
435
436 /* Returns true iff file is contained in dir. */
437 static int
438 dir_contains_file (const char *dir, const char *file)
439 {
440   size_t dirlen = strlen (dir);
441   size_t filelen = strlen (file);
442   size_t len = dirlen + filelen + 2;
443   char path[len];
444
445   snprintf (path, len, "%s/%s", dir, file);
446   return access (path, F_OK) == 0;
447 }
448
449 /* Returns true iff every listed file is contained in 'dir'. */
450 static int
451 dir_contains_files (const char *dir, ...)
452 {
453   va_list args;
454   const char *file;
455
456   va_start (args, dir);
457   while ((file = va_arg (args, const char *)) != NULL) {
458     if (!dir_contains_file (dir, file)) {
459       va_end (args);
460       return 0;
461     }
462   }
463   va_end (args);
464   return 1;
465 }