helper: Print /modules when verbose >= 2
[febootstrap.git] / helper / ext2.c
1 /* febootstrap-supermin-helper reimplementation in C.
2  * Copyright (C) 2009-2011 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 <fcntl.h>
25 #include <unistd.h>
26 #include <errno.h>
27 #include <limits.h>
28 #include <sys/stat.h>
29 #include <assert.h>
30
31 #include "error.h"
32 #include "fts_.h"
33 #include "xvasprintf.h"
34
35 #include "helper.h"
36 #include "ext2internal.h"
37
38 ext2_filsys fs;
39
40 /* The ext2 image that we build always has a fixed size, and we 'hope'
41  * that the files fit in (otherwise we'll get an error).  Note that
42  * the file is sparsely allocated.
43  *
44  * The downside of allocating a very large initial disk is that the
45  * fixed overhead of ext2 is larger (since ext2 calculates it based on
46  * the size of the disk).  For a 4GB disk the overhead is
47  * approximately 66MB.
48  *
49  * In future, make this configurable, or determine it from the input
50  * files (XXX).
51  */
52 #define APPLIANCE_SIZE ((off_t)4*1024*1024*1024)
53
54 static void
55 ext2_start (const char *hostcpu, const char *appliance,
56             const char *modpath, const char *initrd)
57 {
58   initialize_ext2_error_table ();
59
60   /* Make the initrd. */
61   ext2_make_initrd (modpath, initrd);
62
63   /* Make the appliance sparse image. */
64   int fd = open (appliance, O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0644);
65   if (fd == -1)
66     error (EXIT_FAILURE, errno, "open: %s", appliance);
67
68   if (lseek (fd, APPLIANCE_SIZE - 1, SEEK_SET) == (off_t) -1)
69     error (EXIT_FAILURE, errno, "lseek");
70
71   char c = 0;
72   if (write (fd, &c, 1) != 1)
73     error (EXIT_FAILURE, errno, "write");
74
75   if (close (fd) == -1)
76     error (EXIT_FAILURE, errno, "close");
77
78   /* Run mke2fs on the file.
79    * XXX Quoting, but this string doesn't come from an untrusted source.
80    */
81   char *cmd = xasprintf ("%s -t ext2 -F%s '%s'",
82                          MKE2FS,
83                          verbose >= 2 ? "" : "q",
84                          appliance);
85   int r = system (cmd);
86   if (r == -1 || WEXITSTATUS (r) != 0)
87     error (EXIT_FAILURE, 0, "%s: failed", cmd);
88   free (cmd);
89
90   if (verbose)
91     print_timestamped_message ("finished mke2fs");
92
93   /* Open the filesystem. */
94   errcode_t err =
95     ext2fs_open (appliance, EXT2_FLAG_RW, 0, 0, unix_io_manager, &fs);
96   if (err != 0)
97     error (EXIT_FAILURE, 0, "ext2fs_open: %s", error_message (err));
98
99   /* Bitmaps are not loaded by default, so load them.  ext2fs_close will
100    * write out any changes.
101    */
102   err = ext2fs_read_bitmaps (fs);
103   if (err != 0)
104     error (EXIT_FAILURE, 0, "ext2fs_read_bitmaps: %s", error_message (err));
105 }
106
107 static void
108 ext2_end (void)
109 {
110   if (verbose)
111     print_timestamped_message ("closing ext2 filesystem");
112
113   /* Write out changes and close. */
114   errcode_t err;
115 #ifdef HAVE_EXT2FS_CLOSE2
116   err = ext2fs_close2 (fs, EXT2_FLAG_FLUSH_NO_SYNC);
117 #else
118   err = ext2fs_close (fs);
119 #endif
120   if (err != 0)
121     error (EXIT_FAILURE, 0, "ext2fs_close: %s", error_message (err));
122 }
123
124 void
125 ext2_mkdir (ext2_ino_t dir_ino, const char *dirname, const char *basename,
126             mode_t mode, uid_t uid, gid_t gid,
127             time_t ctime, time_t atime, time_t mtime)
128 {
129   errcode_t err;
130
131   mode = LINUX_S_IFDIR | (mode & 0777);
132
133   /* Does the directory exist?  This is legitimate: we just skip
134    * this case.
135    */
136   ext2_ino_t ino;
137   err = ext2fs_namei (fs, EXT2_ROOT_INO, dir_ino, basename, &ino);
138   if (err == 0)
139     return; /* skip */
140
141   /* Otherwise, create it. */
142   err = ext2fs_new_inode (fs, dir_ino, mode, 0, &ino);
143   if (err != 0)
144     error (EXIT_FAILURE, 0, "ext2fs_new_inode: %s", error_message (err));
145
146  try_again:
147   err = ext2fs_mkdir (fs, dir_ino, ino, basename);
148   if (err != 0) {
149     /* See: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=217892 */
150     if (err == EXT2_ET_DIR_NO_SPACE) {
151       err = ext2fs_expand_dir (fs, dir_ino);
152       if (err)
153         error (EXIT_FAILURE, 0, "ext2fs_expand_dir: %s/%s: %s",
154                dirname, basename, error_message (err));
155       goto try_again;
156     } else
157       error (EXIT_FAILURE, 0, "ext2fs_mkdir: %s/%s: %s",
158              dirname, basename, error_message (err));
159   }
160
161   /* Copy the final permissions, UID etc. to the inode. */
162   struct ext2_inode inode;
163   err = ext2fs_read_inode (fs, ino, &inode);
164   if (err != 0)
165     error (EXIT_FAILURE, 0, "ext2fs_read_inode: %s", error_message (err));
166   inode.i_mode = mode;
167   inode.i_uid = uid;
168   inode.i_gid = gid;
169   inode.i_ctime = ctime;
170   inode.i_atime = atime;
171   inode.i_mtime = mtime;
172   err = ext2fs_write_inode (fs, ino, &inode);
173   if (err != 0)
174     error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err));
175 }
176
177 void
178 ext2_empty_inode (ext2_ino_t dir_ino, const char *dirname, const char *basename,
179                   mode_t mode, uid_t uid, gid_t gid,
180                   time_t ctime, time_t atime, time_t mtime,
181                   int major, int minor, int dir_ft, ext2_ino_t *ino_ret)
182 {
183   errcode_t err;
184   struct ext2_inode inode;
185   ext2_ino_t ino;
186
187   err = ext2fs_new_inode (fs, dir_ino, mode, 0, &ino);
188   if (err != 0)
189     error (EXIT_FAILURE, 0, "ext2fs_new_inode: %s", error_message (err));
190
191   memset (&inode, 0, sizeof inode);
192   inode.i_mode = mode;
193   inode.i_uid = uid;
194   inode.i_gid = gid;
195   inode.i_blocks = 0;
196   inode.i_links_count = 1;
197   inode.i_ctime = ctime;
198   inode.i_atime = atime;
199   inode.i_mtime = mtime;
200   inode.i_size = 0;
201   inode.i_block[0] = (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12);
202
203   err = ext2fs_write_new_inode (fs, ino, &inode);
204   if (err != 0)
205     error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err));
206
207   ext2_link (dir_ino, basename, ino, dir_ft);
208
209   ext2fs_inode_alloc_stats2 (fs, ino, 1, 0);
210
211   if (ino_ret)
212     *ino_ret = ino;
213 }
214
215 /* You must create the file first with ext2_empty_inode. */
216 void
217 ext2_write_file (ext2_ino_t ino, const char *buf, size_t size,
218                  const char *orig_filename)
219 {
220   errcode_t err;
221   ext2_file_t file;
222   err = ext2fs_file_open2 (fs, ino, NULL, EXT2_FILE_WRITE, &file);
223   if (err != 0)
224     error (EXIT_FAILURE, 0, "ext2fs_file_open2: %s: %s",
225            orig_filename, error_message (err));
226
227   /* ext2fs_file_write cannot deal with partial writes.  You have
228    * to write the entire file in a single call.
229    */
230   unsigned int written;
231   err = ext2fs_file_write (file, buf, size, &written);
232   if (err != 0)
233     error (EXIT_FAILURE, 0, "ext2fs_file_write: %s: %s\n"
234            "Block allocation failures can happen here for several reasons:\n"
235            " - /lib/modules contains modules with debug that makes the modules very large\n"
236            " - a file listed in 'hostfiles' is unexpectedly very large\n"
237            " - too many packages added to the supermin appliance",
238            orig_filename, error_message (err));
239   if ((size_t) written != size)
240     error (EXIT_FAILURE, 0,
241            "ext2fs_file_write: %s: size = %zu != written = %u\n",
242            orig_filename, size, written);
243
244   err = ext2fs_file_flush (file);
245   if (err != 0)
246     error (EXIT_FAILURE, 0, "ext2fs_file_flush: %s: %s",
247            orig_filename, error_message (err));
248   err = ext2fs_file_close (file);
249   if (err != 0)
250     error (EXIT_FAILURE, 0, "ext2fs_file_close: %s: %s",
251            orig_filename, error_message (err));
252
253   /* Update the true size in the inode. */
254   struct ext2_inode inode;
255   err = ext2fs_read_inode (fs, ino, &inode);
256   if (err != 0)
257     error (EXIT_FAILURE, 0, "ext2fs_read_inode: %s: %s",
258            orig_filename, error_message (err));
259   inode.i_size = size;
260   err = ext2fs_write_inode (fs, ino, &inode);
261   if (err != 0)
262     error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s: %s",
263            orig_filename, error_message (err));
264 }
265
266 /* This is just a wrapper around ext2fs_link which calls
267  * ext2fs_expand_dir as necessary if the directory fills up.  See
268  * definition of expand_dir in the sources of debugfs.
269  */
270 void
271 ext2_link (ext2_ino_t dir_ino, const char *basename, ext2_ino_t ino, int dir_ft)
272 {
273   errcode_t err;
274
275  again:
276   err = ext2fs_link (fs, dir_ino, basename, ino, dir_ft);
277
278   if (err == EXT2_ET_DIR_NO_SPACE) {
279     err = ext2fs_expand_dir (fs, dir_ino);
280     if (err != 0)
281       error (EXIT_FAILURE, 0, "ext2_link: ext2fs_expand_dir: %s: %s",
282              basename, error_message (err));
283     goto again;
284   }
285
286   if (err != 0)
287     error (EXIT_FAILURE, 0, "ext2fs_link: %s: %s",
288              basename, error_message (err));
289 }
290
291 static int
292 release_block (ext2_filsys fs, blk_t *blocknr,
293                 int blockcnt, void *private)
294 {
295   blk_t block;
296
297   block = *blocknr;
298   ext2fs_block_alloc_stats (fs, block, -1);
299   return 0;
300 }
301
302 /* unlink or rmdir path, if it exists. */
303 void
304 ext2_clean_path (ext2_ino_t dir_ino,
305                  const char *dirname, const char *basename,
306                  int isdir)
307 {
308   errcode_t err;
309
310   ext2_ino_t ino;
311   err = ext2fs_lookup (fs, dir_ino, basename, strlen (basename),
312                        NULL, &ino);
313   if (err == EXT2_ET_FILE_NOT_FOUND)
314     return;
315
316   if (!isdir) {
317     struct ext2_inode inode;
318     err = ext2fs_read_inode (fs, ino, &inode);
319     if (err != 0)
320       error (EXIT_FAILURE, 0, "ext2fs_read_inode: %s", error_message (err));
321     inode.i_links_count--;
322     err = ext2fs_write_inode (fs, ino, &inode);
323     if (err != 0)
324       error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err));
325
326     err = ext2fs_unlink (fs, dir_ino, basename, 0, 0);
327     if (err != 0)
328       error (EXIT_FAILURE, 0, "ext2fs_unlink_inode: %s", error_message (err));
329
330     if (inode.i_links_count == 0) {
331       inode.i_dtime = time (NULL);
332       err = ext2fs_write_inode (fs, ino, &inode);
333       if (err != 0)
334         error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err));
335
336       if (ext2fs_inode_has_valid_blocks (&inode)) {
337         int flags = 0;
338         /* From the docs: "BLOCK_FLAG_READ_ONLY is a promise by the
339          * caller that it will not modify returned block number."
340          * RHEL 5 does not have this flag, so just omit it if it is
341          * not defined.
342          */
343 #ifdef BLOCK_FLAG_READ_ONLY
344         flags |= BLOCK_FLAG_READ_ONLY;
345 #endif
346         ext2fs_block_iterate (fs, ino, flags, NULL,
347                               release_block, NULL);
348       }
349
350       ext2fs_inode_alloc_stats2 (fs, ino, -1, isdir);
351     }
352   }
353   /* else it's a directory, what to do? XXX */
354 }
355
356 /* Read in the whole file into memory.  Check the size is still 'size'. */
357 static char *
358 read_whole_file (const char *filename, size_t size)
359 {
360   char *buf = malloc (size);
361   if (buf == NULL)
362     error (EXIT_FAILURE, errno, "malloc");
363
364   int fd = open (filename, O_RDONLY);
365   if (fd == -1)
366     error (EXIT_FAILURE, errno, "open: %s", filename);
367
368   size_t n = 0;
369   char *p = buf;
370
371   while (n < size) {
372     ssize_t r = read (fd, p, size - n);
373     if (r == -1)
374       error (EXIT_FAILURE, errno, "read: %s", filename);
375     if (r == 0)
376       error (EXIT_FAILURE, 0,
377              "error: file has changed size unexpectedly: %s", filename);
378     n += r;
379     p += r;
380   }
381
382   if (close (fd) == -1)
383     error (EXIT_FAILURE, errno, "close: %s", filename);
384
385   return buf;
386 }
387
388 /* Add a file (or directory etc) from the host. */
389 static void
390 ext2_file_stat (const char *orig_filename, const struct stat *statbuf)
391 {
392   errcode_t err;
393
394   if (verbose >= 2)
395     fprintf (stderr, "ext2_file_stat %s 0%o\n",
396              orig_filename, statbuf->st_mode);
397
398   /* Sanity check the path.  These rules are always true for the paths
399    * passed to us here from the appliance layer.  The assertions just
400    * verify that the rules haven't changed.
401    */
402   size_t n = strlen (orig_filename);
403   assert (n <= PATH_MAX);
404   assert (n > 0);
405   assert (orig_filename[0] == '/'); /* always absolute path */
406   assert (n == 1 || orig_filename[n-1] != '/'); /* no trailing slash */
407
408   /* Don't make the root directory, it always exists.  This simplifies
409    * the code that follows.
410    */
411   if (n == 1) return;
412
413   const char *dirname, *basename;
414   const char *p = strrchr (orig_filename, '/');
415   ext2_ino_t dir_ino;
416   if (orig_filename == p) {     /* "/foo" */
417     dirname = "/";
418     basename = orig_filename+1;
419     dir_ino = EXT2_ROOT_INO;
420   } else {                      /* "/foo/bar" */
421     dirname = strndup (orig_filename, p-orig_filename);
422     basename = p+1;
423
424     /* If the parent directory is a symlink to another directory, then
425      * we want to look up the target directory. (RHBZ#698089).
426      */
427     struct stat stat1, stat2;
428     if (lstat (dirname, &stat1) == 0 && S_ISLNK (stat1.st_mode) &&
429         stat (dirname, &stat2) == 0 && S_ISDIR (stat2.st_mode)) {
430       char *new_dirname = malloc (PATH_MAX+1);
431       ssize_t r = readlink (dirname, new_dirname, PATH_MAX+1);
432       if (r == -1)
433         error (EXIT_FAILURE, errno, "readlink: %s", orig_filename);
434       new_dirname[r] = '\0';
435       dirname = new_dirname;
436     }
437
438     /* Look up the parent directory. */
439     err = ext2fs_namei (fs, EXT2_ROOT_INO, EXT2_ROOT_INO, dirname, &dir_ino);
440     if (err != 0)
441       error (EXIT_FAILURE, 0, "ext2: parent directory not found: %s: %s",
442              dirname, error_message (err));
443   }
444
445   ext2_clean_path (dir_ino, dirname, basename, S_ISDIR (statbuf->st_mode));
446
447   int dir_ft;
448
449   /* Create regular file. */
450   if (S_ISREG (statbuf->st_mode)) {
451     /* XXX Hard links get duplicated here. */
452     ext2_ino_t ino;
453     ext2_empty_inode (dir_ino, dirname, basename,
454                       statbuf->st_mode, statbuf->st_uid, statbuf->st_gid,
455                       statbuf->st_ctime, statbuf->st_atime, statbuf->st_mtime,
456                       0, 0, EXT2_FT_REG_FILE, &ino);
457
458     if (statbuf->st_size > 0) {
459       char *buf = read_whole_file (orig_filename, statbuf->st_size);
460       ext2_write_file (ino, buf, statbuf->st_size, orig_filename);
461       free (buf);
462     }
463   }
464   /* Create a symlink. */
465   else if (S_ISLNK (statbuf->st_mode)) {
466     ext2_ino_t ino;
467     ext2_empty_inode (dir_ino, dirname, basename,
468                       statbuf->st_mode, statbuf->st_uid, statbuf->st_gid,
469                       statbuf->st_ctime, statbuf->st_atime, statbuf->st_mtime,
470                       0, 0, EXT2_FT_SYMLINK, &ino);
471
472     char buf[PATH_MAX+1];
473     ssize_t r = readlink (orig_filename, buf, sizeof buf);
474     if (r == -1)
475       error (EXIT_FAILURE, errno, "readlink: %s", orig_filename);
476     ext2_write_file (ino, buf, r, orig_filename);
477   }
478   /* Create directory. */
479   else if (S_ISDIR (statbuf->st_mode))
480     ext2_mkdir (dir_ino, dirname, basename,
481                 statbuf->st_mode, statbuf->st_uid, statbuf->st_gid,
482                 statbuf->st_ctime, statbuf->st_atime, statbuf->st_mtime);
483   /* Create a special file. */
484   else if (S_ISBLK (statbuf->st_mode)) {
485     dir_ft = EXT2_FT_BLKDEV;
486     goto make_special;
487   }
488   else if (S_ISCHR (statbuf->st_mode)) {
489     dir_ft = EXT2_FT_CHRDEV;
490     goto make_special;
491   } else if (S_ISFIFO (statbuf->st_mode)) {
492     dir_ft = EXT2_FT_FIFO;
493     goto make_special;
494   } else if (S_ISSOCK (statbuf->st_mode)) {
495     dir_ft = EXT2_FT_SOCK;
496   make_special:
497     ext2_empty_inode (dir_ino, dirname, basename,
498                       statbuf->st_mode, statbuf->st_uid, statbuf->st_gid,
499                       statbuf->st_ctime, statbuf->st_atime, statbuf->st_mtime,
500                       major (statbuf->st_rdev), minor (statbuf->st_rdev),
501                       dir_ft, NULL);
502   }
503 }
504
505 static void
506 ext2_file (const char *filename)
507 {
508   struct stat statbuf;
509
510   if (lstat (filename, &statbuf) == -1)
511     error (EXIT_FAILURE, errno, "lstat: %s", filename);
512   ext2_file_stat (filename, &statbuf);
513 }
514
515 /* In theory this could be optimized to avoid a namei lookup, but
516  * it probably wouldn't make much difference.
517  */
518 static void
519 ext2_fts_entry (FTSENT *entry)
520 {
521   if (entry->fts_info & FTS_NS || entry->fts_info & FTS_NSOK)
522     ext2_file (entry->fts_path);
523   else
524     ext2_file_stat (entry->fts_path, entry->fts_statp);
525 }
526
527 struct writer ext2_writer = {
528   .wr_start = ext2_start,
529   .wr_end = ext2_end,
530   .wr_file = ext2_file,
531   .wr_file_stat = ext2_file_stat,
532   .wr_fts_entry = ext2_fts_entry,
533   .wr_cpio_file = ext2_cpio_file,
534 };