Add TODO file.
[febootstrap.git] / helper / appliance.c
1 /* febootstrap-supermin-helper reimplementation in C.
2  * Copyright (C) 2009-2010 Red Hat Inc.
3  *
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.
8  *
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.
13  *
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.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <errno.h>
27 #include <dirent.h>
28 #include <fnmatch.h>
29 #include <sys/stat.h>
30 #include <assert.h>
31
32 #include "error.h"
33 #include "fts_.h"
34 #include "xalloc.h"
35 #include "xvasprintf.h"
36
37 #include "helper.h"
38
39 static void iterate_inputs (char **inputs, int nr_inputs, struct writer *);
40 static void iterate_input_directory (const char *dirname, int dirfd, struct writer *);
41 static void add_kernel_modules (const char *whitelist, const char *modpath, struct writer *);
42 static void add_hostfiles (const char *hostfiles_file, struct writer *);
43
44 /* Create the appliance.
45  *
46  * The initrd consists of these components concatenated together:
47  *
48  * (1) The base skeleton appliance that we constructed at build time.
49  *     format = plain cpio
50  * (2) The host files which match wildcards in *.supermin.hostfiles.
51  *     input format = plain text, output format = plain cpio
52  * (3) The modules from modpath which are on the module whitelist.
53  *     output format = plain cpio
54  *
55  * The original shell script used the external cpio program to create
56  * parts (2) and (3), but we have decided it's going to be faster if
57  * we just write out the data outselves.  The reasons are that
58  * external cpio is slow (particularly when used with SELinux because
59  * it does 512 byte reads), and the format that we're writing is
60  * narrow and well understood, because we only care that the Linux
61  * kernel can read it.
62  *
63  * This version contains some improvements over the C version written
64  * for libguestfs, in that we can have multiple base images (or
65  * hostfiles) or use a directory to store these files.
66  */
67 void
68 create_appliance (const char *hostcpu,
69                   char **inputs, int nr_inputs,
70                   const char *whitelist,
71                   const char *modpath,
72                   const char *initrd,
73                   const char *appliance,
74                   struct writer *writer)
75 {
76   writer->wr_start (hostcpu, appliance, modpath, initrd);
77
78   iterate_inputs (inputs, nr_inputs, writer);
79
80   writer->wr_file ("/lib/modules");
81   /* Kernel modules (3). */
82   add_kernel_modules (whitelist, modpath, writer);
83
84   writer->wr_end ();
85 }
86
87 /* Iterate over the inputs to find out what they are, visiting
88  * directories if specified.
89  */
90 static void
91 iterate_inputs (char **inputs, int nr_inputs, struct writer *writer)
92 {
93   int i;
94   for (i = 0; i < nr_inputs; ++i) {
95     if (verbose)
96       print_timestamped_message ("visiting %s", inputs[i]);
97
98     int fd = open (inputs[i], O_RDONLY);
99     if (fd == -1)
100       error (EXIT_FAILURE, errno, "open: %s", inputs[i]);
101
102     struct stat statbuf;
103     if (fstat (fd, &statbuf) == -1)
104       error (EXIT_FAILURE, errno, "fstat: %s", inputs[i]);
105
106     /* Directory? */
107     if (S_ISDIR (statbuf.st_mode))
108       iterate_input_directory (inputs[i], fd, writer);
109     else if (S_ISREG (statbuf.st_mode)) {
110       /* Is it a cpio file? */
111       char buf[6];
112       if (read (fd, buf, 6) == 6 && memcmp (buf, "070701", 6) == 0)
113         /* Yes, a cpio file.  This is a skeleton appliance, case (1). */
114         writer->wr_cpio_file (inputs[i]);
115       else
116         /* No, must be hostfiles, case (2). */
117         add_hostfiles (inputs[i], writer);
118     }
119     else
120       error (EXIT_FAILURE, 0, "%s: input is not a regular file or directory",
121              inputs[i]);
122
123     close (fd);
124   }
125 }
126
127 static int
128 string_compare (const void *p1, const void *p2)
129 {
130   return strcmp (* (char * const *) p1, * (char * const *) p2);
131 }
132
133 static void
134 iterate_input_directory (const char *dirname, int dirfd, struct writer *writer)
135 {
136   DIR *dir = fdopendir (dirfd);
137   if (dir == NULL)
138     error (EXIT_FAILURE, errno, "fdopendir: %s", dirname);
139
140   char **entries = NULL;
141   size_t nr_entries = 0, nr_alloc = 0;
142
143   struct dirent *d;
144   while ((errno = 0, d = readdir (dir)) != NULL) {
145     if (d->d_name[0] == '.') /* ignore ., .. and any hidden files. */
146       continue;
147
148     /* Ignore *~ files created by editors. */
149     size_t len = strlen (d->d_name);
150     if (len > 0 && d->d_name[len-1] == '~')
151       continue;
152
153     add_string (&entries, &nr_entries, &nr_alloc, d->d_name);
154   }
155
156   if (errno != 0)
157     error (EXIT_FAILURE, errno, "readdir: %s", dirname);
158
159   if (closedir (dir) == -1)
160     error (EXIT_FAILURE, errno, "closedir: %s", dirname);
161
162   add_string (&entries, &nr_entries, &nr_alloc, NULL);
163
164   /* Visit directory entries in order.  In febootstrap <= 2.8 we
165    * didn't impose any order, but that led to some difficult
166    * heisenbugs.
167    */
168   sort (entries, string_compare);
169
170   char path[PATH_MAX];
171   strcpy (path, dirname);
172   size_t len = strlen (dirname);
173   path[len++] = '/';
174
175   char *inputs[] = { path };
176
177   size_t i;
178   for (i = 0; entries[i] != NULL; ++i) {
179     strcpy (&path[len], entries[i]);
180     iterate_inputs (inputs, 1, writer);
181   }
182 }
183
184 /* Copy kernel modules.
185  *
186  * Find every file under modpath.
187  *
188  * Exclude all *.ko files, *except* ones which match names in
189  * the whitelist (which may contain wildcards).  Include all
190  * other files.
191  *
192  * Add chosen files to the output.
193  *
194  * whitelist_file may be NULL, to include ALL kernel modules.
195  */
196 static void
197 add_kernel_modules (const char *whitelist_file, const char *modpath,
198                     struct writer *writer)
199 {
200   if (verbose)
201     print_timestamped_message ("adding kernel modules");
202
203   char **whitelist = NULL;
204   if (whitelist_file != NULL)
205     whitelist = load_file (whitelist_file);
206
207   char *paths[2] = { (char *) modpath, NULL };
208   FTS *fts = fts_open (paths, FTS_COMFOLLOW|FTS_PHYSICAL, NULL);
209   if (fts == NULL)
210     error (EXIT_FAILURE, errno, "add_kernel_modules: fts_open: %s", modpath);
211
212   for (;;) {
213     errno = 0;
214     FTSENT *entry = fts_read (fts);
215     if (entry == NULL && errno != 0)
216       error (EXIT_FAILURE, errno, "add_kernel_modules: fts_read: %s", modpath);
217     if (entry == NULL)
218       break;
219
220     /* Ignore directories being visited in post-order. */
221     if (entry->fts_info & FTS_DP)
222       continue;
223
224     /* Is it a *.ko file? */
225     if (entry->fts_namelen >= 3 &&
226         entry->fts_name[entry->fts_namelen-3] == '.' &&
227         entry->fts_name[entry->fts_namelen-2] == 'k' &&
228         entry->fts_name[entry->fts_namelen-1] == 'o') {
229       if (whitelist) {
230         /* Is it a *.ko file which is on the whitelist? */
231         size_t j;
232         for (j = 0; whitelist[j] != NULL; ++j) {
233           int r;
234           r = fnmatch (whitelist[j], entry->fts_name, 0);
235           if (r == 0) {
236             /* It's on the whitelist, so include it. */
237             if (verbose >= 2)
238               fprintf (stderr, "including kernel module %s (matches whitelist entry %s)\n",
239                        entry->fts_name, whitelist[j]);
240             writer->wr_fts_entry (entry);
241             break;
242           } else if (r != FNM_NOMATCH)
243             error (EXIT_FAILURE, 0, "internal error: fnmatch ('%s', '%s', %d) returned unexpected non-zero value %d\n",
244                    whitelist[j], entry->fts_name, 0, r);
245         } /* for (j) */
246       } else { /* whitelist == NULL, always include */
247         if (verbose >= 2)
248           fprintf (stderr, "including kernel module %s\n", entry->fts_name);
249         writer->wr_fts_entry (entry);
250       }
251     } else
252       /* It's some other sort of file, or a directory, always include. */
253       writer->wr_fts_entry (entry);
254   }
255
256   if (fts_close (fts) == -1)
257     error (EXIT_FAILURE, errno, "add_kernel_modules: fts_close: %s", modpath);
258 }
259
260 /* Copy the host files.
261  *
262  * Read the list of entries in hostfiles (which may contain
263  * wildcards).  Look them up in the filesystem, and add those files
264  * that exist.  Ignore any files that don't exist or are not readable.
265  */
266 static void
267 add_hostfiles (const char *hostfiles_file, struct writer *writer)
268 {
269   char **hostfiles = load_file (hostfiles_file);
270
271   /* Hostfiles list can contain "." before each path - ignore it.
272    * It also contains each directory name before we enter it.  But
273    * we don't read that until we see a wildcard for that directory.
274    */
275   size_t i, j;
276   for (i = 0; hostfiles[i] != NULL; ++i) {
277     char *hostfile = hostfiles[i];
278     if (hostfile[0] == '.')
279       hostfile++;
280
281     struct stat statbuf;
282
283     /* Is it a wildcard? */
284     if (strchr (hostfile, '*') || strchr (hostfile, '?')) {
285       char *dirname = xstrdup (hostfile);
286       char *patt = strrchr (dirname, '/');
287       if (!patt)
288         error (EXIT_FAILURE, 0, "%s: line %zu: invalid pattern\n(is this file a supermin appliance hostfiles file?)",
289                hostfiles_file, i+1);
290       *patt++ = '\0';
291
292       char **files = read_dir (dirname);
293       files = filter_fnmatch (files, patt, FNM_NOESCAPE);
294
295       /* Add matching files. */
296       for (j = 0; files[j] != NULL; ++j) {
297         char *tmp = xasprintf ("%s/%s", dirname, files[j]);
298
299         if (verbose >= 2)
300           fprintf (stderr, "including host file %s (matches %s)\n", tmp, patt);
301
302         writer->wr_file (tmp);
303
304         free (tmp);
305       }
306     }
307     /* Else does this file/directory/whatever exist? */
308     else if (lstat (hostfile, &statbuf) == 0) {
309       if (verbose >= 2)
310         fprintf (stderr, "including host file %s (directly referenced)\n",
311                  hostfile);
312
313       writer->wr_file_stat (hostfile, &statbuf);
314     } /* Ignore files that don't exist. */
315   }
316 }