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_grub2_mode
= 0;
41 int g_ventoy_wimboot_mode
= 0;
42 int g_ventoy_iso_uefi_drv
= 0;
43 int g_ventoy_last_entry
= -1;
44 int g_ventoy_suppress_esc
= 0;
45 int g_ventoy_menu_esc
= 0;
46 int g_ventoy_fn_mutex
= 0;
47 int g_ventoy_terminal_output
= 0;
49 /* Time to delay after displaying an error message about a default/fallback
50 entry failing to boot. */
51 #define DEFAULT_ENTRY_ERROR_DELAY_MS 2500
53 grub_err_t (*grub_gfxmenu_try_hook
) (int entry
, grub_menu_t menu
,
58 TIMEOUT_STYLE_COUNTDOWN
,
62 struct timeout_style_name
{
64 enum timeout_style style
;
65 } timeout_style_names
[] = {
66 {"menu", TIMEOUT_STYLE_MENU
},
67 {"countdown", TIMEOUT_STYLE_COUNTDOWN
},
68 {"hidden", TIMEOUT_STYLE_HIDDEN
},
72 /* Wait until the user pushes any key so that the user
73 can see what happened. */
75 grub_wait_after_message (void)
77 grub_uint64_t endtime
;
79 grub_printf_ (N_("Press any key to continue..."));
82 endtime
= grub_get_time_ms () + 10000;
84 while (grub_get_time_ms () < endtime
85 && grub_getkey_noblock () == GRUB_TERM_NO_KEY
);
90 /* Get a menu entry by its index in the entry list. */
92 grub_menu_get_entry (grub_menu_t menu
, int no
)
96 for (e
= menu
->entry_list
; e
&& no
> 0; e
= e
->next
, no
--)
102 /* Get the index of a menu entry associated with a given hotkey, or -1. */
104 get_entry_index_by_hotkey (grub_menu_t menu
, int hotkey
)
106 grub_menu_entry_t entry
;
109 for (i
= 0, entry
= menu
->entry_list
; i
< menu
->size
;
110 i
++, entry
= entry
->next
)
111 if (entry
->hotkey
== hotkey
)
117 /* Return the timeout style. If the variable "timeout_style" is not set or
118 invalid, default to TIMEOUT_STYLE_MENU. */
119 static enum timeout_style
120 get_timeout_style (void)
123 struct timeout_style_name
*style_name
;
125 val
= grub_env_get ("timeout_style");
127 return TIMEOUT_STYLE_MENU
;
129 for (style_name
= timeout_style_names
; style_name
->name
; style_name
++)
130 if (grub_strcmp (style_name
->name
, val
) == 0)
131 return style_name
->style
;
133 return TIMEOUT_STYLE_MENU
;
136 /* Return the current timeout. If the variable "timeout" is not set or
137 invalid, return -1. */
139 grub_menu_get_timeout (void)
144 val
= grub_env_get ("timeout");
150 timeout
= (int) grub_strtoul (val
, 0, 0);
152 /* If the value is invalid, unset the variable. */
153 if (grub_errno
!= GRUB_ERR_NONE
)
155 grub_env_unset ("timeout");
156 grub_errno
= GRUB_ERR_NONE
;
165 /* Set current timeout in the variable "timeout". */
167 grub_menu_set_timeout (int timeout
)
169 /* Ignore TIMEOUT if it is zero, because it will be unset really soon. */
174 grub_snprintf (buf
, sizeof (buf
), "%d", timeout
);
175 grub_env_set ("timeout", buf
);
179 /* Get the first entry number from the value of the environment variable NAME,
180 which is a space-separated list of non-negative integers. The entry number
181 which is returned is stripped from the value of NAME. If no entry number
182 can be found, -1 is returned. */
184 get_and_remove_first_entry_number (const char *name
)
190 val
= grub_env_get (name
);
196 entry
= (int) grub_strtoul (val
, &tail
, 0);
198 if (grub_errno
== GRUB_ERR_NONE
)
200 /* Skip whitespace to find the next digit. */
201 while (*tail
&& grub_isspace (*tail
))
203 grub_env_set (name
, tail
);
207 grub_env_unset (name
);
208 grub_errno
= GRUB_ERR_NONE
;
217 /* Run a menu entry. */
219 grub_menu_execute_entry(grub_menu_entry_t entry
, int auto_boot
)
221 grub_err_t err
= GRUB_ERR_NONE
;
223 grub_menu_t menu
= NULL
;
224 char *optr
, *buf
, *oldchosen
= NULL
, *olddefault
= NULL
;
225 const char *ptr
, *chosen
, *def
;
228 if (entry
->restricted
)
229 err
= grub_auth_check_authentication (entry
->users
);
234 grub_errno
= GRUB_ERR_NONE
;
238 errs_before
= grub_err_printed_errors
;
240 chosen
= grub_env_get ("chosen");
241 def
= grub_env_get ("default");
245 grub_env_context_open ();
246 menu
= grub_zalloc (sizeof (*menu
));
249 grub_env_set_menu (menu
);
251 grub_env_set ("timeout", "0");
254 for (ptr
= entry
->id
; *ptr
; ptr
++)
255 sz
+= (*ptr
== '>') ? 2 : 1;
258 oldchosen
= grub_strdup (chosen
);
264 olddefault
= grub_strdup (def
);
270 sz
+= grub_strlen (chosen
);
272 buf
= grub_malloc (sz
);
280 optr
= grub_stpcpy (optr
, chosen
);
283 for (ptr
= entry
->id
; *ptr
; ptr
++)
290 grub_env_set ("chosen", buf
);
291 grub_env_export ("chosen");
295 for (ptr
= def
; ptr
&& *ptr
; ptr
++)
297 if (ptr
[0] == '>' && ptr
[1] == '>')
306 if (ptr
&& ptr
[0] && ptr
[1])
307 grub_env_set ("default", ptr
+ 1);
309 grub_env_unset ("default");
311 grub_script_execute_new_scope (entry
->sourcecode
, entry
->argc
, entry
->args
);
313 if (errs_before
!= grub_err_printed_errors
)
314 grub_wait_after_message ();
316 errs_before
= grub_err_printed_errors
;
318 if (grub_errno
== GRUB_ERR_NONE
&& grub_loader_is_loaded ())
319 /* Implicit execution of boot, only if something is loaded. */
320 grub_command_execute ("boot", 0, 0);
322 if (errs_before
!= grub_err_printed_errors
)
323 grub_wait_after_message ();
327 if (menu
&& menu
->size
)
329 grub_show_menu (menu
, 1, auto_boot
);
330 grub_normal_free_menu (menu
);
332 grub_env_context_close ();
335 grub_env_set ("chosen", oldchosen
);
337 grub_env_unset ("chosen");
339 grub_env_set ("default", olddefault
);
341 grub_env_unset ("default");
342 grub_env_unset ("timeout");
345 /* Execute ENTRY from the menu MENU, falling back to entries specified
346 in the environment variable "fallback" if it fails. CALLBACK is a
347 pointer to a struct of function pointers which are used to allow the
348 caller provide feedback to the user. */
350 grub_menu_execute_with_fallback (grub_menu_t menu
,
351 grub_menu_entry_t entry
,
353 grub_menu_execute_callback_t callback
,
358 callback
->notify_booting (entry
, callback_data
);
360 grub_menu_execute_entry (entry
, 1);
362 /* Deal with fallback entries. */
363 while ((fallback_entry
= get_and_remove_first_entry_number ("fallback"))
367 grub_errno
= GRUB_ERR_NONE
;
369 entry
= grub_menu_get_entry (menu
, fallback_entry
);
370 callback
->notify_fallback (entry
, callback_data
);
371 grub_menu_execute_entry (entry
, 1);
372 /* If the function call to execute the entry returns at all, then this is
373 taken to indicate a boot failure. For menu entries that do something
374 other than actually boot an operating system, this could assume
375 incorrectly that something failed. */
379 callback
->notify_failure (callback_data
);
382 static struct grub_menu_viewer
*viewers
;
385 menu_set_chosen_entry (int entry
)
387 struct grub_menu_viewer
*cur
;
388 for (cur
= viewers
; cur
; cur
= cur
->next
)
389 cur
->set_chosen_entry (entry
, cur
->data
);
393 menu_print_timeout (int timeout
)
395 struct grub_menu_viewer
*cur
;
396 for (cur
= viewers
; cur
; cur
= cur
->next
)
397 cur
->print_timeout (timeout
, cur
->data
);
403 struct grub_menu_viewer
*cur
, *next
;
404 for (cur
= viewers
; cur
; cur
= next
)
407 cur
->fini (cur
->data
);
414 menu_init (int entry
, grub_menu_t menu
, int nested
)
416 struct grub_term_output
*term
;
419 FOR_ACTIVE_TERM_OUTPUTS(term
)
420 if (term
->fullscreen
)
422 if (grub_env_get ("theme"))
424 if (!grub_gfxmenu_try_hook
)
426 grub_dl_load ("gfxmenu");
429 if (grub_gfxmenu_try_hook
)
432 err
= grub_gfxmenu_try_hook (entry
, menu
, nested
);
440 grub_error (GRUB_ERR_BAD_MODULE
,
441 N_("module `%s' isn't loaded"),
444 grub_wait_after_message ();
446 grub_errno
= GRUB_ERR_NONE
;
451 FOR_ACTIVE_TERM_OUTPUTS(term
)
455 if (grub_strcmp (term
->name
, "gfxterm") == 0 && gfxmenu
)
458 err
= grub_menu_try_text (term
, entry
, menu
, nested
);
462 grub_errno
= GRUB_ERR_NONE
;
469 struct grub_menu_viewer
*cur
;
470 for (cur
= viewers
; cur
; cur
= cur
->next
)
471 cur
->clear_timeout (cur
->data
);
475 grub_menu_register_viewer (struct grub_menu_viewer
*viewer
)
477 viewer
->next
= viewers
;
482 menuentry_eq (const char *id
, const char *spec
)
484 const char *ptr1
, *ptr2
;
489 if (*ptr2
== '>' && ptr2
[1] != '>' && *ptr1
== 0)
491 if (*ptr2
== '>' && ptr2
[1] != '>')
505 /* Get the entry number from the variable NAME. */
507 get_entry_number (grub_menu_t menu
, const char *name
)
512 val
= grub_env_get (name
);
518 entry
= (int) grub_strtoul (val
, 0, 0);
520 if (grub_errno
== GRUB_ERR_BAD_NUMBER
)
522 /* See if the variable matches the title of a menu entry. */
523 grub_menu_entry_t e
= menu
->entry_list
;
526 grub_errno
= GRUB_ERR_NONE
;
530 if (menuentry_eq (e
->title
, val
)
531 || menuentry_eq (e
->id
, val
))
543 if (grub_errno
!= GRUB_ERR_NONE
)
545 grub_errno
= GRUB_ERR_NONE
;
554 /* Check whether a second has elapsed since the last tick. If so, adjust
555 the timer and return 1; otherwise, return 0. */
557 has_second_elapsed (grub_uint64_t
*saved_time
)
559 grub_uint64_t current_time
;
561 current_time
= grub_get_time_ms ();
562 if (current_time
- *saved_time
>= 1000)
564 *saved_time
= current_time
;
572 print_countdown (struct grub_term_coordinate
*pos
, int n
)
574 grub_term_restore_pos (pos
);
575 /* NOTE: Do not remove the trailing space characters.
576 They are required to clear the line. */
577 grub_printf ("%d ", n
);
581 #define GRUB_MENU_PAGE_SIZE 10
583 /* Show the menu and handle menu entry selection. Returns the menu entry
584 index that should be executed or -1 if no entry should be executed (e.g.,
585 Esc pressed to exit a sub-menu or switching menu viewers).
586 If the return value is not -1, then *AUTO_BOOT is nonzero iff the menu
587 entry to be executed is a result of an automatic default selection because
590 run_menu (grub_menu_t menu
, int nested
, int *auto_boot
)
593 grub_uint64_t saved_time
;
594 int default_entry
,current_entry
;
596 enum timeout_style timeout_style
;
598 default_entry
= get_entry_number (menu
, "default");
600 if (g_ventoy_suppress_esc
)
602 else if (g_ventoy_last_entry
>= 0 && g_ventoy_last_entry
< menu
->size
) {
603 default_entry
= g_ventoy_last_entry
;
605 /* If DEFAULT_ENTRY is not within the menu entries, fall back to
607 else if (default_entry
< 0 || default_entry
>= menu
->size
)
610 timeout
= grub_menu_get_timeout ();
612 /* If there is no timeout, the "countdown" and "hidden" styles result in
613 the system doing nothing and providing no or very little indication
614 why. Technically this is what the user asked for, but it's not very
615 useful and likely to be a source of confusion, so we disallow this. */
616 grub_env_unset ("timeout_style");
618 timeout_style
= get_timeout_style ();
620 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
621 || timeout_style
== TIMEOUT_STYLE_HIDDEN
)
623 static struct grub_term_coordinate
*pos
;
626 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
&& timeout
)
628 pos
= grub_term_save_pos ();
629 print_countdown (pos
, timeout
);
632 /* Enter interruptible sleep until Escape or a menu hotkey is pressed,
633 or the timeout expires. */
634 saved_time
= grub_get_time_ms ();
639 key
= grub_getkey_noblock ();
640 if (key
!= GRUB_TERM_NO_KEY
)
642 entry
= get_entry_index_by_hotkey (menu
, key
);
646 if (key
== GRUB_TERM_ESC
)
652 if (timeout
> 0 && has_second_elapsed (&saved_time
))
655 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
)
656 print_countdown (pos
, timeout
);
660 /* We will fall through to auto-booting the default entry. */
664 grub_env_unset ("timeout");
665 grub_env_unset ("timeout_style");
673 /* If timeout is 0, drawing is pointless (and ugly). */
677 return default_entry
;
680 current_entry
= default_entry
;
683 menu_init (current_entry
, menu
, nested
);
685 /* Initialize the time. */
686 saved_time
= grub_get_time_ms ();
688 timeout
= grub_menu_get_timeout ();
691 menu_print_timeout (timeout
);
698 timeout
= grub_menu_get_timeout ();
700 if (grub_normal_exit_level
)
703 if (timeout
> 0 && has_second_elapsed (&saved_time
))
706 grub_menu_set_timeout (timeout
);
707 menu_print_timeout (timeout
);
712 grub_env_unset ("timeout");
715 return default_entry
;
718 c
= grub_getkey_noblock ();
720 /* Negative values are returned on error. */
721 if ((c
!= GRUB_TERM_NO_KEY
) && (c
> 0))
725 grub_env_unset ("timeout");
726 grub_env_unset ("fallback");
732 case GRUB_TERM_KEY_HOME
:
733 case GRUB_TERM_CTRL
| 'a':
735 menu_set_chosen_entry (current_entry
);
738 case GRUB_TERM_KEY_END
:
739 case GRUB_TERM_CTRL
| 'e':
740 current_entry
= menu
->size
- 1;
741 menu_set_chosen_entry (current_entry
);
744 case GRUB_TERM_KEY_UP
:
745 case GRUB_TERM_CTRL
| 'p':
747 if (current_entry
> 0)
749 menu_set_chosen_entry (current_entry
);
752 case GRUB_TERM_CTRL
| 'n':
753 case GRUB_TERM_KEY_DOWN
:
755 if (current_entry
< menu
->size
- 1)
757 menu_set_chosen_entry (current_entry
);
760 case GRUB_TERM_CTRL
| 'g':
761 case GRUB_TERM_KEY_PPAGE
:
762 if (current_entry
< GRUB_MENU_PAGE_SIZE
)
765 current_entry
-= GRUB_MENU_PAGE_SIZE
;
766 menu_set_chosen_entry (current_entry
);
769 case GRUB_TERM_CTRL
| 'c':
770 case GRUB_TERM_KEY_NPAGE
:
771 if (current_entry
+ GRUB_MENU_PAGE_SIZE
< menu
->size
)
772 current_entry
+= GRUB_MENU_PAGE_SIZE
;
774 current_entry
= menu
->size
- 1;
775 menu_set_chosen_entry (current_entry
);
780 // case GRUB_TERM_KEY_RIGHT:
781 case GRUB_TERM_CTRL
| 'f':
784 return current_entry
;
787 if (nested
&& 0 == g_ventoy_suppress_esc
)
796 grub_cmdline_run (1, 0);
802 grub_menu_entry_t e
= grub_menu_get_entry (menu
, current_entry
);
804 grub_menu_entry_run (e
);
808 case GRUB_TERM_KEY_F2
:
810 if (0 == g_ventoy_fn_mutex
) {
811 cmdstr
= grub_env_get("VTOY_F2_CMD");
815 g_ventoy_fn_mutex
= 1;
816 grub_script_execute_sourcecode(cmdstr
);
817 g_ventoy_fn_mutex
= 0;
822 case GRUB_TERM_KEY_F3
:
824 if (0 == g_ventoy_fn_mutex
) {
825 cmdstr
= grub_env_get("VTOY_F3_CMD");
829 grub_script_execute_sourcecode(cmdstr
);
834 case GRUB_TERM_KEY_F4
:
836 if (0 == g_ventoy_fn_mutex
) {
837 cmdstr
= grub_env_get("VTOY_F4_CMD");
841 g_ventoy_fn_mutex
= 1;
842 grub_script_execute_sourcecode(cmdstr
);
843 g_ventoy_fn_mutex
= 0;
848 case GRUB_TERM_KEY_F5
:
850 if (0 == g_ventoy_fn_mutex
) {
851 cmdstr
= grub_env_get("VTOY_F5_CMD");
855 g_ventoy_fn_mutex
= 1;
856 grub_script_execute_sourcecode(cmdstr
);
857 g_ventoy_fn_mutex
= 0;
862 case GRUB_TERM_KEY_F6
:
864 if (0 == g_ventoy_fn_mutex
) {
865 cmdstr
= grub_env_get("VTOY_F6_CMD");
869 g_ventoy_fn_mutex
= 1;
870 grub_script_execute_sourcecode(cmdstr
);
871 g_ventoy_fn_mutex
= 0;
876 case GRUB_TERM_KEY_F7
:
878 if (g_ventoy_terminal_output
== 0)
880 grub_script_execute_sourcecode("terminal_output console");
881 g_ventoy_terminal_output
= 1;
885 grub_script_execute_sourcecode("terminal_output gfxterm");
886 g_ventoy_terminal_output
= 0;
889 case GRUB_TERM_KEY_F1
:
892 g_ventoy_memdisk_mode
= 1 - g_ventoy_memdisk_mode
;
893 g_ventoy_menu_refresh
= 1;
896 case (GRUB_TERM_CTRL
| 'i'):
898 g_ventoy_iso_raw
= 1 - g_ventoy_iso_raw
;
899 g_ventoy_menu_refresh
= 1;
902 case (GRUB_TERM_CTRL
| 'r'):
904 g_ventoy_grub2_mode
= 1 - g_ventoy_grub2_mode
;
905 g_ventoy_menu_refresh
= 1;
908 case (GRUB_TERM_CTRL
| 'w'):
910 g_ventoy_wimboot_mode
= 1 - g_ventoy_wimboot_mode
;
911 g_ventoy_menu_refresh
= 1;
914 case (GRUB_TERM_CTRL
| 'u'):
916 g_ventoy_iso_uefi_drv
= 1 - g_ventoy_iso_uefi_drv
;
917 g_ventoy_menu_refresh
= 1;
924 entry
= get_entry_index_by_hotkey (menu
, c
);
937 /* Never reach here. */
940 /* Callback invoked immediately before a menu entry is executed. */
942 notify_booting (grub_menu_entry_t entry
,
943 void *userdata
__attribute__((unused
)))
946 grub_printf_ (N_("Booting `%s'"), entry
->title
);
947 grub_printf ("\n\n");
950 /* Callback invoked when a default menu entry executed because of a timeout
951 has failed and an attempt will be made to execute the next fallback
954 notify_fallback (grub_menu_entry_t entry
,
955 void *userdata
__attribute__((unused
)))
958 grub_printf_ (N_("Falling back to `%s'"), entry
->title
);
959 grub_printf ("\n\n");
960 grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS
);
963 /* Callback invoked when a menu entry has failed and there is no remaining
964 fallback entry to attempt. */
966 notify_execution_failure (void *userdata
__attribute__((unused
)))
968 if (grub_errno
!= GRUB_ERR_NONE
)
971 grub_errno
= GRUB_ERR_NONE
;
974 grub_printf_ (N_("Failed to boot both default and fallback entries.\n"));
975 grub_wait_after_message ();
978 /* Callbacks used by the text menu to provide user feedback when menu entries
980 static struct grub_menu_execute_callback execution_callback
=
982 .notify_booting
= notify_booting
,
983 .notify_fallback
= notify_fallback
,
984 .notify_failure
= notify_execution_failure
988 show_menu (grub_menu_t menu
, int nested
, int autobooted
)
991 def
= grub_env_get("VTOY_DEFAULT_IMAGE");
999 boot_entry
= run_menu (menu
, nested
, &auto_boot
);
1003 if (auto_boot
&& def
&& grub_strcmp(def
, "VTOY_EXIT") == 0) {
1007 if (autobooted
== 0 && auto_boot
== 0) {
1008 g_ventoy_last_entry
= boot_entry
;
1009 if (g_ventoy_menu_esc
)
1013 e
= grub_menu_get_entry (menu
, boot_entry
);
1015 continue; /* Menu is empty. */
1017 if (2 == e
->argc
&& e
->args
&& e
->args
[1] && grub_strncmp(e
->args
[1], "VTOY_RET", 8) == 0)
1023 grub_menu_execute_with_fallback (menu
, e
, autobooted
,
1024 &execution_callback
, 0);
1026 grub_menu_execute_entry (e
, 0);
1030 if (2 == e
->argc
&& e
->args
&& e
->args
[1] && grub_strncmp(e
->args
[1], "VTOY_RUN_RET", 12) == 0)
1034 return GRUB_ERR_NONE
;
1038 grub_show_menu (grub_menu_t menu
, int nested
, int autoboot
)
1040 grub_err_t err1
, err2
;
1044 err1
= show_menu (menu
, nested
, autoboot
);
1046 grub_print_error ();
1048 if (grub_normal_exit_level
)
1051 err2
= grub_auth_check_authentication (NULL
);
1054 grub_print_error ();
1055 grub_errno
= GRUB_ERR_NONE
;