ui/ncurses: Abstract text-screen code from sysinfo screen
[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 #if defined(HAVE_CONFIG_H)
20 #include "config.h"
21 #endif
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         enum {
37                 STATE_EDIT,
38                 STATE_CANCEL,
39                 STATE_SAVE,
40         }                       state;
41         void                    (*on_exit)(struct cui *cui,
42                                         struct pmenu_item *item,
43                                         struct pb_boot_data *bd);
44
45         int                     label_x;
46         int                     field_x;
47         int                     scroll_y;
48
49         WINDOW                  *pad;
50         struct nc_widgetset     *widgetset;
51         struct {
52                 struct nc_widget_label          *device_l;
53                 struct nc_widget_select         *device_f;
54                 struct nc_widget_label          *image_l;
55                 struct nc_widget_textbox        *image_f;
56                 struct nc_widget_label          *initrd_l;
57                 struct nc_widget_textbox        *initrd_f;
58                 struct nc_widget_label          *dtb_l;
59                 struct nc_widget_textbox        *dtb_f;
60                 struct nc_widget_label          *args_l;
61                 struct nc_widget_textbox        *args_f;
62                 struct nc_widget_button         *ok_b;
63                 struct nc_widget_button         *cancel_b;
64         } widgets;
65
66         const char              *selected_device;
67         char                    *image;
68         char                    *initrd;
69         char                    *dtb;
70         char                    *args;
71 };
72
73 static struct boot_editor *boot_editor_from_scr(struct nc_scr *scr)
74 {
75         struct boot_editor *boot_editor;
76
77         assert(scr->sig == pb_boot_editor_sig);
78         boot_editor = (struct boot_editor *)
79                 ((char *)scr - (size_t)&((struct boot_editor *)0)->scr);
80         assert(boot_editor->scr.sig == pb_boot_editor_sig);
81         return boot_editor;
82 }
83
84 static void pad_refresh(struct boot_editor *boot_editor)
85 {
86         int y, x, rows, cols;
87
88         getmaxyx(boot_editor->scr.sub_ncw, rows, cols);
89         getbegyx(boot_editor->scr.sub_ncw, y, x);
90
91         prefresh(boot_editor->pad, boot_editor->scroll_y, 0,
92                         y, x, rows, cols);
93 }
94
95 static struct boot_editor *boot_editor_from_arg(void *arg)
96 {
97         struct boot_editor *boot_editor = arg;
98
99         assert(boot_editor->scr.sig == pb_boot_editor_sig);
100         return boot_editor;
101 }
102
103 static int boot_editor_post(struct nc_scr *scr)
104 {
105         struct boot_editor *boot_editor = boot_editor_from_scr(scr);
106
107         widgetset_post(boot_editor->widgetset);
108         nc_scr_frame_draw(scr);
109         pad_refresh(boot_editor);
110         return 0;
111 }
112
113 static int boot_editor_unpost(struct nc_scr *scr)
114 {
115         widgetset_unpost(boot_editor_from_scr(scr)->widgetset);
116         return 0;
117 }
118
119 struct nc_scr *boot_editor_scr(struct boot_editor *boot_editor)
120 {
121         return &boot_editor->scr;
122 }
123
124 static void boot_editor_resize(struct nc_scr *scr)
125 {
126         /* FIXME: forms can't be resized, need to recreate here */
127         boot_editor_unpost(scr);
128         boot_editor_post(scr);
129 }
130
131 static char *conditional_prefix(struct pb_boot_data *ctx,
132                 const char *prefix, const char *value)
133 {
134         const char *sep;
135
136         if (!value || !*value)
137                 return NULL;
138
139         sep = "";
140         if (!prefix)
141                 prefix = "";
142         else if (prefix[strlen(prefix)] != '/')
143                 sep = "/";
144
145         return talloc_asprintf(ctx, "%s%s%s", prefix, sep, value);
146 }
147
148 static struct pb_boot_data *boot_editor_prepare_data(
149                 struct boot_editor *boot_editor)
150 {
151         struct pb_boot_data *bd;
152         char *s, *prefix;
153         int idx;
154
155         bd = talloc(boot_editor, struct pb_boot_data);
156
157         if (!bd)
158                 return NULL;
159
160         idx = widget_select_get_value(boot_editor->widgets.device_f);
161         if (idx == -1 || (unsigned int)idx >
162                         boot_editor->cui->sysinfo->n_blockdevs)
163                 prefix = NULL;
164         else
165                 prefix = boot_editor->cui->sysinfo->blockdevs[idx]->mountpoint;
166
167         s = widget_textbox_get_value(boot_editor->widgets.image_f);
168         bd->image = conditional_prefix(bd, prefix, s);
169
170         s = widget_textbox_get_value(boot_editor->widgets.initrd_f);
171         bd->initrd = conditional_prefix(bd, prefix, s);
172
173         s = widget_textbox_get_value(boot_editor->widgets.dtb_f);
174         bd->dtb = conditional_prefix(bd, prefix, s);
175
176         s = widget_textbox_get_value(boot_editor->widgets.args_f);
177         bd->args = *s ? talloc_strdup(bd, s) : NULL;
178
179         return bd;
180 }
181
182 /**
183  * boot_editor_process_key - Process a user keystroke.
184  *
185  * Called from the cui via the scr:process_key method.
186  */
187
188 static void boot_editor_process_key(struct nc_scr *scr, int key)
189 {
190         struct boot_editor *boot_editor = boot_editor_from_scr(scr);
191         struct pmenu_item *item;
192         struct pb_boot_data *bd;
193         bool handled;
194
195         handled = widgetset_process_key(boot_editor->widgetset, key);
196         if (handled)
197                 pad_refresh(boot_editor);
198
199         else if (key == 'x' || key == 27)
200                 boot_editor->state = STATE_CANCEL;
201
202         item = NULL;
203         bd = NULL;
204
205         switch (boot_editor->state) {
206         case STATE_SAVE:
207                 item = boot_editor->item;
208                 bd = boot_editor_prepare_data(boot_editor);
209                 /* fall through */
210         case STATE_CANCEL:
211                 boot_editor->on_exit(boot_editor->cui, item, bd);
212                 break;
213         default:
214                 break;
215         }
216 }
217
218 /**
219  * boot_editor_destructor - The talloc destructor for a boot_editor.
220  */
221
222 static int boot_editor_destructor(void *arg)
223 {
224         struct boot_editor *boot_editor = boot_editor_from_arg(arg);
225         boot_editor->scr.sig = pb_removed_sig;
226         if (boot_editor->pad)
227                 delwin(boot_editor->pad);
228         return 0;
229 }
230
231 static void ok_click(void *arg)
232 {
233         struct boot_editor *boot_editor = arg;
234         boot_editor->state = STATE_SAVE;
235 }
236
237 static void cancel_click(void *arg)
238 {
239         struct boot_editor *boot_editor = arg;
240         boot_editor->state = STATE_CANCEL;
241 }
242
243 static int layout_pair(struct boot_editor *boot_editor, int y,
244                 struct nc_widget_label *label,
245                 struct nc_widget_textbox *field)
246 {
247         struct nc_widget *label_w = widget_label_base(label);
248         struct nc_widget *field_w = widget_textbox_base(field);
249         widget_move(label_w, y, boot_editor->label_x);
250         widget_move(field_w, y, boot_editor->field_x);
251         return max(widget_height(label_w), widget_height(field_w));
252 }
253
254 static int pad_height(int blockdevs_height)
255 {
256         return 10 + blockdevs_height;
257 }
258
259 static void boot_editor_layout_widgets(struct boot_editor *boot_editor)
260 {
261         struct nc_widget *wf, *wl;
262         int y = 1;
263
264         wl = widget_label_base(boot_editor->widgets.device_l);
265         wf = widget_select_base(boot_editor->widgets.device_f);
266         widget_move(wl, y, boot_editor->label_x);
267         widget_move(wf, y, boot_editor->field_x);
268
269         y += widget_height(wf) + 1;
270
271
272         y += layout_pair(boot_editor, y, boot_editor->widgets.image_l,
273                                          boot_editor->widgets.image_f);
274
275         y += layout_pair(boot_editor, y, boot_editor->widgets.initrd_l,
276                                          boot_editor->widgets.initrd_f);
277
278         y += layout_pair(boot_editor, y, boot_editor->widgets.dtb_l,
279                                          boot_editor->widgets.dtb_f);
280
281         y += layout_pair(boot_editor, y, boot_editor->widgets.args_l,
282                                          boot_editor->widgets.args_f);
283
284
285         y++;
286         widget_move(widget_button_base(boot_editor->widgets.ok_b), y, 9);
287         widget_move(widget_button_base(boot_editor->widgets.cancel_b), y, 19);
288 }
289
290 static void boot_editor_widget_focus(struct nc_widget *widget, void *arg)
291 {
292         struct boot_editor *boot_editor = arg;
293         int w_y, s_max;
294
295         w_y = widget_y(widget) + widget_focus_y(widget);
296         s_max = getmaxy(boot_editor->scr.sub_ncw) - 1;
297
298         if (w_y < boot_editor->scroll_y)
299                 boot_editor->scroll_y = w_y;
300
301         else if (w_y + boot_editor->scroll_y + 1 > s_max)
302                 boot_editor->scroll_y = 1 + w_y - s_max;
303
304         else
305                 return;
306
307         pad_refresh(boot_editor);
308 }
309
310 static void boot_editor_device_select_change(void *arg, int idx)
311 {
312         struct boot_editor *boot_editor = arg;
313         if (idx == -1)
314                 boot_editor->selected_device = NULL;
315         else
316                 boot_editor->selected_device =
317                         boot_editor->cui->sysinfo->blockdevs[idx]->name;
318 }
319
320 static void boot_editor_populate_device_select(struct boot_editor *boot_editor,
321                 const struct system_info *sysinfo)
322 {
323         struct nc_widget_select *select = boot_editor->widgets.device_f;
324         unsigned int i;
325         bool selected;
326
327         widget_select_drop_options(select);
328
329         for (i = 0; sysinfo && i < sysinfo->n_blockdevs; i++) {
330                 struct blockdev_info *bd_info = sysinfo->blockdevs[i];
331                 const char *name;
332
333                 name = talloc_asprintf(boot_editor, "%s [%s]",
334                                 bd_info->name, bd_info->uuid);
335                 selected = boot_editor->selected_device &&
336                                 !strcmp(bd_info->name,
337                                                 boot_editor->selected_device);
338
339                 widget_select_add_option(select, i, name, selected);
340         }
341
342         /* If we're editing an existing option, the paths will be fully-
343          * resolved. In this case, we want the manual device pre-selected.
344          * However, we only do this if the widget hasn't been manually
345          * changed. */
346         selected = !boot_editor->selected_device;
347
348         widget_select_add_option(select, -1, "Specify paths/URLs manually",
349                         selected);
350 }
351
352 static bool path_on_device(struct blockdev_info *bd_info,
353                 const char *path)
354 {
355         int len;
356
357         if (!bd_info->mountpoint)
358                 return false;
359
360         len = strlen(bd_info->mountpoint);
361         if (strncmp(bd_info->mountpoint, path, len))
362                 return false;
363
364         /* if the mountpoint doesn't have a trailing slash, ensure that
365          * the path starts with one (so we don't match a "/mnt/sda1/foo" path
366          * on a "/mnt/sda" mountpoint) */
367         return bd_info->mountpoint[len-1] == '/' || path[len] == '/';
368 }
369
370
371 static void boot_editor_find_device(struct boot_editor *boot_editor,
372                 struct pb_boot_data *bd, const struct system_info *sysinfo)
373 {
374         struct blockdev_info *bd_info, *tmp;
375         unsigned int i, len;
376
377         if (!sysinfo || !sysinfo->n_blockdevs)
378                 return;
379
380         /* find the device for our boot image, by finding the longest
381          * matching blockdev's mountpoint */
382         for (len = 0, i = 0, bd_info = NULL; i < sysinfo->n_blockdevs; i++) {
383                 tmp = sysinfo->blockdevs[i];
384                 if (!path_on_device(tmp, bd->image))
385                         continue;
386                 if (strlen(tmp->mountpoint) <= len)
387                         continue;
388                 bd_info = tmp;
389                 len = strlen(tmp->mountpoint);
390         }
391
392         if (!bd_info)
393                 return;
394
395         /* ensure that other paths are on this device */
396         if (bd->initrd && !path_on_device(bd_info, bd->initrd))
397                 return;
398
399         if (bd->dtb && !path_on_device(bd_info, bd->dtb))
400                 return;
401
402         /* ok, we match; preselect the device option, and remove the common
403          * prefix */
404         boot_editor->selected_device = bd_info->name;
405         boot_editor->image += len;
406
407         if (boot_editor->initrd)
408                 boot_editor->initrd += len;
409         if (boot_editor->dtb)
410                 boot_editor->dtb += len;
411 }
412
413 static void boot_editor_setup_widgets(struct boot_editor *boot_editor,
414                 const struct system_info *sysinfo)
415 {
416         struct nc_widgetset *set;
417         int field_size;
418
419         field_size = COLS - 1 - boot_editor->field_x;
420
421         boot_editor->widgetset = set = widgetset_create(boot_editor,
422                         boot_editor->scr.main_ncw,
423                         boot_editor->pad);
424
425         widgetset_set_widget_focus(boot_editor->widgetset,
426                         boot_editor_widget_focus, boot_editor);
427
428         boot_editor->widgets.device_l = widget_new_label(set, 0, 0, "Device:");
429         boot_editor->widgets.device_f = widget_new_select(set, 0, 0,
430                                                 field_size);
431         widget_select_on_change(boot_editor->widgets.device_f,
432                         boot_editor_device_select_change, boot_editor);
433
434         boot_editor_populate_device_select(boot_editor, sysinfo);
435
436         boot_editor->widgets.image_l = widget_new_label(set, 0, 0,
437                         "Kernel:");
438         boot_editor->widgets.image_f = widget_new_textbox(set, 0, 0,
439                                                 field_size, boot_editor->image);
440
441         boot_editor->widgets.initrd_l = widget_new_label(set, 0, 0,
442                         "Initrd:");
443         boot_editor->widgets.initrd_f = widget_new_textbox(set, 0, 0,
444                                                 field_size,
445                                                 boot_editor->initrd);
446
447         boot_editor->widgets.dtb_l = widget_new_label(set, 0, 0,
448                         "Device tree:");
449         boot_editor->widgets.dtb_f = widget_new_textbox(set, 0, 0,
450                                                 field_size, boot_editor->dtb);
451
452         boot_editor->widgets.args_l = widget_new_label(set, 0, 0,
453                         "Boot arguments:");
454         boot_editor->widgets.args_f = widget_new_textbox(set, 0, 0,
455                                         field_size, boot_editor->args);
456
457         boot_editor->widgets.ok_b = widget_new_button(set, 0, 0, 6,
458                                         "OK", ok_click, boot_editor);
459         boot_editor->widgets.cancel_b = widget_new_button(set, 0, 0, 6,
460                                         "Cancel", cancel_click, boot_editor);
461 }
462
463 void boot_editor_update(struct boot_editor *boot_editor,
464                 const struct system_info *sysinfo)
465 {
466         int height;
467
468         widgetset_unpost(boot_editor->widgetset);
469
470         height = pad_height(sysinfo ? sysinfo->n_blockdevs : 0);
471         if (getmaxy(boot_editor->pad) < height) {
472                 delwin(boot_editor->pad);
473                 boot_editor->pad = newpad(height, COLS);
474                 widgetset_set_windows(boot_editor->widgetset,
475                                 boot_editor->scr.main_ncw,
476                                 boot_editor->pad);
477         }
478
479         boot_editor_populate_device_select(boot_editor, sysinfo);
480
481         boot_editor_layout_widgets(boot_editor);
482
483         widgetset_post(boot_editor->widgetset);
484
485         pad_refresh(boot_editor);
486 }
487
488 struct boot_editor *boot_editor_init(struct cui *cui,
489                 struct pmenu_item *item,
490                 const struct system_info *sysinfo,
491                 void (*on_exit)(struct cui *cui,
492                                 struct pmenu_item *item,
493                                 struct pb_boot_data *bd))
494 {
495         struct boot_editor *boot_editor;
496
497         boot_editor = talloc_zero(cui, struct boot_editor);
498
499         if (!boot_editor)
500                 return NULL;
501
502         talloc_set_destructor(boot_editor, boot_editor_destructor);
503         boot_editor->cui = cui;
504         boot_editor->item = item;
505         boot_editor->on_exit = on_exit;
506         boot_editor->state = STATE_EDIT;
507
508         boot_editor->label_x = 1;
509         boot_editor->field_x = 17;
510
511         nc_scr_init(&boot_editor->scr, pb_boot_editor_sig, 0,
512                         cui, boot_editor_process_key,
513                 boot_editor_post, boot_editor_unpost, boot_editor_resize);
514
515         boot_editor->scr.frame.ltitle = talloc_strdup(boot_editor,
516                         "Petitboot Option Editor");
517         boot_editor->scr.frame.rtitle = NULL;
518         boot_editor->scr.frame.help = talloc_strdup(boot_editor,
519                         "Enter=accept");
520         nc_scr_frame_draw(&boot_editor->scr);
521
522         if (item) {
523                 struct pb_boot_data *bd = cod_from_item(item)->bd;
524                 boot_editor->image = bd->image;
525                 boot_editor->initrd = bd->initrd;
526                 boot_editor->dtb = bd->dtb;
527                 boot_editor->args = bd->args;
528                 boot_editor_find_device(boot_editor, bd, sysinfo);
529         } else {
530                 boot_editor->image = boot_editor->initrd =
531                         boot_editor->dtb = boot_editor->args = "";
532         }
533
534         boot_editor->pad = newpad(
535                                 pad_height(sysinfo ? sysinfo->n_blockdevs : 0),
536                                 COLS);
537
538         boot_editor_setup_widgets(boot_editor, sysinfo);
539
540         boot_editor_layout_widgets(boot_editor);
541         wrefresh(boot_editor->scr.main_ncw);
542
543         return boot_editor;
544 }