discover/boot: Add 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 <system/system.h>
15 #include <talloc/talloc.h>
16 #include <url/url.h>
17
18 #include "device-handler.h"
19 #include "boot.h"
20 #include "paths.h"
21 #include "resource.h"
22
23 static const char *boot_hook_dir = PKG_SYSCONF_DIR "/boot.d";
24
25 struct boot_task {
26         char *local_image;
27         char *local_initrd;
28         char *local_dtb;
29         const char *args;
30
31         bool dry_run;
32 };
33
34 /**
35  * kexec_load - kexec load helper.
36  */
37 static int kexec_load(struct boot_task *boot_task)
38 {
39         int result;
40         const char *argv[7];
41         const char **p;
42         char *s_initrd = NULL;
43         char *s_dtb = NULL;
44         char *s_args = NULL;
45
46         p = argv;
47         *p++ = pb_system_apps.kexec;    /* 1 */
48         *p++ = "-l";                    /* 2 */
49
50         if (boot_task->local_initrd) {
51                 s_initrd = talloc_asprintf(NULL, "--initrd=%s",
52                                 boot_task->local_initrd);
53                 assert(s_initrd);
54                 *p++ = s_initrd;         /* 3 */
55         }
56
57         if (boot_task->local_dtb) {
58                 s_dtb = talloc_asprintf(NULL, "--dtb=%s", boot_task->local_dtb);
59                 assert(s_dtb);
60                 *p++ = s_dtb;            /* 4 */
61         }
62
63         if (boot_task->args) {
64                 s_args = talloc_asprintf(NULL, "--append=%s", boot_task->args);
65                 assert(s_args);
66                 *p++ = s_args;          /* 5 */
67         }
68
69         *p++ = boot_task->local_image;  /* 6 */
70         *p++ = NULL;                    /* 7 */
71
72         result = pb_run_cmd(argv, 1, boot_task->dry_run);
73
74         if (result)
75                 pb_log("%s: failed: (%d)\n", __func__, result);
76
77         talloc_free(s_initrd);
78         talloc_free(s_dtb);
79         talloc_free(s_args);
80
81         return result;
82 }
83
84 /**
85  * kexec_reboot - Helper to boot the new kernel.
86  *
87  * Must only be called after a successful call to kexec_load().
88  */
89
90 static int kexec_reboot(bool dry_run)
91 {
92         int result = 0;
93         const char *argv[4];
94         const char **p;
95
96         /* First try running shutdown.  Init scripts should run 'exec -e' */
97
98         p = argv;
99         *p++ = pb_system_apps.shutdown; /* 1 */
100         *p++ =  "-r";                   /* 2 */
101         *p++ =  "now";                  /* 3 */
102         *p++ =  NULL;                   /* 4 */
103
104         result = pb_run_cmd(argv, 1, dry_run);
105
106         /* On error, force a kexec with the -e option */
107
108         if (result) {
109                 p = argv;
110                 *p++ = pb_system_apps.kexec;    /* 1 */
111                 *p++ = "-e";                    /* 2 */
112                 *p++ = NULL;                    /* 3 */
113
114                 result = pb_run_cmd(argv, 1, 0);
115         }
116
117         if (result)
118                 pb_log("%s: failed: (%d)\n", __func__, result);
119
120         /* okay, kexec -e -f */
121         if (result) {
122                 p = argv;
123                 *p++ = pb_system_apps.kexec;    /* 1 */
124                 *p++ = "-e";                    /* 2 */
125                 *p++ = "-f";                    /* 3 */
126                 *p++ = NULL;                    /* 4 */
127
128                 result = pb_run_cmd(argv, 1, 0);
129         }
130
131         if (result)
132                 pb_log("%s: failed: (%d)\n", __func__, result);
133
134
135         return result;
136 }
137
138 static void update_status(boot_status_fn fn, void *arg, int type,
139                 char *message)
140 {
141         struct boot_status status;
142
143         status.type = type;
144         status.message = message;
145         status.progress = -1;
146         status.detail = NULL;
147
148         fn(arg, &status);
149 }
150
151 static int hook_filter(const struct dirent *dirent)
152 {
153         return dirent->d_type == DT_REG || dirent->d_type == DT_LNK;
154 }
155
156 static int hook_cmp(const struct dirent **a, const struct dirent **b)
157 {
158         return strcmp((*a)->d_name, (*b)->d_name);
159 }
160
161 static void run_boot_hooks(void *ctx, struct boot_task *task,
162                 boot_status_fn status_fn, void *status_arg)
163 {
164         struct dirent **hooks;
165         int i, n;
166
167         n = scandir(boot_hook_dir, &hooks, hook_filter, hook_cmp);
168         if (n < 1)
169                 return;
170
171         update_status(status_fn, status_arg, BOOT_STATUS_INFO,
172                         "running boot hooks");
173
174         /* pass boot data to hooks */
175         setenv("boot_image", task->local_image, 1);
176         if (task->local_initrd)
177                 setenv("boot_initrd", task->local_initrd, 1);
178         if (task->local_dtb)
179                 setenv("boot_dtb", task->local_dtb, 1);
180         if (task->args)
181                 setenv("boot_args", task->args, 1);
182
183         for (i = 0; i < n; i++) {
184                 char *path;
185                 const char *argv[2] = { NULL, NULL };
186
187                 path = join_paths(ctx, boot_hook_dir, hooks[i]->d_name);
188
189                 if (access(path, X_OK))
190                         continue;
191
192                 pb_log("running boot hook %s\n", hooks[i]->d_name);
193
194                 argv[0] = path;
195                 pb_run_cmd(argv, 1, task->dry_run);
196
197                 talloc_free(path);
198         }
199
200         free(hooks);
201 }
202
203 int boot(void *ctx, struct discover_boot_option *opt, struct boot_command *cmd,
204                 int dry_run, boot_status_fn status_fn, void *status_arg)
205 {
206         struct boot_task boot_task;
207         struct pb_url *image, *initrd, *dtb;
208         unsigned int clean_image = 0;
209         unsigned int clean_initrd = 0;
210         unsigned int clean_dtb = 0;
211         int result;
212
213         image = NULL;
214         initrd = NULL;
215         dtb = NULL;
216         boot_task.dry_run = dry_run;
217
218         if (cmd && cmd->boot_image_file) {
219                 image = pb_url_parse(opt, cmd->boot_image_file);
220         } else if (opt && opt->boot_image) {
221                 image = opt->boot_image->url;
222         } else {
223                 pb_log("%s: no image specified", __func__);
224                 return -1;
225         }
226
227         if (cmd && cmd->initrd_file) {
228                 initrd = pb_url_parse(opt, cmd->initrd_file);
229         } else if (opt && opt->initrd) {
230                 initrd = opt->initrd->url;
231         }
232
233         if (cmd && cmd->dtb_file) {
234                 dtb = pb_url_parse(opt, cmd->dtb_file);
235         } else if (opt && opt->dtb) {
236                 dtb = opt->dtb->url;
237         }
238
239         if (cmd && cmd->boot_args) {
240                 boot_task.args = talloc_strdup(ctx, cmd->boot_args);
241         } else if (opt && opt->option->boot_args) {
242                 boot_task.args = talloc_strdup(ctx, opt->option->boot_args);
243         } else {
244                 boot_task.args = NULL;
245         }
246
247         result = -1;
248
249         update_status(status_fn, status_arg, BOOT_STATUS_INFO,
250                         "loading kernel");
251         boot_task.local_image = load_url(NULL, image, &clean_image);
252         if (!boot_task.local_image) {
253                 update_status(status_fn, status_arg, BOOT_STATUS_ERROR,
254                                 "Couldn't load kernel image");
255                 goto no_load;
256         }
257
258         boot_task.local_initrd = NULL;
259         if (initrd) {
260                 update_status(status_fn, status_arg, BOOT_STATUS_INFO,
261                                 "loading initrd");
262                 boot_task.local_initrd = load_url(NULL, initrd, &clean_initrd);
263                 if (!boot_task.local_initrd) {
264                         update_status(status_fn, status_arg, BOOT_STATUS_ERROR,
265                                         "Couldn't load initrd image");
266                         goto no_load;
267                 }
268         }
269
270         boot_task.local_dtb = NULL;
271         if (dtb) {
272                 update_status(status_fn, status_arg, BOOT_STATUS_INFO,
273                                 "loading device tree");
274                 boot_task.local_dtb = load_url(NULL, dtb, &clean_dtb);
275                 if (!boot_task.local_dtb) {
276                         update_status(status_fn, status_arg, BOOT_STATUS_ERROR,
277                                         "Couldn't load device tree");
278                         goto no_load;
279                 }
280         }
281
282         run_boot_hooks(ctx, &boot_task, status_fn, status_arg);
283
284         update_status(status_fn, status_arg, BOOT_STATUS_INFO,
285                         "performing kexec_load");
286
287         result = kexec_load(&boot_task);
288
289         if (result) {
290                 update_status(status_fn, status_arg, BOOT_STATUS_ERROR,
291                                 "kexec load failed");
292         }
293
294 no_load:
295         if (clean_image)
296                 unlink(boot_task.local_image);
297         if (clean_initrd)
298                 unlink(boot_task.local_initrd);
299         if (clean_dtb)
300                 unlink(boot_task.local_dtb);
301
302         talloc_free(boot_task.local_image);
303         talloc_free(boot_task.local_initrd);
304         talloc_free(boot_task.local_dtb);
305
306         if (!result) {
307                 update_status(status_fn, status_arg, BOOT_STATUS_INFO,
308                                 "performing kexec reboot");
309
310                 result = kexec_reboot(boot_task.dry_run);
311
312                 if (result) {
313                         update_status(status_fn, status_arg, BOOT_STATUS_ERROR,
314                                         "kexec reboot failed");
315                 }
316         }
317
318         return result;
319 }