mirror of
				https://git.proxmox.com/git/grub2
				synced 2025-10-31 13:55:51 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			913 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			913 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* menu.c - General supporting functionality for menus.  */
 | |
| /*
 | |
|  *  GRUB  --  GRand Unified Bootloader
 | |
|  *  Copyright (C) 2003,2004,2005,2006,2007,2008,2009,2010  Free Software Foundation, Inc.
 | |
|  *
 | |
|  *  GRUB is free software: you can redistribute it and/or modify
 | |
|  *  it under the terms of the GNU General Public License as published by
 | |
|  *  the Free Software Foundation, either version 3 of the License, or
 | |
|  *  (at your option) any later version.
 | |
|  *
 | |
|  *  GRUB is distributed in the hope that it will be useful,
 | |
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  *  GNU General Public License for more details.
 | |
|  *
 | |
|  *  You should have received a copy of the GNU General Public License
 | |
|  *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include <grub/normal.h>
 | |
| #include <grub/misc.h>
 | |
| #include <grub/loader.h>
 | |
| #include <grub/mm.h>
 | |
| #include <grub/time.h>
 | |
| #include <grub/env.h>
 | |
| #include <grub/menu_viewer.h>
 | |
| #include <grub/command.h>
 | |
| #include <grub/parser.h>
 | |
| #include <grub/auth.h>
 | |
| #include <grub/i18n.h>
 | |
| #include <grub/term.h>
 | |
| #include <grub/script_sh.h>
 | |
| #include <grub/gfxterm.h>
 | |
| #include <grub/dl.h>
 | |
| 
 | |
| /* Time to delay after displaying an error message about a default/fallback
 | |
|    entry failing to boot.  */
 | |
| #define DEFAULT_ENTRY_ERROR_DELAY_MS  2500
 | |
| 
 | |
| grub_err_t (*grub_gfxmenu_try_hook) (int entry, grub_menu_t menu,
 | |
| 				     int nested) = NULL;
 | |
| 
 | |
| enum timeout_style {
 | |
|   TIMEOUT_STYLE_MENU,
 | |
|   TIMEOUT_STYLE_COUNTDOWN,
 | |
|   TIMEOUT_STYLE_HIDDEN
 | |
| };
 | |
| 
 | |
| struct timeout_style_name {
 | |
|   const char *name;
 | |
|   enum timeout_style style;
 | |
| } timeout_style_names[] = {
 | |
|   {"menu", TIMEOUT_STYLE_MENU},
 | |
|   {"countdown", TIMEOUT_STYLE_COUNTDOWN},
 | |
|   {"hidden", TIMEOUT_STYLE_HIDDEN},
 | |
|   {NULL, 0}
 | |
| };
 | |
| 
 | |
| /* Wait until the user pushes any key so that the user
 | |
|    can see what happened.  */
 | |
| void
 | |
| grub_wait_after_message (void)
 | |
| {
 | |
|   grub_uint64_t endtime;
 | |
|   grub_xputs ("\n");
 | |
|   grub_printf_ (N_("Press any key to continue..."));
 | |
|   grub_refresh ();
 | |
| 
 | |
|   endtime = grub_get_time_ms () + 10000;
 | |
| 
 | |
|   while (grub_get_time_ms () < endtime
 | |
| 	 && grub_getkey_noblock () == GRUB_TERM_NO_KEY);
 | |
| 
 | |
|   grub_xputs ("\n");
 | |
| }
 | |
| 
 | |
| /* Get a menu entry by its index in the entry list.  */
 | |
| grub_menu_entry_t
 | |
| grub_menu_get_entry (grub_menu_t menu, int no)
 | |
| {
 | |
|   grub_menu_entry_t e;
 | |
| 
 | |
|   for (e = menu->entry_list; e && no > 0; e = e->next, no--)
 | |
|     ;
 | |
| 
 | |
|   return e;
 | |
| }
 | |
| 
 | |
| /* Get the index of a menu entry associated with a given hotkey, or -1.  */
 | |
| static int
 | |
| get_entry_index_by_hotkey (grub_menu_t menu, int hotkey)
 | |
