Force redraw upon menu item selection
[qi-bootmenu.git] / kexec.c
blob977c67b284994b0ed992695672a265c9c0270246
1 #include <errno.h>
2 #include <ctype.h>
3 #include <signal.h>
4 #include <string.h>
5 #include <asm/setup.h> /* for COMMAND_LINE_SIZE */
6 #include <sys/fcntl.h>
7 #include <sys/mount.h>
8 #include <sys/stat.h>
9 #include <sys/wait.h>
10 #include <sys/utsname.h>
11 #include "fstype/fstype.h"
13 #ifndef COMMAND_LINE_SIZE
14 # define COMMAND_LINE_SIZE 256
15 #endif
17 /* filesystems built into the kernel */
18 static Eina_List *fs_core;
19 /* filesystems supported by kernel modules */
20 static Eina_List *fs_mods;
21 /* partitions read from /proc/partitions */
22 static Eina_List *partitions;
23 /* systems which were found to boot */
24 static Eina_List *systems;
26 static void skip_until(char** str, char c) {
27 while (**str && **str != c)
28 (*str)++;
31 static void skip_spaces(char **str) {
32 while (isspace(**str))
33 (*str)++;
36 /* originated from kexecboot, it is used instead of system(3) because
37 * there is no shell involved which means it *could* be a bit faster
40 static int fexecw(const char *path, char *const argv[], char *const envp[]) {
41 pid_t pid;
42 struct sigaction ignore, old_int, old_quit;
43 sigset_t masked, oldmask;
44 int status;
46 /* Block SIGCHLD and ignore SIGINT and SIGQUIT before forking
47 * restore the original signal handlers afterwards. */
49 ignore.sa_handler = SIG_IGN;
50 sigemptyset(&ignore.sa_mask);
51 ignore.sa_flags = 0;
52 sigaction(SIGINT, &ignore, &old_int);
53 sigaction(SIGQUIT, &ignore, &old_quit);
55 sigemptyset(&masked);
56 sigaddset(&masked, SIGCHLD);
57 sigprocmask(SIG_BLOCK, &masked, &oldmask);
59 pid = fork();
61 if (pid < 0)
62 return -1; /* can't fork */
63 else if (pid == 0) { /* child process */
64 sigaction(SIGINT, &old_int, NULL);
65 sigaction(SIGQUIT, &old_quit, NULL);
66 sigprocmask(SIG_SETMASK, &oldmask, NULL);
67 execve(path, (char *const *)argv, (char *const *)envp);
68 _exit(127);
71 /* wait for our child and store it's exit status */
72 waitpid(pid, &status, 0);
74 /* restore signal handlers */
75 sigaction(SIGINT, &old_int, NULL);
76 sigaction(SIGQUIT, &old_quit, NULL);
77 sigprocmask(SIG_SETMASK, &oldmask, NULL);
79 return status;
82 /* returns all partions known to the system with it's full paths */
83 static Eina_List* get_partitions() {
84 FILE *f = fopen("/proc/partitions", "r");
86 if (!f)
87 return NULL;
89 Eina_List *partitions = NULL;
91 char line[64];
93 /* skip header */
94 fgets(line, sizeof(line), f);
95 fgets(line, sizeof(line), f);
97 while (fgets(line, sizeof(line), f)) {
98 char *dev, *q, *p = line;
99 long int blocks;
100 int len;
101 /* skip first two columns */
102 skip_spaces(&p);
103 skip_until(&p, ' ');
104 skip_spaces(&p);
105 skip_until(&p, ' ');
106 /* read number of blocks */
107 blocks = strtol(p, &q, 10);
108 if (!blocks || p == q)
109 continue;
110 /* ignore small partitions < 10MB */
111 if (blocks < 10 * 1024)
112 continue;
113 /* read partition name and remove trailing \n */
114 skip_spaces(&q);
115 p = q;
116 skip_until(&q, '\n');
117 *q = '\0';
118 /* prepend /dev */
119 len = q - p;
120 dev = malloc(sstrlen("/dev/") + len + 1);
121 strcpy(dev, "/dev/");
122 strncat(dev, p, len);
123 partitions = eina_list_append(partitions, dev);
126 fclose(f);
127 return partitions;
130 static Eina_List* get_kernel_filesystems_builtin() {
131 FILE *f = fopen("/proc/filesystems", "r");
133 if (!f)
134 return NULL;
136 Eina_List *fs = NULL;
138 char line[32];
140 while (fgets(line, sizeof(line), f)) {
141 /* overwrite new line */
142 line[strlen(line) - 1] = '\0';
143 /* skip tab or "nodev\t" */
144 fs = eina_list_append(fs, strdup(line + (line[0] == '\t' ? 1 : sstrlen("nodev\t"))));
147 fclose(f);
148 return fs;
151 static Eina_List* get_kernel_filesystems_modules() {
153 #if CONFIG_SUPPORT_KERNEL_FS_MODULES
155 Eina_List *fs = NULL;
156 struct utsname uts;
157 FILE *f;
158 char buf[1024], *p, *q;
159 int len;
161 if (uname(&uts))
162 return NULL;
164 len = snprintf(buf, sizeof(buf), "/lib/modules/%s/modules.dep", uts.release);
166 if (len < 0)
167 return NULL;
169 f = fopen(buf, "r");
170 if (!f)
171 return NULL;
173 /* calculate the length of the prefix for the modules, this assumes that the
174 * modules are installed under the directroy where we found the modules.dep file.
177 len -= sstrlen("modules.dep");
179 /* Maybe we should readdir("/lib/modules/`uname -r`/kernel/fs") instead? */
181 while (fgets(buf, sizeof(buf), f)) {
182 /* skip lines which don't contain a colon */
183 if (!(p = strchr(buf, ':')))
184 continue;
185 /* we are only interested in the part before '.ko:' */
186 *(p - sstrlen(".ko")) = '\0';
189 /* skip the prefix '/lib/modules/`uname -r`/' */
190 p = buf + len;
192 if (strncmp(p, "kernel/fs/", sstrlen("kernel/fs/")))
193 continue;
195 q = p += sstrlen("kernel/fs/");
197 skip_until(&q, '/');
198 *q = '\0';
200 /* XXX: check for duplicates? */
201 fs = eina_list_append(fs, strdup(p));
204 fclose(f);
205 return fs;
206 #else
207 return NULL;
208 #endif /* CONFIG_SUPPORT_KERNEL_FS_MODULES */
211 static bool is_supported_filesystem(const char *fs) {
213 if (eina_list_search_unsorted(fs_core, EINA_COMPARE_CB(strcmp), fs))
214 return true;
216 #if CONFIG_SUPPORT_KERNEL_FS_MODULES
217 const char *modprobe[] = { MODPROBE , fs };
218 if (eina_list_search_unsorted(fs_mods, EINA_COMPARE_CB(strcmp), fs) &&
219 fexecw(modprobe[0], (char *const *)modprobe, NULL) == 0) {
220 debug("Successfully loaded kernel filesystem module: '%s'\n", fs);
221 fs_mods = eina_list_remove(fs_mods, fs);
222 fs_core = eina_list_append(fs_core, fs);
223 return true;
225 #endif
227 return false;
230 static BootItem* scan_partition(const char *dev) {
231 BootItem *sys = NULL;
232 const char *mnt, *fs;
233 char buf[COMMAND_LINE_SIZE];
234 struct stat st;
236 int fd = open(dev, O_RDONLY);
237 if (fd < 0) {
238 perror("open");
239 return NULL;
242 if (identify_fs(fd, &fs, NULL, 0)) {
243 eprint("Couldn't identify filesystem on '%s'\n", dev);
244 goto out;
247 if (!is_supported_filesystem(fs)) {
248 eprint("Unsupported filesystem '%s' on '%s'\n", fs, dev);
249 goto out;
252 /* we chdir()-ed and now use relative paths */
253 mnt = dev + sstrlen("/dev/");
254 if (mkdir(mnt, 0755) && errno != EEXIST) {
255 perror("mkdir");
256 goto out;
259 if (mount(dev, mnt, fs, MS_RDONLY, NULL)) {
260 perror("mount");
261 goto out;
264 snprintf(buf, sizeof buf, "%s/%s/boot/uImage-%s.bin", MOUNTPOINT, mnt, machine);
265 if (stat(buf, &st)) {
266 /* no uImage present now check for zImage */
267 buf[sstrlen(MOUNTPOINT) + sstrlen("/") + strlen(mnt) + sstrlen("/boot/")] = 'z';
268 if (stat(buf, &st)) {
269 eprint("No kernel found at '%s'\n", buf);
270 umount(mnt);
271 goto out;
275 sys = calloc(sizeof(BootItem), 1);
276 sys->fs = fs;
277 sys->dev = dev;
278 sys->kernel = strdup(buf);
280 snprintf(buf, sizeof buf, "%s/%s/boot/append-%s", MOUNTPOINT, mnt, machine);
281 FILE *f = fopen(buf, "r");
282 if (f) {
283 fgets(buf, sizeof buf, f);
284 sys->cmdline = strdup(buf);
285 fclose(f);
288 snprintf(buf, sizeof buf, "%s/%s/boot/bootlogo.png", MOUNTPOINT, mnt);
289 if (!stat(buf, &st))
290 sys->logo = strdup(buf);
291 else
292 sys->logo = DEFAULT_LOGO;
294 out:
295 close(fd);
296 return sys;
299 static Eina_List* scan_system(Eina_List *dev_ignore) {
301 Eina_List *l;
302 const char *dev;
304 if (systems)
305 return systems;
307 /* check if we need to read data from /proc first */
308 if (!partitions)
309 partitions = get_partitions();
310 if (!fs_core)
311 fs_core = get_kernel_filesystems_builtin();
312 if (!fs_mods)
313 fs_mods = get_kernel_filesystems_modules();
315 /* XXX: doesn't handle sub directories assumes /mnt */
316 mkdir(MOUNTPOINT, 0755);
317 /* chdir so we can use relative paths for mount(2) */
318 if (chdir(MOUNTPOINT)) {
319 perror("chdir");
320 return NULL;
323 EINA_LIST_FOREACH(partitions, l, dev) {
325 if (eina_list_search_unsorted(dev_ignore, EINA_COMPARE_CB(strcmp), dev))
326 continue;
327 BootItem *sys = scan_partition(dev);
328 if (sys)
329 systems = eina_list_append(systems, sys);
332 return systems;
336 /* Genereate kernel command line for use with kexec. It consist of the following:
338 * - /proc/cmdline of the running system
339 * - root=$PARTITON rootfstype=$FS
340 * - append-$MACHINE in the same directory as the kernel
343 static char* get_kernel_cmdline(BootItem *i) {
344 static char cmdline[2 * COMMAND_LINE_SIZE] = KEXEC_CMDLINE, *s = cmdline + sstrlen(KEXEC_CMDLINE);
346 /* read the cmdline of the currently running system only once */
347 if (!cmdline[sstrlen(KEXEC_CMDLINE) + 1]) {
348 FILE *f = fopen("/proc/cmdline","r");
349 if (f && fgets(s, sizeof(cmdline) - sstrlen(KEXEC_CMDLINE), f)) {
350 /* /proc/cmdline ends with a new line which we want to overwrite thus -1 */
351 s += strlen(s) - 1;
353 fclose(f);
356 /* append root partition, filesystem and the image specific command line */
357 int len = snprintf(s, sizeof(cmdline) - (s - cmdline), "rootwait root=%s rootfstype=%s %s",
358 i->dev, i->fs, i->cmdline ? i->cmdline : "");
359 if (len > 0 && s[len - 1] == '\n')
360 s[len - 1] = '\0';
362 return cmdline;
365 static bool boot_kernel(BootItem *i) {
366 Eina_List *l;
367 BootItem *s;
369 /* umount all filesystem we probed except the one where the kernel is located */
370 EINA_LIST_FOREACH(systems, l, s) {
371 if (s != i) {
372 /* XXX: assumes we are still chdir()-ed */
373 umount(s->dev + sstrlen("/dev/"));
377 const char *kexec_load[] = { KEXEC, get_kernel_cmdline(i), "-l", i->kernel, NULL };
378 if (fexecw(kexec_load[0], (char *const *)kexec_load, NULL)) {
379 gui_show_error("Couldn't load kernel from '%s'", i->kernel);
380 return false;
383 const char *kexec_exec[] = { KEXEC , "-e", NULL };
385 /* XXX: assumes we are still chdir()-ed */
386 umount(i->dev + sstrlen("/dev/"));
388 if (execve(kexec_exec[0], (char *const *)kexec_exec, NULL)) {
389 gui_show_error("Couldn't exec kernel '%s'", i->kernel);
390 return false;
393 /* can't be reached just here to silence compiler warning */
394 return true;
397 static void diagnostics(Eina_List *dev_ignore) {
398 Eina_List *l;
399 BootItem *s;
400 char *p;
402 systems = scan_system(dev_ignore);
404 puts("Partitions:");
406 EINA_LIST_FOREACH(partitions, l, p) {
407 puts(p);
410 puts("Built in filesystems:");
412 EINA_LIST_FOREACH(fs_core, l, p) {
413 puts(p);
416 #if CONFIG_SUPPORT_KERNEL_FS_MODULES
417 puts("Filesystem modules:");
419 EINA_LIST_FOREACH(fs_mods, l, p) {
420 puts(p);
422 #endif
424 puts("Bootable images:");
426 EINA_LIST_FOREACH(systems, l, s) {
427 printf("mount -t %s %s /mnt/%s\n", s->fs, s->dev, s->dev + sstrlen("/dev/"));
428 printf("kexec %s'%s' -l %s\n", KEXEC_CMDLINE,
429 get_kernel_cmdline(s) + sstrlen(KEXEC_CMDLINE), s->kernel);
430 printf("umount %s\n", s->dev);
431 printf("kexec -e\n\n");
432 /* XXX: assumes we are still chdir()-ed */
433 umount(s->dev + sstrlen("/dev/"));
437 static void umount_all() {
438 Eina_List *l;
439 BootItem *s;
441 EINA_LIST_FOREACH(systems, l, s) {
442 umount(s->dev + sstrlen("/dev/"));