Change initrd -> appliance.
[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 <limits.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <dirent.h>
29 #include <fnmatch.h>
30 #include <sys/stat.h>
31 #include <assert.h>
32
33 #include "error.h"
34 #include "fts_.h"
35 #include "full-write.h"
36 #include "xalloc.h"
37 #include "xvasprintf.h"
38
39 #include "helper.h"
40
41 /* Buffer size used in copy operations throughout.  Large for
42  * greatest efficiency.
43  */
44 #define BUFFER_SIZE 65536
45
46 static void iterate_inputs (char **inputs, int nr_inputs);
47 static void iterate_input_directory (const char *dirname, int dirfd);
48 static void write_kernel_modules (const char *whitelist, const char *modpath);
49 static void write_hostfiles (const char *hostfiles_file);
50 static void write_to_fd (const void *buffer, size_t len);
51 static void write_file_to_fd (const char *filename);
52 static void write_file_len_to_fd (const char *filename, size_t len);
53 static void write_padding (size_t len);
54 static void cpio_append_fts_entry (FTSENT *entry);
55 static void cpio_append_stat (const char *filename, struct stat *);
56 static void cpio_append (const char *filename);
57 static void cpio_append_trailer (void);
58
59 static int out_fd = -1;
60 static off_t out_offset = 0;
61
62 /* Create the appliance.
63  *
64  * The initrd consists of these components concatenated together:
65  *
66  * (1) The base skeleton appliance that we constructed at build time.
67  *     format = plain cpio
68  * (2) The host files which match wildcards in *.supermin.hostfiles.
69  *     input format = plain text, output format = plain cpio
70  * (3) The modules from modpath which are on the module whitelist.
71  *     output format = plain cpio
72  *
73  * The original shell script used the external cpio program to create
74  * parts (2) and (3), but we have decided it's going to be faster if
75  * we just write out the data outselves.  The reasons are that
76  * external cpio is slow (particularly when used with SELinux because
77  * it does 512 byte reads), and the format that we're writing is
78  * narrow and well understood, because we only care that the Linux
79  * kernel can read it.
80  *
81  * This version contains some improvements over the C version written
82  * for libguestfs, in that we can have multiple base images (or
83  * hostfiles) or use a directory to store these files.
84  */
85 void
86 create_appliance (char **inputs, int nr_inputs,
87                   const char *whitelist,
88                   const char *modpath,
89                   const char *appliance)
90 {
91   out_fd = open (appliance, O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0644);
92   if (out_fd == -1)
93     error (EXIT_FAILURE, errno, "open: %s", appliance);
94   out_offset = 0;
95
96   iterate_inputs (inputs, nr_inputs);
97
98   /* Kernel modules (3). */
99   write_kernel_modules (whitelist, modpath);
100
101   cpio_append_trailer ();
102
103   /* Finish off and close output file. */
104   if (close (out_fd) == -1)
105     error (EXIT_FAILURE, errno, "close: %s", appliance);
106 }
107
108 /* Iterate over the inputs to find out what they are, visiting
109  * directories if specified.
110  */
111 static void
112 iterate_inputs (char **inputs, int nr_inputs)
113 {
114   int i;
115   for (i = 0; i < nr_inputs; ++i) {
116     if (verbose)
117       print_timestamped_message ("visiting %s", inputs[i]);
118
119     int fd = open (inputs[i], O_RDONLY);
120     if (fd == -1)
121       error (EXIT_FAILURE, errno, "open: %s", inputs[i]);
122
123     struct stat statbuf;
124     if (fstat (fd, &statbuf) == -1)
125       error (EXIT_FAILURE, errno, "fstat: %s", inputs[i]);
126
127     /* Directory? */
128     if (S_ISDIR (statbuf.st_mode))
129       iterate_input_directory (inputs[i], fd);
130     else if (S_ISREG (statbuf.st_mode)) {
131       /* Is it a cpio file? */
132       char buf[6];
133       if (read (fd, buf, 6) == 6 && memcmp (buf, "070701", 6) == 0)
134         /* Yes, a cpio file.  This is a skeleton appliance, case (1). */
135         write_file_to_fd (inputs[i]);
136       else
137         /* No, must be hostfiles, case (2). */
138         write_hostfiles (inputs[i]);
139     }
140     else
141       error (EXIT_FAILURE, 0, "%s: input is not a regular file or directory",
142              inputs[i]);
143
144     close (fd);
145   }
146 }
147
148 static void
149 iterate_input_directory (const char *dirname, int dirfd)
150 {
151   char path[PATH_MAX];
152   strcpy (path, dirname);
153   size_t len = strlen (dirname);
154   path[len++] = '/';
155
156   char *inputs[] = { path };
157
158   DIR *dir = fdopendir (dirfd);
159   if (dir == NULL)
160     error (EXIT_FAILURE, errno, "fdopendir: %s", dirname);
161
162   struct dirent *d;
163   while ((errno = 0, d = readdir (dir)) != NULL) {
164     if (d->d_name[0] == '.') /* ignore ., .. and any hidden files. */
165       continue;
166
167     strcpy (&path[len], d->d_name);
168     iterate_inputs (inputs, 1);
169   }
170
171   if (errno != 0)
172     error (EXIT_FAILURE, errno, "readdir: %s", dirname);
173
174   if (closedir (dir) == -1)
175     error (EXIT_FAILURE, errno, "closedir: %s", dirname);
176 }
177
178 /* Copy kernel modules.
179  *
180  * Find every file under modpath.
181  *
182  * Exclude all *.ko files, *except* ones which match names in
183  * the whitelist (which may contain wildcards).  Include all
184  * other files.
185  *
186  * Add chosen files to the output.
187  *
188  * whitelist_file may be NULL, to include ALL kernel modules.
189  */
190 static void
191 write_kernel_modules (const char *whitelist_file, const char *modpath)
192 {
193   char **whitelist = NULL;
194   if (whitelist_file != NULL)
195     whitelist = load_file (whitelist_file);
196
197   char *paths[2] = { (char *) modpath, NULL };
198   FTS *fts = fts_open (paths, FTS_COMFOLLOW|FTS_PHYSICAL, NULL);
199   if (fts == NULL)
200     error (EXIT_FAILURE, errno, "write_kernel_modules: fts_open: %s", modpath);
201
202   for (;;) {
203     errno = 0;
204     FTSENT *entry = fts_read (fts);
205     if (entry == NULL && errno != 0)
206       error (EXIT_FAILURE, errno, "write_kernel_modules: fts_read: %s", modpath);
207     if (entry == NULL)
208       break;
209
210     /* Ignore directories being visited in post-order. */
211     if (entry->fts_info & FTS_DP)
212       continue;
213
214     /* Is it a *.ko file? */
215     if (entry->fts_namelen >= 3 &&
216         entry->fts_name[entry->fts_namelen-3] == '.' &&
217         entry->fts_name[entry->fts_namelen-2] == 'k' &&
218         entry->fts_name[entry->fts_namelen-1] == 'o') {
219       if (whitelist) {
220         /* Is it a *.ko file which is on the whitelist? */
221         size_t j;
222         for (j = 0; whitelist[j] != NULL; ++j) {
223           int r;
224           r = fnmatch (whitelist[j], entry->fts_name, 0);
225           if (r == 0) {
226             /* It's on the whitelist, so include it. */
227             if (verbose >= 2)
228               fprintf (stderr, "including kernel module %s (matches whitelist entry %s)\n",
229                        entry->fts_name, whitelist[j]);
230             cpio_append_fts_entry (entry);
231             break;
232           } else if (r != FNM_NOMATCH)
233             error (EXIT_FAILURE, 0, "internal error: fnmatch ('%s', '%s', %d) returned unexpected non-zero value %d\n",
234                    whitelist[j], entry->fts_name, 0, r);
235         } /* for (j) */
236       } else { /* whitelist == NULL, always include */
237         if (verbose >= 2)
238           fprintf (stderr, "including kernel module %s\n", entry->fts_name);
239         cpio_append_fts_entry (entry);
240       }
241     } else
242       /* It's some other sort of file, or a directory, always include. */
243       cpio_append_fts_entry (entry);
244   }
245
246   if (fts_close (fts) == -1)
247     error (EXIT_FAILURE, errno, "write_kernel_modules: fts_close: %s", modpath);
248 }
249
250 /* Copy the host files.
251  *
252  * Read the list of entries in hostfiles (which may contain
253  * wildcards).  Look them up in the filesystem, and add those files
254  * that exist.  Ignore any files that don't exist or are not readable.
255  */
256 static void
257 write_hostfiles (const char *hostfiles_file)
258 {
259   char **hostfiles = load_file (hostfiles_file);
260
261   /* Hostfiles list can contain "." before each path - ignore it.
262    * It also contains each directory name before we enter it.  But
263    * we don't read that until we see a wildcard for that directory.
264    */
265   size_t i, j;
266   for (i = 0; hostfiles[i] != NULL; ++i) {
267     char *hostfile = hostfiles[i];
268     if (hostfile[0] == '.')
269       hostfile++;
270
271     struct stat statbuf;
272
273     /* Is it a wildcard? */
274     if (strchr (hostfile, '*') || strchr (hostfile, '?')) {
275       char *dirname = xstrdup (hostfile);
276       char *patt = strrchr (dirname, '/');
277       assert (patt);
278       *patt++ = '\0';
279
280       char **files = read_dir (dirname);
281       files = filter_fnmatch (files, patt, FNM_NOESCAPE);
282
283       /* Add matching files. */
284       for (j = 0; files[j] != NULL; ++j) {
285         char *tmp = xasprintf ("%s/%s", dirname, files[j]);
286
287         if (verbose >= 2)
288           fprintf (stderr, "including host file %s (matches %s)\n", tmp, patt);
289
290         cpio_append (tmp);
291
292         free (tmp);
293       }
294     }
295     /* Else does this file/directory/whatever exist? */
296     else if (lstat (hostfile, &statbuf) == 0) {
297       if (verbose >= 2)
298         fprintf (stderr, "including host file %s (directly referenced)\n",
299                  hostfile);
300
301       cpio_append_stat (hostfile, &statbuf);
302     } /* Ignore files that don't exist. */
303   }
304 }
305
306 /* Copy contents of buffer to out_fd and keep out_offset correct. */
307 static void
308 write_to_fd (const void *buffer, size_t len)
309 {
310   if (full_write (out_fd, buffer, len) != len)
311     error (EXIT_FAILURE, errno, "write");
312   out_offset += len;
313 }
314
315 /* Copy contents of file to out_fd. */
316 static void
317 write_file_to_fd (const char *filename)
318 {
319   char buffer[BUFFER_SIZE];
320   int fd2;
321   ssize_t r;
322
323   if (verbose >= 2)
324     fprintf (stderr, "write_file_to_fd %s -> %d\n", filename, out_fd);
325
326   fd2 = open (filename, O_RDONLY);
327   if (fd2 == -1)
328     error (EXIT_FAILURE, errno, "open: %s", filename);
329   for (;;) {
330     r = read (fd2, buffer, sizeof buffer);
331     if (r == 0)
332       break;
333     if (r == -1) {
334       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
335         continue;
336       error (EXIT_FAILURE, errno, "read: %s", filename);
337     }
338     write_to_fd (buffer, r);
339   }
340
341   if (close (fd2) == -1)
342     error (EXIT_FAILURE, errno, "close: %s", filename);
343 }
344
345 /* Copy file of given length to output, and fail if the file has
346  * changed size.
347  */
348 static void
349 write_file_len_to_fd (const char *filename, size_t len)
350 {
351   char buffer[BUFFER_SIZE];
352   size_t count = 0;
353
354   if (verbose >= 2)
355     fprintf (stderr, "write_file_to_fd %s -> %d\n", filename, out_fd);
356
357   int fd2 = open (filename, O_RDONLY);
358   if (fd2 == -1)
359     error (EXIT_FAILURE, errno, "open: %s", filename);
360   for (;;) {
361     ssize_t r = read (fd2, buffer, sizeof buffer);
362     if (r == 0)
363       break;
364     if (r == -1) {
365       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
366         continue;
367       error (EXIT_FAILURE, errno, "read: %s", filename);
368     }
369     write_to_fd (buffer, r);
370     count += r;
371     if (count > len)
372       error (EXIT_FAILURE, 0, "write_file_len_to_fd: %s: file has increased in size\n", filename);
373   }
374
375   if (close (fd2) == -1)
376     error (EXIT_FAILURE, errno, "close: %s", filename);
377
378   if (count != len)
379     error (EXIT_FAILURE, 0, "febootstrap-supermin-helper: write_file_len_to_fd: %s: file has changed size\n", filename);
380 }
381
382 /* Append the file pointed to by FTSENT to the cpio output. */
383 static void
384 cpio_append_fts_entry (FTSENT *entry)
385 {
386   if (entry->fts_info & FTS_NS || entry->fts_info & FTS_NSOK)
387     cpio_append (entry->fts_path);
388   else
389     cpio_append_stat (entry->fts_path, entry->fts_statp);
390 }
391
392 /* Append the file named 'filename' to the cpio output. */
393 static void
394 cpio_append (const char *filename)
395 {
396   struct stat statbuf;
397
398   if (lstat (filename, &statbuf) == -1)
399     error (EXIT_FAILURE, errno, "lstat: %s", filename);
400   cpio_append_stat (filename, &statbuf);
401 }
402
403 /* Append the file to the cpio output. */
404 #define PADDING(len) ((((len) + 3) & ~3) - (len))
405
406 #define CPIO_HEADER_LEN (6 + 13*8)
407
408 static void
409 cpio_append_stat (const char *filename, struct stat *statbuf)
410 {
411   const char *orig_filename = filename;
412
413   if (*filename == '/')
414     filename++;
415   if (*filename == '\0')
416     filename = ".";
417
418   if (verbose >= 2)
419     fprintf (stderr, "cpio_append_stat %s 0%o -> %d\n",
420              orig_filename, statbuf->st_mode, out_fd);
421
422   /* Regular files and symlinks are the only ones that have a "body"
423    * in this cpio entry.
424    */
425   int has_body = S_ISREG (statbuf->st_mode) || S_ISLNK (statbuf->st_mode);
426
427   size_t len = strlen (filename) + 1;
428
429   char header[CPIO_HEADER_LEN + 1];
430   snprintf (header, sizeof header,
431             "070701"            /* magic */
432             "%08X"              /* inode */
433             "%08X"              /* mode */
434             "%08X" "%08X"       /* uid, gid */
435             "%08X"              /* nlink */
436             "%08X"              /* mtime */
437             "%08X"              /* file length */
438             "%08X" "%08X"       /* device holding file major, minor */
439             "%08X" "%08X"       /* for specials, device major, minor */
440             "%08X"              /* name length (including \0 byte) */
441             "%08X",             /* checksum (not used by the kernel) */
442             (unsigned) statbuf->st_ino, statbuf->st_mode,
443             statbuf->st_uid, statbuf->st_gid,
444             (unsigned) statbuf->st_nlink, (unsigned) statbuf->st_mtime,
445             has_body ? (unsigned) statbuf->st_size : 0,
446             major (statbuf->st_dev), minor (statbuf->st_dev),
447             major (statbuf->st_rdev), minor (statbuf->st_rdev),
448             (unsigned) len, 0);
449
450   /* Write the header. */
451   write_to_fd (header, CPIO_HEADER_LEN);
452
453   /* Follow with the filename, and pad it. */
454   write_to_fd (filename, len);
455   size_t padding_len = PADDING (CPIO_HEADER_LEN + len);
456   write_padding (padding_len);
457
458   /* Follow with the file or symlink content, and pad it. */
459   if (has_body) {
460     if (S_ISREG (statbuf->st_mode))
461       write_file_len_to_fd (orig_filename, statbuf->st_size);
462     else if (S_ISLNK (statbuf->st_mode)) {
463       char tmp[PATH_MAX];
464       if (readlink (orig_filename, tmp, sizeof tmp) == -1)
465         error (EXIT_FAILURE, errno, "readlink: %s", orig_filename);
466       write_to_fd (tmp, statbuf->st_size);
467     }
468
469     padding_len = PADDING (statbuf->st_size);
470     write_padding (padding_len);
471   }
472 }
473
474 /* CPIO voodoo. */
475 static void
476 cpio_append_trailer (void)
477 {
478   struct stat statbuf;
479   memset (&statbuf, 0, sizeof statbuf);
480   statbuf.st_nlink = 1;
481   cpio_append_stat ("TRAILER!!!", &statbuf);
482
483   /* CPIO seems to pad up to the next block boundary, ie. up to
484    * the next 512 bytes.
485    */
486   write_padding (((out_offset + 511) & ~511) - out_offset);
487   assert ((out_offset & 511) == 0);
488 }
489
490 /* Write 'len' bytes of zeroes out. */
491 static void
492 write_padding (size_t len)
493 {
494   static const char buffer[512] = { 0 };
495
496   while (len > 0) {
497     size_t n = len < sizeof buffer ? len : sizeof buffer;
498     write_to_fd (buffer, n);
499     len -= n;
500   }
501 }