discover/boot: Copy local paths before running boot hooks
[petitboot] / discover / boot.c
1
2 #define _GNU_SOURCE
3
4 #include <stdbool.h>
5 #include <stdlib.h>
6 #include <assert.h>
7 #include <dirent.h>
8 #include <string.h>
9 #include <fcntl.h>
10 #include <sys/types.h>
11
12 #include <log/log.h>
13 #include <pb-protocol/pb-protocol.h>
14 #include <process/process.h>
15 #include <system/system.h>
16 #include <talloc/talloc.h>
17 #include <url/url.h>
18 #include <util/util.h>
19
20 #include "device-handler.h"
21 #include "boot.h"
22 #include "paths.h"
23 #include "resource.h"
24
25 static const char *boot_hook_dir = PKG_SYSCONF_DIR "/boot.d";
26 enum {
27         BOOT_HOOK_EXIT_OK       = 0,
28         BOOT_HOOK_EXIT_UPDATE   = 2,
29 };
30
31 struct boot_task {
32         struct load_url_result *image;
33         struct load_url_result *initrd;
34         struct load_url_result *dtb;
35         const char *local_image;
36         const char *local_initrd;
37         const char *local_dtb;
38         const char *args;
39         boot_status_fn status_fn;
40         void *status_arg;
41         bool dry_run;
42         bool cancelled;
43 };
44
45 /**
46  * kexec_load - kexec load helper.
47  */
48 static int kexec_load(struct boot_task *boot_task)
49 {
50         int result;
51         const char *argv[7];
52         const char **p;
53         char *s_initrd = NULL;
54         char *s_dtb = NULL;
55         char *s_args = NULL;
56
57         p = argv;
58         *p++ = pb_system_apps.kexec;    /* 1 */
59         *p++ = "-l";                    /* 2 */
60
61         if (boot_task->local_initrd) {
62                 s_initrd = talloc_asprintf(boot_task, "--initrd=%s",
63                                 boot_task->local_initrd);
64                 assert(s_initrd);
65                 *p++ = s_initrd;         /* 3 */
66         }
67
68         if (boot_task->local_dtb) {
69                 s_dtb = talloc_asprintf(boot_task, "--dtb=%s",
70                                                 boot_task->local_dtb);
71                 assert(s_dtb);
72                 *p++ = s_dtb;            /* 4 */
73         }
74
75         if (boot_task->args) {
76                 s_args = talloc_asprintf(boot_task, "--append=%s",
77                                                 boot_task->args);
78                 assert(s_args);
79                 *p++ = s_args;          /* 5 */
80         }
81
82         *p++ = boot_task->local_image;  /* 6 */
83         *p++ = NULL;                    /* 7 */
84
85         result = process_run_simple_argv(boot_task, argv);
86
87         if (result)
88                 pb_log("%s: failed: (%d)\n", __func__, result);
89
90         return result;
91 }
92
93 /**
94  * kexec_reboot - Helper to boot the new kernel.
95  *
96  * Must only be called after a successful call to kexec_load().
97  */
98
99 static int kexec_reboot(struct boot_task *task)
100 {
101         int result;
102
103         /* First try running shutdown.  Init scripts should run 'exec -e' */
104         result = process_run_simple(task, pb_system_apps.shutdown, "-r",
105                         "now", NULL);
106
107         /* On error, force a kexec with the -e option */
108         if (result) {
109                 result = process_run_simple(task, pb_system_apps.kexec,
110                                                 "-e", NULL);
111         }
112
113         if (result)
114                 pb_log("%s: failed: (%d)\n", __func__, result);
115
116         /* okay, kexec -e -f */
117         if (result) {
118                 result = process_run_simple(task, pb_system_apps.kexec,
119                                                 "-e", "-f", NULL);
120         }
121
122         if (result)
123                 pb_log("%s: failed: (%d)\n", __func__, result);
124
125
126         return result;
127 }
128
129 static void __attribute__((format(__printf__, 4, 5))) update_status(
130                 boot_status_fn fn, void *arg, int type, char *fmt, ...)
131 {
132         struct boot_status status;
133         va_list ap;
134
135         va_start(ap, fmt);
136         status.message = talloc_vasprintf(NULL, fmt, ap);
137         va_end(ap);
138
139         status.type = type;
140         status.progress = -1;
141         status.detail = NULL;
142
143         pb_debug("boot status: [%d] %s\n", type, status.message);
144
145         fn(arg, &status);
146
147         talloc_free(status.message);
148 }
149
150 static void boot_hook_update_param(void *ctx, struct boot_task *task,
151                 const char *name, const char *value)
152 {
153         struct p {
154                 const char *name;
155                 const char **p;
156         } *param, params[] = {
157                 { "boot_image",         &task->local_image },
158                 { "boot_initrd",        &task->local_initrd },
159                 { "boot_dtb",           &task->local_dtb },
160                 { "boot_args",          &task->args },
161                 { NULL, NULL },
162         };
163
164         for (param = params; param->name; param++) {
165                 if (strcmp(param->name, name))
166                         continue;
167
168                 *param->p = talloc_strdup(ctx, value);
169                 return;
170         }
171 }
172
173 static void boot_hook_update(struct boot_task *task, const char *hookname,
174                 char *buf)
175 {
176         char *line, *name, *val, *sep;
177         char *saveptr;
178
179         for (;; buf = NULL) {
180
181                 line = strtok_r(buf, "\n", &saveptr);
182                 if (!line)
183                         break;
184
185                 sep = strchr(line, '=');
186                 if (!sep)
187                         continue;
188
189                 *sep = '\0';
190                 name = line;
191                 val = sep + 1;
192
193                 boot_hook_update_param(task, task, name, val);
194
195                 pb_log("boot hook %s specified %s=%s\n",
196                                 hookname, name, val);
197         }
198 }
199
200 static void boot_hook_setenv(struct boot_task *task)
201 {
202         unsetenv("boot_image");
203         unsetenv("boot_initrd");
204         unsetenv("boot_dtb");
205         unsetenv("boot_args");
206
207         setenv("boot_image", task->local_image, 1);
208         if (task->local_initrd)
209                 setenv("boot_initrd", task->local_initrd, 1);
210         if (task->local_dtb)
211                 setenv("boot_dtb", task->local_dtb, 1);
212         if (task->args)
213                 setenv("boot_args", task->args, 1);
214 }
215
216 static int hook_filter(const struct dirent *dirent)
217 {
218         return dirent->d_type == DT_REG || dirent->d_type == DT_LNK;
219 }
220
221 static int hook_cmp(const struct dirent **a, const struct dirent **b)
222 {
223         return strcmp((*a)->d_name, (*b)->d_name);
224 }
225
226 static void run_boot_hooks(struct boot_task *task)
227 {
228         struct dirent **hooks;
229         int i, n;
230
231         n = scandir(boot_hook_dir, &hooks, hook_filter, hook_cmp);
232         if (n < 1)
233                 return;
234
235         update_status(task->status_fn, task->status_arg, BOOT_STATUS_INFO,
236                         "running boot hooks");
237
238         boot_hook_setenv(task);
239
240         for (i = 0; i < n; i++) {
241                 const char *argv[2] = { NULL, NULL };
242                 struct process *process;
243                 char *path;
244                 int rc;
245
246                 path = join_paths(task, boot_hook_dir, hooks[i]->d_name);
247
248                 if (access(path, X_OK)) {
249                         talloc_free(path);
250                         continue;
251                 }
252
253                 process = process_create(task);
254
255                 argv[0] = path;
256                 process->path = path;
257                 process->argv = argv;
258                 process->keep_stdout = true;
259
260                 pb_log("running boot hook %s\n", hooks[i]->d_name);
261
262                 rc = process_run_sync(process);
263                 if (rc) {
264                         pb_log("boot hook exec failed!\n");
265
266                 } else if (WIFEXITED(process->exit_status) &&
267                            WEXITSTATUS(process->exit_status)
268                                 == BOOT_HOOK_EXIT_UPDATE) {
269                         /* if the hook returned with BOOT_HOOK_EXIT_UPDATE,
270                          * then we process stdout to look for updated params
271                          */
272                         boot_hook_update(task, hooks[i]->d_name,
273                                         process->stdout_buf);
274                         boot_hook_setenv(task);
275                 }
276
277                 process_release(process);
278                 talloc_free(path);
279         }
280
281         free(hooks);
282 }
283
284 static bool load_pending(struct load_url_result *result)
285 {
286         return result && result->status == LOAD_ASYNC;
287 }
288
289 static int check_load(struct boot_task *task, const char *name,
290                 struct load_url_result *result)
291 {
292         if (!result)
293                 return 0;
294         if (result->status != LOAD_ERROR)
295                 return 0;
296
297         update_status(task->status_fn, task->status_arg,
298                         BOOT_STATUS_ERROR,
299                         "Couldn't load %s", name);
300         return -1;
301 }
302
303 static void cleanup_load(struct load_url_result *result)
304 {
305         if (!result)
306                 return;
307         if (result->status != LOAD_OK)
308                 return;
309         if (!result->cleanup_local)
310                 return;
311         unlink(result->local);
312 }
313
314 static void cleanup_cancellations(struct boot_task *task,
315                 struct load_url_result *cur_result)
316 {
317         struct load_url_result *result, **results[] = {
318                 &task->image, &task->initrd, &task->dtb,
319         };
320         bool pending = false;
321         unsigned int i;
322
323         for (i = 0; i < ARRAY_SIZE(results); i++) {
324                 result = *results[i];
325
326                 if (!result)
327                         continue;
328
329                 /* We need to cleanup and free any completed loads */
330                 if (result == cur_result || result->status == LOAD_OK
331                                 || result->status == LOAD_ERROR) {
332                         cleanup_load(result);
333                         talloc_free(result);
334                         *results[i] = NULL;
335
336                 /* ... and cancel any pending loads, which we'll free in
337                  * the completion callback */
338                 } else if (result->status == LOAD_ASYNC) {
339                         load_url_async_cancel(result);
340                         pending = true;
341                 }
342         }
343
344         if (!pending)
345                 talloc_free(task);
346 }
347
348 static void boot_process(struct load_url_result *result, void *data)
349 {
350         struct boot_task *task = data;
351         int rc = -1;
352
353         if (task->cancelled) {
354                 cleanup_cancellations(task, result);
355                 return;
356         }
357
358         if (load_pending(task->image) ||
359                         load_pending(task->initrd) ||
360                         load_pending(task->dtb))
361                 return;
362
363         if (check_load(task, "kernel image", task->image) ||
364                         check_load(task, "initrd", task->initrd) ||
365                         check_load(task, "dtb", task->dtb))
366                 goto no_load;
367
368         /* we make a copy of the local paths, as the boot hooks might update
369          * and/or create these */
370         task->local_image = task->image ? task->image->local : NULL;
371         task->local_initrd = task->initrd ? task->initrd->local : NULL;
372         task->local_dtb = task->dtb ? task->dtb->local : NULL;
373
374         run_boot_hooks(task);
375
376         update_status(task->status_fn, task->status_arg, BOOT_STATUS_INFO,
377                         "performing kexec_load");
378
379         rc = kexec_load(task);
380         if (rc) {
381                 update_status(task->status_fn, task->status_arg,
382                                 BOOT_STATUS_ERROR, "kexec load failed");
383         }
384
385 no_load:
386         cleanup_load(task->image);
387         cleanup_load(task->initrd);
388         cleanup_load(task->dtb);
389
390         if (!rc) {
391                 update_status(task->status_fn, task->status_arg,
392                                 BOOT_STATUS_INFO,
393                                 "performing kexec reboot");
394
395                 rc = kexec_reboot(task);
396                 if (rc) {
397                         update_status(task->status_fn, task->status_arg,
398                                         BOOT_STATUS_ERROR,
399                                         "kexec reboot failed");
400                 }
401         }
402 }
403
404 static int start_url_load(struct boot_task *task, const char *name,
405                 struct pb_url *url, struct load_url_result **result)
406 {
407         if (!url)
408                 return 0;
409
410         *result = load_url_async(task, url, boot_process, task);
411         if (!*result) {
412                 update_status(task->status_fn, task->status_arg,
413                                 BOOT_STATUS_ERROR,
414                                 "Error loading %s", name);
415                 return -1;
416         }
417         return 0;
418 }
419
420 struct boot_task *boot(void *ctx, struct discover_boot_option *opt,
421                 struct boot_command *cmd, int dry_run,
422                 boot_status_fn status_fn, void *status_arg)
423 {
424         struct pb_url *image = NULL, *initrd = NULL, *dtb = NULL;
425         struct boot_task *boot_task;
426         const char *boot_desc;
427         int rc;
428
429         if (opt && opt->option->name)
430                 boot_desc = opt->option->name;
431         else if (cmd && cmd->boot_image_file)
432                 boot_desc = cmd->boot_image_file;
433         else
434                 boot_desc = "(unknown)";
435
436         update_status(status_fn, status_arg, BOOT_STATUS_INFO,
437                         "Booting %s.", boot_desc);
438
439         if (cmd && cmd->boot_image_file) {
440                 image = pb_url_parse(opt, cmd->boot_image_file);
441         } else if (opt && opt->boot_image) {
442                 image = opt->boot_image->url;
443         } else {
444                 pb_log("%s: no image specified\n", __func__);
445                 update_status(status_fn, status_arg, BOOT_STATUS_INFO,
446                                 "Boot failed: no image specified");
447                 return NULL;
448         }
449
450         if (cmd && cmd->initrd_file) {
451                 initrd = pb_url_parse(opt, cmd->initrd_file);
452         } else if (opt && opt->initrd) {
453                 initrd = opt->initrd->url;
454         }
455
456         if (cmd && cmd->dtb_file) {
457                 dtb = pb_url_parse(opt, cmd->dtb_file);
458         } else if (opt && opt->dtb) {
459                 dtb = opt->dtb->url;
460         }
461
462         boot_task = talloc_zero(ctx, struct boot_task);
463         boot_task->dry_run = dry_run;
464         boot_task->status_fn = status_fn;
465         boot_task->status_arg = status_arg;
466
467         if (cmd && cmd->boot_args) {
468                 boot_task->args = talloc_strdup(boot_task, cmd->boot_args);
469         } else if (opt && opt->option->boot_args) {
470                 boot_task->args = talloc_strdup(boot_task,
471                                                 opt->option->boot_args);
472         } else {
473                 boot_task->args = NULL;
474         }
475
476         /* start async loads for boot resources */
477         rc = start_url_load(boot_task, "kernel image", image, &boot_task->image)
478           || start_url_load(boot_task, "initrd", initrd, &boot_task->initrd)
479           || start_url_load(boot_task, "dtb", dtb, &boot_task->dtb);
480
481         /* If all URLs are local, we may be done. */
482         if (rc) {
483                 talloc_free(boot_task);
484                 return NULL;
485         }
486
487         boot_process(NULL, boot_task);
488
489         return boot_task;
490 }
491
492 void boot_cancel(struct boot_task *task)
493 {
494         task->cancelled = true;
495
496         update_status(task->status_fn, task->status_arg, BOOT_STATUS_INFO,
497                         "Boot cancelled");
498
499         cleanup_cancellations(task, NULL);
500 }