~ubuntu-branches/ubuntu/trusty/grub2/trusty-updates

« back to all changes in this revision

Viewing changes to grub-core/normal/menu.c

Tags: upstream-1.99~20101122
ImportĀ upstreamĀ versionĀ 1.99~20101122

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* menu.c - General supporting functionality for menus.  */
 
2
/*
 
3
 *  GRUB  --  GRand Unified Bootloader
 
4
 *  Copyright (C) 2003,2004,2005,2006,2007,2008,2009,2010  Free Software Foundation, Inc.
 
5
 *
 
6
 *  GRUB is free software: you can redistribute it and/or modify
 
7
 *  it under the terms of the GNU General Public License as published by
 
8
 *  the Free Software Foundation, either version 3 of the License, or
 
9
 *  (at your option) any later version.
 
10
 *
 
11
 *  GRUB is distributed in the hope that it will be useful,
 
12
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
 *  GNU General Public License for more details.
 
15
 *
 
16
 *  You should have received a copy of the GNU General Public License
 
17
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 
18
 */
 
19
 
 
20
#include <grub/normal.h>
 
21
#include <grub/misc.h>
 
22
#include <grub/loader.h>
 
23
#include <grub/mm.h>
 
24
#include <grub/time.h>
 
25
#include <grub/env.h>
 
26
#include <grub/menu_viewer.h>
 
27
#include <grub/command.h>
 
28
#include <grub/parser.h>
 
29
#include <grub/auth.h>
 
30
#include <grub/i18n.h>
 
31
#include <grub/term.h>
 
32
#include <grub/script_sh.h>
 
33
 
 
34
/* Time to delay after displaying an error message about a default/fallback
 
35
   entry failing to boot.  */
 
36
#define DEFAULT_ENTRY_ERROR_DELAY_MS  2500
 
37
 
 
38
grub_err_t (*grub_gfxmenu_try_hook) (int entry, grub_menu_t menu,
 
39
                                     int nested) = NULL;
 
40
 
 
41
/* Wait until the user pushes any key so that the user
 
42
   can see what happened.  */
 
43
void
 
44
grub_wait_after_message (void)
 
45
{
 
46
  grub_uint64_t endtime;
 
47
  grub_xputs ("\n");
 
48
  grub_printf_ (N_("Press any key to continue..."));
 
49
  grub_refresh ();
 
50
 
 
51
  endtime = grub_get_time_ms () + 10000;
 
52
 
 
53
  while (grub_get_time_ms () < endtime)
 
54
    if (grub_checkkey () >= 0)
 
55
      {
 
56
        grub_getkey ();
 
57
        break;
 
58
      }
 
59
 
 
60
  grub_xputs ("\n");
 
61
}
 
62
 
 
63
/* Get a menu entry by its index in the entry list.  */
 
64
grub_menu_entry_t
 
65
grub_menu_get_entry (grub_menu_t menu, int no)
 
66
{
 
67
  grub_menu_entry_t e;
 
68
 
 
69
  for (e = menu->entry_list; e && no > 0; e = e->next, no--)
 
70
    ;
 
71
 
 
72
  return e;
 
73
}
 
74
 
 
75
/* Return the current timeout. If the variable "timeout" is not set or
 
76
   invalid, return -1.  */
 
77
int
 
78
grub_menu_get_timeout (void)
 
79
{
 
80
  char *val;
 
81
  int timeout;
 
82
 
 
83
  val = grub_env_get ("timeout");
 
84
  if (! val)
 
85
    return -1;
 
86
 
 
87
  grub_error_push ();
 
88
 
 
89
  timeout = (int) grub_strtoul (val, 0, 0);
 
90
 
 
91
  /* If the value is invalid, unset the variable.  */
 
92
  if (grub_errno != GRUB_ERR_NONE)
 
93
    {
 
94
      grub_env_unset ("timeout");
 
95
      grub_errno = GRUB_ERR_NONE;
 
96
      timeout = -1;
 
97
    }
 
98
 
 
99
  grub_error_pop ();
 
100
 
 
101
  return timeout;
 
102
}
 
