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