7ad120badbe229319a837afc048e8d05d03230a3
[febootstrap.git] / helper / ext2.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 <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 1GB disk the overhead is
47  * approximately 16MB.
48  *
49  * In future, make this configurable, or determine it from the input
50  * files (XXX).
51  */
52 #define APPLIANCE_SIZE (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) == -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   /* Write out changes and close. */
111   errcode_t err = ext2fs_close (fs);
112   if (err != 0)
113     error (EXIT_FAILURE, 0, "ext2fs_close: %s", error_message (err));
114 }
115
116 void
117 ext2_mkdir (ext2_ino_t dir_ino, const char *dirname, const char *basename,
118             mode_t mode, uid_t uid, gid_t gid,
119             time_t ctime, time_t atime, time_t mtime)
120 {
121   errcode_t err;
122
123   mode = LINUX_S_IFDIR | (mode & 0777);
124
125   /* Does the directory exist?  This is legitimate: we just skip
126    * this case.
127    */
128   ext2_ino_t ino;
129   err = ext2fs_namei (fs, EXT2_ROOT_INO, dir_ino, basename, &ino);
130   if (err == 0)
131     return; /* skip */
132
133   /* Otherwise, create it. */
134   err = ext2fs_new_inode (fs, dir_ino, mode, 0, &ino);
135   if (err != 0)
136     error (EXIT_FAILURE, 0, "ext2fs_new_inode: %s", error_message (err));
137
138   err = ext2fs_mkdir (fs, dir_ino, ino, basename);
139   if (err != 0)
140     error (EXIT_FAILURE, 0, "ext2fs_mkdir: %s/%s: %s",
141            dirname, basename, error_message (err));
142
143   /* Copy the final permissions, UID etc. to the inode. */
144   struct ext2_inode inode;
145   err = ext2fs_read_inode (fs, ino, &inode);
146   if (err != 0)
147     error (EXIT_FAILURE, 0, "ext2fs_read_inode: %s", error_message (err));
148   inode.i_mode = mode;
149   inode.i_uid = uid;
150   inode.i_gid = gid;
151   inode.i_ctime = ctime;
152   inode.i_atime = atime;
153   inode.i_mtime = mtime;
154   err = ext2fs_write_inode (fs, ino, &inode);
155   if (err != 0)
156     error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err));
157 }
158
159 void
160 ext2_empty_inode (ext2_ino_t dir_ino, const char *dirname, const char *basename,
161                   mode_t mode, uid_t uid, gid_t gid,
162                   time_t ctime, time_t atime, time_t mtime,
163                   int major, int minor, int dir_ft, ext2_ino_t *ino_ret)
164 {
165   errcode_t err;
166   struct ext2_inode inode;
167   ext2_ino_t ino;
168
169   err = ext2fs_new_inode (fs, dir_ino, mode, 0, &ino);
170   if (err != 0)
171     error (EXIT_FAILURE, 0, "ext2fs_new_inode: %s", error_message (err));
172
173   memset (&inode, 0, sizeof inode);
174   inode.i_mode = mode;
175   inode.i_uid = uid;
176   inode.i_gid = gid;
177   inode.i_blocks = 0;
178   inode.i_links_count = 1;
179   inode.i_ctime = ctime;
180   inode.i_atime = atime;
181   inode.i_mtime = mtime;
182   inode.i_size = 0;
183   inode.i_block[0] = (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12);
184
185   err = ext2fs_write_new_inode (fs, ino, &inode);
186   if (err != 0)
187     error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err));
188
189   ext2_link (dir_ino, basename, ino, dir_ft);
190
191   ext2fs_inode_alloc_stats2 (fs, ino, 1, 0);
192
193   if (ino_ret)
194     *ino_ret = ino;
195 }
196
197 /* You must create the file first with ext2_empty_inode. */
198 void
199 ext2_write_file (ext2_ino_t ino, const char *buf, size_t size)
200 {
201   errcode_t err;
202   ext2_file_t file;
203   err = ext2fs_file_open2 (fs, ino, NULL, EXT2_FILE_WRITE, &file);
204   if (err != 0)
205     error (EXIT_FAILURE, 0, "ext2fs_file_open2: %s", error_message (err));
206
207   /* ext2fs_file_write cannot deal with partial writes.  You have
208    * to write the entire file in a single call.
209    */
210   unsigned int written;
211   err = ext2fs_file_write (file, buf, size, &written);
212   if (err != 0)
213     error (EXIT_FAILURE, 0, "ext2fs_file_write: %s", error_message (err));
214   if ((size_t) written != size)
215     error (EXIT_FAILURE, 0,
216            "ext2fs_file_write: size = %zu != written = %u\n",
217            size, written);
218
219   err = ext2fs_file_flush (file);
220   if (err != 0)
221     error (EXIT_FAILURE, 0, "ext2fs_file_flush: %s", error_message (err));
222   err = ext2fs_file_close (file);
223   if (err != 0)
224     error (EXIT_FAILURE, 0, "ext2fs_file_close: %s", error_message (err));
225
226   /* Update the true size in the inode. */
227   struct ext2_inode inode;
228   err = ext2fs_read_inode (fs, ino, &inode);
229   if (err != 0)
230     error (EXIT_FAILURE, 0, "ext2fs_read_inode: %s", error_message (err));
231   inode.i_size = size;
232   err = ext2fs_write_inode (fs, ino, &inode);
233   if (err != 0)
234     error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err));
235 }
236
237 /* This is just a wrapper around ext2fs_link which calls
238  * ext2fs_expand_dir as necessary if the directory fills up.  See
239  * definition of expand_dir in the sources of debugfs.
240  */
241 void
242 ext2_link (ext2_ino_t dir_ino, const char *basename, ext2_ino_t ino, int dir_ft)
243 {
244   errcode_t err;
245
246  again:
247   err = ext2fs_link (fs, dir_ino, basename, ino, dir_ft);
248
249   if (err == EXT2_ET_DIR_NO_SPACE) {
250     err = ext2fs_expand_dir (fs, dir_ino);
251     if (err != 0)
252       error (EXIT_FAILURE, 0, "ext2_link: ext2fs_expand_dir: %s: %s",
253              basename, error_message (err));
254     goto again;
255   }
256
257   if (err != 0)
258     error (EXIT_FAILURE, 0, "ext2fs_link: %s: %s",
259              basename, error_message (err));
260 }
261
262 static int
263 release_block (ext2_filsys fs, blk_t *blocknr,
264                 int blockcnt, void *private)
265 {
266   blk_t block;
267
268   block = *blocknr;
269   ext2fs_block_alloc_stats (fs, block, -1);
270   return 0;
271 }
272
273 /* unlink or rmdir path, if it exists. */
274 void
275 ext2_clean_path (ext2_ino_t dir_ino,
276                  const char *dirname, const char *basename,
277                  int isdir)
278 {
279   errcode_t err;
280
281   ext2_ino_t ino;
282   err = ext2fs_lookup (fs, dir_ino, basename, strlen (basename),
283                        NULL, &ino);
284   if (err == EXT2_ET_FILE_NOT_FOUND)
285     return;
286
287   if (!isdir) {
288     struct ext2_inode inode;
289     err = ext2fs_read_inode (fs, ino, &inode);
290     if (err != 0)
291       error (EXIT_FAILURE, 0, "ext2fs_read_inode: %s", error_message (err));
292     inode.i_links_count--;
293     err = ext2fs_write_inode (fs, ino, &inode);
294     if (err != 0)
295       error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err));
296
297     err = ext2fs_unlink (fs, dir_ino, basename, 0, 0);
298     if (err != 0)
299       error (EXIT_FAILURE, 0, "ext2fs_unlink_inode: %s", error_message (err));
300
301     if (inode.i_links_count == 0) {
302       inode.i_dtime = time (NULL);
303       err = ext2fs_write_inode (fs, ino, &inode);
304       if (err != 0)
305         error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err));
306
307       if (ext2fs_inode_has_valid_blocks (&inode)) {
308         int flags = 0;
309         /* From the docs: "BLOCK_FLAG_READ_ONLY is a promise by the
310          * caller that it will not modify returned block number."
311          * RHEL 5 does not have this flag, so just omit it if it is
312          * not defined.
313          */
314 #ifdef BLOCK_FLAG_READ_ONLY
315         flags |= BLOCK_FLAG_READ_ONLY;
316 #endif
317         ext2fs_block_iterate (fs, ino, flags, NULL,
318                               release_block, NULL);
319       }
320
321       ext2fs_inode_alloc_stats2 (fs, ino, -1, isdir);
322     }
323   }
324   /* else it's a directory, what to do? XXX */
325 }
326
327 /* Read in the whole file into memory.  Check the size is still 'size'. */
328 static char *
329 read_whole_file (const char *filename, size_t size)
330 {
331   char *buf = malloc (size);
332   if (buf == NULL)
333     error (EXIT_FAILURE, errno, "malloc");
334
335   int fd = open (filename, O_RDONLY);
336   if (fd == -1)
337     error (EXIT_FAILURE, errno, "open: %s", filename);
338
339   size_t n = 0;
340   char *p = buf;
341
342   while (n < size) {
343     ssize_t r = read (fd, p, size - n);
344     if (r == -1)
345       error (EXIT_FAILURE, errno, "read: %s", filename);
346     if (r == 0)
347       error (EXIT_FAILURE, 0,
348              "error: file has changed size unexpectedly: %s", filename);
349     n += r;
350     p += r;
351   }
352
353   if (close (fd) == -1)
354     error (EXIT_FAILURE, errno, "close: %s", filename);
355
356   return buf;
357 }
358
359 /* Add a file (or directory etc) from the host. */
360 static void
361 ext2_file_stat (const char *orig_filename, const struct stat *statbuf)
362 {
363   errcode_t err;
364
365   if (verbose >= 2)
366     fprintf (stderr, "ext2_file_stat %s 0%o\n",
367              orig_filename, statbuf->st_mode);
368
369   /* Sanity check the path.  These rules are always true for the paths
370    * passed to us here from the appliance layer.  The assertions just
371    * verify that the rules haven't changed.
372    */
373   size_t n = strlen (orig_filename);
374   assert (n <= PATH_MAX);
375   assert (n > 0);
376   assert (orig_filename[0] == '/'); /* always absolute path */
377   assert (n == 1 || orig_filename[n-1] != '/'); /* no trailing slash */
378
379   /* Don't make the root directory, it always exists.  This simplifies
380    * the code that follows.
381    */
382   if (n == 1) return;
383
384   const char *dirname, *basename;
385   const char *p = strrchr (orig_filename, '/');
386   ext2_ino_t dir_ino;
387   if (orig_filename == p) {     /* "/foo" */
388     dirname = "/";
389     basename = orig_filename+1;
390     dir_ino = EXT2_ROOT_INO;
391   } else {                      /* "/foo/bar" */
392     dirname = strndup (orig_filename, p-orig_filename);
393     basename = p+1;
394
395     /* Look up the parent directory. */
396     err = ext2fs_namei (fs, EXT2_ROOT_INO, EXT2_ROOT_INO, dirname, &dir_ino);
397     if (err != 0)
398       error (EXIT_FAILURE, 0, "ext2: parent directory not found: %s: %s",
399              dirname, error_message (err));
400   }
401
402   ext2_clean_path (dir_ino, dirname, basename, S_ISDIR (statbuf->st_mode));
403
404   int dir_ft;
405
406   /* Create regular file. */
407   if (S_ISREG (statbuf->st_mode)) {
408     /* XXX Hard links get duplicated here. */
409     ext2_ino_t ino;
410     ext2_empty_inode (dir_ino, dirname, basename,
411                       statbuf->st_mode, statbuf->st_uid, statbuf->st_gid,
412                       statbuf->st_ctime, statbuf->st_atime, statbuf->st_mtime,
413                       0, 0, EXT2_FT_REG_FILE, &ino);
414
415     if (statbuf->st_size > 0) {
416       char *buf = read_whole_file (orig_filename, statbuf->st_size);
417       ext2_write_file (ino, buf, statbuf->st_size);
418       free (buf);
419     }
420   }
421   /* Create a symlink. */
422   else if (S_ISLNK (statbuf->st_mode)) {
423     ext2_ino_t ino;
424     ext2_empty_inode (dir_ino, dirname, basename,
425                       statbuf->st_mode, statbuf->st_uid, statbuf->st_gid,
426                       statbuf->st_ctime, statbuf->st_atime, statbuf->st_mtime,
427                       0, 0, EXT2_FT_SYMLINK, &ino);
428
429     char buf[PATH_MAX+1];
430     ssize_t r = readlink (orig_filename, buf, sizeof buf);
431     if (r == -1)
432       error (EXIT_FAILURE, errno, "readlink: %s", orig_filename);
433     ext2_write_file (ino, buf, r);
434   }
435   /* Create directory. */
436   else if (S_ISDIR (statbuf->st_mode))
437     ext2_mkdir (dir_ino, dirname, basename,
438                 statbuf->st_mode, statbuf->st_uid, statbuf->st_gid,
439                 statbuf->st_ctime, statbuf->st_atime, statbuf->st_mtime);
440   /* Create a special file. */
441   else if (S_ISBLK (statbuf->st_mode)) {
442     dir_ft = EXT2_FT_BLKDEV;
443     goto make_special;
444   }
445   else if (S_ISCHR (statbuf->st_mode)) {
446     dir_ft = EXT2_FT_CHRDEV;
447     goto make_special;
448   } else if (S_ISFIFO (statbuf->st_mode)) {
449     dir_ft = EXT2_FT_FIFO;
450     goto make_special;
451   } else if (S_ISSOCK (statbuf->st_mode)) {
452     dir_ft = EXT2_FT_SOCK;
453   make_special:
454     ext2_empty_inode (dir_ino, dirname, basename,
455                       statbuf->st_mode, statbuf->st_uid, statbuf->st_gid,
456                       statbuf->st_ctime, statbuf->st_atime, statbuf->st_mtime,
457                       major (statbuf->st_rdev), minor (statbuf->st_rdev),
458                       dir_ft, NULL);
459   }
460 }
461
462 static void
463 ext2_file (const char *filename)
464 {
465   struct stat statbuf;
466
467   if (lstat (filename, &statbuf) == -1)
468     error (EXIT_FAILURE, errno, "lstat: %s", filename);
469   ext2_file_stat (filename, &statbuf);
470 }
471
472 /* In theory this could be optimized to avoid a namei lookup, but
473  * it probably wouldn't make much difference.
474  */
475 static void
476 ext2_fts_entry (FTSENT *entry)
477 {
478   if (entry->fts_info & FTS_NS || entry->fts_info & FTS_NSOK)
479     ext2_file (entry->fts_path);
480   else
481     ext2_file_stat (entry->fts_path, entry->fts_statp);
482 }
483
484 struct writer ext2_writer = {
485   .wr_start = ext2_start,
486   .wr_end = ext2_end,
487   .wr_file = ext2_file,
488   .wr_file_stat = ext2_file_stat,
489   .wr_fts_entry = ext2_fts_entry,
490   .wr_cpio_file = ext2_cpio_file,
491 };