103
 
 
104
/* Set current timeout in the variable "timeout".  */
 
105
void
 
106
grub_menu_set_timeout (int timeout)
 
107
{
 
108
  /* Ignore TIMEOUT if it is zero, because it will be unset really soon.  */
 
109
  if (timeout > 0)
 
110
    {
 
111
      char buf[16];
 
112
 
 
113
      grub_snprintf (buf, sizeof (buf), "%d", timeout);
 
114
      grub_env_set ("timeout", buf);
 
115
    }
 
116
}
 
117
 
 
118
/* Get the first entry number from the value of the environment variable NAME,
 
119
   which is a space-separated list of non-negative integers.  The entry number
 
120
   which is returned is stripped from the value of NAME.  If no entry number
 
121
   can be found, -1 is returned.  */
 
122
static int
 
123
get_and_remove_first_entry_number (const char *name)
 
124
{
 
125
  char *val;
 
126
  char *tail;
 
127
  int entry;
 
128
 
 
129
  val = grub_env_get (name);
 
130
  if (! val)
 
131
    return -1;
 
132
 
 
133
  grub_error_push ();
 
134
 
 
135
  entry = (int) grub_strtoul (val, &tail, 0);
 
136
 
 
137
  if (grub_errno == GRUB_ERR_NONE)
 
138
    {
 
139
      /* Skip whitespace to find the next digit.  */
 
140
      while (*tail && grub_isspace (*tail))
 
141
        tail++;
 
142
      grub_env_set (name, tail);
 
143
    }
 
144
  else
 
145
    {
 
146
      grub_env_unset (name);
 
147
      grub_errno = GRUB_ERR_NONE;
 
148
      entry = -1;
 
149
    }
 
150
 
 
151
  grub_error_pop ();
 
152
 
 
153
  return entry;
 
154
}
 
155
 
 
156
/* Run a menu entry.  */
 
157
void
 
158
grub_menu_execute_entry(grub_menu_entry_t entry)
 
159
{
 
160
  grub_err_t err = GRUB_ERR_NONE;
 
161
  int errs_before;
 
162
  grub_menu_t menu;
 
163
 
 
164
  if (entry->restricted)
 
165
    err = grub_auth_check_authentication (entry->users);
 
166
 
 
167
  if (err)
 
168
    {
 
169
      grub_print_error ();
 
170
      grub_errno = GRUB_ERR_NONE;
 
171
      return;
 
172
    }
 
173
 
 
174
  errs_before = grub_err_printed_errors;
 
175
 
 
176
  if (entry->submenu)
 
177
    {
 
178
      grub_env_context_open ();
 
179
      menu = grub_zalloc (sizeof (*menu));
 
180
      if (! menu)
 
181
        return;
 
182
      grub_env_set_menu (menu);
 
183
    }
 
184
 
 
185
  grub_env_set ("chosen", entry->title);
 
186
  grub_script_execute_sourcecode (entry->sourcecode, entry->argc, entry->args);
 
187
 
 
188
  if (errs_before != grub_err_printed_errors)
 
189
    grub_wait_after_message ();
 
190
 
 
191
  if (grub_errno == GRUB_ERR_NONE && grub_loader_is_loaded ())
 
192
    /* Implicit execution of boot, only if something is loaded.  */
 
193
    grub_command_execute ("boot", 0, 0);
 
194
 
 
195
  if (entry->submenu)
 
196
    {
 
197
      if (menu && menu->size)
 
198
        {
 
199
          grub_show_menu (menu, 1);
 
200
          grub_normal_free_menu (menu);
 
201
        }
 
202
      grub_env_context_close ();
 
203
    }
 
204
}
 
205
 
 
206
/* Execute ENTRY from the menu MENU, falling back to entries specified
 
207
   in the environment variable "fallback" if it fails.  CALLBACK is a
 
208
   pointer to a struct of function pointers which are used to allow the
 
209
   caller provide feedback to the user.  */
 