| {
 | |
|   grub_menu_entry_t entry;
 | |
|   int i;
 | |
| 
 | |
|   for (i = 0, entry = menu->entry_list; i < menu->size;
 | |
|        i++, entry = entry->next)
 | |
|     if (entry->hotkey == hotkey)
 | |
|       return i;
 | |
| 
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /* Return the timeout style.  If the variable "timeout_style" is not set or
 | |
|    invalid, default to TIMEOUT_STYLE_MENU.  */
 | |
| static enum timeout_style
 | |
| get_timeout_style (void)
 | |
| {
 | |
|   const char *val;
 | |
|   struct timeout_style_name *style_name;
 | |
| 
 | |
|   val = grub_env_get ("timeout_style");
 | |
|   if (!val)
 | |
|     return TIMEOUT_STYLE_MENU;
 | |
| 
 | |
|   for (style_name = timeout_style_names; style_name->name; style_name++)
 | |
|     if (grub_strcmp (style_name->name, val) == 0)
 | |
|       return style_name->style;
 | |
| 
 | |
|   return TIMEOUT_STYLE_MENU;
 | |
| }
 | |
| 
 | |
| /* Return the current timeout. If the variable "timeout" is not set or
 | |
|    invalid, return -1.  */
 | |
| int
 | |
| grub_menu_get_timeout (void)
 | |
| {
 | |
|   const char *val;
 | |
|   int timeout;
 | |
| 
 | |
|   val = grub_env_get ("timeout");
 | |
|   if (! val)
 | |
|     return -1;
 | |
| 
 | |
|   grub_error_push ();
 | |
| 
 | |
|   timeout = (int) grub_strtoul (val, 0, 0);
 | |
| 
 | |
|   /* If the value is invalid, unset the variable.  */
 | |
|   if (grub_errno != GRUB_ERR_NONE)
 | |
|     {
 | |
|       grub_env_unset ("timeout");
 | |
|       grub_errno = GRUB_ERR_NONE;
 | |
|       timeout = -1;
 | |
|     }
 | |
| 
 | |
|   grub_error_pop ();
 | |
| 
 | |
|   return timeout;
 | |
| }
 | |
| 
 | |
| /* Set current timeout in the variable "timeout".  */
 | |
| void
 | |
| grub_menu_set_timeout (int timeout)
 | |
| {
 | |
|   /* Ignore TIMEOUT if it is zero, because it will be unset really soon.  */
 | |
|   if (timeout > 0)
 | |
|     {
 | |
|       char buf[16];
 | |
| 
 | |
|       grub_snprintf (buf, sizeof (buf), "%d", timeout);
 | |
|       grub_env_set ("timeout", buf);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Get the first entry number from the value of the environment variable NAME,
 | |
|    which is a space-separated list of non-negative integers.  The entry number
 | |
|    which is returned is stripped from the value of NAME.  If no entry number
 | |
|    can be found, -1 is returned.  */
 | |
| static int
 | |
| get_and_remove_first_entry_number (const char *name)
 | |
| {
 | |
|   const char *val, *tail;
 | |
|   int entry;
 | |
| 
 | |
|   val = grub_env_get (name);
 | |
|   if (! val)
 | |
|     return -1;
 | |
| 
 | |
|   grub_error_push ();
 | |
| 
 | |
|   entry = (int) grub_strtoul (val, &tail, 0);
 | |
| 
 | |
|   if (grub_errno == GRUB_ERR_NONE)
 | |
|     {
 | |
|       /* Skip whitespace to find the next digit.  */
 | |
|       while (*tail && grub_isspace (*tail))
 | |
| 	tail++;
 | |
|       grub_env_set (name, tail);
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       grub_env_unset (name);
 | |
|       grub_errno = GRUB_ERR_NONE;
 | |
|       entry = -1;
 | |
|     }
 | |
| 
 | |
|   grub_error_pop ();
 | |
| 
 | |
|   return entry;
 | |
| }
 | |
| 
 | |
| /* Run a menu entry.  */
 | |
| static void
 | |
| grub_menu_execute_entry(grub_menu_entry_t entry, int auto_boot)
 | |
| {
 | |
|   grub_err_t err = GRUB_ERR_NONE;
 | |
|   int errs_before;
 | |
|   grub_menu_t menu = NULL;
 | |
|   char *optr, *buf, *oldchosen = NULL, *olddefault = NULL;
 | |
|   const char *ptr, *chosen, *def;
 | |
|   grub_size_t sz = 0;
 | |
| 
 | |
|   if (entry->restricted)
 | |
|     err = grub_auth_check_authentication (entry->users);
 | |
| 
 | |
|   if (err)
 | |
|     {
 | |
|       grub_print_error ();
 | |
|       grub_errno = GRUB_ERR_NONE;
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   errs_before = grub_err_printed_errors;
 | |
| 
 | |
|   chosen = grub_env_get ("chosen");
 | |
|   def = grub_env_get ("default");
 | |
| 
 | |
|   if (entry->submenu)
 | |
|     {
 | |
|       grub_env_context_open ();
 | |
|       menu = grub_zalloc (sizeof (*menu));
 | |
|       if (! menu)
 | |
| 	return;
 | |
|       grub_env_set_menu (menu);
 | |
|       if (auto_boot)
 | |
| 	grub_env_set ("timeout", "0");
 | |
|     }
 | |
| 
 | |
|   for (ptr = entry->id; *ptr; ptr++)
 | |
|     sz += (*ptr == '>') ? 2 : 1;
 | |
|   if (chosen)
 | |
|     {
 | |
|       oldchosen = grub_strdup (chosen);
 | |
|       if (!oldchosen)
 | |
| 	grub_print_error ();
 | |
|     }
 | |
|   if (def)
 | |
|     {
 | |
|       olddefault = grub_strdup (def);
 | |
|       if (!olddefault)
 | |
| 	grub_print_error ();
 | |
|     }
 | |
|   sz++;
 | |
|   if (chosen)
 | |
|     sz += grub_strlen (chosen);
 | |
|   sz++;
 | |
|   buf = grub_malloc (sz);
 | |
|   if (!buf)
 | |
|     grub_print_error ();
 | |
|   else
 | |
|     {
 | |
|       optr = buf;
 | |
|       if (chosen)
 | |
| 	{
 | |
| 	  optr = grub_stpcpy (optr, chosen);
 | |
| 	  *optr++ = '>';
 | |
| 	}
 | |
|       for (ptr = entry->id; *ptr; ptr++)
 | |
| 	{
 | |
| 	  if (*ptr == '>')
 | |
| 	    *optr++ = '>';
 | |
| 	  *optr++ = *ptr;
 | |
| 	}
 | |
|       *optr = 0;
 | |
|       grub_env_set ("chosen", buf);
 | |
|       grub_env_export ("chosen");
 | |
|       grub_free (buf);
 | |
|     }
 | |
| 
 | |
|   for (ptr = def; ptr && *ptr; ptr++)
 | |
|     {
 | |
|       if (ptr[0] == '>' && ptr[1] == '>')
 | |
| 	{
 | |
| 	  ptr++;
 | |
| 	  continue;
 | |
| 	}
 | |
|       if (ptr[0] == '>')
 | |
| 	break;
 | |
|     }
 | |
| 
 | |
|   if (ptr && ptr[0] && ptr[1])
 | |
|     grub_env_set ("default", ptr + 1);
 | |
|   else
 | |
|     grub_env_unset ("default");
 | |
| 
 | |
|   grub_script_execute_new_scope (entry->sourcecode, entry->argc, entry->args);
 | |
| 
 | |
|   if (errs_before != grub_err_printed_errors)
 | |
|     grub_wait_after_message ();
 | |
| 
 | |
|   errs_before = grub_err_printed_errors;
 | |
| 
 | |
|   if (grub_errno == GRUB_ERR_NONE && grub_loader_is_loaded ())
 | |
|     /* Implicit execution of boot, only if something is loaded.  */
 | |
|     grub_command_execute ("boot", 0, 0);
 | |
| 
 | |
|   if (errs_before != grub_err_printed_errors)
 | |
|     grub_wait_after_message ();
 | |
| 
 | |
|   if (entry->submenu)
 | |
|     {
 | |
|       if (menu && menu->size)
 | |
| 	{
 | |
| 	  grub_show_menu (menu, 1, auto_boot);
 | |
| 	  grub_normal_free_menu (menu);
 | |
| 	}
 | |
|       grub_env_context_close ();
 | |
|     }
 | |
|   if (oldchosen)
 | |
|     grub_env_set ("chosen", oldchosen);
 | |
|   else
 | |
|     grub_env_unset ("chosen");
 | |
|   if (olddefault)
 | |
|     grub_env_set ("default", olddefault);
 | |
|   else
 | |
|     grub_env_unset ("default");
 | |
|   grub_env_unset ("timeout");
 | |
| }
 | |
| 
 | |
| /* Execute ENTRY from the menu MENU, falling back to entries specified
 | |
|    in the environment variable "fallback" if it fails.  CALLBACK is a
 | |
|    pointer to a struct of function pointers which are used to allow the
 | |
|    caller provide feedback to the user.  */
 | |
| static void
 | |
| grub_menu_execute_with_fallback (grub_menu_t menu,
 | |
| 				 grub_menu_entry_t entry,
 | |
| 				 int autobooted,
 | |
| 				 grub_menu_execute_callback_t callback,
 | |
| 				 void *callback_data)
 | |
| {
 | |
|   int fallback_entry;
 | |
| 
 | |
|   callback->notify_booting (entry, callback_data);
 | |
| 
 | |
|   grub_menu_execute_entry (entry, 1);
 | |
| 
 | |
|   /* Deal with fallback entries.  */
 | |
|   while ((fallback_entry = get_and_remove_first_entry_number ("fallback"))
 | |
| 	 >= 0)
 | |
|     {
 | |
|       grub_print_error ();
 | |
|       grub_errno = GRUB_ERR_NONE;
 | |
| 
 | |
|       entry = grub_menu_get_entry (menu, fallback_entry);
 | |
|       callback->notify_fallback (entry, callback_data);
 | |
|       grub_menu_execute_entry (entry, 1);
 | |
|       /* If the function call to execute the entry returns at all, then this is
 | |
| 	 taken to indicate a boot failure.  For menu entries that do something
 | |
| 	 other than actually boot an operating system, this could assume
 | |
| 	 incorrectly that something failed.  */
 | |
|     }
 | |
| 
 | |
|   if (!autobooted)
 | |
|     callback->notify_failure (callback_data);
 | |
| }
 | |
| 
 | |
| static struct grub_menu_viewer *viewers;
 | |
| 
 | |
| static void
 | |
| menu_set_chosen_entry (int entry)
 | |
| {
 | |
|   struct grub_menu_viewer *cur;
 | |
|   for (cur = viewers; cur; cur = cur->next)
 | |
|     cur->set_chosen_entry (entry, cur->data);
 | |
| }
 | |
| 
 | |
| static void
 | |
| menu_print_timeout (int timeout)
 | |
| {
 | |
|   struct grub_menu_viewer *cur;
 | |
|   for (cur = viewers; cur; cur = cur->next)
 | |
|     cur->print_timeout (timeout, cur->data);
 | |
| }
 | |
| 
 | |
| static void
 | |
| menu_fini (void)
 | |
| {
 | |
|   struct grub_menu_viewer *cur, *next;
 | |
|   for (cur = viewers; cur; cur = next)
 | |
|     {
 | |
|       next = cur->next;
 | |
|       cur->fini (cur->data);
 | |
|       grub_free (cur);
 | |
|     }
 | |
|   viewers = NULL;
 | |
| }
 | |
| 
 | |
| static void
 | |
| menu_init (int entry, grub_menu_t menu, int nested)
 | |
| {
 | |
|   struct grub_term_output *term;
 | |
|   int gfxmenu = 0;
 | |
| 
 | |
|   FOR_ACTIVE_TERM_OUTPUTS(term)
 | |
|     if (term->fullscreen)
 | |
|       {
 | |
| 	if (grub_env_get ("theme"))
 | |
| 	  {
 | |
| 	    if (!grub_gfxmenu_try_hook)
 | |
| 	      {
 | |
| 		grub_dl_load ("gfxmenu");
 | |
| 		grub_print_error ();
 | |
| 	      }
 | |
| 	    if (grub_gfxmenu_try_hook)
 | |
| 	      {
 | |
| 		grub_err_t err;
 | |
| 		err = grub_gfxmenu_try_hook (entry, menu, nested);
 | |
| 		if(!err)
 | |
| 		  {
 | |
| 		    gfxmenu = 1;
 | |
| 		    break;
 | |
| 		  }
 | |
| 	      }
 | |
| 	    else
 | |
| 	      grub_error (GRUB_ERR_BAD_MODULE,
 | |
| 			  N_("module `%s' isn't loaded"),
 | |
| 			  "gfxmenu");
 | |
| 	    grub_print_error ();
 | |
| 	    grub_wait_after_message ();
 | |
| 	  }
 | |
| 	grub_errno = GRUB_ERR_NONE;
 | |
| 	term->fullscreen ();
 | |
| 	break;
 | |
|       }
 | |
| 
 | |
|   FOR_ACTIVE_TERM_OUTPUTS(term)
 | |
|   {
 | |
|     grub_err_t err;
 | |
| 
 | |
|     if (grub_strcmp (term->name, "gfxterm") == 0 && gfxmenu)
 | |
|       continue;
 | |
| 
 | |
|     err = grub_menu_try_text (term, entry, menu, nested);
 | |
|     if(!err)
 | |
|       continue;
 | |
|     grub_print_error ();
 | |
|     grub_errno = GRUB_ERR_NONE;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void
 | |
| clear_timeout (void)
 | |
| {
 | |
|   struct grub_menu_viewer *cur;
 | |
|   for (cur = viewers; cur; cur = cur->next)
 | |
|     cur->clear_timeout (cur->data);
 | |
| }
 | |
| 
 | |
| void
 | |
| grub_menu_register_viewer (struct grub_menu_viewer *viewer)
 | |
| {
 | |
|   viewer->next = viewers;
 | |
|   viewers = viewer;
 | |
| }
 | |
| 
 | |
| static int
 | |
| menuentry_eq (const char *id, const char *spec)
 | |
| {
 | |
|   const char *ptr1, *ptr2;
 | |
|   ptr1 = id;
 | |
|   ptr2 = spec;
 | |
|   while (1)
 | |
|     {
 | |
|       if (*ptr2 == '>' && ptr2[1] != '>' && *ptr1 == 0)
 | |
| 	return 1;
 | |
|       if (*ptr2 == '>' && ptr2[1] != '>')
 | |
| 	return 0;
 | |
|       if (*ptr2 == '>')
 | |
| 	ptr2++;
 | |
|       if (*ptr1 != *ptr2)
 | |
| 	return 0;
 | |
|       if (*ptr1 == 0)
 | |
| 	return 1;
 | |
|       ptr1++;
 | |
|       ptr2++;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Get the entry number from the variable NAME.  */
 | |
| static int
 | |
| get_entry_number (grub_menu_t menu, const char *name)
 | |
| {
 | |
|   const char *val;
 | |
|   int entry;
 | |
| 
 | |
|   val = grub_env_get (name);
 | |
|   if (! val)
 | |
|     return -1;
 | |
| 
 | |
|   grub_error_push ();
 | |
| 
 | |
|   entry = (int) grub_strtoul (val, 0, 0);
 | |
| 
 | |
|   if (grub_errno == GRUB_ERR_BAD_NUMBER)
 | |
|     {
 | |
|       /* See if the variable matches the title of a menu entry.  */
 | |
|       grub_menu_entry_t e = menu->entry_list;
 | |
|       int i;
 | |
| 
 | |
|       grub_errno = GRUB_ERR_NONE;
 | |
| 
 | |
|       for (i = 0; e; i++)
 | |
| 	{
 | |
| 	  if (menuentry_eq (e->title, val)
 | |
| 	      || menuentry_eq (e->id, val))
 | |
| 	    {
 | |
| 	      entry = i;
 | |
| 	      break;
 | |
| 	    }
 | |
| 	  e = e->next;
 | |
| 	}
 | |
| 
 | |
|       if (! e)
 | |
| 	entry = -1;
 | |
|     }
 | |
| 
 | |
|   if (grub_errno != GRUB_ERR_NONE)
 | |
|     {
 | |
|       grub_errno = GRUB_ERR_NONE;
 | |
|       entry = -1;
 | |
|     }
 | |
| 
 | |
|   grub_error_pop ();
 | |
| 
 | |
|   return entry;
 | |
| }
 | |
| 
 | |
| /* Check whether a second has elapsed since the last tick.  If so, adjust
 | |
|    the timer and return 1; otherwise, return 0.  */
 | |
| static int
 | |
| has_second_elapsed (grub_uint64_t *saved_time)
 | |
| {
 | |
|   grub_uint64_t current_time;
 | |
| 
 | |
|   current_time = grub_get_time_ms ();
 | |
|   if (current_time - *saved_time >= 1000)
 | |
|     {
 | |
|       *saved_time = current_time;
 | |
|       return 1;
 | |
|     }
 | |
|   else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static void
 | |
| print_countdown (struct grub_term_coordinate *pos, int n)
 | |
| {
 | |
|   grub_term_restore_pos (pos);
 | |
|   /* NOTE: Do not remove the trailing space characters.
 | |
|      They are required to clear the line.  */
 | |
|   grub_printf ("%d    ", n);
 | |
|   grub_refresh ();
 | |
| }
 | |
| 
 | |
| #define GRUB_MENU_PAGE_SIZE 10
 | |
| 
 | |
| /* Show the menu and handle menu entry selection.  Returns the menu entry
 | |
|    index that should be executed or -1 if no entry should be executed (e.g.,
 | |
|    Esc pressed to exit a sub-menu or switching menu viewers).
 | |
|    If the return value is not -1, then *AUTO_BOOT is nonzero iff the menu
 | |
|    entry to be executed is a result of an automatic default selection because
 | |
|    of the timeout.  */
 | |
| static int
 | |
| run_menu (grub_menu_t menu, int nested, int *auto_boot)
 | |
| {
 | |
|   grub_uint64_t saved_time;
 | |
|   int default_entry, current_entry;
 | |
|   int timeout;
 | |
|   enum timeout_style timeout_style;
 | |
| 
 | |
|   default_entry = get_entry_number (menu, "default");
 | |
| 
 | |
|   /* If DEFAULT_ENTRY is not within the menu entries, fall back to
 | |
|      the first entry.  */
 | |
|   if (default_entry < 0 || default_entry >= menu->size)
 | |
|     default_entry = 0;
 | |
| 
 | |
|   timeout = grub_menu_get_timeout ();
 | |
|   if (timeout < 0)
 | |
|     /* If there is no timeout, the "countdown" and "hidden" styles result in
 | |
|        the system doing nothing and providing no or very little indication
 | |
|        why.  Technically this is what the user asked for, but it's not very
 | |
|        useful and likely to be a source of confusion, so we disallow this.  */
 | |
|     grub_env_unset ("timeout_style");
 | |
| 
 | |
|   timeout_style = get_timeout_style ();
 | |
| 
 | |
|   if (timeout_style == TIMEOUT_STYLE_COUNTDOWN
 | |
|       || timeout_style == TIMEOUT_STYLE_HIDDEN)
 | |
|     {
 | |
|       static struct grub_term_coordinate *pos;
 | |
|       int entry = -1;
 | |
| 
 | |
|       if (timeout_style == TIMEOUT_STYLE_COUNTDOWN && timeout)
 | |
| 	{
 | |
| 	  pos = grub_term_save_pos ();
 | |
| 	  print_countdown (pos, timeout);
 | |
| 	}
 | |
| 
 | |
|       /* Enter interruptible sleep until Escape or a menu hotkey is pressed,
 | |
|          or the timeout expires.  */
 | |
|       saved_time = grub_get_time_ms ();
 | |
|       while (1)
 | |
| 	{
 | |
| 	  int key;
 | |
| 
 | |
| 	  key = grub_getkey_noblock ();
 | |
| 	  if (key != GRUB_TERM_NO_KEY)
 | |
| 	    {
 | |
| 	      entry = get_entry_index_by_hotkey (menu, key);
 | |
| 	      if (entry >= 0)
 | |
| 		break;
 | |
| 	    }
 | |
| 	  if (grub_key_is_interrupt (key))
 | |
| 	    {
 | |
| 	      timeout = -1;
 | |
| 	      break;
 | |
| 	    }
 | |
| 
 | |
| 	  if (timeout > 0 && has_second_elapsed (&saved_time))
 | |
| 	    {
 | |
| 	      timeout--;
 | |
| 	      if (timeout_style == TIMEOUT_STYLE_COUNTDOWN)
 | |
| 		print_countdown (pos, timeout);
 | |
| 	    }
 | |
| 
 | |
| 	  if (timeout == 0)
 | |
| 	    /* We will fall through to auto-booting the default entry.  */
 | |
| 	    break;
 | |
| 	}
 | |
| 
 | |
|       grub_env_unset ("timeout");
 | |
|       grub_env_unset ("timeout_style");
 | |
|       if (entry >= 0)
 | |
| 	{
 | |
| 	  *auto_boot = 0;
 | |
| 	  return entry;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   /* If timeout is 0, drawing is pointless (and ugly).  */
 | |
|   if (timeout == 0)
 | |
|     {
 | |
|       *auto_boot = 1;
 | |
|       return default_entry;
 | |
|     }
 | |
| 
 | |
|   current_entry = default_entry;
 | |
| 
 | |
|  refresh:
 | |
|   menu_init (current_entry, menu, nested);
 | |
| 
 | |
|   /* Initialize the time.  */
 | |
|   saved_time = grub_get_time_ms ();
 | |
| 
 | |
|   timeout = grub_menu_get_timeout ();
 | |
| 
 | |
|   if (timeout > 0)
 | |
|     menu_print_timeout (timeout);
 | |
|   else
 | |
|     clear_timeout ();
 | |
| 
 | |
|   while (1)
 | |
|     {
 | |
|       int c;
 | |
|       timeout = grub_menu_get_timeout ();
 | |
| 
 | |
|       if (grub_normal_exit_level)
 | |
| 	return -1;
 | |
| 
 | |
|       if (timeout > 0 && has_second_elapsed (&saved_time))
 | |
| 	{
 | |
| 	  timeout--;
 | |
| 	  grub_menu_set_timeout (timeout);
 | |
| 	  menu_print_timeout (timeout);
 | |
| 	}
 | |
| 
 | |
|       if (timeout == 0)
 | |
| 	{
 | |
| 	  grub_env_unset ("timeout");
 | |
|           *auto_boot = 1;
 | |
| 	  menu_fini ();
 | |
| 	  return default_entry;
 | |
| 	}
 | |
| 
 | |
|       c = grub_getkey_noblock ();
 | |
| 
 | |
|       /* Negative values are returned on error. */
 | |
|       if ((c != GRUB_TERM_NO_KEY) && (c > 0))
 | |
| 	{
 | |
| 	  if (timeout >= 0)
 | |
| 	    {
 | |
| 	      grub_env_unset ("timeout");
 | |
| 	      grub_env_unset ("fallback");
 | |
| 	      clear_timeout ();
 | |
| 	    }
 | |
| 
 | |
| 	  switch (c)
 | |
| 	    {
 | |
| 	    case GRUB_TERM_KEY_HOME:
 | |
| 	    case GRUB_TERM_CTRL | 'a':
 | |
| 	      current_entry = 0;
 | |
| 	      menu_set_chosen_entry (current_entry);
 | |
| 	      break;
 | |
| 
 | |
| 	    case GRUB_TERM_KEY_END:
 | |
| 	    case GRUB_TERM_CTRL | 'e':
 | |
| 	      current_entry = menu->size - 1;
 | |
| 	      menu_set_chosen_entry (current_entry);
 | |
| 	      break;
 | |
| 
 | |
| 	    case GRUB_TERM_KEY_UP:
 | |
| 	    case GRUB_TERM_CTRL | 'p':
 | |
| 	    case '^':
 | |
| 	      if (current_entry > 0)
 | |
| 		current_entry--;
 | |
| 	      menu_set_chosen_entry (current_entry);
 | |
| 	      break;
 | |
| 
 | |
| 	    case GRUB_TERM_CTRL | 'n':
 | |
| 	    case GRUB_TERM_KEY_DOWN:
 | |
| 	    case 'v':
 | |
| 	      if (current_entry < menu->size - 1)
 | |
| 		current_entry++;
 | |
| 	      menu_set_chosen_entry (current_entry);
 | |
| 	      break;
 | |
| 
 | |
| 	    case GRUB_TERM_CTRL | 'g':
 | |
| 	    case GRUB_TERM_KEY_PPAGE:
 | |
| 	      if (current_entry < GRUB_MENU_PAGE_SIZE)
 | |
| 		current_entry = 0;
 | |
| 	      else
 | |
| 		current_entry -= GRUB_MENU_PAGE_SIZE;
 | |
| 	      menu_set_chosen_entry (current_entry);
 | |
| 	      break;
 | |
| 
 | |
| 	    case GRUB_TERM_CTRL | 'c':
 | |
| 	    case GRUB_TERM_KEY_NPAGE:
 | |
| 	      if (current_entry + GRUB_MENU_PAGE_SIZE < menu->size)
 | |
| 		current_entry += GRUB_MENU_PAGE_SIZE;
 | |
| 	      else
 | |
| 		current_entry = menu->size - 1;
 | |
| 	      menu_set_chosen_entry (current_entry);
 | |
| 	      break;
 | |
| 
 | |
| 	    case '\n':
 | |
| 	    case '\r':
 | |
| 	    case GRUB_TERM_KEY_RIGHT:
 | |
| 	    case GRUB_TERM_CTRL | 'f':
 | |
| 	      menu_fini ();
 | |
|               *auto_boot = 0;
 | |
| 	      return current_entry;
 | |
| 
 | |
| 	    case GRUB_TERM_ESC:
 | |
| 	      if (nested)
 | |
| 		{
 | |
| 		  menu_fini ();
 | |
| 		  return -1;
 | |
| 		}
 | |
| 	      break;
 | |
| 
 | |
| 	    case 'c':
 | |
| 	      menu_fini ();
 | |
| 	      grub_cmdline_run (1, 0);
 | |
| 	      goto refresh;
 | |
| 
 | |
| 	    case 'e':
 | |
| 	      menu_fini ();
 | |
| 		{
 | |
| 		  grub_menu_entry_t e = grub_menu_get_entry (menu, current_entry);
 | |
| 		  if (e)
 | |
| 		    grub_menu_entry_run (e);
 | |
| 		}
 | |
| 	      goto refresh;
 | |
| 
 | |
| 	    default:
 | |
| 	      {
 | |
| 		int entry;
 | |
| 
 | |
| 		entry = get_entry_index_by_hotkey (menu, c);
 | |
| 		if (entry >= 0)
 | |
| 		  {
 | |
| 		    menu_fini ();
 | |
| 		    *auto_boot = 0;
 | |
| 		    return entry;
 | |
| 		  }
 | |
| 	      }
 | |
| 	      break;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   /* Never reach here.  */
 | |
| }
 | |
| 
 | |
| /* Callback invoked immediately before a menu entry is executed.  */
 | |
| static void
 | |
| notify_booting (grub_menu_entry_t entry,
 | |
| 		void *userdata __attribute__((unused)))
 | |
| {
 | |
|   grub_printf ("  ");
 | |
|   grub_printf_ (N_("Booting `%s'"), entry->title);
 | |
|   grub_printf ("\n\n");
 | |
| }
 | |
| 
 | |
| /* Callback invoked when a default menu entry executed because of a timeout
 | |
|    has failed and an attempt will be made to execute the next fallback
 | |
|    entry, ENTRY.  */
 | |
| static void
 | |
| notify_fallback (grub_menu_entry_t entry,
 | |
| 		 void *userdata __attribute__((unused)))
 | |
| {
 | |
|   grub_printf ("\n   ");
 | |
|   grub_printf_ (N_("Falling back to `%s'"), entry->title);
 | |
|   grub_printf ("\n\n");
 | |
|   grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS);
 | |
| }
 | |
| 
 | |
| /* Callback invoked when a menu entry has failed and there is no remaining
 | |
|    fallback entry to attempt.  */
 | |
| static void
 | |
| notify_execution_failure (void *userdata __attribute__((unused)))
 | |
| {
 | |
|   if (grub_errno != GRUB_ERR_NONE)
 | |
|     {
 | |
|       grub_print_error ();
 | |
|       grub_errno = GRUB_ERR_NONE;
 | |
|     }
 | |
|   grub_printf ("\n  ");
 | |
|   grub_printf_ (N_("Failed to boot both default and fallback entries.\n"));
 | |
|   grub_wait_after_message ();
 | |
| }
 | |
| 
 | |
| /* Callbacks used by the text menu to provide user feedback when menu entries
 | |
|    are executed.  */
 | |
| static struct grub_menu_execute_callback execution_callback =
 | |
| {
 | |
|   .notify_booting = notify_booting,
 | |
|   .notify_fallback = notify_fallback,
 | |
|   .notify_failure = notify_execution_failure
 | |
| };
 | |
| 
 | |
| static grub_err_t
 | |
| show_menu (grub_menu_t menu, int nested, int autobooted)
 | |
| {
 | |
|   while (1)
 | |
|     {
 | |
|       int boot_entry;
 | |
|       grub_menu_entry_t e;
 | |
|       int auto_boot;
 | |
| 
 | |
|       boot_entry = run_menu (menu, nested, &auto_boot);
 | |
|       if (boot_entry < 0)
 | |
| 	break;
 | |
| 
 | |
|       e = grub_menu_get_entry (menu, boot_entry);
 | |
|       if (! e)
 | |
| 	continue; /* Menu is empty.  */
 | |
| 
 | |
|       grub_cls ();
 | |
| 
 | |
|       if (auto_boot)
 | |
| 	grub_menu_execute_with_fallback (menu, e, autobooted,
 | |
| 					 &execution_callback, 0);
 | |
|       else
 | |
| 	grub_menu_execute_entry (e, 0);
 | |
|       if (autobooted)
 | |
| 	break;
 | |
|     }
 | |
| 
 | |
|   return GRUB_ERR_NONE;
 | |
| }
 | |
| 
 | |
| grub_err_t
 | |
| grub_show_menu (grub_menu_t menu, int nested, int autoboot)
 | |
| {
 | |
|   grub_err_t err1, err2;
 | |
| 
 | |
|   while (1)
 | |
|     {
 | |
|       err1 = show_menu (menu, nested, autoboot);
 | |
|       autoboot = 0;
 | |
|       grub_print_error ();
 | |
| 
 | |
|       if (grub_normal_exit_level)
 | |
| 	break;
 | |
| 
 | |
|       err2 = grub_auth_check_authentication (NULL);
 | |
|       if (err2)
 | |
| 	{
 | |
| 	  grub_print_error ();
 | |
| 	  grub_errno = GRUB_ERR_NONE;
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|   return err1;
 | |
| }
 | 
