helper/init: Display version and zlib support.
[febootstrap.git] / helper / init.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 /* This very minimal init "script" goes in the mini-initrd used to
20  * boot the ext2-based appliance.  Note we have no shell, so we cannot
21  * use system(3) to run external commands.  In fact, we don't have
22  * very much at all, except this program, and some kernel modules.
23  */
24
25 #include <config.h>
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <dirent.h>
34 #include <sys/types.h>
35 #include <sys/mount.h>
36 #include <sys/stat.h>
37 #include <sys/wait.h>
38
39 #include <asm/unistd.h>
40
41 #ifdef HAVE_LIBZ
42 #include <zlib.h>
43 #endif
44
45 extern long init_module (void *, unsigned long, const char *);
46
47 /* translation taken from module-init-tools/insmod.c  */
48 static const char *moderror(int err)
49 {
50   switch (err) {
51   case ENOEXEC:
52     return "Invalid module format";
53   case ENOENT:
54     return "Unknown symbol in module";
55   case ESRCH:
56     return "Module has wrong symbol version";
57   case EINVAL:
58     return "Invalid parameters";
59   default:
60     return strerror(err);
61   }
62 }
63
64 /* Leave this enabled for now.  When we get more confident in the boot
65  * process we can turn this off or make it configurable.
66  */
67 #define verbose 1
68
69 static void mount_proc (void);
70 static void print_uptime (void);
71 static void insmod (const char *filename);
72 static void show_directory (const char *dir);
73
74 static char line[1024];
75
76 int
77 main ()
78 {
79   mount_proc ();
80
81   print_uptime ();
82   fprintf (stderr, "febootstrap: ext2 mini initrd starting up: "
83            PACKAGE_VERSION
84 #ifdef HAVE_LIBZ
85            " zlib"
86 #endif
87            "\n");
88
89   /* Create some fixed directories. */
90   mkdir ("/dev", 0755);
91   mkdir ("/root", 0755);
92   mkdir ("/sys", 0755);
93
94   /* Mount /sys. */
95   if (verbose)
96     fprintf (stderr, "febootstrap: mounting /sys\n");
97   if (mount ("sysfs", "/sys", "sysfs", 0, "") == -1) {
98     perror ("mount: /sys");
99     exit (EXIT_FAILURE);
100   }
101
102   FILE *fp = fopen ("/modules", "r");
103   if (fp == NULL) {
104     perror ("fopen: /modules");
105     exit (EXIT_FAILURE);
106   }
107   while (fgets (line, sizeof line, fp)) {
108     size_t n = strlen (line);
109     if (n > 0 && line[n-1] == '\n')
110       line[--n] = '\0';
111
112     /* XXX Because of the way we construct the module list, the
113      * "modules" file can contain non-existent modules.  Ignore those
114      * for now.  Really we should add them as missing dependencies.
115      * See ext2initrd.c:ext2_make_initrd().
116      */
117     if (access (line, R_OK) == 0)
118       insmod (line);
119     else
120       fprintf (stderr, "skipped %s, module is missing\n", line);
121   }
122   fclose (fp);
123
124   /* Look for the ext2 filesystem device.  It's always the last
125    * one that was added.
126    * XXX More than 25 devices?
127    */
128   char path[] = "/sys/block/xdx/dev";
129   char class[3] = { 'v', 's', 'h' };
130   size_t i, j;
131   fp = NULL;
132   for (i = 0; i < sizeof class; ++i) {
133     for (j = 'z'; j >= 'a'; --j) {
134       path[11] = class[i];
135       path[13] = j;
136       fp = fopen (path, "r");
137       if (fp != NULL)
138         goto found;
139     }
140   }
141   fprintf (stderr,
142            "febootstrap: no ext2 root device found\n"
143            "Please include FULL verbose output in your bug report.\n");
144   exit (EXIT_FAILURE);
145
146  found:
147   if (verbose)
148     fprintf (stderr, "febootstrap: picked %s as root device\n", path);
149
150   fgets (line, sizeof line, fp);
151   int major = atoi (line);
152   char *p = line + strcspn (line, ":") + 1;
153   int minor = atoi (p);
154
155   fclose (fp);
156   if (umount ("/sys") == -1) {
157     perror ("umount: /sys");
158     exit (EXIT_FAILURE);
159   }
160
161   if (verbose)
162     fprintf (stderr, "febootstrap: creating /dev/root as block special %d:%d\n",
163              major, minor);
164
165   if (mknod ("/dev/root", S_IFBLK|0700, makedev (major, minor)) == -1) {
166     perror ("mknod: /dev/root");
167     exit (EXIT_FAILURE);
168   }
169
170   /* Mount new root and chroot to it. */
171   if (verbose)
172     fprintf (stderr, "febootstrap: mounting new root on /root\n");
173   if (mount ("/dev/root", "/root", "ext2", MS_NOATIME, "") == -1) {
174     perror ("mount: /root");
175     exit (EXIT_FAILURE);
176   }
177
178   /* Note that pivot_root won't work.  See the note in
179    * Documentation/filesystems/ramfs-rootfs-initramfs.txt
180    * We could remove the old initramfs files, but let's not bother.
181    */
182   if (verbose)
183     fprintf (stderr, "febootstrap: chroot\n");
184
185   if (chroot ("/root") == -1) {
186     perror ("chroot: /root");
187     exit (EXIT_FAILURE);
188   }
189
190   chdir ("/");
191
192   /* Run /init from ext2 filesystem. */
193   print_uptime ();
194   execl ("/init", "init", NULL);
195   perror ("execl: /init");
196
197   /* /init failed to execute, but why?  Before we ditch, print some
198    * debug.  Although we have a full appliance, the fact that /init
199    * failed to run means we may not be able to run any commands.
200    */
201   show_directory ("/");
202   show_directory ("/bin");
203   show_directory ("/lib");
204   show_directory ("/lib64");
205   fflush (stderr);
206
207   exit (EXIT_FAILURE);
208 }
209
210 static void
211 insmod (const char *filename)
212 {
213   size_t size;
214
215   if (verbose)
216     fprintf (stderr, "febootstrap: internal insmod %s\n", filename);
217
218 #ifdef HAVE_LIBZ
219   gzFile gzfp = gzopen (filename, "rb");
220   int capacity = 64*1024;
221   char *buf = (char *) malloc (capacity);
222   int tmpsize = 8 * 1024;
223   char tmp[tmpsize];
224   int num;
225
226   size = 0;
227
228   if (gzfp == NULL) {
229     fprintf (stderr, "insmod: gzopen failed: %s", filename);
230     exit (EXIT_FAILURE);
231   }
232   while ((num = gzread (gzfp, tmp, tmpsize)) > 0) {
233     if (num > capacity) {
234       buf = (char*) realloc (buf, size*2);
235       capacity = size;
236     }
237     memcpy (buf+size, tmp, num);
238     capacity -= num;
239     size += num;
240   }
241   if (num == -1) {
242     perror ("insmod: gzread");
243     exit (EXIT_FAILURE);
244   }
245   gzclose (gzfp);
246 #else
247   int fd = open (filename, O_RDONLY);
248   if (fd == -1) {
249     fprintf (stderr, "insmod: open: %s: %m\n", filename);
250     exit (EXIT_FAILURE);
251   }
252   struct stat st;
253   if (fstat (fd, &st) == -1) {
254     perror ("insmod: fstat");
255     exit (EXIT_FAILURE);
256   }
257   size = st.st_size;
258   char buf[size];
259   size_t offset = 0;
260   do {
261     ssize_t rc = read (fd, buf + offset, size - offset);
262     if (rc == -1) {
263       perror ("insmod: read");
264       exit (EXIT_FAILURE);
265     }
266     offset += rc;
267   } while (offset < size);
268   close (fd);
269 #endif
270
271   if (init_module (buf, size, "") != 0) {
272     fprintf (stderr, "insmod: init_module: %s: %s\n", filename, moderror (errno));
273     /* However ignore the error because this can just happen because
274      * of a missing device.
275      */
276   }
277
278 #ifdef HAVE_LIBZ
279   free (buf);
280 #endif
281 }
282
283 /* Mount /proc unless it's mounted already. */
284 static void
285 mount_proc (void)
286 {
287   if (access ("/proc/uptime", R_OK) == -1) {
288     mkdir ("/proc", 0755);
289
290     if (verbose)
291       fprintf (stderr, "febootstrap: mounting /proc\n");
292
293     if (mount ("proc", "/proc", "proc", 0, "") == -1) {
294       perror ("mount: /proc");
295       /* Non-fatal. */
296     }
297   }
298 }
299
300 /* Print contents of /proc/uptime. */
301 static void
302 print_uptime (void)
303 {
304   FILE *fp = fopen ("/proc/uptime", "r");
305   if (fp == NULL) {
306     perror ("/proc/uptime");
307     return;
308   }
309
310   fgets (line, sizeof line, fp);
311   fclose (fp);
312
313   fprintf (stderr, "febootstrap: uptime: %s", line);
314 }
315
316 /* Display a directory on stderr.  This is used for debugging only. */
317 static char
318 dirtype (int dt)
319 {
320   switch (dt) {
321   case DT_BLK: return 'b';
322   case DT_CHR: return 'c';
323   case DT_DIR: return 'd';
324   case DT_FIFO: return 'p';
325   case DT_LNK: return 'l';
326   case DT_REG: return '-';
327   case DT_SOCK: return 's';
328   case DT_UNKNOWN: return 'u';
329   default: return '?';
330   }
331 }
332
333 static void
334 show_directory (const char *dirname)
335 {
336   DIR *dir;
337   struct dirent *d;
338   struct stat statbuf;
339   char link[PATH_MAX+1];
340   ssize_t n;
341
342   fprintf (stderr, "febootstrap: debug: listing directory %s\n", dirname);
343
344   if (chdir (dirname) == -1) {
345     perror (dirname);
346     return;
347   }
348
349   dir = opendir (".");
350   if (!dir) {
351     perror (dirname);
352     chdir ("/");
353     return;
354   }
355
356   while ((d = readdir (dir)) != NULL) {
357     fprintf (stderr, "%5lu %c %-16s", d->d_ino, dirtype (d->d_type), d->d_name);
358     if (lstat (d->d_name, &statbuf) >= 0) {
359       fprintf (stderr, " %06o %ld %d:%d",
360                statbuf.st_mode, statbuf.st_size,
361                statbuf.st_uid, statbuf.st_gid);
362       if (S_ISLNK (statbuf.st_mode)) {
363         n = readlink (d->d_name, link, PATH_MAX);
364         if (n >= 0) {
365           link[n] = '\0';
366           fprintf (stderr, " -> %s", link);
367         }
368       }
369     }
370     fprintf (stderr, "\n");
371   }
372
373   closedir (dir);
374   chdir ("/");
375 }