Move boot to discover server
[petitboot] / ui / ncurses / nc-cui.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 #define _GNU_SOURCE
20
21 #include <assert.h>
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/ioctl.h>
26
27 #include "log/log.h"
28 #include "talloc/talloc.h"
29 #include "waiter/waiter.h"
30 #include "ui/common/discover-client.h"
31 #include "nc-cui.h"
32
33 static struct cui_opt_data *cod_from_item(struct pmenu_item *item)
34 {
35         return item->data;
36 }
37
38 /**
39  * cui_abort - Signal the main cui program loop to exit.
40  *
41  * Sets cui.abort, which causes the cui_run() routine to return.
42  */
43
44 void cui_abort(struct cui *cui)
45 {
46         pb_log("%s: exiting\n", __func__);
47         cui->abort = 1;
48 }
49
50 /**
51  * cui_resize - Signal the main cui program loop to resize
52  *
53  * Called at SIGWINCH.
54  */
55
56 void cui_resize(struct cui *cui)
57 {
58         pb_log("%s: resizing\n", __func__);
59         cui->resize = 1;
60 }
61
62 /**
63  * cui_make_item_name - Format the menu item name srting.
64  *
65  * Returns a talloc string.
66  */
67
68 static char *cui_make_item_name(struct pmenu_item *i, struct cui_opt_data *cod)
69 {
70         char *name;
71
72         assert(cod->name);
73         assert(cod->bd);
74
75         name = talloc_asprintf(i, "%s:", cod->name);
76
77         if (cod->bd->image)
78                 name = talloc_asprintf_append(name, " %s", cod->bd->image);
79
80         if (cod->bd->initrd)
81                 name = talloc_asprintf_append(name, " initrd=%s",
82                         cod->bd->initrd);
83
84         if (cod->bd->args)
85                 name = talloc_asprintf_append(name, " %s", cod->bd->args);
86
87         DBGS("@%s@\n", name);
88         return name;
89 }
90
91 /**
92  * cui_on_exit - A generic main menu ESC callback.
93  */
94
95 void cui_on_exit(struct pmenu *menu)
96 {
97         cui_abort(cui_from_pmenu(menu));
98 }
99
100 /**
101  * cui_run_cmd - A generic cb to run the supplied command.
102  */
103
104 int cui_run_cmd(struct pmenu_item *item)
105 {
106         int result;
107         struct cui *cui = cui_from_item(item);
108         const char *const *cmd_argv = item->data;
109
110         nc_scr_status_printf(cui->current, "Running %s...", cmd_argv[0]);
111
112         def_prog_mode();
113
114         result = pb_run_cmd(cmd_argv, 1, 0);
115
116         reset_prog_mode();
117         redrawwin(cui->current->main_ncw);
118
119         if (result) {
120                 pb_log("%s: failed: '%s'\n", __func__, cmd_argv[0]);
121                 nc_scr_status_printf(cui->current, "Failed: %s", cmd_argv[0]);
122         }
123
124         return result;
125 }
126
127 /**
128  * cui_boot - A generic cb to run kexec.
129  */
130
131 static int cui_boot(struct pmenu_item *item)
132 {
133         int result;
134         struct cui *cui = cui_from_item(item);
135         struct cui_opt_data *cod = cod_from_item(item);
136
137         assert(cui->current == &cui->main->scr);
138
139         pb_log("%s: %s\n", __func__, cod->name);
140         nc_scr_status_printf(cui->current, "Booting %s...", cod->name);
141
142         def_prog_mode();
143
144         result = discover_client_boot(cui->client, cod->dev, cod->opt, cod->bd);
145
146         reset_prog_mode();
147         redrawwin(cui->current->main_ncw);
148
149         if (!result) {
150                 clear();
151                 mvaddstr(1, 0, "system is going down now...");
152                 refresh();
153         } else {
154                 nc_scr_status_printf(cui->current,
155                                 "Failed: boot %s", cod->bd->image);
156         }
157
158         return 0;
159 }
160
161 /**
162  * cui_boot_editor_on_exit - The boot_editor on_exit callback.
163  */
164
165 static void cui_boot_editor_on_exit(struct boot_editor *boot_editor, enum boot_editor_result boot_editor_result,
166         struct pb_boot_data *bd)
167 {
168         struct cui *cui = cui_from_arg(boot_editor->scr.ui_ctx);
169
170         if (boot_editor_result == boot_editor_update) {
171                 struct pmenu_item *i = pmenu_find_selected(cui->main);
172                 struct cui_opt_data *cod = cod_from_item(i);
173                 char *name;
174
175                 assert(bd);
176
177                 talloc_steal(i, bd);
178                 talloc_free(cod->bd);
179                 cod->bd = bd;
180
181                 name = cui_make_item_name(i, cod);
182                 pmenu_item_replace(i, name);
183
184                 /* FIXME: need to make item visible somehow */
185                 set_current_item(cui->main->ncm, i->nci);
186
187                 pb_log("%s: updating opt '%s'\n", __func__, cod->name);
188                 pb_log(" image  '%s'\n", cod->bd->image);
189                 pb_log(" initrd '%s'\n", cod->bd->initrd);
190                 pb_log(" args   '%s'\n", cod->bd->args);
191         }
192
193         cui_set_current(cui, &cui->main->scr);
194
195         talloc_free(boot_editor);
196 }
197
198 int cui_boot_editor_run(struct pmenu_item *item)
199 {
200         struct cui *cui = cui_from_item(item);
201         struct cui_opt_data *cod = cod_from_item(item);
202         struct boot_editor *boot_editor;
203
204         boot_editor = boot_editor_init(cui, cod->bd, cui_boot_editor_on_exit);
205         cui_set_current(cui, &boot_editor->scr);
206
207         return 0;
208 }
209
210 /**
211  * cui_set_current - Set the currently active screen and redraw it.
212  */
213
214 struct nc_scr *cui_set_current(struct cui *cui, struct nc_scr *scr)
215 {
216         struct nc_scr *old;
217
218         DBGS("%p -> %p\n", cui->current, scr);
219
220         assert(cui->current != scr);
221
222         old = cui->current;
223         old->unpost(old);
224
225         cui->current = scr;
226         cui->current->post(cui->current);
227
228         return old;
229 }
230
231 /**
232  * cui_process_key - Process input on stdin.
233  */
234
235 static int cui_process_key(void *arg)
236 {
237         struct cui *cui = cui_from_arg(arg);
238
239         assert(cui->current);
240
241         ui_timer_disable(&cui->timer);
242         cui->current->process_key(cui->current);
243
244         return 0;
245 }
246
247 /**
248  * cui_process_js - Process joystick events.
249  */
250
251 static int cui_process_js(void *arg)
252 {
253         struct cui *cui = cui_from_arg(arg);
254         int c;
255
256         c = pjs_process_event(cui->pjs);
257
258         if (c) {
259                 ungetch(c);
260                 cui_process_key(arg);
261         }
262
263         return 0;
264 }
265
266 /**
267  * cui_handle_timeout - Handle the timeout.
268  */
269
270 static void cui_handle_timeout(struct ui_timer *timer)
271 {
272         struct cui *cui = cui_from_timer(timer);
273         struct pmenu_item *i = pmenu_find_selected(cui->main);
274
275 #if defined(DEBUG)
276         {
277                 struct cui_opt_data *cod = cod_from_item(i);
278                 assert(cod && (cod->opt_hash == cui->default_item));
279         }
280 #endif
281         i->on_execute(i);
282 }
283
284 /**
285  * cui_handle_resize - Handle the term resize.
286  */
287
288 static void cui_handle_resize(struct cui *cui)
289 {
290         struct winsize ws;
291
292         if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
293                 pb_log("%s: ioctl failed: %s\n", __func__, strerror(errno));
294                 return;
295         }
296
297         pb_log("%s: {%u,%u}\n", __func__, ws.ws_row, ws.ws_col);
298
299         wclear(cui->current->main_ncw);
300         resize_term(ws.ws_row, ws.ws_col);
301         cui->current->resize(cui->current);
302
303         /* For some reason this makes ncurses redraw the screen */
304         getch();
305         redrawwin(cui->current->main_ncw);
306         wrefresh(cui->current->main_ncw);
307 }
308
309 /**
310  * cui_on_open - Open new item callback.
311  */
312
313 void cui_on_open(struct pmenu *menu)
314 {
315         unsigned int insert_pt;
316         struct pmenu_item *i;
317         struct cui_opt_data *cod;
318
319         menu->scr.unpost(&menu->scr);
320
321         /* This disconnects items array from menu. */
322
323         set_menu_items(menu->ncm, NULL);
324
325         /* Insert new items at insert_pt. */
326
327         insert_pt = pmenu_grow(menu, 1);
328         i = pmenu_item_alloc(menu);
329
330         i->on_edit = cui_boot_editor_run;
331         i->on_execute = cui_boot;
332         i->data = cod = talloc_zero(i, struct cui_opt_data);
333
334         cod->name = talloc_asprintf(i, "User item %u:", insert_pt);
335         cod->bd = talloc_zero(i, struct pb_boot_data);
336
337         pmenu_item_setup(menu, i, insert_pt, talloc_strdup(i, cod->name));
338
339         /* Re-attach the items array. */
340
341         set_menu_items(menu->ncm, menu->items);
342
343         menu->scr.post(&menu->scr);
344         set_current_item(menu->ncm, i->nci);
345
346         i->on_edit(i);
347 }
348
349 /**
350  * cui_device_add - Client device_add callback.
351  *
352  * Creates menu_items for all the device boot_options and inserts those
353  * menu_items into the main menu.  Redraws the main menu if it is active.
354  */
355
356 static int cui_device_add(struct device *dev, void *arg)
357 {
358         struct cui *cui = cui_from_arg(arg);
359         int result;
360         struct boot_option *opt;
361         unsigned int o_count; /* device opts */
362         unsigned int insert_pt;
363         ITEM *selected;
364
365         pb_log("%s: %p %s\n", __func__, dev, dev->id);
366
367         selected = current_item(cui->main->ncm);
368
369         if (cui->current == &cui->main->scr)
370                 cui->current->unpost(cui->current);
371
372         /* This disconnects items array from menu. */
373
374         result = set_menu_items(cui->main->ncm, NULL);
375
376         if (result)
377                 pb_log("%s: set_menu_items failed: %d\n", __func__, result);
378
379         o_count = 0;
380         list_for_each_entry(&dev->boot_options, opt, list)
381                 o_count++;
382
383         /* Insert new items at insert_pt. */
384
385         insert_pt = pmenu_grow(cui->main, o_count);
386
387         list_for_each_entry(&dev->boot_options, opt, list) {
388                 struct pmenu_item *i;
389                 struct cui_opt_data *cod;
390                 char *name;
391
392                 /* Save the item in opt->ui_info for cui_device_remove() */
393
394                 opt->ui_info = i = pmenu_item_alloc(cui->main);
395
396                 i->on_edit = cui_boot_editor_run;
397                 i->on_execute = cui_boot;
398                 i->data = cod = talloc(i, struct cui_opt_data);
399
400                 cod->dev = dev;
401                 cod->opt = opt;
402                 cod->opt_hash = pb_opt_hash(dev, opt);
403                 cod->name = opt->name;
404                 cod->bd = talloc(i, struct pb_boot_data);
405
406                 cod->bd->image = talloc_strdup(cod->bd, opt->boot_image_file);
407                 cod->bd->initrd = talloc_strdup(cod->bd, opt->initrd_file);
408                 cod->bd->args = talloc_strdup(cod->bd, opt->boot_args);
409
410                 name = cui_make_item_name(i, cod);
411                 pmenu_item_setup(cui->main, i, insert_pt, name);
412
413                 insert_pt++;
414
415                 pb_log("%s: adding opt '%s'\n", __func__, cod->name);
416                 pb_log("   image  '%s'\n", cod->bd->image);
417                 pb_log("   initrd '%s'\n", cod->bd->initrd);
418                 pb_log("   args   '%s'\n", cod->bd->args);
419
420                 /* If this is the default_item select it and start timer. */
421
422                 if (cod->opt_hash == cui->default_item) {
423                         selected = i->nci;
424                         ui_timer_kick(&cui->timer);
425                 }
426         }
427
428         /* Re-attach the items array. */
429
430         result = set_menu_items(cui->main->ncm, cui->main->items);
431
432         if (result)
433                 pb_log("%s: set_menu_items failed: %d\n", __func__, result);
434
435         if (0) {
436                 pb_log("%s\n", __func__);
437                 pmenu_dump_items(cui->main->items,
438                         item_count(cui->main->ncm) + 1);
439         }
440
441         /* FIXME: need to make item visible somehow */
442         menu_driver(cui->main->ncm, REQ_SCR_UPAGE);
443         menu_driver(cui->main->ncm, REQ_SCR_DPAGE);
444         set_current_item(cui->main->ncm, selected);
445
446         if (cui->current == &cui->main->scr)
447                 cui->current->post(cui->current);
448
449         return 0;
450 }
451
452 /**
453  * cui_device_remove - Client device remove callback.
454  *
455  * Removes all the menu_items for the device from the main menu and redraws the
456  * main menu if it is active.
457  */
458
459 static void cui_device_remove(struct device *dev, void *arg)
460 {
461         struct cui *cui = cui_from_arg(arg);
462         int result;
463         struct boot_option *opt;
464
465         pb_log("%s: %p %s\n", __func__, dev, dev->id);
466
467         if (cui->current == &cui->main->scr)
468                 cui->current->unpost(cui->current);
469
470         /* This disconnects items array from menu. */
471
472         result = set_menu_items(cui->main->ncm, NULL);
473
474         if (result)
475                 pb_log("%s: set_menu_items failed: %d\n", __func__, result);
476
477         list_for_each_entry(&dev->boot_options, opt, list) {
478                 struct pmenu_item *i = pmenu_item_from_arg(opt->ui_info);
479                 struct cui_opt_data *cod = cod_from_item(i);
480
481                 assert(pb_protocol_device_cmp(dev, cod->dev));
482                 pmenu_remove(cui->main, i);
483
484                 /* If this is the default_item disable timer. */
485
486                 if (cod->opt_hash == cui->default_item)
487                         ui_timer_disable(&cui->timer);
488         }
489
490         /* Re-attach the items array. */
491
492         result = set_menu_items(cui->main->ncm, cui->main->items);
493
494         if (result)
495                 pb_log("%s: set_menu_items failed: %d\n", __func__, result);
496
497         if (0) {
498                 pb_log("%s\n", __func__);
499                 pmenu_dump_items(cui->main->items,
500                         item_count(cui->main->ncm) + 1);
501         }
502
503         if (cui->current == &cui->main->scr)
504                 cui->current->post(cui->current);
505 }
506
507 static struct discover_client_ops cui_client_ops = {
508         .device_add = cui_device_add,
509         .device_remove = cui_device_remove,
510 };
511
512 /**
513  * cui_init - Setup the cui instance.
514  * @platform_info: A value for the struct cui platform_info member.
515  *
516  * Returns a pointer to a struct cui on success, or NULL on error.
517  *
518  * Allocates the cui instance, sets up the client and stdin waiters, and
519  * sets up the ncurses menu screen.
520  */
521
522 struct cui *cui_init(void* platform_info,
523         int (*js_map)(const struct js_event *e), int start_deamon, int dry_run)
524 {
525         struct cui *cui;
526         unsigned int i;
527
528         cui = talloc_zero(NULL, struct cui);
529
530         if (!cui) {
531                 pb_log("%s: alloc cui failed.\n", __func__);
532                 fprintf(stderr, "%s: alloc cui failed.\n", __func__);
533                 goto fail_alloc;
534         }
535
536         cui->c_sig = pb_cui_sig;
537         cui->platform_info = platform_info;
538         cui->timer.handle_timeout = cui_handle_timeout;
539         cui->dry_run = dry_run;
540         cui->waitset = waitset_create(cui);
541
542         /* Loop here for scripts that just started the server. */
543
544 retry_start:
545         for (i = start_deamon ? 2 : 10; i; i--) {
546                 cui->client = discover_client_init(cui->waitset,
547                                 &cui_client_ops, cui);
548                 if (cui->client || !i)
549                         break;
550                 pb_log("%s: waiting for server %d\n", __func__, i);
551                 sleep(1);
552         }
553
554         if (!cui->client && start_deamon) {
555                 int result;
556
557                 start_deamon = 0;
558
559                 result = pb_start_daemon();
560
561                 if (!result)
562                         goto retry_start;
563
564                 pb_log("%s: discover_client_init failed.\n", __func__);
565                 fprintf(stderr, "%s: error: discover_client_init failed.\n",
566                         __func__);
567                 fprintf(stderr, "could not start pb-discover, the petitboot "
568                         "daemon.\n");
569                 goto fail_client_init;
570         }
571
572         if (!cui->client) {
573                 pb_log("%s: discover_client_init failed.\n", __func__);
574                 fprintf(stderr, "%s: error: discover_client_init failed.\n",
575                         __func__);
576                 fprintf(stderr, "check that pb-discover, "
577                         "the petitboot daemon is running.\n");
578                 goto fail_client_init;
579         }
580
581         atexit(nc_atexit);
582         nc_start();
583
584         waiter_register(cui->waitset, STDIN_FILENO, WAIT_IN,
585                         cui_process_key, cui);
586
587         if (js_map) {
588
589                 cui->pjs = pjs_init(cui, js_map);
590
591                 if (cui->pjs)
592                         waiter_register(cui->waitset, pjs_get_fd(cui->pjs),
593                                         WAIT_IN, cui_process_js, cui);
594         }
595
596         return cui;
597
598 fail_client_init:
599         talloc_free(cui);
600 fail_alloc:
601         return NULL;
602 }
603
604 /**
605  * cui_run - The main cui program loop.
606  * @cui: The cui instance.
607  * @main: The menu to use as the main menu.
608  *
609  * Runs the cui engine.  Does not return until indicated to do so by some
610  * user action, or an error occurs.  Frees the cui object on return.
611  * Returns 0 on success (return to shell), -1 on error (should restart).
612  */
613
614 int cui_run(struct cui *cui, struct pmenu *main, unsigned int default_item)
615 {
616         assert(main);
617
618         cui->main = main;
619         cui->current = &cui->main->scr;
620         cui->default_item = default_item;
621
622         cui->current->post(cui->current);
623
624         while (1) {
625                 int result = waiter_poll(cui->waitset);
626
627                 if (result < 0 && errno != EINTR) {
628                         pb_log("%s: poll: %s\n", __func__, strerror(errno));
629                         break;
630                 }
631
632                 if (cui->abort)
633                         break;
634
635                 ui_timer_process_sig(&cui->timer);
636
637                 while (cui->resize) {
638                         cui->resize = 0;
639                         cui_handle_resize(cui);
640                 }
641         }
642
643         nc_atexit();
644
645         return cui->abort ? 0 : -1;
646 }