210
void
 
211
grub_menu_execute_with_fallback (grub_menu_t menu,
 
212
                                 grub_menu_entry_t entry,
 
213
                                 grub_menu_execute_callback_t callback,
 
214
                                 void *callback_data)
 
215
{
 
216
  int fallback_entry;
 
217
 
 
218
  callback->notify_booting (entry, callback_data);
 
219
 
 
220
  grub_menu_execute_entry (entry);
 
221
 
 
222
  /* Deal with fallback entries.  */
 
223
  while ((fallback_entry = get_and_remove_first_entry_number ("fallback"))
 
224
         >= 0)
 
225
    {
 
226
      grub_print_error ();
 
227
      grub_errno = GRUB_ERR_NONE;
 
228
 
 
229
      entry = grub_menu_get_entry (menu, fallback_entry);
 
230
      callback->notify_fallback (entry, callback_data);
 
231
      grub_menu_execute_entry (entry);
 
232
      /* If the function call to execute the entry returns at all, then this is
 
233
         taken to indicate a boot failure.  For menu entries that do something
 
234
         other than actually boot an operating system, this could assume
 
235
         incorrectly that something failed.  */
 
236
    }
 
237
 
 
238
  callback->notify_failure (callback_data);
 
239
}
 
240
 
 
241
static struct grub_menu_viewer *viewers;
 
242
 
 
243
static void
 
244
menu_set_chosen_entry (int entry)
 
245
{
 
246
  struct grub_menu_viewer *cur;
 
247
  for (cur = viewers; cur; cur = cur->next)
 
248
    cur->set_chosen_entry (entry, cur->data);
 
249
}
 
250
 
 
251
static void
 
252
menu_print_timeout (int timeout)
 
253
{
 
254
  struct grub_menu_viewer *cur;
 
255
  for (cur = viewers; cur; cur = cur->next)
 
256
    cur->print_timeout (timeout, cur->data);
 
257
}
 
258
 
 
259
static void
 
260
menu_fini (void)
 
261
{
 
262
  struct grub_menu_viewer *cur, *next;
 
263
  for (cur = viewers; cur; cur = next)
 
264
    {
 
265
      next = cur->next;
 
266
      cur->fini (cur->data);
 
267
      grub_free (cur);
 
268
    }
 
269
  viewers = NULL;
 
270
}
 
271
 
 
272
static void
 
273
menu_init (int entry, grub_menu_t menu, int nested)
 
274
{
 
275
  struct grub_term_output *term;
 
276
 
 
277
  FOR_ACTIVE_TERM_OUTPUTS(term)
 
278
  {
 
279
    grub_err_t err;
 
280
 
 
281
    if (grub_gfxmenu_try_hook && grub_strcmp (term->name, "gfxterm") == 0)
 
282
      {
 
283
        err = grub_gfxmenu_try_hook (entry, menu, nested);
 
284
        if(!err)
 
285
          continue;
 
286
        grub_errno = GRUB_ERR_NONE;
 
287
      }
 
288
 
 
289
    err = grub_menu_try_text (term, entry, menu, nested);
 
290
    if(!err)
 
291
      continue;
 
292
    grub_print_error ();
 
293
    grub_errno = GRUB_ERR_NONE;
 
294
  }
 
295
}
 
296
 
 
297
static void
 
298
clear_timeout (void)
 
299
{
 
300
  struct grub_menu_viewer *cur;
 
301
  for (cur = viewers; cur; cur = cur->next)
 
302
    cur->clear_timeout (cur->data);
 
303
}
 
304
 
 
305
void
 
306
grub_menu_register_viewer (struct grub_menu_viewer *viewer)
 
307
{
 
308
  viewer->next = viewers;
 
309
  viewers = viewer;
 
310
}
 
311
 
 
312
/* Get the entry number from the variable NAME.  */
 
313
static int
 
