]> git.ozlabs.org Git - petitboot/blob - discover/boot.c
discover/powerpc: Use process_get_stdout
[petitboot] / discover / boot.c
1
2 #if defined(HAVE_CONFIG_H)
3 #include "config.h"
4 #endif
5
6 #include <stdbool.h>
7 #include <stdlib.h>
8 #include <assert.h>
9 #include <dirent.h>
10 #include <string.h>
11 #include <fcntl.h>
12 #include <sys/types.h>
13
14 #include <log/log.h>
15 #include <pb-protocol/pb-protocol.h>
16 #include <process/process.h>
17 #include <system/system.h>
18 #include <talloc/talloc.h>
19 #include <url/url.h>
20 #include <util/util.h>
21 #include <i18n/i18n.h>
22
23 #include "device-handler.h"
24 #include "boot.h"
25 #include "paths.h"
26 #include "resource.h"
27 #include "platform.h"
28
29 #include <security/security.h>
30
31 static const char *boot_hook_dir = PKG_SYSCONF_DIR "/boot.d";
32 enum {
33         BOOT_HOOK_EXIT_OK       = 0,
34         BOOT_HOOK_EXIT_UPDATE   = 2,
35 };
36
37 static void __attribute__((format(__printf__, 4, 5))) update_status(
38                 boot_status_fn fn, void *arg, int type, char *fmt, ...)
39 {
40         struct status status;
41         va_list ap;
42
43         va_start(ap, fmt);
44         status.message = talloc_vasprintf(NULL, fmt, ap);
45         va_end(ap);
46
47         status.type = type;
48         status.backlog = false;
49
50         pb_debug("boot status: [%d] %s\n", type, status.message);
51
52         fn(arg, &status);
53
54         talloc_free(status.message);
55 }
56
57 /**
58  * kexec_load - kexec load helper.
59  */
60 static int kexec_load(struct boot_task *boot_task)
61 {
62         struct process *process;
63         char *s_initrd = NULL;
64         char *s_args = NULL;
65         const char *argv[8];
66         char *s_dtb = NULL;
67         const char **p;
68         int result;
69
70
71         boot_task->local_initrd_override = NULL;
72         boot_task->local_dtb_override = NULL;
73         boot_task->local_image_override = NULL;
74
75         if ((result = validate_boot_files(boot_task))) {
76                 if (result == KEXEC_LOAD_DECRYPTION_FALURE) {
77                         pb_log("%s: Aborting kexec due to"
78                                 " decryption failure\n", __func__);
79                 }
80                 if (result == KEXEC_LOAD_SIGNATURE_FAILURE) {
81                         pb_log("%s: Aborting kexec due to signature"
82                                 " verification failure\n", __func__);
83                 }
84
85                 goto abort_kexec;
86         }
87
88         const char* local_initrd = (boot_task->local_initrd_override) ?
89                 boot_task->local_initrd_override : boot_task->local_initrd;
90         const char* local_dtb = (boot_task->local_dtb_override) ?
91                 boot_task->local_dtb_override : boot_task->local_dtb;
92         const char* local_image = (boot_task->local_image_override) ?
93                 boot_task->local_image_override : boot_task->local_image;
94
95         process = process_create(boot_task);
96         if (!process) {
97                 pb_log_fn("failed to create process\n");
98                 return -1;
99         }
100
101         process->path = pb_system_apps.kexec;
102         process->argv = argv;
103         process->keep_stdout = true;
104         process->add_stderr = true;
105
106         p = argv;
107         *p++ = pb_system_apps.kexec;    /* 1 */
108         *p++ = "-l";                    /* 2 */
109
110         if (pb_log_get_debug()) {
111                 *p++ = "--debug";       /* 3 */
112         }
113
114         if (local_initrd) {
115                 s_initrd = talloc_asprintf(boot_task, "--initrd=%s",
116                                 local_initrd);
117                 assert(s_initrd);
118                 *p++ = s_initrd;         /* 4 */
119         }
120
121         if (local_dtb) {
122                 s_dtb = talloc_asprintf(boot_task, "--dtb=%s",
123                                                 local_dtb);
124                 assert(s_dtb);
125                 *p++ = s_dtb;            /* 5 */
126         }
127
128         s_args = talloc_asprintf(boot_task, "--append=%s",
129                                 boot_task->args ?: "\"\"");
130         assert(s_args);
131         *p++ = s_args;                  /* 6 */
132
133         *p++ = local_image;             /* 7 */
134         *p++ = NULL;                    /* 8 */
135
136         result = process_run_sync(process);
137         if (result) {
138                 pb_log_fn("failed to run process\n");
139                 goto abort_kexec;
140         }
141
142         result = process->exit_status;
143
144         if (result) {
145                 pb_log_fn("failed: (%d)\n", result);
146                 update_status(boot_task->status_fn, boot_task->status_arg,
147                                 STATUS_ERROR, "%s", process->stdout_buf);
148         }
149
150 abort_kexec:
151         validate_boot_files_cleanup(boot_task);
152
153         return result;
154 }
155
156 /**
157  * kexec_reboot - Helper to boot the new kernel.
158  *
159  * Must only be called after a successful call to kexec_load().
160  */
161
162 static int kexec_reboot(struct boot_task *task)
163 {
164         int result;
165
166         /* First try running shutdown.  Init scripts should run 'exec -e' */
167         result = process_run_simple(task, pb_system_apps.shutdown, "-r",
168                         "now", NULL);
169
170         /* On error, force a kexec with the -e option */
171         if (result) {
172                 result = process_run_simple(task, pb_system_apps.kexec,
173                                                 "-e", NULL);
174         }
175
176         if (result)
177                 pb_log_fn("failed: (%d)\n", result);
178
179         /* okay, kexec -e -f */
180         if (result) {
181                 result = process_run_simple(task, pb_system_apps.kexec,
182                                                 "-e", "-f", NULL);
183         }
184
185         if (result)
186                 pb_log_fn("failed: (%d)\n", result);
187
188
189         return result;
190 }
191
192 static void boot_hook_update_param(void *ctx, struct boot_task *task,
193                 const char *name, const char *value)
194 {
195         struct p {
196                 const char *name;
197                 const char **p;
198         } *param, params[] = {
199                 { "boot_image",         &task->local_image },
200                 { "boot_initrd",        &task->local_initrd },
201                 { "boot_dtb",           &task->local_dtb },
202                 { "boot_args",          &task->args },
203                 { NULL, NULL },
204         };
205
206         for (param = params; param->name; param++) {
207                 if (strcmp(param->name, name))
208                         continue;
209
210                 *param->p = talloc_strdup(ctx, value);
211                 return;
212         }
213 }
214
215 static void boot_hook_update(struct boot_task *task, const char *hookname,
216                 char *buf)
217 {
218         char *line, *name, *val, *sep;
219         char *saveptr = NULL;
220
221         for (;; buf = NULL) {
222
223                 line = strtok_r(buf, "\n", &saveptr);
224                 if (!line)
225                         break;
226
227                 sep = strchr(line, '=');
228                 if (!sep)
229                         continue;
230
231                 *sep = '\0';
232                 name = line;
233                 val = sep + 1;
234
235                 boot_hook_update_param(task, task, name, val);
236
237                 pb_log("boot hook %s specified %s=%s\n",
238                                 hookname, name, val);
239         }
240 }
241
242 static void boot_hook_setenv(struct boot_task *task)
243 {
244         unsetenv("boot_image");
245         unsetenv("boot_initrd");
246         unsetenv("boot_dtb");
247         unsetenv("boot_args");
248         unsetenv("boot_console");
249
250         setenv("boot_image", task->local_image, 1);
251         if (task->local_initrd)
252                 setenv("boot_initrd", task->local_initrd, 1);
253         if (task->local_dtb)
254                 setenv("boot_dtb", task->local_dtb, 1);
255         if (task->args)
256                 setenv("boot_args", task->args, 1);
257         if (task->boot_console)
258                 setenv("boot_console", task->boot_console, 1);
259 }
260
261 static int hook_filter(const struct dirent *dirent)
262 {
263         return dirent->d_type == DT_REG || dirent->d_type == DT_LNK;
264 }
265
266 static int hook_cmp(const struct dirent **a, const struct dirent **b)
267 {
268         return strcmp((*a)->d_name, (*b)->d_name);
269 }
270
271 static void run_boot_hooks(struct boot_task *task)
272 {
273         struct dirent **hooks;
274         int i, n;
275
276         n = scandir(boot_hook_dir, &hooks, hook_filter, hook_cmp);
277         if (n < 1)
278                 return;
279
280         update_status(task->status_fn, task->status_arg, STATUS_INFO,
281                         _("Running boot hooks"));
282
283         boot_hook_setenv(task);
284
285         for (i = 0; i < n; i++) {
286                 const char *argv[2] = { NULL, NULL };
287                 struct process *process;
288                 char *path;
289                 int rc;
290
291                 path = join_paths(task, boot_hook_dir, hooks[i]->d_name);
292
293                 if (access(path, X_OK)) {
294                         talloc_free(path);
295                         continue;
296                 }
297
298                 process = process_create(task);
299
300                 argv[0] = path;
301                 process->path = path;
302                 process->argv = argv;
303                 process->keep_stdout = true;
304
305                 pb_log("running boot hook %s\n", hooks[i]->d_name);
306
307                 rc = process_run_sync(process);
308                 if (rc) {
309                         pb_log("boot hook exec failed!\n");
310
311                 } else if (WIFEXITED(process->exit_status) &&
312                            WEXITSTATUS(process->exit_status)
313                                 == BOOT_HOOK_EXIT_UPDATE) {
314                         /* if the hook returned with BOOT_HOOK_EXIT_UPDATE,
315                          * then we process stdout to look for updated params
316                          */
317                         boot_hook_update(task, hooks[i]->d_name,
318                                         process->stdout_buf);
319                         boot_hook_setenv(task);
320                 }
321
322                 process_release(process);
323                 talloc_free(path);
324         }
325
326         free(hooks);
327 }
328
329 static bool load_pending(struct load_url_result *result)
330 {
331         return !result || result->status == LOAD_ASYNC;
332 }
333
334 static int check_load(struct boot_task *task, const char *name,
335                 struct load_url_result *result)
336 {
337         if (!result)
338                 return 0;
339
340         if (result->status != LOAD_ERROR) {
341                 update_status(task->status_fn, task->status_arg,
342                                 STATUS_ERROR,
343                                 _("Loaded %s from %s"), name,
344                                 pb_url_to_string(result->url));
345                 return 0;
346         }
347
348         pb_log("Failed to load %s from %s\n", name,
349                         pb_url_to_string(result->url));
350         update_status(task->status_fn, task->status_arg,
351                         STATUS_ERROR,
352                         _("Couldn't load %s from %s"), name,
353                         pb_url_to_string(result->url));
354         return -1;
355 }
356
357 static void cleanup_load(struct load_url_result *result)
358 {
359         if (!result)
360                 return;
361         if (result->status != LOAD_OK)
362                 return;
363         if (!result->cleanup_local)
364                 return;
365         unlink(result->local);
366 }
367
368 static void cleanup_cancellations(struct boot_task *task,
369                 struct load_url_result *cur_result)
370 {
371         struct boot_resource *resource;
372         struct load_url_result *result;
373         bool pending = false;
374
375         list_for_each_entry(&task->resources, resource, list) {
376                 result = resource->result;
377
378                 /* Nothing to do if a load hasn't actually started yet */
379                 if (!result)
380                         continue;
381
382                 /* We need to cleanup and free any completed loads */
383                 if (result == cur_result || result->status == LOAD_OK
384                                 || result->status == LOAD_ERROR) {
385                         cleanup_load(result);
386                 /* ... and cancel any pending loads, which we'll free in
387                  * the completion callback */
388                 } else if (result->status == LOAD_ASYNC) {
389                         load_url_async_cancel(result);
390                         pending = true;
391
392                 /* if we're waiting for a cancellation, we still need to
393                  * wait for the completion before freeing the boot task */
394                 } else if (result->status == LOAD_CANCELLED) {
395                         pending = true;
396                 }
397         }
398
399         if (!pending)
400                 talloc_free(task);
401 }
402
403 static void boot_process(struct load_url_result *result, void *data)
404 {
405         struct boot_task *task = data;
406         struct boot_resource *resource;
407         int rc = -1;
408
409         if (task->cancelled) {
410                 cleanup_cancellations(task, result);
411                 return;
412         }
413
414         list_for_each_entry(&task->resources, resource, list)
415                 if (load_pending(resource->result))
416                         return;
417
418         list_for_each_entry(&task->resources, resource, list) {
419                 if (check_load(task, resource->name, resource->result))
420                         goto no_load;
421                 *resource->local_path = resource->result->local;
422         }
423
424         run_boot_hooks(task);
425
426         update_status(task->status_fn, task->status_arg, STATUS_INFO,
427                         _("Performing kexec load"));
428
429         rc = kexec_load(task);
430         pb_log_fn("kexec_load returned %d\n", rc);
431         if (rc == KEXEC_LOAD_DECRYPTION_FALURE) {
432                 update_status(task->status_fn, task->status_arg,
433                                 STATUS_ERROR, _("Decryption failed"));
434         }
435         else if (rc == KEXEC_LOAD_SIGNATURE_FAILURE) {
436                 update_status(task->status_fn, task->status_arg,
437                                 STATUS_ERROR,
438                                 _("Signature verification failed"));
439         }
440         else if (rc == KEXEC_LOAD_SIG_SETUP_INVALID) {
441                 update_status(task->status_fn, task->status_arg,
442                                 STATUS_ERROR,
443                                 _("Invalid signature configuration"));
444         }
445
446 no_load:
447         list_for_each_entry(&task->resources, resource, list)
448                 cleanup_load(resource->result);
449
450         if (!rc) {
451                 update_status(task->status_fn, task->status_arg,
452                                 STATUS_INFO, _("Performing kexec reboot"));
453
454                 rc = kexec_reboot(task);
455                 if (rc) {
456                         update_status(task->status_fn, task->status_arg,
457                                         STATUS_ERROR,
458                                         _("kexec reboot failed"));
459                 }
460         } else {
461                 pb_log("Failed to load all boot resources\n");
462         }
463 }
464
465 static int start_url_load(struct boot_task *task, struct boot_resource *res)
466 {
467         if (!res)
468                 return 0;
469
470         res->result = load_url_async(task, res->url, boot_process,
471                                  task, NULL, task->status_arg);
472         if (!res->result) {
473                 pb_log("Error starting load for %s at %s\n",
474                                 res->name, pb_url_to_string(res->url));
475                 update_status(task->status_fn, task->status_arg,
476                                 STATUS_ERROR, _("Error loading %s"),
477                                 res->name);
478                 return -1;
479         }
480         return 0;
481 }
482
483 static struct boot_resource *add_boot_resource(struct boot_task *task,
484                 const char *name, struct pb_url *url,
485                 const char **local_path)
486 {
487         struct boot_resource *res;
488
489         if (!url)
490                 return NULL;
491
492         res = talloc_zero(task, struct boot_resource);
493         if (!res)
494                 return NULL;
495
496         res->name = talloc_strdup(res, name);
497         res->url = pb_url_copy(res, url);
498         res->local_path = local_path;
499
500         list_add(&task->resources, &res->list);
501         return res;
502 }
503
504 struct boot_task *boot(void *ctx, struct discover_boot_option *opt,
505                 struct boot_command *cmd, int dry_run,
506                 boot_status_fn status_fn, void *status_arg)
507 {
508         struct pb_url *image = NULL, *initrd = NULL, *dtb = NULL;
509         struct pb_url *image_sig = NULL, *initrd_sig = NULL, *dtb_sig = NULL,
510                 *cmdline_sig = NULL;
511         struct boot_resource *image_res, *initrd_res, *dtb_res, *tmp;
512         const struct config *config = config_get();
513         struct boot_task *boot_task;
514         const char *boot_desc;
515         int rc;
516         int lockdown_type;
517
518         if (opt && opt->option->name)
519                 boot_desc = opt->option->name;
520         else if (cmd && cmd->boot_image_file)
521                 boot_desc = cmd->boot_image_file;
522         else
523                 boot_desc = _("(unknown)");
524
525         update_status(status_fn, status_arg, STATUS_INFO,
526                         _("Booting %s"), boot_desc);
527
528         if (cmd && cmd->boot_image_file) {
529                 image = pb_url_parse(opt, cmd->boot_image_file);
530         } else if (opt && opt->boot_image) {
531                 image = opt->boot_image->url;
532         } else {
533                 pb_log_fn("no image specified\n");
534                 update_status(status_fn, status_arg, STATUS_INFO,
535                                 _("Boot failed: no image specified"));
536                 return NULL;
537         }
538
539         if (cmd && cmd->initrd_file) {
540                 initrd = pb_url_parse(opt, cmd->initrd_file);
541         } else if (opt && opt->initrd) {
542                 initrd = opt->initrd->url;
543         }
544
545         if (cmd && cmd->dtb_file) {
546                 dtb = pb_url_parse(opt, cmd->dtb_file);
547         } else if (opt && opt->dtb) {
548                 dtb = opt->dtb->url;
549         }
550
551         if (opt && opt->proxy) {
552                 setenv("http_proxy", opt->proxy, 1);
553                 setenv("https_proxy", opt->proxy, 1);
554         }
555
556         boot_task = talloc_zero(ctx, struct boot_task);
557         boot_task->dry_run = dry_run;
558         boot_task->status_fn = status_fn;
559         boot_task->status_arg = status_arg;
560         list_init(&boot_task->resources);
561
562         lockdown_type = lockdown_status();
563         boot_task->verify_signature = (lockdown_type == PB_LOCKDOWN_SIGN);
564         boot_task->decrypt_files = (lockdown_type == PB_LOCKDOWN_DECRYPT);
565
566         if (cmd && cmd->boot_args) {
567                 boot_task->args = talloc_strdup(boot_task, cmd->boot_args);
568         } else if (opt && opt->option->boot_args) {
569                 boot_task->args = talloc_strdup(boot_task,
570                                                 opt->option->boot_args);
571         } else {
572                 boot_task->args = NULL;
573         }
574
575         if (cmd && cmd->console && !config->manual_console)
576                 boot_task->boot_console = talloc_strdup(boot_task, cmd->console);
577         else
578                 boot_task->boot_console = config ? config->boot_console : NULL;
579
580         if (boot_task->verify_signature || boot_task->decrypt_files) {
581                 if (cmd && cmd->args_sig_file) {
582                         cmdline_sig = pb_url_parse(opt, cmd->args_sig_file);
583                 } else if (opt && opt->args_sig_file) {
584                         cmdline_sig = opt->args_sig_file->url;
585                 } else {
586                         pb_log("%s: no command line signature file"
587                                 " specified\n", __func__);
588                         update_status(status_fn, status_arg, STATUS_INFO,
589                                         _("Boot failed: no command line"
590                                                 " signature file specified"));
591                         talloc_free(boot_task);
592                         return NULL;
593                 }
594         }
595
596         image_res = add_boot_resource(boot_task, _("kernel image"), image,
597                         &boot_task->local_image);
598         initrd_res = add_boot_resource(boot_task, _("initrd"), initrd,
599                         &boot_task->local_initrd);
600         dtb_res = add_boot_resource(boot_task, _("dtb"), dtb,
601                         &boot_task->local_dtb);
602
603         /* start async loads for boot resources */
604         rc = start_url_load(boot_task, image_res)
605           || start_url_load(boot_task, initrd_res)
606           || start_url_load(boot_task, dtb_res);
607
608         if (boot_task->verify_signature) {
609                 /* Generate names of associated signature files and load */
610                 if (image) {
611                         image_sig = get_signature_url(ctx, image);
612                         tmp = add_boot_resource(boot_task,
613                                         _("kernel image signature"), image_sig,
614                                         &boot_task->local_image_signature);
615                         rc |= start_url_load(boot_task, tmp);
616                 }
617                 if (initrd) {
618                         initrd_sig = get_signature_url(ctx, initrd);
619                         tmp = add_boot_resource(boot_task,
620                                         _("initrd signature"), initrd_sig,
621                                         &boot_task->local_initrd_signature);
622                         rc |= start_url_load(boot_task, tmp);
623                 }
624                 if (dtb) {
625                         dtb_sig = get_signature_url(ctx, dtb);
626                         tmp = add_boot_resource(boot_task,
627                                         _("dtb signature"), dtb_sig,
628                                         &boot_task->local_dtb_signature);
629                         rc |= start_url_load(boot_task, tmp);
630                 }
631         }
632
633         if (boot_task->verify_signature || boot_task->decrypt_files) {
634                 tmp = add_boot_resource(boot_task,
635                                 _("kernel command line signature"), cmdline_sig,
636                                 &boot_task->local_cmdline_signature);
637                 rc |= start_url_load(boot_task, tmp);
638         }
639
640         /* If all URLs are local, we may be done. */
641         if (rc) {
642                 /* Don't call boot_cancel() to preserve the status update */
643                 boot_task->cancelled = true;
644                 cleanup_cancellations(boot_task, NULL);
645                 return NULL;
646         }
647
648         boot_process(NULL, boot_task);
649
650         return boot_task;
651 }
652
653 void boot_cancel(struct boot_task *task)
654 {
655         task->cancelled = true;
656
657         update_status(task->status_fn, task->status_arg, STATUS_INFO,
658                         _("Boot cancelled"));
659
660         cleanup_cancellations(task, NULL);
661 }