1 /* menu.c - General supporting functionality for menus. */
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2003,2004,2005,2006,2007,2008,2009,2010 Free Software Foundation, Inc.
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.
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.
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/>.
20 #include <grub/normal.h>
21 #include <grub/misc.h>
22 #include <grub/loader.h>
24 #include <grub/time.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 #include <grub/gfxterm.h>
37 int g_ventoy_menu_refresh
= 0;
38 int g_ventoy_memdisk_mode
= 0;
39 int g_ventoy_iso_raw
= 0;
40 int g_ventoy_iso_uefi_drv
= 0;
41 int g_ventoy_last_entry
= -1;
42 int g_ventoy_suppress_esc
= 0;
43 int g_ventoy_menu_esc
= 0;
44 int g_ventoy_fn_mutex
= 0;
46 /* Time to delay after displaying an error message about a default/fallback
47 entry failing to boot. */
48 #define DEFAULT_ENTRY_ERROR_DELAY_MS 2500
50 grub_err_t (*grub_gfxmenu_try_hook
) (int entry
, grub_menu_t menu
,
55 TIMEOUT_STYLE_COUNTDOWN
,
59 struct timeout_style_name
{
61 enum timeout_style style
;
62 } timeout_style_names
[] = {
63 {"menu", TIMEOUT_STYLE_MENU
},
64 {"countdown", TIMEOUT_STYLE_COUNTDOWN
},
65 {"hidden", TIMEOUT_STYLE_HIDDEN
},
69 /* Wait until the user pushes any key so that the user
70 can see what happened. */
72 grub_wait_after_message (void)
74 grub_uint64_t endtime
;
76 grub_printf_ (N_("Press any key to continue..."));
79 endtime
= grub_get_time_ms () + 10000;
81 while (grub_get_time_ms () < endtime
82 && grub_getkey_noblock () == GRUB_TERM_NO_KEY
);
87 /* Get a menu entry by its index in the entry list. */
89 grub_menu_get_entry (grub_menu_t menu
, int no
)
93 for (e
= menu
->entry_list
; e
&& no
> 0; e
= e
->next
, no
--)
99 /* Get the index of a menu entry associated with a given hotkey, or -1. */
101 get_entry_index_by_hotkey (grub_menu_t menu
, int hotkey
)
103 grub_menu_entry_t entry
;
106 for (i
= 0, entry
= menu
->entry_list
; i
< menu
->size
;
107 i
++, entry
= entry
->next
)
108 if (entry
->hotkey
== hotkey
)
114 /* Return the timeout style. If the variable "timeout_style" is not set or
115 invalid, default to TIMEOUT_STYLE_MENU. */
116 static enum timeout_style
117 get_timeout_style (void)
120 struct timeout_style_name
*style_name
;
122 val
= grub_env_get ("timeout_style");
124 return TIMEOUT_STYLE_MENU
;
126 for (style_name
= timeout_style_names
; style_name
->name
; style_name
++)
127 if (grub_strcmp (style_name
->name
, val
) == 0)
128 return style_name
->style
;
130 return TIMEOUT_STYLE_MENU
;
133 /* Return the current timeout. If the variable "timeout" is not set or
134 invalid, return -1. */
136 grub_menu_get_timeout (void)
141 val
= grub_env_get ("timeout");
147 timeout
= (int) grub_strtoul (val
, 0, 0);
149 /* If the value is invalid, unset the variable. */
150 if (grub_errno
!= GRUB_ERR_NONE
)
152 grub_env_unset ("timeout");
153 grub_errno
= GRUB_ERR_NONE
;
162 /* Set current timeout in the variable "timeout". */
164 grub_menu_set_timeout (int timeout
)
166 /* Ignore TIMEOUT if it is zero, because it will be unset really soon. */
171 grub_snprintf (buf
, sizeof (buf
), "%d", timeout
);
172 grub_env_set ("timeout", buf
);
176 /* Get the first entry number from the value of the environment variable NAME,
177 which is a space-separated list of non-negative integers. The entry number
178 which is returned is stripped from the value of NAME. If no entry number
179 can be found, -1 is returned. */
181 get_and_remove_first_entry_number (const char *name
)
187 val
= grub_env_get (name
);
193 entry
= (int) grub_strtoul (val
, &tail
, 0);
195 if (grub_errno
== GRUB_ERR_NONE
)
197 /* Skip whitespace to find the next digit. */
198 while (*tail
&& grub_isspace (*tail
))
200 grub_env_set (name
, tail
);
204 grub_env_unset (name
);
205 grub_errno
= GRUB_ERR_NONE
;
214 /* Run a menu entry. */
216 grub_menu_execute_entry(grub_menu_entry_t entry
, int auto_boot
)
218 grub_err_t err
= GRUB_ERR_NONE
;
220 grub_menu_t menu
= NULL
;
221 char *optr
, *buf
, *oldchosen
= NULL
, *olddefault
= NULL
;
222 const char *ptr
, *chosen
, *def
;
225 if (entry
->restricted
)
226 err
= grub_auth_check_authentication (entry
->users
);
231 grub_errno
= GRUB_ERR_NONE
;
235 errs_before
= grub_err_printed_errors
;
237 chosen
= grub_env_get ("chosen");
238 def
= grub_env_get ("default");
242 grub_env_context_open ();
243 menu
= grub_zalloc (sizeof (*menu
));
246 grub_env_set_menu (menu
);
248 grub_env_set ("timeout", "0");
251 for (ptr
= entry
->id
; *ptr
; ptr
++)
252 sz
+= (*ptr
== '>') ? 2 : 1;
255 oldchosen
= grub_strdup (chosen
);
261 olddefault
= grub_strdup (def
);
267 sz
+= grub_strlen (chosen
);
269 buf
= grub_malloc (sz
);
277 optr
= grub_stpcpy (optr
, chosen
);
280 for (ptr
= entry
->id
; *ptr
; ptr
++)
287 grub_env_set ("chosen", buf
);
288 grub_env_export ("chosen");
292 for (ptr
= def
; ptr
&& *ptr
; ptr
++)
294 if (ptr
[0] == '>' && ptr
[1] == '>')
303 if (ptr
&& ptr
[0] && ptr
[1])
304 grub_env_set ("default", ptr
+ 1);
306 grub_env_unset ("default");
308 grub_script_execute_new_scope (entry
->sourcecode
, entry
->argc
, entry
->args
);
310 if (errs_before
!= grub_err_printed_errors
)
311 grub_wait_after_message ();
313 errs_before
= grub_err_printed_errors
;
315 if (grub_errno
== GRUB_ERR_NONE
&& grub_loader_is_loaded ())
316 /* Implicit execution of boot, only if something is loaded. */
317 grub_command_execute ("boot", 0, 0);
319 if (errs_before
!= grub_err_printed_errors
)
320 grub_wait_after_message ();
324 if (menu
&& menu
->size
)
326 grub_show_menu (menu
, 1, auto_boot
);
327 grub_normal_free_menu (menu
);
329 grub_env_context_close ();
332 grub_env_set ("chosen", oldchosen
);
334 grub_env_unset ("chosen");
336 grub_env_set ("default", olddefault
);
338 grub_env_unset ("default");
339 grub_env_unset ("timeout");
342 /* Execute ENTRY from the menu MENU, falling back to entries specified
343 in the environment variable "fallback" if it fails. CALLBACK is a
344 pointer to a struct of function pointers which are used to allow the
345 caller provide feedback to the user. */
347 grub_menu_execute_with_fallback (grub_menu_t menu
,
348 grub_menu_entry_t entry
,
350 grub_menu_execute_callback_t callback
,
355 callback
->notify_booting (entry
, callback_data
);
357 grub_menu_execute_entry (entry
, 1);
359 /* Deal with fallback entries. */
360 while ((fallback_entry
= get_and_remove_first_entry_number ("fallback"))
364 grub_errno
= GRUB_ERR_NONE
;
366 entry
= grub_menu_get_entry (menu
, fallback_entry
);
367 callback
->notify_fallback (entry
, callback_data
);
368 grub_menu_execute_entry (entry
, 1);
369 /* If the function call to execute the entry returns at all, then this is
370 taken to indicate a boot failure. For menu entries that do something
371 other than actually boot an operating system, this could assume
372 incorrectly that something failed. */
376 callback
->notify_failure (callback_data
);
379 static struct grub_menu_viewer
*viewers
;
382 menu_set_chosen_entry (int entry
)
384 struct grub_menu_viewer
*cur
;
385 for (cur
= viewers
; cur
; cur
= cur
->next
)
386 cur
->set_chosen_entry (entry
, cur
->data
);
390 menu_print_timeout (int timeout
)
392 struct grub_menu_viewer
*cur
;
393 for (cur
= viewers
; cur
; cur
= cur
->next
)
394 cur
->print_timeout (timeout
, cur
->data
);
400 struct grub_menu_viewer
*cur
, *next
;
401 for (cur
= viewers
; cur
; cur
= next
)
404 cur
->fini (cur
->data
);
411 menu_init (int entry
, grub_menu_t menu
, int nested
)
413 struct grub_term_output
*term
;
416 FOR_ACTIVE_TERM_OUTPUTS(term
)
417 if (term
->fullscreen
)
419 if (grub_env_get ("theme"))
421 if (!grub_gfxmenu_try_hook
)
423 grub_dl_load ("gfxmenu");
426 if (grub_gfxmenu_try_hook
)
429 err
= grub_gfxmenu_try_hook (entry
, menu
, nested
);
437 grub_error (GRUB_ERR_BAD_MODULE
,
438 N_("module `%s' isn't loaded"),
441 grub_wait_after_message ();
443 grub_errno
= GRUB_ERR_NONE
;
448 FOR_ACTIVE_TERM_OUTPUTS(term
)
452 if (grub_strcmp (term
->name
, "gfxterm") == 0 && gfxmenu
)
455 err
= grub_menu_try_text (term
, entry
, menu
, nested
);
459 grub_errno
= GRUB_ERR_NONE
;
466 struct grub_menu_viewer
*cur
;
467 for (cur
= viewers
; cur
; cur
= cur
->next
)
468 cur
->clear_timeout (cur
->data
);
472 grub_menu_register_viewer (struct grub_menu_viewer
*viewer
)
474 viewer
->next
= viewers
;
479 menuentry_eq (const char *id
, const char *spec
)
481 const char *ptr1
, *ptr2
;
486 if (*ptr2
== '>' && ptr2
[1] != '>' && *ptr1
== 0)
488 if (*ptr2
== '>' && ptr2
[1] != '>')
502 /* Get the entry number from the variable NAME. */
504 get_entry_number (grub_menu_t menu
, const char *name
)
509 val
= grub_env_get (name
);
515 entry
= (int) grub_strtoul (val
, 0, 0);
517 if (grub_errno
== GRUB_ERR_BAD_NUMBER
)
519 /* See if the variable matches the title of a menu entry. */
520 grub_menu_entry_t e
= menu
->entry_list
;
523 grub_errno
= GRUB_ERR_NONE
;
527 if (menuentry_eq (e
->title
, val
)
528 || menuentry_eq (e
->id
, val
))
540 if (grub_errno
!= GRUB_ERR_NONE
)
542 grub_errno
= GRUB_ERR_NONE
;
551 /* Check whether a second has elapsed since the last tick. If so, adjust
552 the timer and return 1; otherwise, return 0. */
554 has_second_elapsed (grub_uint64_t
*saved_time
)
556 grub_uint64_t current_time
;
558 current_time
= grub_get_time_ms ();
559 if (current_time
- *saved_time
>= 1000)
561 *saved_time
= current_time
;
569 print_countdown (struct grub_term_coordinate
*pos
, int n
)
571 grub_term_restore_pos (pos
);
572 /* NOTE: Do not remove the trailing space characters.
573 They are required to clear the line. */
574 grub_printf ("%d ", n
);
578 #define GRUB_MENU_PAGE_SIZE 10
580 /* Show the menu and handle menu entry selection. Returns the menu entry
581 index that should be executed or -1 if no entry should be executed (e.g.,
582 Esc pressed to exit a sub-menu or switching menu viewers).
583 If the return value is not -1, then *AUTO_BOOT is nonzero iff the menu
584 entry to be executed is a result of an automatic default selection because
587 run_menu (grub_menu_t menu
, int nested
, int *auto_boot
)
590 grub_uint64_t saved_time
;
591 int default_entry
,current_entry
;
593 enum timeout_style timeout_style
;
595 default_entry
= get_entry_number (menu
, "default");
597 if (g_ventoy_suppress_esc
)
599 else if (g_ventoy_last_entry
>= 0 && g_ventoy_last_entry
< menu
->size
) {
600 default_entry
= g_ventoy_last_entry
;
602 /* If DEFAULT_ENTRY is not within the menu entries, fall back to
604 else if (default_entry
< 0 || default_entry
>= menu
->size
)
607 timeout
= grub_menu_get_timeout ();
609 /* If there is no timeout, the "countdown" and "hidden" styles result in
610 the system doing nothing and providing no or very little indication
611 why. Technically this is what the user asked for, but it's not very
612 useful and likely to be a source of confusion, so we disallow this. */
613 grub_env_unset ("timeout_style");
615 timeout_style
= get_timeout_style ();
617 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
618 || timeout_style
== TIMEOUT_STYLE_HIDDEN
)
620 static struct grub_term_coordinate
*pos
;
623 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
&& timeout
)
625 pos
= grub_term_save_pos ();
626 print_countdown (pos
, timeout
);
629 /* Enter interruptible sleep until Escape or a menu hotkey is pressed,
630 or the timeout expires. */
631 saved_time
= grub_get_time_ms ();
636 key
= grub_getkey_noblock ();
637 if (key
!= GRUB_TERM_NO_KEY
)
639 entry
= get_entry_index_by_hotkey (menu
, key
);
643 if (key
== GRUB_TERM_ESC
)
649 if (timeout
> 0 && has_second_elapsed (&saved_time
))
652 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
)
653 print_countdown (pos
, timeout
);
657 /* We will fall through to auto-booting the default entry. */
661 grub_env_unset ("timeout");
662 grub_env_unset ("timeout_style");
670 /* If timeout is 0, drawing is pointless (and ugly). */
674 return default_entry
;
677 current_entry
= default_entry
;
680 menu_init (current_entry
, menu
, nested
);
682 /* Initialize the time. */
683 saved_time
= grub_get_time_ms ();
685 timeout
= grub_menu_get_timeout ();
688 menu_print_timeout (timeout
);
695 timeout
= grub_menu_get_timeout ();
697 if (grub_normal_exit_level
)
700 if (timeout
> 0 && has_second_elapsed (&saved_time
))
703 grub_menu_set_timeout (timeout
);
704 menu_print_timeout (timeout
);
709 grub_env_unset ("timeout");
712 return default_entry
;
715 c
= grub_getkey_noblock ();
717 /* Negative values are returned on error. */
718 if ((c
!= GRUB_TERM_NO_KEY
) && (c
> 0))
722 grub_env_unset ("timeout");
723 grub_env_unset ("fallback");
729 case GRUB_TERM_KEY_HOME
:
730 case GRUB_TERM_CTRL
| 'a':
732 menu_set_chosen_entry (current_entry
);
735 case GRUB_TERM_KEY_END
:
736 case GRUB_TERM_CTRL
| 'e':
737 current_entry
= menu
->size
- 1;
738 menu_set_chosen_entry (current_entry
);
741 case GRUB_TERM_KEY_UP
:
742 case GRUB_TERM_CTRL
| 'p':
744 if (current_entry
> 0)
746 menu_set_chosen_entry (current_entry
);
749 case GRUB_TERM_CTRL
| 'n':
750 case GRUB_TERM_KEY_DOWN
:
752 if (current_entry
< menu
->size
- 1)
754 menu_set_chosen_entry (current_entry
);
757 case GRUB_TERM_CTRL
| 'g':
758 case GRUB_TERM_KEY_PPAGE
:
759 if (current_entry
< GRUB_MENU_PAGE_SIZE
)
762 current_entry
-= GRUB_MENU_PAGE_SIZE
;
763 menu_set_chosen_entry (current_entry
);
766 case GRUB_TERM_CTRL
| 'c':
767 case GRUB_TERM_KEY_NPAGE
:
768 if (current_entry
+ GRUB_MENU_PAGE_SIZE
< menu
->size
)
769 current_entry
+= GRUB_MENU_PAGE_SIZE
;
771 current_entry
= menu
->size
- 1;
772 menu_set_chosen_entry (current_entry
);
777 // case GRUB_TERM_KEY_RIGHT:
778 case GRUB_TERM_CTRL
| 'f':
781 return current_entry
;
784 if (nested
&& 0 == g_ventoy_suppress_esc
)
793 grub_cmdline_run (1, 0);
799 grub_menu_entry_t e
= grub_menu_get_entry (menu
, current_entry
);
801 grub_menu_entry_run (e
);
805 case GRUB_TERM_KEY_F2
:
807 if (0 == g_ventoy_fn_mutex
) {
808 cmdstr
= grub_env_get("VTOY_F2_CMD");
812 g_ventoy_fn_mutex
= 1;
813 grub_script_execute_sourcecode(cmdstr
);
814 g_ventoy_fn_mutex
= 0;
819 case GRUB_TERM_KEY_F3
:
821 if (0 == g_ventoy_fn_mutex
) {
822 cmdstr
= grub_env_get("VTOY_F3_CMD");
826 grub_script_execute_sourcecode(cmdstr
);
831 case GRUB_TERM_KEY_F4
:
833 if (0 == g_ventoy_fn_mutex
) {
834 cmdstr
= grub_env_get("VTOY_F4_CMD");
838 g_ventoy_fn_mutex
= 1;
839 grub_script_execute_sourcecode(cmdstr
);
840 g_ventoy_fn_mutex
= 0;
845 case GRUB_TERM_KEY_F5
:
847 if (0 == g_ventoy_fn_mutex
) {
848 cmdstr
= grub_env_get("VTOY_F5_CMD");
852 g_ventoy_fn_mutex
= 1;
853 grub_script_execute_sourcecode(cmdstr
);
854 g_ventoy_fn_mutex
= 0;
859 case GRUB_TERM_KEY_F6
:
861 if (0 == g_ventoy_fn_mutex
) {
862 cmdstr
= grub_env_get("VTOY_F6_CMD");
866 g_ventoy_fn_mutex
= 1;
867 grub_script_execute_sourcecode(cmdstr
);
868 g_ventoy_fn_mutex
= 0;
873 case GRUB_TERM_KEY_F7
:
875 cmdstr
= grub_env_get("VTOY_F7_CMD");
879 grub_script_execute_sourcecode(cmdstr
);
883 case GRUB_TERM_KEY_F1
:
886 g_ventoy_memdisk_mode
= 1 - g_ventoy_memdisk_mode
;
887 g_ventoy_menu_refresh
= 1;
890 case (GRUB_TERM_CTRL
| 'i'):
892 g_ventoy_iso_raw
= 1 - g_ventoy_iso_raw
;
893 g_ventoy_menu_refresh
= 1;
896 case (GRUB_TERM_CTRL
| 'u'):
898 g_ventoy_iso_uefi_drv
= 1 - g_ventoy_iso_uefi_drv
;
899 g_ventoy_menu_refresh
= 1;
906 entry
= get_entry_index_by_hotkey (menu
, c
);
919 /* Never reach here. */
922 /* Callback invoked immediately before a menu entry is executed. */
924 notify_booting (grub_menu_entry_t entry
,
925 void *userdata
__attribute__((unused
)))
928 grub_printf_ (N_("Booting `%s'"), entry
->title
);
929 grub_printf ("\n\n");
932 /* Callback invoked when a default menu entry executed because of a timeout
933 has failed and an attempt will be made to execute the next fallback
936 notify_fallback (grub_menu_entry_t entry
,
937 void *userdata
__attribute__((unused
)))
940 grub_printf_ (N_("Falling back to `%s'"), entry
->title
);
941 grub_printf ("\n\n");
942 grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS
);
945 /* Callback invoked when a menu entry has failed and there is no remaining
946 fallback entry to attempt. */
948 notify_execution_failure (void *userdata
__attribute__((unused
)))
950 if (grub_errno
!= GRUB_ERR_NONE
)
953 grub_errno
= GRUB_ERR_NONE
;
956 grub_printf_ (N_("Failed to boot both default and fallback entries.\n"));
957 grub_wait_after_message ();
960 /* Callbacks used by the text menu to provide user feedback when menu entries
962 static struct grub_menu_execute_callback execution_callback
=
964 .notify_booting
= notify_booting
,
965 .notify_fallback
= notify_fallback
,
966 .notify_failure
= notify_execution_failure
970 show_menu (grub_menu_t menu
, int nested
, int autobooted
)
978 boot_entry
= run_menu (menu
, nested
, &auto_boot
);
982 g_ventoy_last_entry
= boot_entry
;
983 if (g_ventoy_menu_esc
)
986 e
= grub_menu_get_entry (menu
, boot_entry
);
988 continue; /* Menu is empty. */
990 if (2 == e
->argc
&& e
->args
&& e
->args
[1] && grub_strncmp(e
->args
[1], "VTOY_RET", 8) == 0)
996 grub_menu_execute_with_fallback (menu
, e
, autobooted
,
997 &execution_callback
, 0);
999 grub_menu_execute_entry (e
, 0);
1004 return GRUB_ERR_NONE
;
1008 grub_show_menu (grub_menu_t menu
, int nested
, int autoboot
)
1010 grub_err_t err1
, err2
;
1014 err1
= show_menu (menu
, nested
, autoboot
);
1016 grub_print_error ();
1018 if (grub_normal_exit_level
)
1021 err2
= grub_auth_check_authentication (NULL
);
1024 grub_print_error ();
1025 grub_errno
= GRUB_ERR_NONE
;