314
get_entry_number (grub_menu_t menu, const char *name)
 
315
{
 
316
  char *val;
 
317
  int entry;
 
318
 
 
319
  val = grub_env_get (name);
 
320
  if (! val)
 
321
    return -1;
 
322
 
 
323
  grub_error_push ();
 
324
 
 
325
  entry = (int) grub_strtoul (val, 0, 0);
 
326
 
 
327
  if (grub_errno == GRUB_ERR_BAD_NUMBER)
 
328
    {
 
329
      /* See if the variable matches the title of a menu entry.  */
 
330
      grub_menu_entry_t e = menu->entry_list;
 
331
      int i;
 
332
 
 
333
      grub_errno = GRUB_ERR_NONE;
 
334
 
 
335
      for (i = 0; e; i++)
 
336
        {
 
337
          if (grub_strcmp (e->title, val) == 0)
 
338
            {
 
339
              entry = i;
 
340
              break;
 
341
            }
 
342
          e = e->next;
 
343
        }
 
344
 
 
345
      if (! e)
 
346
        entry = -1;
 
347
    }
 
348
 
 
349
  if (grub_errno != GRUB_ERR_NONE)
 
350
    {
 
351
      grub_errno = GRUB_ERR_NONE;
 
352
      entry = -1;
 
353
    }
 
354
 
 
355
  grub_error_pop ();
 
356
 
 
357
  return entry;
 
358
}
 
359
 
 
360
#define GRUB_MENU_PAGE_SIZE 10
 
361
 
 
362
/* Show the menu and handle menu entry selection.  Returns the menu entry
 
363
   index that should be executed or -1 if no entry should be executed (e.g.,
 
364
   Esc pressed to exit a sub-menu or switching menu viewers).
 
365
   If the return value is not -1, then *AUTO_BOOT is nonzero iff the menu
 
366
   entry to be executed is a result of an automatic default selection because
 
367
   of the timeout.  */
 
368
static int
 
369
run_menu (grub_menu_t menu, int nested, int *auto_boot)
 
