1 /* libguestfs-supermin-helper reimplementation in C.
2 * Copyright (C) 2009-2010 Red Hat Inc.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program 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
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 /* This script builds the supermin appliance on the fly each
20 * time the appliance runs.
22 * *NOTE*: This program is designed to be very short-lived, and so we
23 * don't normally bother to free up any memory that we allocate.
24 * That's not completely true - we free up stuff if it's obvious and
25 * easy to free up, and ignore the rest.
43 #include <sys/types.h>
49 #include "filevercmp.h"
51 #include "full-write.h"
55 #include "xvasprintf.h"
57 /* Directory containing candidate kernels. We could make this
58 * configurable at some point.
60 #define KERNELDIR "/boot"
61 #define MODULESDIR "/lib/modules"
63 /* Buffer size used in copy operations throughout. Large for
64 * greatest efficiency.
66 #define BUFFER_SIZE 65536
68 static struct timeval start_t;
69 static int verbose = 0;
71 static void print_timestamped_message (const char *fs, ...);
72 static const char *create_kernel (const char *hostcpu, const char *kernel);
73 static void create_appliance (const char *sourcedir, const char *hostcpu, const char *repo, const char *modpath, const char *initrd);
75 enum { HELP_OPTION = CHAR_MAX + 1 };
77 static const char *options = "vV";
78 static const struct option long_options[] = {
79 { "help", 0, 0, HELP_OPTION },
80 { "verbose", 0, 0, 'v' },
81 { "version", 0, 0, 'V' },
86 usage (const char *progname)
88 printf ("%s: build the supermin appliance on the fly\n"
91 " %s [-options] sourcedir host_cpu repo kernel initrd\n"
95 "This script is used by libguestfs to build the supermin appliance\n"
96 "(kernel and initrd output files). You should NOT need to run this\n"
97 "program directly except if you are debugging tricky supermin\n"
98 "appliance problems.\n"
100 "NB: The kernel and initrd parameters are OUTPUT parameters. If\n"
101 "those files exist, they are overwritten by the output.\n"
105 " Display this help text and exit.\n"
107 " Enable verbose messages (give multiple times for more verbosity).\n"
109 " Display version number and exit.\n"
111 "Typical usage when debugging supermin appliance problems:\n"
112 " %s -v /usr/lib*/guestfs x86_64 fedora-12 /tmp/kernel /tmp/initrd\n"
113 "Note: This will OVERWRITE any existing files called /tmp/kernel\n"
114 "and /tmp/initrd.\n",
115 progname, progname, progname, progname, progname);
119 main (int argc, char *argv[])
121 /* First thing: start the clock. */
122 gettimeofday (&start_t, NULL);
124 /* Command line arguments. */
126 int c = getopt_long (argc, argv, options, long_options, NULL);
139 printf (PACKAGE_NAME " " PACKAGE_VERSION "\n");
148 if (argc - optind != 5) {
153 const char *sourcedir = argv[optind];
155 /* Host CPU and repo constants passed from the library (see:
156 * https://bugzilla.redhat.com/show_bug.cgi?id=558593).
158 const char *hostcpu = argv[optind+1];
159 const char *repo = argv[optind+2];
162 const char *kernel = argv[optind+3];
163 const char *initrd = argv[optind+4];
166 print_timestamped_message ("sourcedir = %s, "
171 sourcedir, hostcpu, repo, kernel, initrd);
173 /* Remove the output files if they exist. */
177 /* Create kernel output file. */
179 modpath = create_kernel (hostcpu, kernel);
182 print_timestamped_message ("finished creating kernel");
184 /* Create the appliance. */
185 create_appliance (sourcedir, hostcpu, repo, modpath, initrd);
188 print_timestamped_message ("finished creating appliance");
193 /* Compute Y - X and return the result in milliseconds.
194 * Approximately the same as this code:
195 * http://www.mpp.mpg.de/~huber/util/timevaldiff.c
198 timeval_diff (const struct timeval *x, const struct timeval *y)
202 msec = (y->tv_sec - x->tv_sec) * 1000;
203 msec += (y->tv_usec - x->tv_usec) / 1000;
208 print_timestamped_message (const char *fs, ...)
211 gettimeofday (&tv, NULL);
218 err = vasprintf (&msg, fs, args);
223 fprintf (stderr, "supermin helper [%05" PRIi64 "ms] %s\n",
224 timeval_diff (&start_t, &tv), msg);
229 static char **read_dir (const char *dir);
230 static char **filter_fnmatch (char **strings, const char *patt, int flags);
231 static char **filter_notmatching_substring (char **strings, const char *sub);
232 static void sort (char **strings, int (*compare) (const void *, const void *));
233 static int isdir (const char *path);
236 reverse_filevercmp (const void *p1, const void *p2)
238 const char *s1 = * (char * const *) p1;
239 const char *s2 = * (char * const *) p2;
241 /* Note, arguments are reversed to achieve a reverse sort. */
242 return filevercmp (s2, s1);
245 /* Create the kernel. This chooses an appropriate kernel and makes a
248 * Look for the most recent kernel named vmlinuz-*.<arch>* which has a
249 * corresponding directory in /lib/modules/. If the architecture is
250 * x86, look for any x86 kernel.
252 * RHEL 5 didn't append the arch to the kernel name, so look for
253 * kernels without arch second.
255 * If no suitable kernel can be found, exit with an error.
257 * This function returns the module path (ie. /lib/modules/<version>).
260 create_kernel (const char *hostcpu, const char *kernel)
262 char **all_files = read_dir (KERNELDIR);
264 /* In original: ls -1dvr /boot/vmlinuz-*.$arch* 2>/dev/null | grep -v xen */
266 if (hostcpu[0] == 'i' && hostcpu[2] == '8' && hostcpu[3] == '6' &&
268 patt = "vmlinuz-*.i?86*";
270 patt = xasprintf ("vmlinuz-*.%s*", hostcpu);
273 candidates = filter_fnmatch (all_files, patt, FNM_NOESCAPE);
274 candidates = filter_notmatching_substring (candidates, "xen");
276 if (candidates[0] == NULL) {
277 /* In original: ls -1dvr /boot/vmlinuz-* 2>/dev/null | grep -v xen */
279 candidates = filter_fnmatch (all_files, patt, FNM_NOESCAPE);
280 candidates = filter_notmatching_substring (candidates, "xen");
282 if (candidates[0] == NULL)
286 sort (candidates, reverse_filevercmp);
288 /* Choose the first candidate which has a corresponding /lib/modules
292 for (i = 0; candidates[i] != NULL; ++i) {
294 fprintf (stderr, "candidate kernel: " KERNELDIR "/%s\n", candidates[i]);
296 /* Ignore "vmlinuz-" at the beginning of the kernel name. */
297 const char *version = &candidates[i][8];
299 /* /lib/modules/<version> */
300 char *modpath = xasprintf (MODULESDIR "/%s", version);
303 fprintf (stderr, "checking modpath %s is a directory\n", modpath);
305 if (isdir (modpath)) {
307 fprintf (stderr, "picked %s because modpath %s exists\n",
308 candidates[i], modpath);
310 char *tmp = xasprintf (KERNELDIR "/%s", candidates[i]);
313 fprintf (stderr, "creating symlink %s -> %s\n", kernel, tmp);
315 if (symlink (tmp, kernel) == -1)
316 error (EXIT_FAILURE, errno, "symlink kernel");
324 /* Print more diagnostics here than the old script did. */
327 "libguestfs-supermin-helper: failed to find a suitable kernel.\n"
328 "I looked for kernels in " KERNELDIR " and modules in " MODULESDIR
330 "If this is a Xen guest, and you only have Xen domU kernels\n"
331 "installed, try installing a fullvirt kernel (only for\n"
332 "libguestfs use, you shouldn't boot the Xen guest with it).\n");
336 static void write_kernel_modules (const char *sourcedir, const char *modpath);
337 static void write_hostfiles (const char *sourcedir, const char *hostcpu, const char *repo);
338 static void write_to_fd (const void *buffer, size_t len);
339 static void write_file_to_fd (const char *filename);
340 static void write_file_len_to_fd (const char *filename, size_t len);
341 static void write_padding (size_t len);
342 static char **load_file (const char *filename);
343 static void cpio_append_fts_entry (FTSENT *entry);
344 static void cpio_append_stat (const char *filename, struct stat *);
345 static void cpio_append (const char *filename);
346 static void cpio_append_trailer (void);
348 static int out_fd = -1;
349 static off_t out_offset = 0;
351 /* Create the appliance.
353 * The initrd consists of these components concatenated together:
355 * (1) The base skeleton appliance that we constructed at build time.
356 * name = initramfs.$repo.$host_cpu.supermin.img
357 * format = plain cpio
358 * (2) The modules from modpath which are on the module whitelist.
359 * format = plain cpio
360 * (3) The host files which match wildcards in *.supermin.hostfiles.
361 * format = plain cpio
363 * The original shell scripted used the external cpio program to
364 * create parts (2) and (3), but we have decided it's going to be
365 * faster if we just write out the data outselves. The reasons are
366 * that external cpio is slow (particularly when used with SELinux
367 * because it does 512 byte reads), and the format that we're writing
368 * is narrow and well understood, because we only care that the Linux
369 * kernel can read it.
372 create_appliance (const char *sourcedir,
373 const char *hostcpu, const char *repo,
377 out_fd = open (initrd, O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0644);
379 error (EXIT_FAILURE, errno, "open: %s", initrd);
382 /* Copy the base skeleton appliance (1). */
383 char *tmp = xasprintf ("%s/initramfs.%s.%s.supermin.img",
384 sourcedir, repo, hostcpu);
385 write_file_to_fd (tmp);
388 /* Kernel modules (2). */
389 write_kernel_modules (sourcedir, modpath);
391 /* Copy hostfiles (3). */
392 write_hostfiles (sourcedir, hostcpu, repo);
394 cpio_append_trailer ();
396 /* Finish off and close output file. */
397 if (close (out_fd) == -1)
398 error (EXIT_FAILURE, errno, "close: %s", initrd);
401 /* Copy kernel modules.
403 * Find every file under modpath.
405 * Exclude all *.ko files, *except* ones which match names in
406 * the whitelist (which may contain wildcards). Include all
409 * Add chosen files to the output.
412 write_kernel_modules (const char *sourcedir, const char *modpath)
414 char *tmp = xasprintf ("%s/kmod.whitelist", sourcedir);
415 char **whitelist = load_file (tmp);
418 char *paths[2] = { (char *) modpath, NULL };
419 FTS *fts = fts_open (paths, FTS_COMFOLLOW|FTS_PHYSICAL, NULL);
421 error (EXIT_FAILURE, errno, "write_kernel_modules: fts_open: %s", modpath);
425 FTSENT *entry = fts_read (fts);
426 if (entry == NULL && errno != 0)
427 error (EXIT_FAILURE, errno, "write_kernel_modules: fts_read: %s", modpath);
431 /* Ignore directories being visited in post-order. */
432 if (entry->fts_info & FTS_DP)
435 /* Is it a *.ko file? */
436 if (entry->fts_namelen >= 3 &&
437 entry->fts_name[entry->fts_namelen-3] == '.' &&
438 entry->fts_name[entry->fts_namelen-2] == 'k' &&
439 entry->fts_name[entry->fts_namelen-1] == 'o') {
440 /* Is it a *.ko file which is on the whitelist? */
442 for (j = 0; whitelist[j] != NULL; ++j) {
444 r = fnmatch (whitelist[j], entry->fts_name, 0);
446 /* It's on the whitelist, so include it. */
448 fprintf (stderr, "including kernel module %s (matches whitelist entry %s)\n",
449 entry->fts_name, whitelist[j]);
450 cpio_append_fts_entry (entry);
452 } else if (r != FNM_NOMATCH)
453 error (EXIT_FAILURE, 0, "internal error: fnmatch ('%s', '%s', %d) returned unexpected non-zero value %d\n",
454 whitelist[j], entry->fts_name, 0, r);
457 /* It's some other sort of file, or a directory, always include. */
458 cpio_append_fts_entry (entry);
461 if (fts_close (fts) == -1)
462 error (EXIT_FAILURE, errno, "write_kernel_modules: fts_close: %s", modpath);
465 /* Copy the host files.
467 * Read the list of entries in *.supermin.hostfiles (which may contain
468 * wildcards). Look them up in the filesystem, and add those files
469 * that exist. Ignore any files that don't exist or are not readable.
472 write_hostfiles (const char *sourcedir, const char *hostcpu, const char *repo)
474 char *tmp = xasprintf ("%s/initramfs.%s.%s.supermin.hostfiles",
475 sourcedir, repo, hostcpu);
476 char **hostfiles = load_file (tmp);
479 /* Hostfiles list can contain "." before each path - ignore it.
480 * It also contains each directory name before we enter it. But
481 * we don't read that until we see a wildcard for that directory.
484 for (i = 0; hostfiles[i] != NULL; ++i) {
485 char *hostfile = hostfiles[i];
486 if (hostfile[0] == '.')
491 /* Is it a wildcard? */
492 if (strchr (hostfile, '*') || strchr (hostfile, '?')) {
493 char *dirname = xstrdup (hostfile);
494 char *patt = strrchr (dirname, '/');
498 char **files = read_dir (dirname);
499 files = filter_fnmatch (files, patt, FNM_NOESCAPE);
501 /* Add matching files. */
502 for (j = 0; files[j] != NULL; ++j) {
503 tmp = xasprintf ("%s/%s", dirname, files[j]);
506 fprintf (stderr, "including host file %s (matches %s)\n", tmp, patt);
513 /* Else does this file/directory/whatever exist? */
514 else if (lstat (hostfile, &statbuf) == 0) {
516 fprintf (stderr, "including host file %s (directly referenced)\n",
519 cpio_append_stat (hostfile, &statbuf);
520 } /* Ignore files that don't exist. */
525 /* Helper functions. */
528 add_string (char ***argv, size_t *n_used, size_t *n_alloc, const char *str)
533 if (*n_used >= *n_alloc)
534 *argv = x2nrealloc (*argv, n_alloc, sizeof (char *));
537 new_str = xstrdup (str);
541 (*argv)[*n_used] = new_str;
547 count_strings (char *const *argv)
551 for (argc = 0; argv[argc] != NULL; ++argc)
562 dir_cache_hash (void const *x, size_t table_size)
564 struct dir_cache const *p = x;
565 return hash_pjw (p->path, table_size);
569 dir_cache_compare (void const *x, void const *y)
571 struct dir_cache const *p = x;
572 struct dir_cache const *q = y;
573 return strcmp (p->path, q->path) == 0;
576 /* Read a directory into a list of strings.
578 * Previously looked up directories are cached and returned quickly,
579 * saving some considerable amount of time compared to reading the
580 * directory over again. However this means you really must not
581 * alter the array of strings that are returned.
583 * Returns an empty list if the directory cannot be opened.
586 read_dir (const char *name)
588 static Hash_table *ht = NULL;
591 ht = hash_initialize (1024, NULL, dir_cache_hash, dir_cache_compare, NULL);
593 struct dir_cache key = { .path = (char *) name };
594 struct dir_cache *p = hash_lookup (ht, &key);
599 size_t n_used = 0, n_alloc = 0;
601 DIR *dir = opendir (name);
603 /* If it fails to open, that's OK, skip to the end. */
610 struct dirent *d = readdir (dir);
613 /* But if it fails here, after opening and potentially reading
614 * part of the directory, that's a proper failure - inform the
617 error (EXIT_FAILURE, errno, "%s", name);
621 add_string (&files, &n_used, &n_alloc, d->d_name);
624 if (closedir (dir) == -1)
625 error (EXIT_FAILURE, errno, "closedir: %s", name);
628 /* NULL-terminate the array. */
629 add_string (&files, &n_used, &n_alloc, NULL);
631 /* Add it to the hash for next time. */
632 p = xmalloc (sizeof *p);
633 p->path = (char *) name;
635 p = hash_insert (ht, p);
641 /* Filter a list of strings and return only those matching the wildcard. */
643 filter_fnmatch (char **strings, const char *patt, int flags)
646 size_t n_used = 0, n_alloc = 0;
649 for (i = 0; strings[i] != NULL; ++i) {
650 r = fnmatch (patt, strings[i], flags);
652 add_string (&out, &n_used, &n_alloc, strings[i]);
653 else if (r != FNM_NOMATCH)
654 error (EXIT_FAILURE, 0, "internal error: fnmatch ('%s', '%s', %d) returned unexpected non-zero value %d\n",
655 patt, strings[i], flags, r);
658 add_string (&out, &n_used, &n_alloc, NULL);
662 /* Filter a list of strings and return only those which DON'T contain sub. */
664 filter_notmatching_substring (char **strings, const char *sub)
667 size_t n_used = 0, n_alloc = 0;
670 for (i = 0; strings[i] != NULL; ++i) {
671 if (strstr (strings[i], sub) == NULL)
672 add_string (&out, &n_used, &n_alloc, strings[i]);
675 add_string (&out, &n_used, &n_alloc, NULL);
679 /* Sort a list of strings, in place, with the comparison function supplied. */
681 sort (char **strings, int (*compare) (const void *, const void *))
683 qsort (strings, count_strings (strings), sizeof (char *), compare);
686 /* Return true iff path exists and is a directory. This version
690 isdir (const char *path)
694 if (stat (path, &statbuf) == -1)
697 return S_ISDIR (statbuf.st_mode);
700 /* Copy contents of buffer to out_fd and keep out_offset correct. */
702 write_to_fd (const void *buffer, size_t len)
704 if (full_write (out_fd, buffer, len) != len)
705 error (EXIT_FAILURE, errno, "write");
709 /* Copy contents of file to out_fd. */
711 write_file_to_fd (const char *filename)
713 char buffer[BUFFER_SIZE];
718 fprintf (stderr, "write_file_to_fd %s -> %d\n", filename, out_fd);
720 fd2 = open (filename, O_RDONLY);
722 error (EXIT_FAILURE, errno, "open: %s", filename);
724 r = read (fd2, buffer, sizeof buffer);
728 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
730 error (EXIT_FAILURE, errno, "read: %s", filename);
732 write_to_fd (buffer, r);
735 if (close (fd2) == -1)
736 error (EXIT_FAILURE, errno, "close: %s", filename);
739 /* Copy file of given length to output, and fail if the file has
743 write_file_len_to_fd (const char *filename, size_t len)
745 char buffer[BUFFER_SIZE];
749 fprintf (stderr, "write_file_to_fd %s -> %d\n", filename, out_fd);
751 int fd2 = open (filename, O_RDONLY);
753 error (EXIT_FAILURE, errno, "open: %s", filename);
755 ssize_t r = read (fd2, buffer, sizeof buffer);
759 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
761 error (EXIT_FAILURE, errno, "read: %s", filename);
763 write_to_fd (buffer, r);
766 error (EXIT_FAILURE, 0, "write_file_len_to_fd: %s: file has increased in size\n", filename);
769 if (close (fd2) == -1)
770 error (EXIT_FAILURE, errno, "close: %s", filename);
773 error (EXIT_FAILURE, 0, "libguestfs-supermin-helper: write_file_len_to_fd: %s: file has changed size\n", filename);
776 /* Load in a file, returning a list of lines. */
778 load_file (const char *filename)
781 size_t n_used = 0, n_alloc = 0;
784 fp = fopen (filename, "r");
786 error (EXIT_FAILURE, errno, "fopen: %s", filename);
789 while (fgets (line, sizeof line, fp)) {
790 size_t len = strlen (line);
791 if (len > 0 && line[len-1] == '\n')
793 add_string (&lines, &n_used, &n_alloc, line);
796 add_string (&lines, &n_used, &n_alloc, NULL);
800 /* Append the file pointed to by FTSENT to the cpio output. */
802 cpio_append_fts_entry (FTSENT *entry)
804 if (entry->fts_info & FTS_NS || entry->fts_info & FTS_NSOK)
805 cpio_append (entry->fts_path);
807 cpio_append_stat (entry->fts_path, entry->fts_statp);
810 /* Append the file named 'filename' to the cpio output. */
812 cpio_append (const char *filename)
816 if (lstat (filename, &statbuf) == -1)
817 error (EXIT_FAILURE, errno, "lstat: %s", filename);
818 cpio_append_stat (filename, &statbuf);
821 /* Append the file to the cpio output. */
822 #define PADDING(len) ((((len) + 3) & ~3) - (len))
824 #define CPIO_HEADER_LEN (6 + 13*8)
827 cpio_append_stat (const char *filename, struct stat *statbuf)
829 const char *orig_filename = filename;
831 if (*filename == '/')
833 if (*filename == '\0')
837 fprintf (stderr, "cpio_append_stat %s 0%o -> %d\n",
838 orig_filename, statbuf->st_mode, out_fd);
840 /* Regular files and symlinks are the only ones that have a "body"
841 * in this cpio entry.
843 int has_body = S_ISREG (statbuf->st_mode) || S_ISLNK (statbuf->st_mode);
845 size_t len = strlen (filename) + 1;
847 char header[CPIO_HEADER_LEN + 1];
848 snprintf (header, sizeof header,
852 "%08X" "%08X" /* uid, gid */
855 "%08X" /* file length */
856 "%08X" "%08X" /* device holding file major, minor */
857 "%08X" "%08X" /* for specials, device major, minor */
858 "%08X" /* name length (including \0 byte) */
859 "%08X", /* checksum (not used by the kernel) */
860 (unsigned) statbuf->st_ino, statbuf->st_mode,
861 statbuf->st_uid, statbuf->st_gid,
862 (unsigned) statbuf->st_nlink, (unsigned) statbuf->st_mtime,
863 has_body ? (unsigned) statbuf->st_size : 0,
864 major (statbuf->st_dev), minor (statbuf->st_dev),
865 major (statbuf->st_rdev), minor (statbuf->st_rdev),
868 /* Write the header. */
869 write_to_fd (header, CPIO_HEADER_LEN);
871 /* Follow with the filename, and pad it. */
872 write_to_fd (filename, len);
873 size_t padding_len = PADDING (CPIO_HEADER_LEN + len);
874 write_padding (padding_len);
876 /* Follow with the file or symlink content, and pad it. */
878 if (S_ISREG (statbuf->st_mode))
879 write_file_len_to_fd (orig_filename, statbuf->st_size);
880 else if (S_ISLNK (statbuf->st_mode)) {
882 if (readlink (orig_filename, tmp, sizeof tmp) == -1)
883 error (EXIT_FAILURE, errno, "readlink: %s", orig_filename);
884 write_to_fd (tmp, statbuf->st_size);
887 padding_len = PADDING (statbuf->st_size);
888 write_padding (padding_len);
894 cpio_append_trailer (void)
897 memset (&statbuf, 0, sizeof statbuf);
898 statbuf.st_nlink = 1;
899 cpio_append_stat ("TRAILER!!!", &statbuf);
901 /* CPIO seems to pad up to the next block boundary, ie. up to
902 * the next 512 bytes.
904 write_padding (((out_offset + 511) & ~511) - out_offset);
905 assert ((out_offset & 511) == 0);
908 /* Write 'len' bytes of zeroes out. */
910 write_padding (size_t len)
912 static const char buffer[512] = { 0 };
915 size_t n = len < sizeof buffer ? len : sizeof buffer;
916 write_to_fd (buffer, n);