helper: When /init fails, print some debugging information.
[febootstrap.git] / helper / init.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 /* 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, insmod.static, and some
23  * kernel modules.
24  */
25
26 #include <config.h>
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <errno.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 /* Leave this enabled for now.  When we get more confident in the boot
40  * process we can turn this off or make it configurable.
41  */
42 #define verbose 1
43
44 static void print_uptime (void);
45 static void insmod (const char *filename);
46 static void show_directory (const char *dir);
47
48 static char line[1024];
49
50 int
51 main ()
52 {
53   print_uptime ();
54   fprintf (stderr, "febootstrap: ext2 mini initrd starting up\n");
55
56   /* Create some fixed directories. */
57   mkdir ("/dev", 0755);
58   mkdir ("/root", 0755);
59   mkdir ("/sys", 0755);
60
61   /* Mount /sys. */
62   if (verbose)
63     fprintf (stderr, "febootstrap: mounting /sys\n");
64   if (mount ("sysfs", "/sys", "sysfs", 0, "") == -1) {
65     perror ("mount: /sys");
66     exit (EXIT_FAILURE);
67   }
68
69   /* A perennial problem is that /sbin/insmod.static is not
70    * executable.  Just make it executable.  It's easier than fixing
71    * everyone's distro.
72    */
73   chmod ("/sbin/insmod.static", 0755);
74
75   FILE *fp = fopen ("/modules", "r");
76   if (fp == NULL) {
77     perror ("fopen: /modules");
78     exit (EXIT_FAILURE);
79   }
80   while (fgets (line, sizeof line, fp)) {
81     size_t n = strlen (line);
82     if (n > 0 && line[n-1] == '\n')
83       line[--n] = '\0';
84     insmod (line);
85   }
86   fclose (fp);
87
88   /* Look for the ext2 filesystem device.  It's always the last
89    * one that was added.
90    * XXX More than 25 devices?
91    */
92   char path[] = "/sys/block/xdx/dev";
93   char class[3] = { 'v', 's', 'h' };
94   size_t i, j;
95   fp = NULL;
96   for (i = 0; i < sizeof class; ++i) {
97     for (j = 'z'; j >= 'a'; --j) {
98       path[11] = class[i];
99       path[13] = j;
100       fp = fopen (path, "r");
101       if (fp != NULL)
102         goto found;
103     }
104   }
105   fprintf (stderr,
106            "febootstrap: no ext2 root device found\n"
107            "Please include FULL verbose output in your bug report.\n");
108   exit (EXIT_FAILURE);
109
110  found:
111   if (verbose)
112     fprintf (stderr, "febootstrap: picked %s as root device\n", path);
113
114   fgets (line, sizeof line, fp);
115   int major = atoi (line);
116   char *p = line + strcspn (line, ":") + 1;
117   int minor = atoi (p);
118
119   fclose (fp);
120   if (umount ("/sys") == -1) {
121     perror ("umount: /sys");
122     exit (EXIT_FAILURE);
123   }
124
125   if (verbose)
126     fprintf (stderr, "febootstrap: creating /dev/root as block special %d:%d\n",
127              major, minor);
128
129   if (mknod ("/dev/root", S_IFBLK|0700, makedev (major, minor)) == -1) {
130     perror ("mknod: /dev/root");
131     exit (EXIT_FAILURE);
132   }
133
134   /* Mount new root and chroot to it. */
135   if (verbose)
136     fprintf (stderr, "febootstrap: mounting new root on /root\n");
137   if (mount ("/dev/root", "/root", "ext2", MS_NOATIME, "") == -1) {
138     perror ("mount: /root");
139     exit (EXIT_FAILURE);
140   }
141
142   /* Note that pivot_root won't work.  See the note in
143    * Documentation/filesystems/ramfs-rootfs-initramfs.txt
144    * We could remove the old initramfs files, but let's not bother.
145    */
146   if (verbose)
147     fprintf (stderr, "febootstrap: chroot\n");
148
149   if (chroot ("/root") == -1) {
150     perror ("chroot: /root");
151     exit (EXIT_FAILURE);
152   }
153
154   chdir ("/");
155
156   /* Run /init from ext2 filesystem. */
157   print_uptime ();
158   execl ("/init", "init", NULL);
159   perror ("execl: /init");
160
161   /* /init failed to execute, but why?  Before we ditch, print some
162    * debug.  Although we have a full appliance, the fact that /init
163    * failed to run means we may not be able to run any commands.
164    */
165   show_directory ("/");
166   show_directory ("/bin");
167   show_directory ("/lib");
168   show_directory ("/lib64");
169   fflush (stderr);
170
171   exit (EXIT_FAILURE);
172 }
173
174 static void
175 insmod (const char *filename)
176 {
177   if (verbose)
178     fprintf (stderr, "febootstrap: insmod %s\n", filename);
179
180   pid_t pid = fork ();
181   if (pid == -1) {
182     perror ("insmod: fork");
183     exit (EXIT_FAILURE);
184   }
185
186   if (pid == 0) { /* Child. */
187     execl ("/insmod.static", "insmod.static", filename, NULL);
188     perror ("insmod: execl");
189     _exit (EXIT_FAILURE);
190   }
191
192   /* Parent. */
193   int status;
194   if (wait (&status) == -1 ||
195       WEXITSTATUS (status) != 0)
196     perror ("insmod: wait");
197     /* but ignore the error, some will be because the device is not found */
198 }
199
200 /* Print contents of /proc/uptime. */
201 static void
202 print_uptime (void)
203 {
204   FILE *fp = fopen ("/proc/uptime", "r");
205   if (fp == NULL) {
206     perror ("/proc/uptime");
207     return;
208   }
209
210   fgets (line, sizeof line, fp);
211   fclose (fp);
212
213   fprintf (stderr, "febootstrap: uptime: %s", line);
214 }
215
216 /* Display a directory on stderr.  This is used for debugging only. */
217 static char
218 dirtype (int dt)
219 {
220   switch (dt) {
221   case DT_BLK: return 'b';
222   case DT_CHR: return 'c';
223   case DT_DIR: return 'd';
224   case DT_FIFO: return 'p';
225   case DT_LNK: return 'l';
226   case DT_REG: return '-';
227   case DT_SOCK: return 's';
228   case DT_UNKNOWN: return 'u';
229   default: return '?';
230   }
231 }
232
233 static void
234 show_directory (const char *dirname)
235 {
236   DIR *dir;
237   struct dirent *d;
238   struct stat statbuf;
239   char link[PATH_MAX+1];
240   ssize_t n;
241
242   fprintf (stderr, "febootstrap: debug: listing directory %s\n", dirname);
243
244   if (chdir (dirname) == -1) {
245     perror (dirname);
246     return;
247   }
248
249   dir = opendir (".");
250   if (!dir) {
251     perror (dirname);
252     chdir ("/");
253     return;
254   }
255
256   while ((d = readdir (dir)) != NULL) {
257     fprintf (stderr, "%5d %c %-16s", d->d_ino, dirtype (d->d_type), d->d_name);
258     if (lstat (d->d_name, &statbuf) >= 0) {
259       fprintf (stderr, " %06o %d %d:%d",
260                statbuf.st_mode, statbuf.st_size,
261                statbuf.st_uid, statbuf.st_gid);
262       if (S_ISLNK (statbuf.st_mode)) {
263         n = readlink (d->d_name, link, PATH_MAX);
264         if (n >= 0) {
265           link[n] = '\0';
266           fprintf (stderr, " -> %s", link);
267         }
268       }
269     }
270     fprintf (stderr, "\n");
271   }
272
273   closedir (dir);
274   chdir ("/");
275 }