370
{
 
371
  grub_uint64_t saved_time;
 
372
  int default_entry, current_entry;
 
373
  int timeout;
 
374
 
 
375
  default_entry = get_entry_number (menu, "default");
 
376
 
 
377
  /* If DEFAULT_ENTRY is not within the menu entries, fall back to
 
378
     the first entry.  */
 
379
  if (default_entry < 0 || default_entry >= menu->size)
 
380
    default_entry = 0;
 
381
 
 
382
  /* If timeout is 0, drawing is pointless (and ugly).  */
 
383
  if (grub_menu_get_timeout () == 0)
 
384
    {
 
385
      *auto_boot = 1;
 
386
      return default_entry;
 
387
    }
 
388
 
 
389
  current_entry = default_entry;
 
390
 
 
391
  /* Initialize the time.  */
 
392
  saved_time = grub_get_time_ms ();
 
393
 
 
394
 refresh:
 
395
  menu_init (current_entry, menu, nested);
 
396
 
 
397
  timeout = grub_menu_get_timeout ();
 
398
 
 
399
  if (timeout > 0)
 
400
    menu_print_timeout (timeout);
 
401
  else
 
402
    clear_timeout ();
 
403
 
 
404
  while (1)
 
405
    {
 
406
      int c;
 
407
      timeout = grub_menu_get_timeout ();
 
408
 
 
409
      if (grub_normal_exit_level)
 
410
        return -1;
 
411
 
 
412
      if (timeout > 0)
 
413
        {
 
414
          grub_uint64_t current_time;
 
415
 
 
416
          current_time = grub_get_time_ms ();
 
417
          if (current_time - saved_time >= 1000)
 
418
            {
 
419
              timeout--;
 
420
              grub_menu_set_timeout (timeout);
 
421
              saved_time = current_time;
 
422
              menu_print_timeout (timeout);
 
423
            }
 
424
        }
 
425
 
 
426
      if (timeout == 0)
 
427
        {
 
428
          grub_env_unset ("timeout");
 
429
          *auto_boot = 1;
 
430
          menu_fini ();
 
431
          return default_entry;
 
432
        }
 
433
 
 
434
      if (grub_checkkey () >= 0 || timeout < 0)
 
435
        {
 
436
          c = grub_getkey ();
 
437
 
 
438
          if (timeout >= 0)
 
439
            {
 
440
              grub_env_unset ("timeout");
 
441
              grub_env_unset ("fallback");
 
442
              clear_timeout ();
 
443
            }
 
444
 
 
445
          switch (c)
 
446
            {
 
447
            case GRUB_TERM_KEY_HOME:
 
448
            case GRUB_TERM_CTRL | 'a':
 
449
              current_entry = 0;
 
450
              menu_set_chosen_entry (current_entry);
 
451
              break;
 
452
 
 
453
            case GRUB_TERM_KEY_END:
 
454
            case GRUB_TERM_CTRL | 'e':
 
455
              current_entry = menu->size - 1;
 
456
              menu_set_chosen_entry (current_entry);
 
457
              break;
 
458
 
 
459
            case GRUB_TERM_KEY_UP:
 
460
            case GRUB_TERM_CTRL | 'p':
 
461
            case '^':
 
462
              if (current_entry > 0)
 
463
                current_entry--;
 
464
              menu_set_chosen_entry (current_entry);
 
465
              break;
 
466
 
 
467
            case GRUB_TERM_CTRL | 'n':
 
468
            case GRUB_TERM_KEY_DOWN:
 
469
            case 'v':
 
470
              if (current_entry < menu->size - 1)
 
471
                current_entry++;
 
472
              menu_set_chosen_entry (current_entry);
 
473
              break;
 
474
 
 
475
            case GRUB_TERM_CTRL | 'g':
 
476
            case GRUB_TERM_KEY_PPAGE:
 
477
              if (current_entry < GRUB_MENU_PAGE_SIZE)
 
478
                current_entry = 0;
 
479
              else
 
480
                current_entry -= GRUB_MENU_PAGE_SIZE;
 
481
              menu_set_chosen_entry (current_entry);
 
482
              break;
 
483
 
 
484
            case GRUB_TERM_CTRL | 'c':
 
485
            case GRUB_TERM_KEY_NPAGE:
 
486
              if (current_entry + GRUB_MENU_PAGE_SIZE < menu->size)
 
487
                current_entry += GRUB_MENU_PAGE_SIZE;
 
488
              else
 
489
                current_entry = menu->size - 1;
 
490
              menu_set_chosen_entry (current_entry);
 
491
              break;
 
492
 
 
493
            case '\n':
 
494
            case '\r':
 
495
            case GRUB_TERM_KEY_RIGHT:
 
496
            case GRUB_TERM_CTRL | 'f':
 
497
              menu_fini ();
 
498
              *auto_boot = 0;
 
499
              return current_entry;
 
500
 
 
501
            case '\e':
 
502
              if (nested)
 
503
                {
 
504
                  menu_fini ();
 
505
                  return -1;
 
506
                }
 
507
              break;
 
508
 
 
509
            case 'c':
 
510
              menu_fini ();
 
511
              grub_cmdline_run (1);
 
512
              goto refresh;
 
513
 
 
514
            case 'e':
 
515
              menu_fini ();
 
516
                {
 
517
                  grub_menu_entry_t e = grub_menu_get_entry (menu, current_entry);
 
518
                  if (e)
 
519
                    grub_menu_entry_run (e);
 
520
                }
 
521
              goto refresh;
 
522
 
 
523
            default:
 
524
              {
 
525
                grub_menu_entry_t entry;
 
526
                int i;
 
527
                for (i = 0, entry = menu->entry_list; i < menu->size;
 
528
                     i++, entry = entry->next)
 
529
                  if (entry->hotkey == c)
 
530
                    {
 
531
                      menu_fini ();
 
532
                      *auto_boot = 0;
 
533
                      return i;
 
534
                    }
 
535
              }
 
536
              break;
 
537
            }
 
538
        }
 
539
    }
 
540
 
 
541
  /* Never reach here.  */
 
542
  return -1;
 
543
}
 
