helper: Change to root directory before running find command.
[febootstrap.git] / helper / ext2initrd.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 /* ext2 requires a small initrd in order to boot.  This builds it. */
20
21 #include <config.h>
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <dirent.h>
28 #include <errno.h>
29 #include <assert.h>
30
31 #include "error.h"
32 #include "full-write.h"
33 #include "xalloc.h"
34 #include "xvasprintf.h"
35
36 #include "helper.h"
37 #include "ext2internal.h"
38
39 static void read_module_deps (const char *modpath);
40 static void free_module_deps (void);
41 static const char *get_module_dep (const char *);
42
43 /* The init binary. */
44 extern char _binary_init_start, _binary_init_end, _binary_init_size;
45
46 /* The list of modules (wildcards) we consider for inclusion in the
47  * mini initrd.  Only what is needed in order to find a device with an
48  * ext2 filesystem on it.
49  */
50 static const char *kmods[] = {
51   "ext2.ko",
52   "virtio*.ko",
53   "ide*.ko",
54   "libata*.ko",
55   "piix*.ko",
56   "scsi_transport_spi.ko",
57   "scsi_mod.ko",
58   "sd_mod.ko",
59   "sym53c8xx.ko",
60   "ata_piix.ko",
61   "sr_mod.ko",
62   "mbcache.ko",
63   "crc*.ko",
64   "libcrc*.ko",
65   NULL
66 };
67
68 void
69 ext2_make_initrd (const char *modpath, const char *initrd)
70 {
71   char dir[] = "/tmp/ext2initrdXXXXXX";
72   if (mkdtemp (dir) == NULL)
73     error (EXIT_FAILURE, errno, "mkdtemp");
74
75   char *cmd;
76   int r;
77
78   /* Copy kernel modules into tmpdir. */
79   size_t n = strlen (modpath) + strlen (dir) + 64;
80   size_t i;
81   for (i = 0; kmods[i] != NULL; ++i)
82     n += strlen (kmods[i]) + 16;
83   cmd = malloc (n);
84   /* "cd /" here is for virt-v2v.  It's cwd might not be accessible by
85    * the current user (because it sometimes sets its own uid) and the
86    * "find" command works by changing directory then changing back to
87    * the cwd.  This results in a warning:
88    *
89    * find: failed to restore initial working directory: Permission denied
90    *
91    * Note this only works because "modpath" and temporary "dir" are
92    * currently guaranteed to be absolute paths, hence assertion.
93    */
94   assert (modpath[0] == '/');
95   sprintf (cmd, "cd / ; find '%s' ", modpath);
96   for (i = 0; kmods[i] != NULL; ++i) {
97     if (i > 0) strcat (cmd, "-o ");
98     strcat (cmd, "-name '");
99     strcat (cmd, kmods[i]);
100     strcat (cmd, "' ");
101   }
102   strcat (cmd, "| xargs cp -t ");
103   strcat (cmd, dir);
104   if (verbose >= 2) fprintf (stderr, "%s\n", cmd);
105   r = system (cmd);
106   if (r == -1 || WEXITSTATUS (r) != 0)
107     error (EXIT_FAILURE, 0, "ext2_make_initrd: copy kmods failed");
108   free (cmd);
109
110   /* The above command effectively gives us the final list of modules.
111    * Calculate dependencies from modpath/modules.dep and write that
112    * into the output.
113    */
114   read_module_deps (modpath);
115
116   cmd = xasprintf ("tsort > %s/modules", dir);
117   if (verbose >= 2) fprintf (stderr, "%s\n", cmd);
118   FILE *pp = popen (cmd, "w");
119   if (pp == NULL)
120     error (EXIT_FAILURE, errno, "tsort: failed to create modules list");
121
122   DIR *dr = opendir (dir);
123   if (dr == NULL)
124     error (EXIT_FAILURE, errno, "opendir: %s", dir);
125
126   struct dirent *d;
127   while ((errno = 0, d = readdir (dr)) != NULL) {
128     size_t n = strlen (d->d_name);
129     if (n >= 3 &&
130         d->d_name[n-3] == '.' &&
131         d->d_name[n-2] == 'k' &&
132         d->d_name[n-1] == 'o') {
133       const char *dep = get_module_dep (d->d_name);
134       if (dep)
135         /* Reversed so that tsort will print the final list in the
136          * order that it has to be loaded.
137          */
138         fprintf (pp, "%s %s\n", dep, d->d_name);
139       else
140         /* No dependencies, just make it depend on itself so that
141          * tsort prints it.
142          */
143         fprintf (pp, "%s %s\n", d->d_name, d->d_name);
144     }
145   }
146   if (errno)
147     error (EXIT_FAILURE, errno, "readdir: %s", dir);
148
149   if (closedir (dr) == -1)
150     error (EXIT_FAILURE, errno, "closedir: %s", dir);
151
152   if (pclose (pp) == -1)
153     error (EXIT_FAILURE, errno, "pclose: %s", cmd);
154
155   free (cmd);
156   free_module_deps ();
157
158   /* Copy in insmod static binary. */
159   cmd = xasprintf ("cp %s %s", INSMODSTATIC, dir);
160   if (verbose >= 2) fprintf (stderr, "%s\n", cmd);
161   r = system (cmd);
162   if (r == -1 || WEXITSTATUS (r) != 0)
163     error (EXIT_FAILURE, 0,
164            "ext2_make_initrd: copy %s failed", INSMODSTATIC);
165   free (cmd);
166
167   /* Copy in the init program, linked into this program as a data blob. */
168   char *init = xasprintf ("%s/init", dir);
169   int fd = open (init, O_WRONLY|O_TRUNC|O_CREAT|O_NOCTTY, 0755);
170   if (fd == -1)
171     error (EXIT_FAILURE, errno, "open: %s", init);
172
173   n = (size_t) &_binary_init_size;
174   if (full_write (fd, &_binary_init_start, n) != n)
175     error (EXIT_FAILURE, errno, "write: %s", init);
176
177   if (close (fd) == -1)
178     error (EXIT_FAILURE, errno, "close: %s", init);
179
180   free (init);
181
182   /* Build the cpio file. */
183   cmd = xasprintf ("(cd %s && (echo . ; ls -1)"
184                    " | cpio --quiet -o -H newc) > '%s'",
185                    dir, initrd);
186   if (verbose >= 2) fprintf (stderr, "%s\n", cmd);
187   r = system (cmd);
188   if (r == -1 || WEXITSTATUS (r) != 0)
189     error (EXIT_FAILURE, 0, "ext2_make_initrd: cpio failed");
190   free (cmd);
191
192   /* Construction of 'dir' above ensures this is safe. */
193   cmd = xasprintf ("rm -rf %s", dir);
194   if (verbose >= 2) fprintf (stderr, "%s\n", cmd);
195   system (cmd);
196   free (cmd);
197 }
198
199 /* Module dependencies. */
200 struct moddep {
201   struct moddep *next;
202   char *name;
203   char *dep;
204 };
205 struct moddep *moddeps = NULL;
206
207 static void add_module_dep (const char *name, const char *dep);
208
209 static void
210 free_module_deps (void)
211 {
212   /* Short-lived program, don't bother to free it. */
213   moddeps = NULL;
214 }
215
216 /* Read modules.dep into internal structure. */
217 static void
218 read_module_deps (const char *modpath)
219 {
220   free_module_deps ();
221
222   char *filename = xasprintf ("%s/modules.dep", modpath);
223   FILE *fp = fopen (filename, "r");
224   if (fp == NULL)
225     error (EXIT_FAILURE, errno, "open: %s", modpath);
226
227   char *line = NULL;
228   size_t llen = 0;
229   ssize_t len;
230   while ((len = getline (&line, &llen, fp)) != -1) {
231     if (len > 0 && line[len-1] == '\n')
232       line[--len] = '\0';
233
234     char *name = strtok (line, ": ");
235     if (!name) continue;
236
237     /* Only want the module basename, but keep the ".ko" extension. */
238     char *p = strrchr (name, '/');
239     if (p) name = p+1;
240
241     char *dep;
242     while ((dep = strtok (NULL, " ")) != NULL) {
243       p = strrchr (dep, '/');
244       if (p) dep = p+1;
245
246       add_module_dep (name, dep);
247     }
248   }
249
250   free (line);
251   fclose (fp);
252 }
253
254 /* Module 'name' requires 'dep' to be loaded first. */
255 static void
256 add_module_dep (const char *name, const char *dep)
257 {
258   struct moddep *m = xmalloc (sizeof *m);
259   m->next = moddeps;
260   moddeps = m;
261   m->name = xstrdup (name);
262   m->dep = xstrdup (dep);
263 }
264
265 static const char *
266 get_module_dep (const char *name)
267 {
268   struct moddep *m;
269
270   for (m = moddeps; m; m = m->next)
271     if (strcmp (m->name, name) == 0)
272       return m->dep;
273
274   return NULL;
275 }