Also provide rootfstype as kernel boot option
[qi-bootmenu/guyou.git] / kexec.c
blob16287562aca1281424d9446c540e6cc94ac0a6ae
1 #include <errno.h>
2 #include <stdio.h>
3 #include <stdbool.h>
4 #include <sys/fcntl.h>
5 #include <string.h>
6 #include <asm/setup.h> /* for COMMAND_LINE_SIZE */
7 #include <sys/mount.h>
8 #include <sys/stat.h>
9 #include <sys/utsname.h>
10 #include <Eina.h>
11 #include "config.h"
12 #include "kexec.h"
13 #include "util.h"
14 #include "fstype/fstype.h"
16 #ifndef COMMAND_LINE_SIZE
17 # define COMMAND_LINE_SIZE 256
18 #endif
20 /* filesystems built into the kernel */
21 static Eina_List *fs_core;
22 /* filesystems supported by kernel modules */
23 static Eina_List *fs_mods;
24 /* partitions read from /proc/partitions */
25 static Eina_List *partitions;
26 /* systems which were found to boot */
27 static Eina_List *systems;
29 static Eina_List* get_partitions() {
30 FILE *f = fopen("/proc/partitions", "r");
32 if (!f)
33 return NULL;
35 Eina_List *partitions = NULL;
37 char line[64];
39 /* skip header */
40 fgets(line, sizeof(line), f);
41 fgets(line, sizeof(line), f);
43 while (fgets(line, sizeof(line), f)) {
44 char *dev, *q, *p = line;
45 long int blocks;
46 int len;
47 /* skip first two columns */
48 skip_spaces(&p);
49 skip_until(&p, ' ');
50 skip_spaces(&p);
51 skip_until(&p, ' ');
52 /* read number of blocks */
53 blocks = strtol(p, &q, 10);
54 if (!blocks || p == q)
55 continue;
56 /* ignore small partitions < 10MB */
57 if (blocks < 10 * 1024)
58 continue;
59 /* read partition name and remove trailing \n */
60 skip_spaces(&q);
61 p = q;
62 skip_until(&q, '\n');
63 *q = '\0';
64 /* prepend /dev */
65 len = q - p;
66 dev = malloc(sstrlen("/dev/") + len + 1);
67 strcpy(dev, "/dev/");
68 strncat(dev, p, len);
69 partitions = eina_list_append(partitions, dev);
72 fclose(f);
73 return partitions;
76 static Eina_List* get_kernel_filesystems_builtin() {
77 FILE *f = fopen("/proc/filesystems", "r");
79 if (!f)
80 return NULL;
82 Eina_List *fs = NULL;
84 char line[32];
86 while (fgets(line, sizeof(line), f)) {
87 /* ignore lines starting with nodev */
88 if (!strncmp(line, "nodev", sstrlen("nodev")))
89 continue;
90 line[strlen(line) - 1] = '\0';
91 /* skip tab with line + 1*/
92 fs = eina_list_append(fs, strdup(line + 1));
95 fclose(f);
96 return fs;
99 static Eina_List* get_kernel_filesystems_modules() {
101 #ifdef CONFIG_SUPPORT_KERNEL_FS_MODULES
103 Eina_List *fs = NULL;
104 struct utsname uts;
105 FILE *f;
106 char buf[1024], *p, *q;
107 int len;
109 if (uname(&uts))
110 return NULL;
112 len = snprintf(buf, sizeof(buf), "/lib/modules/%s/modules.dep", uts.release);
114 if (len < 0)
115 return NULL;
117 f = fopen(buf, "r");
118 if (!f)
119 return NULL;
121 /* calculate the length of the prefix for the modules, this assumes that the
122 * modules are installed under the directroy where we found the modules.dep file.
125 len -= sstrlen("modules.dep");
127 /* Maybe we should readdir("/lib/modules/`uname -r`/kernel/fs") instead? */
129 while (fgets(buf, sizeof(buf), f)) {
130 /* skip lines which don't contain a colon */
131 if (!(p = strchr(buf, ':')))
132 continue;
133 /* we are only interested in the part before '.ko:' */
134 *(p - sstrlen(".ko")) = '\0';
137 /* skip the prefix '/lib/modules/`uname -r`/' */
138 p = buf + len;
140 if (strncmp(p, "kernel/fs/", sstrlen("kernel/fs/")))
141 continue;
143 q = p += sstrlen("kernel/fs/");
145 skip_until(&q, '/');
146 *q = '\0';
148 /* XXX: check for duplicates? */
149 fs = eina_list_append(fs, strdup(p));
152 fclose(f);
153 return fs;
154 #else
155 return NULL;
156 #endif /* CONFIG_SUPPORT_KERNEL_FS_MODULES */
159 static bool is_supported_filesystem(const char *fs) {
161 if (eina_list_search_unsorted(fs_core, EINA_COMPARE_CB(strcmp), fs))
162 return true;
164 #ifdef CONFIG_SUPPORT_KERNEL_FS_MODULES
165 const char *modprobe[] = { MODPROBE , fs };
166 if (eina_list_search_unsorted(fs_mods, EINA_COMPARE_CB(strcmp), fs) &&
167 fexecw(modprobe[0], (char *const *)modprobe, NULL) == 0) {
168 debug("Successfully loaded kernel filesystem module: '%s'\n", fs);
169 fs_mods = eina_list_remove(fs_mods, fs);
170 fs_core = eina_list_append(fs_core, fs);
171 return true;
173 #endif
175 return false;
178 Eina_List* scan_system() {
180 Eina_List *l;
181 const char *dev, *mnt, *fs;
182 char buf[COMMAND_LINE_SIZE];
183 struct stat st;
185 if (systems)
186 return systems;
188 /* check if we need to read data from /proc first */
189 if (!partitions)
190 partitions = get_partitions();
191 if (!fs_core)
192 fs_core = get_kernel_filesystems_builtin();
193 if (!fs_mods)
194 fs_mods = get_kernel_filesystems_modules();
196 /* XXX: doesn't handle sub directories assumes /mnt */
197 mkdir(MOUNTPOINT, 0755);
198 /* chdir so we can use relative paths for mount(2) */
199 if (chdir(MOUNTPOINT)) {
200 perror("chdir");
201 return NULL;
204 EINA_LIST_FOREACH(partitions, l, dev) {
206 int fd = open(dev, O_RDONLY);
207 if (fd < 0) {
208 perror("open");
209 continue;
212 if (identify_fs(fd, &fs, NULL, 0)) {
213 eprint("Couldn't identify filesystem on '%s'\n", dev);
214 goto next;
217 if (!is_supported_filesystem(fs)) {
218 eprint("Unsupported filesystem '%s' on '%s'\n", fs, dev);
219 goto next;
222 /* we chdir()-ed and now use relative paths */
223 mnt = dev + sstrlen("/dev/");
224 if (mkdir(mnt, 0755) && errno != EEXIST) {
225 perror("mkdir");
226 goto next;
229 if (mount(dev, mnt, fs, MS_RDONLY, NULL) && errno != EBUSY) {
230 perror("mount");
231 goto next;
234 /* XXX: replace fixed GTA02 with machine type */
235 snprintf(buf, sizeof buf, "%s/%s/boot/uImage-GTA02.bin", MOUNTPOINT, mnt);
236 if (stat(buf, &st)) {
237 /* no uImage present now check for zImage */
238 buf[sstrlen(MOUNTPOINT) + sstrlen("/") + strlen(mnt) + sstrlen("/boot/")] = 'z';
239 if (stat(buf, &st)) {
240 eprint("No kernel found at '%s'\n", buf);
241 umount(mnt);
242 goto next;
246 BootItem *sys = calloc(sizeof(BootItem), 1);
247 sys->fs = fs;
248 sys->dev = dev;
249 sys->kernel = strdup(buf);
251 /* XXX: replace fixed GTA02 with machine type */
252 snprintf(buf, sizeof buf, "%s/%s/boot/append-GTA02", MOUNTPOINT, mnt);
253 FILE *f = fopen(buf, "r");
254 if (f) {
255 fgets(buf, sizeof buf, f);
256 sys->cmdline = strdup(buf);
257 fclose(f);
260 snprintf(buf, sizeof buf, "%s/%s/boot/bootlogo.png", MOUNTPOINT, mnt);
261 if (!stat(buf, &st))
262 sys->logo = strdup(buf);
263 else
264 sys->logo = DEFAULT_LOGO;
266 systems = eina_list_append(systems, sys);
268 next:
269 close(fd);
272 return systems;
275 /* Genereate kernel command line for use with kexec. It consist of the following:
277 * - /proc/cmdline of the running system
278 * - root=$PARTITON rootfstype=$FS
279 * - append-$MACHINE in the same directory as the kernel
282 char* get_kernel_cmdline(BootItem *i) {
283 static char cmdline[2 * COMMAND_LINE_SIZE] = KEXEC_CMDLINE, *s = cmdline + sstrlen(KEXEC_CMDLINE);
285 /* read the cmdline of the currently running system only once */
286 if (!cmdline[sstrlen(KEXEC_CMDLINE) + 1]) {
287 FILE *f = fopen("/proc/cmdline","r");
288 if (f && fgets(s, sizeof(cmdline) - sstrlen(KEXEC_CMDLINE), f)) {
289 /* /proc/cmdline ends with a new line which we want to overwrite thus -1 */
290 s += strlen(s) - 1;
292 fclose(f);
295 /* append root partition, filesystem and the image specific command line */
296 int len = snprintf(s, sizeof(cmdline) - (s - cmdline), "root=%s rootfstype=%s %s",
297 i->dev, i->fs, i->cmdline || "");
298 if (len > 0 && s[len - 1] == '\n')
299 s[len - 1] = '\0';
301 return cmdline;
304 bool boot_kernel(BootItem *i) {
305 Eina_List *l;
306 BootItem *s;
308 /* umount all filesystem we probed except the one where the kernel is located */
309 EINA_LIST_FOREACH(systems, l, s) {
310 if (s != i) {
311 /* XXX: assumes we are still chdir()-ed */
312 umount(s->dev + sstrlen("/dev/"));
316 const char *kexec_load[] = { KEXEC, get_kernel_cmdline(i), "-l", i->kernel, NULL };
317 if (fexecw(kexec_load[0], (char *const *)kexec_load, NULL)) {
318 eprint("Couldn't load kernel from '%s'\n", i->kernel);
319 return false;
322 const char *kexec_exec[] = { KEXEC , "-e", NULL };
324 /* XXX: assumes we are still chdir()-ed */
325 umount(i->dev + sstrlen("/dev/"));
327 if (execve(kexec_exec[0], (char *const *)kexec_exec, NULL)) {
328 eprint("Couldn't exec kernel '%s'\n", i->kernel);
329 return false;
332 /* can't be reached just here to silence compiler warning */
333 return true;
336 void diagnostics() {
337 Eina_List *l;
338 BootItem *s;
339 char *p;
341 if (!eina_mempool_init() || !eina_list_init())
342 return;
344 puts("Partitions:");
345 partitions = get_partitions();
347 EINA_LIST_FOREACH(partitions, l, p) {
348 puts(p);
351 puts("Built in filesystems:");
352 fs_core = get_kernel_filesystems_builtin();
354 EINA_LIST_FOREACH(fs_core, l, p) {
355 puts(p);
358 #ifdef CONFIG_SUPPORT_KERNEL_FS_MODULES
359 puts("Filesystem modules:");
360 fs_mods = get_kernel_filesystems_modules();
362 EINA_LIST_FOREACH(fs_mods, l, p) {
363 puts(p);
365 #endif
367 puts("Bootable images:");
368 systems = scan_system();
370 EINA_LIST_FOREACH(systems, l, s) {
371 printf("kexec %s'%s' -l %s\n", KEXEC_CMDLINE,
372 get_kernel_cmdline(s) + sstrlen(KEXEC_CMDLINE), s->kernel);
373 printf("umount '%s'\n", s->dev);
374 printf("kexec -e\n\n");