Add TODO file.
[febootstrap.git] / helper / ext2cpio.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
33 #include "helper.h"
34 #include "ext2internal.h"
35
36 /* This function must unpack the cpio file and add the files it
37  * contains to the ext2 filesystem.  Essentially this is doing the
38  * same thing as the kernel init/initramfs.c code.  Note that we
39  * assume that the cpio is uncompressed newc format and can't/won't
40  * deal with anything else.  All this cpio parsing code is copied to
41  * some extent from init/initramfs.c in the kernel.
42  */
43 #define N_ALIGN(len) ((((len) + 1) & ~3) + 2)
44
45 static unsigned long cpio_ino, nlink;
46 static mode_t mode;
47 static unsigned long body_len, name_len;
48 static uid_t uid;
49 static gid_t gid;
50 static time_t mtime;
51 static int dev_major, dev_minor, rdev_major, rdev_minor;
52 static loff_t curr, next_header;
53 static FILE *fp;
54
55 static void parse_header (char *s);
56 static int parse_next_entry (void);
57 static void skip_to_next_header (void);
58 static void read_file (void);
59 static char *read_whole_body (void);
60 static ext2_ino_t maybe_link (void);
61 static void add_link (ext2_ino_t real_ino);
62 static void clear_links (void);
63
64 void
65 ext2_cpio_file (const char *cpio_file)
66 {
67   fp = fopen (cpio_file, "r");
68   if (fp == NULL)
69     error (EXIT_FAILURE, errno, "open: %s", cpio_file);
70
71   curr = 0;
72   while (parse_next_entry ())
73     ;
74
75   fclose (fp);
76 }
77
78 static int
79 parse_next_entry (void)
80 {
81   clearerr (fp);
82
83   char header[110];
84
85   /* Skip padding and synchronize with the next header. */
86  again:
87   if (fread (&header[0], 4, 1, fp) != 1) {
88     if (feof (fp))
89       return 0;
90     error (EXIT_FAILURE, errno, "read failure reading cpio file");
91   }
92   curr += 4;
93   if (memcmp (header, "\0\0\0\0", 4) == 0)
94     goto again;
95
96   /* Read the rest of the header field. */
97   if (fread (&header[4], sizeof header - 4, 1, fp) != 1)
98     error (EXIT_FAILURE, errno, "read failure reading cpio file");
99   curr += sizeof header - 4;
100
101   if (verbose >= 2)
102     fprintf (stderr, "cpio header %s\n", header);
103
104   if (memcmp (header, "070707", 6) == 0)
105     error (EXIT_FAILURE, 0, "incorrect cpio method: use -H newc option");
106   if (memcmp (header, "070701", 6) != 0)
107     error (EXIT_FAILURE, 0, "input is not a cpio file");
108
109   parse_header (header);
110
111   next_header = curr + N_ALIGN(name_len) + body_len;
112   next_header = (next_header + 3) & ~3;
113   if (name_len <= 0 || name_len > PATH_MAX)
114     skip_to_next_header ();
115   else if (S_ISLNK (mode)) {
116     if (body_len <= 0 || body_len > PATH_MAX)
117       skip_to_next_header ();
118     else
119       read_file ();
120   }
121   else if (!S_ISREG (mode) && body_len > 0)
122     skip_to_next_header (); /* only regular files have bodies */
123   else
124     read_file (); /* could be file, directory, block special, ... */
125
126   return 1;
127 }
128
129 static void
130 parse_header (char *s)
131 {
132   unsigned long parsed[12];
133   char buf[9];
134   int i;
135
136   buf[8] = '\0';
137   for (i = 0, s += 6; i < 12; i++, s += 8) {
138     memcpy (buf, s, 8);
139     parsed[i] = strtoul (buf, NULL, 16);
140   }
141   cpio_ino = parsed[0]; /* fake inode number from cpio file */
142   mode = parsed[1];
143   uid = parsed[2];
144   gid = parsed[3];
145   nlink = parsed[4];
146   mtime = parsed[5];
147   body_len = parsed[6];
148   dev_major = parsed[7];
149   dev_minor = parsed[8];
150   rdev_major = parsed[9];
151   rdev_minor = parsed[10];
152   name_len = parsed[11];
153 }
154
155 static void
156 skip_to_next_header (void)
157 {
158   char buf[65536];
159
160   while (curr < next_header) {
161     size_t bytes = (size_t) (next_header - curr);
162     if (bytes > sizeof buf)
163       bytes = sizeof buf;
164     size_t r = fread (buf, 1, bytes, fp);
165     if (r == 0)
166       error (EXIT_FAILURE, errno, "error or unexpected end of cpio file");
167     curr += r;
168   }
169 }
170
171 /* Read any sort of file.  The body will only be present for
172  * regular files and symlinks.
173  */
174 static void
175 read_file (void)
176 {
177   errcode_t err;
178   int dir_ft;
179   char name[N_ALIGN(name_len)+1]; /* asserted above this is <= PATH_MAX */
180
181   if (fread (name, N_ALIGN(name_len), 1, fp) != 1)
182     error (EXIT_FAILURE, errno, "read failure reading name field in cpio file");
183   curr += N_ALIGN(name_len);
184
185   name[name_len] = '\0';
186
187   if (verbose >= 2)
188     fprintf (stderr, "ext2 read_file %s %o\n", name, mode);
189
190   if (strcmp (name, "TRAILER!!!") == 0) {
191     clear_links ();
192     goto skip;
193   }
194
195   /* The name will be something like "bin/ls" or "./bin/ls".  It won't
196    * (ever?) be an absolute path.  Skip leading parts, and if it refers
197    * to the root directory just skip it entirely.
198    */
199   char *dirname = name, *basename;
200   if (*dirname == '.')
201     dirname++;
202   if (*dirname == '/')
203     dirname++;
204   if (*dirname == '\0')
205     goto skip;
206
207   ext2_ino_t dir_ino;
208   basename = strrchr (dirname, '/');
209   if (basename == NULL) {
210     basename = dirname;
211     dir_ino = EXT2_ROOT_INO;
212   } else {
213     *basename++ = '\0';
214
215     /* Look up the parent directory. */
216     err = ext2fs_namei (fs, EXT2_ROOT_INO, EXT2_ROOT_INO, dirname, &dir_ino);
217     if (err != 0)
218       error (EXIT_FAILURE, 0, "ext2: parent directory not found: %s: %s",
219              dirname, error_message (err));
220   }
221
222   if (verbose >= 2)
223     fprintf (stderr, "ext2 read_file dirname %s basename %s\n",
224              dirname, basename);
225
226   ext2_clean_path (dir_ino, dirname, basename, S_ISDIR (mode));
227
228   /* Create a regular file. */
229   if (S_ISREG (mode)) {
230     ext2_ino_t ml = maybe_link ();
231     ext2_ino_t ino;
232     if (ml <= 1) {
233       ext2_empty_inode (dir_ino, dirname, basename,
234                         mode, uid, gid, mtime, mtime, mtime,
235                         0, 0, EXT2_FT_REG_FILE, &ino);
236       if (ml == 1)
237         add_link (ino);
238     }
239     else /* ml >= 2 */ {
240       /* It's a hard link back to a previous file. */
241       ino = ml;
242       ext2_link (dir_ino, basename, ino, EXT2_FT_REG_FILE);
243     }
244
245     if (body_len) {
246       char *buf = read_whole_body ();
247       ext2_write_file (ino, buf, body_len, name);
248       free (buf);
249     }
250   }
251   /* Create a symlink. */
252   else if (S_ISLNK (mode)) {
253     ext2_ino_t ino;
254     ext2_empty_inode (dir_ino, dirname, basename,
255                       mode, uid, gid, mtime, mtime, mtime,
256                       0, 0, EXT2_FT_SYMLINK, &ino);
257
258     char *buf = read_whole_body ();
259     ext2_write_file (ino, buf, body_len, name);
260     free (buf);
261   }
262   /* Create a directory. */
263   else if (S_ISDIR (mode)) {
264     ext2_mkdir (dir_ino, dirname, basename,
265                 mode, uid, gid, mtime, mtime, mtime);
266   }
267   /* Create a special file. */
268   else if (S_ISBLK (mode)) {
269     dir_ft = EXT2_FT_BLKDEV;
270     goto make_special;
271   }
272   else if (S_ISCHR (mode)) {
273     dir_ft = EXT2_FT_CHRDEV;
274     goto make_special;
275   } else if (S_ISFIFO (mode)) {
276     dir_ft = EXT2_FT_FIFO;
277     goto make_special;
278   } else if (S_ISSOCK (mode)) {
279     dir_ft = EXT2_FT_SOCK;
280   make_special:
281     /* Just like the kernel, we ignore special files with nlink > 1. */
282     if (maybe_link () == 0)
283       ext2_empty_inode (dir_ino, dirname, basename,
284                         mode, uid, gid, mtime, mtime, mtime,
285                         rdev_major, rdev_minor, dir_ft, NULL);
286   }
287
288  skip:
289   skip_to_next_header ();
290 }
291
292 static char *
293 read_whole_body (void)
294 {
295   char *buf = malloc (body_len);
296   if (buf == NULL)
297     error (EXIT_FAILURE, errno, "malloc");
298
299   size_t r = fread (buf, body_len, 1, fp);
300   if (r != 1)
301     error (EXIT_FAILURE, errno, "read failure reading body in cpio file");
302   curr += body_len;
303
304   return buf;
305 }
306
307 struct links {
308   struct links *next;
309   unsigned long cpio_ino;       /* fake ino from cpio file */
310   int minor;
311   int major;
312   ext2_ino_t real_ino;          /* real inode number on ext2 filesystem */
313 };
314 static struct links *links_head = NULL;
315
316 /* If it's a hard link, return the linked inode number in the real
317  * ext2 filesystem.
318  *
319  * Returns: 0 = not a hard link
320  *          1 = possible unresolved hard link
321  *          inode number = resolved hard link to this inode
322  */
323 static ext2_ino_t
324 maybe_link (void)
325 {
326   if (nlink >= 2) {
327     struct links *p;
328     for (p = links_head; p; p = p->next) {
329       if (p->cpio_ino != cpio_ino)
330         continue;
331       if (p->minor != dev_minor)
332         continue;
333       if (p->major != dev_major)
334         continue;
335       return p->real_ino;
336     }
337     return 1;
338   }
339
340   return 0;
341 }
342
343 static void
344 add_link (ext2_ino_t real_ino)
345 {
346   struct links *p = malloc (sizeof (*p));
347   p->cpio_ino = cpio_ino;
348   p->minor = dev_minor;
349   p->major = dev_major;
350   p->real_ino = real_ino;
351 }
352
353 static void
354 clear_links (void)
355 {
356   /* Don't bother to free the linked list in this short-lived program. */
357   links_head = NULL;
358 }