Add a simple GUI
[qi-bootmenu/guyou.git] / kexec.c
blob7cf5dcd89fae4e718c83cce49a0451c787ab8d68
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 "qi-bootmenu.h"
13 #include "util.h"
14 #include "fstype/fstype.h"
16 #ifndef COMMAND_LINE_SIZE
17 # define COMMAND_LINE_SIZE 256
18 #endif
20 #define MOUNTPOINT "/mnt"
21 #define MODPROBE "/sbin/modprobe"
22 #define KEXEC "/sbin/kexec"
23 /* XXX: use --command-line= instead? */
24 #define KEXEC_CMDLINE "--append="
26 /* filesystems built into the kernel */
27 static Eina_List *fs_core;
28 /* filesystems supported by kernel modules */
29 static Eina_List *fs_mods;
30 /* partitions read from /proc/partitions */
31 static Eina_List *partitions;
32 /* systems which were found to boot */
33 static Eina_List *systems;
35 static Eina_List* get_partitions() {
36 FILE *f = fopen("/proc/partitions", "r");
38 if (!f)
39 return NULL;
41 Eina_List *partitions = NULL;
43 char line[64];
45 /* skip header */
46 fgets(line, sizeof(line), f);
47 fgets(line, sizeof(line), f);
49 while (fgets(line, sizeof(line), f)) {
50 char *dev, *q, *p = line;
51 long int blocks;
52 int len;
53 /* skip first two columns */
54 skip_spaces(&p);
55 skip_until(&p, ' ');
56 skip_spaces(&p);
57 skip_until(&p, ' ');
58 /* read number of blocks */
59 blocks = strtol(p, &q, 10);
60 if (!blocks || p == q)
61 continue;
62 /* ignore small partitions < 10MB */
63 if (blocks < 10 * 1024)
64 continue;
65 /* read partition name and remove trailing \n */
66 skip_spaces(&q);
67 p = q;
68 skip_until(&q, '\n');
69 *q = '\0';
70 /* prepend /dev */
71 len = q - p;
72 dev = malloc(sstrlen("/dev/") + len + 1);
73 strcpy(dev, "/dev/");
74 strncat(dev, p, len);
75 partitions = eina_list_append(partitions, dev);
78 fclose(f);
79 return partitions;
82 static Eina_List* get_kernel_filesystems_builtin() {
83 FILE *f = fopen("/proc/filesystems", "r");
85 if (!f)
86 return NULL;
88 Eina_List *fs = NULL;
90 char line[32];
92 while (fgets(line, sizeof(line), f)) {
93 /* ignore lines starting with nodev */
94 if (!strncmp(line, "nodev", sstrlen("nodev")))
95 continue;
96 line[strlen(line) - 1] = '\0';
97 /* skip tab with line + 1*/
98 fs = eina_list_append(fs, strdup(line + 1));
101 fclose(f);
102 return fs;
105 static Eina_List* get_kernel_filesystems_modules() {
107 #ifdef CONFIG_SUPPORT_KERNEL_FS_MODULES
109 Eina_List *fs = NULL;
110 struct utsname uts;
111 FILE *f;
112 char buf[1024], *p, *q;
113 int len;
115 if (uname(&uts))
116 return NULL;
118 len = snprintf(buf, sizeof(buf), "/lib/modules/%s/modules.dep", uts.release);
120 if (len < 0)
121 return NULL;
123 f = fopen(buf, "r");
124 if (!f)
125 return NULL;
127 /* calculate the length of the prefix for the modules, this assumes that the
128 * modules are installed under the directroy where we found the modules.dep file.
131 len -= sstrlen("modules.dep");
133 /* Maybe we should readdir("/lib/modules/`uname -r`/kernel/fs") instead? */
135 while (fgets(buf, sizeof(buf), f)) {
136 /* skip lines which don't contain a colon */
137 if (!(p = strchr(buf, ':')))
138 continue;
139 /* we are only interested in the part before '.ko:' */
140 *(p - sstrlen(".ko")) = '\0';
143 /* skip the prefix '/lib/modules/`uname -r`/' */
144 p = buf + len;
146 if (strncmp(p, "kernel/fs/", sstrlen("kernel/fs/")))
147 continue;
149 q = p += sstrlen("kernel/fs/");
151 skip_until(&q, '/');
152 *q = '\0';
154 /* XXX: check for duplicates? */
155 fs = eina_list_append(fs, strdup(p));
158 fclose(f);
159 return fs;
160 #else
161 return NULL;
162 #endif /* CONFIG_SUPPORT_KERNEL_FS_MODULES */
165 static bool is_supported_filesystem(const char *fs) {
167 if (eina_list_search_unsorted(fs_core, EINA_COMPARE_CB(strcmp), fs))
168 return true;
170 #ifdef CONFIG_SUPPORT_KERNEL_FS_MODULES
171 const char *modprobe[] = { MODPROBE , fs };
172 if (eina_list_search_unsorted(fs_mods, EINA_COMPARE_CB(strcmp), fs) &&
173 fexecw(modprobe[0], (char *const *)modprobe, NULL) == 0) {
174 debug("Successfully loaded kernel filesystem module: '%s'\n", fs);
175 fs_mods = eina_list_remove(fs_mods, fs);
176 fs_core = eina_list_append(fs_core, fs);
177 return true;
179 #endif
181 return false;
184 Eina_List* scan_system() {
186 Eina_List *l;
187 const char *dev, *mnt, *fs;
188 char buf[COMMAND_LINE_SIZE];
189 struct stat st;
191 /* check if we need to read data from /proc first */
192 if (!partitions)
193 partitions = get_partitions();
194 if (!fs_core)
195 fs_core = get_kernel_filesystems_builtin();
196 if (!fs_mods)
197 fs_mods = get_kernel_filesystems_modules();
199 /* XXX: doesn't handle sub directories assumes /mnt */
200 mkdir(MOUNTPOINT, 0755);
201 /* chdir so we can use relative paths for mount(2) */
202 if (chdir(MOUNTPOINT)) {
203 perror("chdir");
204 return NULL;
207 EINA_LIST_FOREACH(partitions, l, dev) {
209 int fd = open(dev, O_RDONLY);
210 if (fd < 0) {
211 perror("open");
212 continue;
215 if (identify_fs(fd, &fs, NULL, 0)) {
216 eprint("Couldn't identify filesystem on '%s'\n", dev);
217 goto next;
220 if (!is_supported_filesystem(fs)) {
221 eprint("Unsupported filesystem '%s' on '%s'\n", fs, dev);
222 goto next;
225 /* we chdir()-ed and now use relative paths */
226 mnt = dev + sstrlen("/dev/");
227 if (mkdir(mnt, 0755) && errno != EEXIST) {
228 perror("mkdir");
229 goto next;
232 if (mount(dev, mnt, fs, MS_RDONLY, NULL) && errno != EBUSY) {
233 perror("mount");
234 goto next;
237 /* XXX: replace fixed GTA02 with machine type */
238 snprintf(buf, sizeof buf, "%s/%s/boot/uImage-GTA02.bin", MOUNTPOINT, mnt);
239 if (stat(buf, &st)) {
240 /* no uImage present now check for zImage */
241 buf[sstrlen(MOUNTPOINT) + sstrlen("/") + strlen(mnt) + sstrlen("/boot/")] = 'z';
242 puts(buf);
243 if (stat(buf, &st)) {
244 eprint("No kernel found at '%s'\n", buf);
245 umount(mnt);
246 goto next;
250 BootItem *sys = calloc(sizeof(BootItem), 1);
251 sys->kernel = strdup(buf);
252 sys->dev = dev;
254 /* XXX: replace fixed GTA02 with machine type */
255 snprintf(buf, sizeof buf, "%s/%s/boot/append-GTA02", MOUNTPOINT, mnt);
256 FILE *f = fopen(buf, "r");
257 if (f) {
258 fgets(buf, sizeof buf, f);
259 sys->cmdline = strdup(buf);
260 fclose(f);
263 snprintf(buf, sizeof buf, "%s/%s/boot/bootlogo.png", MOUNTPOINT, mnt);
264 if (!stat(buf, &st))
265 sys->logo = strdup(buf);
267 systems = eina_list_append(systems, sys);
269 next:
270 close(fd);
273 return systems;
276 /* Genereate kernel command line for use with kexec. It consist of the following:
278 * - /proc/cmdline of the running system
279 * - root=$PARTITON
280 * - append-$MACHINE in the same directory as the kernel
283 char* get_kernel_cmdline(BootItem *i) {
284 static char cmdline[2 * COMMAND_LINE_SIZE] = KEXEC_CMDLINE, *s = cmdline + sstrlen(KEXEC_CMDLINE);
286 /* read the cmdline of the currently running system only once */
287 if (!cmdline[sstrlen(KEXEC_CMDLINE) + 1]) {
288 FILE *f = fopen("/proc/cmdline","r");
289 if (f && fgets(s, sizeof(cmdline) - sstrlen(KEXEC_CMDLINE), f)) {
290 /* /proc/cmdline ends with a new line which we want to overwrite thus -1 */
291 s += strlen(s) - 1;
293 fclose(f);
296 /* append root=/dev/... and the image specific command line */
297 int len = snprintf(s, sizeof(cmdline) - (s - cmdline), "root=%s %s", i->dev, 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 puts("Partitions:");
342 partitions = get_partitions();
344 EINA_LIST_FOREACH(partitions, l, p) {
345 puts(p);
348 puts("Built in filesystems:");
349 fs_core = get_kernel_filesystems_builtin();
351 EINA_LIST_FOREACH(fs_core, l, p) {
352 puts(p);
355 #ifdef CONFIG_SUPPORT_KERNEL_FS_MODULES
356 puts("Filesystem modules:");
357 fs_mods = get_kernel_filesystems_modules();
359 EINA_LIST_FOREACH(fs_mods, l, p) {
360 puts(p);
362 #endif
364 puts("Bootable images:");
365 systems = scan_system();
367 EINA_LIST_FOREACH(systems, l, s) {
368 printf("kexec %s'%s' -l %s\n", KEXEC_CMDLINE,
369 get_kernel_cmdline(s) + sstrlen(KEXEC_CMDLINE), s->kernel);
370 printf("umount '%s'\n", s->dev);
371 printf("kexec -e\n\n");