]> git.ozlabs.org Git - petitboot/blob - ui/ncurses/nc-boot-editor.c
80383005a3c3b612129f7e574dcce8b5ec0acffc
[petitboot] / ui / ncurses / nc-boot-editor.c
1 /*
2  *  Copyright (C) 2009 Sony Computer Entertainment Inc.
3  *  Copyright 2009 Sony Corp.
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; version 2 of the License.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19 #include "config.h"
20
21 #define _GNU_SOURCE
22
23 #include <assert.h>
24 #include <string.h>
25
26 #include "log/log.h"
27 #include "talloc/talloc.h"
28 #include "nc-boot-editor.h"
29 #include "nc-widgets.h"
30
31 struct boot_editor {
32         struct nc_scr           scr;
33         struct cui              *cui;
34         void                    *data;
35         struct pmenu_item       *item;
36         void                    (*on_exit)(struct cui *cui,
37                                         struct pmenu_item *item,
38                                         struct pb_boot_data *bd);
39
40         int                     label_x;
41         int                     field_x;
42
43         struct nc_widgetset     *widgetset;
44         struct {
45                 struct nc_widget_label          *device_l;
46                 struct nc_widget_select         *device_f;
47                 struct nc_widget_label          *image_l;
48                 struct nc_widget_textbox        *image_f;
49                 struct nc_widget_label          *initrd_l;
50                 struct nc_widget_textbox        *initrd_f;
51                 struct nc_widget_label          *dtb_l;
52                 struct nc_widget_textbox        *dtb_f;
53                 struct nc_widget_label          *args_l;
54                 struct nc_widget_textbox        *args_f;
55                 struct nc_widget_button         *ok_b;
56                 struct nc_widget_button         *cancel_b;
57         } widgets;
58
59         const char              *selected_device;
60 };
61
62 static struct boot_editor *boot_editor_from_scr(struct nc_scr *scr)
63 {
64         struct boot_editor *boot_editor;
65
66         assert(scr->sig == pb_boot_editor_sig);
67         boot_editor = (struct boot_editor *)
68                 ((char *)scr - (size_t)&((struct boot_editor *)0)->scr);
69         assert(boot_editor->scr.sig == pb_boot_editor_sig);
70         return boot_editor;
71 }
72
73 static struct boot_editor *boot_editor_from_arg(void *arg)
74 {
75         struct boot_editor *boot_editor = arg;
76
77         assert(boot_editor->scr.sig == pb_boot_editor_sig);
78         return boot_editor;
79 }
80
81 static int boot_editor_post(struct nc_scr *scr)
82 {
83         struct boot_editor *boot_editor = boot_editor_from_scr(scr);
84
85         widgetset_post(boot_editor->widgetset);
86         nc_scr_frame_draw(scr);
87         redrawwin(boot_editor->scr.main_ncw);
88         wrefresh(boot_editor->scr.main_ncw);
89
90         return 0;
91 }
92
93 static int boot_editor_unpost(struct nc_scr *scr)
94 {
95         widgetset_unpost(boot_editor_from_scr(scr)->widgetset);
96         return 0;
97 }
98
99 struct nc_scr *boot_editor_scr(struct boot_editor *boot_editor)
100 {
101         return &boot_editor->scr;
102 }
103
104 static void boot_editor_resize(struct nc_scr *scr)
105 {
106         /* FIXME: forms can't be resized, need to recreate here */
107         boot_editor_unpost(scr);
108         boot_editor_post(scr);
109 }
110
111 static char *conditional_prefix(struct pb_boot_data *ctx,
112                 const char *prefix, const char *value)
113 {
114         const char *sep;
115
116         if (!value || !*value)
117                 return NULL;
118
119         sep = NULL;
120         if (!prefix)
121                 prefix = "";
122         else if (prefix[strlen(prefix)] != '/')
123                 sep = "/";
124
125         return talloc_asprintf(ctx, "%s%s%s", prefix, sep, value);
126 }
127
128 static struct pb_boot_data *boot_editor_prepare_data(
129                 struct boot_editor *boot_editor)
130 {
131         struct pb_boot_data *bd;
132         char *s, *prefix;
133         int idx;
134
135         bd = talloc(boot_editor, struct pb_boot_data);
136
137         if (!bd)
138                 return NULL;
139
140         idx = widget_select_get_value(boot_editor->widgets.device_f);
141         if (idx == -1 || (unsigned int)idx >
142                         boot_editor->cui->sysinfo->n_blockdevs)
143                 prefix = NULL;
144         else
145                 prefix = boot_editor->cui->sysinfo->blockdevs[idx]->mountpoint;
146
147         s = widget_textbox_get_value(boot_editor->widgets.image_f);
148         bd->image = conditional_prefix(bd, prefix, s);
149
150         s = widget_textbox_get_value(boot_editor->widgets.initrd_f);
151         bd->initrd = conditional_prefix(bd, prefix, s);
152
153         s = widget_textbox_get_value(boot_editor->widgets.dtb_f);
154         bd->dtb = conditional_prefix(bd, prefix, s);
155
156         s = widget_textbox_get_value(boot_editor->widgets.args_f);
157         bd->args = *s ? talloc_strdup(bd, s) : NULL;
158
159         return bd;
160 }
161
162 /**
163  * boot_editor_process_key - Process a user keystroke.
164  *
165  * Called from the cui via the scr:process_key method.
166  */
167
168 static void boot_editor_process_key(struct nc_scr *scr, int key)
169 {
170         struct boot_editor *boot_editor = boot_editor_from_scr(scr);
171         bool handled;
172
173         handled = widgetset_process_key(boot_editor->widgetset, key);
174         if (handled) {
175                 wrefresh(boot_editor->scr.main_ncw);
176                 return;
177         }
178
179         switch (key) {
180         case 'x':
181         case 27: /* ESC */
182                 boot_editor->on_exit(boot_editor->cui, NULL, NULL);
183                 nc_flush_keys();
184         }
185 }
186
187 /**
188  * boot_editor_destructor - The talloc destructor for a boot_editor.
189  */
190
191 static int boot_editor_destructor(void *arg)
192 {
193         struct boot_editor *boot_editor = boot_editor_from_arg(arg);
194         boot_editor->scr.sig = pb_removed_sig;
195         return 0;
196 }
197
198 static void ok_click(void *arg)
199 {
200         struct boot_editor *boot_editor = arg;
201         struct pb_boot_data *bd;
202
203         bd = boot_editor_prepare_data(boot_editor);
204         boot_editor->on_exit(boot_editor->cui, boot_editor->item, bd);
205 }
206
207 static void cancel_click(void *arg)
208 {
209         struct boot_editor *boot_editor = arg;
210         boot_editor->on_exit(boot_editor->cui, NULL, NULL);
211 }
212
213 static int layout_pair(struct boot_editor *boot_editor, int y,
214                 struct nc_widget_label *label,
215                 struct nc_widget_textbox *field)
216 {
217         struct nc_widget *label_w = widget_label_base(label);
218         struct nc_widget *field_w = widget_textbox_base(field);
219         widget_move(label_w, y, boot_editor->label_x);
220         widget_move(field_w, y, boot_editor->field_x);
221         return max(widget_height(label_w), widget_height(field_w));
222 }
223
224 static void boot_editor_layout_widgets(struct boot_editor *boot_editor)
225 {
226         struct nc_widget *wf, *wl;
227         int y = 1;
228
229         wl = widget_label_base(boot_editor->widgets.device_l);
230         wf = widget_select_base(boot_editor->widgets.device_f);
231         widget_move(wl, y, boot_editor->label_x);
232         widget_move(wf, y, boot_editor->field_x);
233
234         y += widget_height(wf) + 1;
235
236
237         y += layout_pair(boot_editor, y, boot_editor->widgets.image_l,
238                                          boot_editor->widgets.image_f);
239
240         y += layout_pair(boot_editor, y, boot_editor->widgets.initrd_l,
241                                          boot_editor->widgets.initrd_f);
242
243         y += layout_pair(boot_editor, y, boot_editor->widgets.dtb_l,
244                                          boot_editor->widgets.dtb_f);
245
246         y += layout_pair(boot_editor, y, boot_editor->widgets.args_l,
247                                          boot_editor->widgets.args_f);
248
249
250         y++;
251         widget_move(widget_button_base(boot_editor->widgets.ok_b), y, 9);
252         widget_move(widget_button_base(boot_editor->widgets.cancel_b), y, 19);
253 }
254
255 static void boot_editor_device_select_change(void *arg, int idx)
256 {
257         struct boot_editor *boot_editor = arg;
258         if (idx == -1)
259                 boot_editor->selected_device = NULL;
260         else
261                 boot_editor->selected_device =
262                         boot_editor->cui->sysinfo->blockdevs[idx]->name;
263 }
264
265 static void boot_editor_populate_device_select(struct boot_editor *boot_editor,
266                 const struct system_info *sysinfo)
267 {
268         struct nc_widget_select *select = boot_editor->widgets.device_f;
269         unsigned int i;
270         bool selected;
271
272         widget_select_drop_options(select);
273
274         for (i = 0; i < sysinfo->n_blockdevs; i++) {
275                 struct blockdev_info *bd_info = sysinfo->blockdevs[i];
276                 const char *name;
277
278                 name = talloc_asprintf(boot_editor, "%s [%s]",
279                                 bd_info->name, bd_info->uuid);
280                 selected = boot_editor->selected_device &&
281                                 !strcmp(bd_info->name,
282                                                 boot_editor->selected_device);
283
284                 widget_select_add_option(boot_editor->widgets.device_f,
285                                 i, name, selected);
286         }
287
288         /* If we're editing an existing option, the paths will be fully-
289          * resolved. In this case, we want the manual device pre-selected.
290          * However, we only do this if the widget hasn't been manually
291          * changed. */
292         selected = !boot_editor->selected_device;
293
294         widget_select_add_option(boot_editor->widgets.device_f,
295                         -1, "Specify paths/URLs manually", selected);
296 }
297
298 static bool path_on_device(struct blockdev_info *bd_info,
299                 const char *path)
300 {
301         int len;
302
303         if (!bd_info->mountpoint)
304                 return false;
305
306         len = strlen(bd_info->mountpoint);
307         if (strncmp(bd_info->mountpoint, path, len))
308                 return false;
309
310         /* if the mountpoint doesn't have a trailing slash, ensure that
311          * the path starts with one (so we don't match a "/mnt/sda1/foo" path
312          * on a "/mnt/sda" mountpoint) */
313         return bd_info->mountpoint[len-1] == '/' || path[len] == '/';
314 }
315
316
317 static void boot_editor_find_device(struct boot_editor *boot_editor,
318                 struct pb_boot_data *bd, const struct system_info *sysinfo,
319                 char **image, char **initrd, char **dtb)
320 {
321         struct blockdev_info *bd_info, *tmp;
322         unsigned int i, len;
323
324         if (!sysinfo || !sysinfo->n_blockdevs)
325                 return;
326
327         /* find the device for our boot image, by finding the longest
328          * matching blockdev's mountpoint */
329         for (len = 0, i = 0, bd_info = NULL; i < sysinfo->n_blockdevs; i++) {
330                 tmp = sysinfo->blockdevs[i];
331                 if (!path_on_device(tmp, bd->image))
332                         continue;
333                 if (strlen(tmp->mountpoint) <= len)
334                         continue;
335                 bd_info = tmp;
336                 len = strlen(tmp->mountpoint);
337         }
338
339         if (!bd_info)
340                 return;
341
342         /* ensure that other paths are on this device */
343         if (bd->initrd && !path_on_device(bd_info, bd->initrd))
344                 return;
345
346         if (bd->dtb && !path_on_device(bd_info, bd->dtb))
347                 return;
348
349         /* ok, we match; preselect the device option, and remove the common
350          * prefix */
351         boot_editor->selected_device = bd_info->name;
352         *image += len;
353
354         if (*initrd)
355                 *initrd += len;
356         if (*dtb)
357                 *dtb += len;
358 }
359
360 struct boot_editor *boot_editor_init(struct cui *cui,
361                 struct pmenu_item *item,
362                 const struct system_info *sysinfo,
363                 void (*on_exit)(struct cui *cui,
364                                 struct pmenu_item *item,
365                                 struct pb_boot_data *bd))
366 {
367         char *image, *initrd, *dtb, *args;
368         struct boot_editor *boot_editor;
369         struct nc_widgetset *set;
370         int field_size;
371
372         (void)sysinfo;
373
374         boot_editor = talloc_zero(cui, struct boot_editor);
375
376         if (!boot_editor)
377                 return NULL;
378
379         talloc_set_destructor(boot_editor, boot_editor_destructor);
380         boot_editor->cui = cui;
381         boot_editor->item = item;
382         boot_editor->on_exit = on_exit;
383
384         boot_editor->label_x = 1;
385         boot_editor->field_x = 9;
386
387         nc_scr_init(&boot_editor->scr, pb_boot_editor_sig, 0,
388                         cui, boot_editor_process_key,
389                 boot_editor_post, boot_editor_unpost, boot_editor_resize);
390
391         boot_editor->scr.frame.ltitle = talloc_strdup(boot_editor,
392                         "Petitboot Option Editor");
393         boot_editor->scr.frame.rtitle = NULL;
394         boot_editor->scr.frame.help = talloc_strdup(boot_editor,
395                         "Enter=accept");
396
397         if (item) {
398                 struct pb_boot_data *bd = cod_from_item(item)->bd;
399                 image = bd->image;
400                 initrd = bd->initrd;
401                 dtb = bd->dtb;
402                 args = bd->args;
403                 boot_editor_find_device(boot_editor, bd, sysinfo,
404                                 &image, &initrd, &dtb);
405         } else {
406                 image = initrd = dtb = args = "";
407         }
408
409         field_size = COLS - 1 - boot_editor->field_x;
410
411         boot_editor->widgetset = set = widgetset_create(boot_editor,
412                         boot_editor->scr.main_ncw,
413                         boot_editor->scr.sub_ncw);
414
415         boot_editor->widgets.device_l = widget_new_label(set, 0, 0, "device:");
416         boot_editor->widgets.device_f = widget_new_select(set, 0, 0,
417                                                 field_size);
418         widget_select_on_change(boot_editor->widgets.device_f,
419                         boot_editor_device_select_change, boot_editor);
420
421         boot_editor_populate_device_select(boot_editor, sysinfo);
422
423         boot_editor->widgets.image_l = widget_new_label(set, 0, 0, "image:");
424         boot_editor->widgets.image_f = widget_new_textbox(set, 0, 0,
425                                                 field_size, image);
426
427         boot_editor->widgets.initrd_l = widget_new_label(set, 0, 0, "initrd:");
428         boot_editor->widgets.initrd_f = widget_new_textbox(set, 0, 0,
429                                                 field_size, initrd);
430
431         boot_editor->widgets.dtb_l = widget_new_label(set, 0, 0, "dtb:");
432         boot_editor->widgets.dtb_f = widget_new_textbox(set, 0, 0,
433                                                 field_size, dtb);
434
435         boot_editor->widgets.args_l = widget_new_label(set, 0, 0, "args:");
436         boot_editor->widgets.args_f = widget_new_textbox(set, 0, 0,
437                                         field_size, args);
438
439         boot_editor->widgets.ok_b = widget_new_button(set, 0, 0, 6,
440                                         "OK", ok_click, boot_editor);
441         boot_editor->widgets.cancel_b = widget_new_button(set, 0, 0, 6,
442                                         "Cancel", cancel_click, boot_editor);
443
444         boot_editor_layout_widgets(boot_editor);
445
446         return boot_editor;
447 }