544
 
 
545
/* Callback invoked immediately before a menu entry is executed.  */
 
546
static void
 
547
notify_booting (grub_menu_entry_t entry,
 
548
                void *userdata __attribute__((unused)))
 
549
{
 
550
  grub_printf ("  ");
 
551
  grub_printf_ (N_("Booting \'%s\'"), entry->title);
 
552
  grub_printf ("\n\n");
 
553
}
 
554
 
 
555
/* Callback invoked when a default menu entry executed because of a timeout
 
556
   has failed and an attempt will be made to execute the next fallback
 
557
   entry, ENTRY.  */
 
558
static void
 
559
notify_fallback (grub_menu_entry_t entry,
 
560
                 void *userdata __attribute__((unused)))
 
561
{
 
562
  grub_printf ("\n   ");
 
563
  grub_printf_ (N_("Falling back to \'%s\'"), entry->title);
 
564
  grub_printf ("\n\n");
 
565
  grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS);
 
566
}
 
567
 
 
568
/* Callback invoked when a menu entry has failed and there is no remaining
 
569
   fallback entry to attempt.  */
 
570
static void
 
571
notify_execution_failure (void *userdata __attribute__((unused)))
 
572
{
 
573
  if (grub_errno != GRUB_ERR_NONE)
 
574
    {
 
575
      grub_print_error ();
 
576
      grub_errno = GRUB_ERR_NONE;
 
577
    }
 
578
  grub_printf ("\n  ");
 
579
  grub_printf_ (N_("Failed to boot both default and fallback entries.\n"));
 
580
  grub_wait_after_message ();
 
581
}
 
582
 
 
583
/* Callbacks used by the text menu to provide user feedback when menu entries
 
584
   are executed.  */
 
585
static struct grub_menu_execute_callback execution_callback =
 
586
{
 
587
  .notify_booting = notify_booting,
 
588
  .notify_fallback = notify_fallback,
 
589
  .notify_failure = notify_execution_failure
 
590
};
 
591
 
 
592
static grub_err_t
 
593
show_menu (grub_menu_t menu, int nested)
 
594
{
 
595
  while (1)
 
596
    {
 
597
      int boot_entry;
 
598
      grub_menu_entry_t e;
 
599
      int auto_boot;
 
600
 
 
601
      boot_entry = run_menu (menu, nested, &auto_boot);
 
602
      if (boot_entry < 0)
 
603
        break;
 
604
 
 
605
      e = grub_menu_get_entry (menu, boot_entry);
 
606
      if (! e)
 
607
        continue; /* Menu is empty.  */
 
608
 
 
609
      grub_cls ();
 
610
 
 
611
      if (auto_boot)
 
612
        grub_menu_execute_with_fallback (menu, e, &execution_callback, 0);
 
613
      else
 
614
        grub_menu_execute_entry (e);
 
615
    }
 
616
 
 
617
  return GRUB_ERR_NONE;
 
618
}
 
619
 
 
620
grub_err_t
 
621
grub_show_menu (grub_menu_t menu, int nested)
 
622
{
 
623
  grub_err_t err1, err2;
 
624
 
 
625
  while (1)
 
626
    {
 
627
      err1 = show_menu (menu, nested);
 
628
      grub_print_error ();
 
629
 
 
630
      if (grub_normal_exit_level)
 
631
        break;
 
632
 
 
633
      err2 = grub_auth_check_authentication (NULL);
 
634
      if (err2)
 
635
        {
 
636
          grub_print_error ();
 
637
          grub_errno = GRUB_ERR_NONE;
 
638
          continue;
 
639
        }
 
640
 
 
641
      break;
 
642
    }
 
643
 
 
644
  return err1;
 
645
}