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_iso_uefi_drv
= 0;
42 int g_ventoy_last_entry
= -1;
43 int g_ventoy_suppress_esc
= 0;
44 int g_ventoy_menu_esc
= 0;
45 int g_ventoy_fn_mutex
= 0;
46 int g_ventoy_terminal_output
= 0;
48 /* Time to delay after displaying an error message about a default/fallback
49 entry failing to boot. */
50 #define DEFAULT_ENTRY_ERROR_DELAY_MS 2500
52 grub_err_t (*grub_gfxmenu_try_hook
) (int entry
, grub_menu_t menu
,
57 TIMEOUT_STYLE_COUNTDOWN
,
61 struct timeout_style_name
{
63 enum timeout_style style
;
64 } timeout_style_names
[] = {
65 {"menu", TIMEOUT_STYLE_MENU
},
66 {"countdown", TIMEOUT_STYLE_COUNTDOWN
},
67 {"hidden", TIMEOUT_STYLE_HIDDEN
},
71 /* Wait until the user pushes any key so that the user
72 can see what happened. */
74 grub_wait_after_message (void)
76 grub_uint64_t endtime
;
78 grub_printf_ (N_("Press any key to continue..."));
81 endtime
= grub_get_time_ms () + 10000;
83 while (grub_get_time_ms () < endtime
84 && grub_getkey_noblock () == GRUB_TERM_NO_KEY
);
89 /* Get a menu entry by its index in the entry list. */
91 grub_menu_get_entry (grub_menu_t menu
, int no
)
95 for (e
= menu
->entry_list
; e
&& no
> 0; e
= e
->next
, no
--)
101 /* Get the index of a menu entry associated with a given hotkey, or -1. */
103 get_entry_index_by_hotkey (grub_menu_t menu
, int hotkey
)
105 grub_menu_entry_t entry
;
108 for (i
= 0, entry
= menu
->entry_list
; i
< menu
->size
;
109 i
++, entry
= entry
->next
)
110 if (entry
->hotkey
== hotkey
)
116 /* Return the timeout style. If the variable "timeout_style" is not set or
117 invalid, default to TIMEOUT_STYLE_MENU. */
118 static enum timeout_style
119 get_timeout_style (void)
122 struct timeout_style_name
*style_name
;
124 val
= grub_env_get ("timeout_style");
126 return TIMEOUT_STYLE_MENU
;
128 for (style_name
= timeout_style_names
; style_name
->name
; style_name
++)
129 if (grub_strcmp (style_name
->name
, val
) == 0)
130 return style_name
->style
;
132 return TIMEOUT_STYLE_MENU
;
135 /* Return the current timeout. If the variable "timeout" is not set or
136 invalid, return -1. */
138 grub_menu_get_timeout (void)
143 val
= grub_env_get ("timeout");
149 timeout
= (int) grub_strtoul (val
, 0, 0);
151 /* If the value is invalid, unset the variable. */
152 if (grub_errno
!= GRUB_ERR_NONE
)
154 grub_env_unset ("timeout");
155 grub_errno
= GRUB_ERR_NONE
;
164 /* Set current timeout in the variable "timeout". */
166 grub_menu_set_timeout (int timeout
)
168 /* Ignore TIMEOUT if it is zero, because it will be unset really soon. */
173 grub_snprintf (buf
, sizeof (buf
), "%d", timeout
);
174 grub_env_set ("timeout", buf
);
178 /* Get the first entry number from the value of the environment variable NAME,
179 which is a space-separated list of non-negative integers. The entry number
180 which is returned is stripped from the value of NAME. If no entry number
181 can be found, -1 is returned. */
183 get_and_remove_first_entry_number (const char *name
)
189 val
= grub_env_get (name
);
195 entry
= (int) grub_strtoul (val
, &tail
, 0);
197 if (grub_errno
== GRUB_ERR_NONE
)
199 /* Skip whitespace to find the next digit. */
200 while (*tail
&& grub_isspace (*tail
))
202 grub_env_set (name
, tail
);
206 grub_env_unset (name
);
207 grub_errno
= GRUB_ERR_NONE
;
216 /* Run a menu entry. */
218 grub_menu_execute_entry(grub_menu_entry_t entry
, int auto_boot
)
220 grub_err_t err
= GRUB_ERR_NONE
;
222 grub_menu_t menu
= NULL
;
223 char *optr
, *buf
, *oldchosen
= NULL
, *olddefault
= NULL
;
224 const char *ptr
, *chosen
, *def
;
227 if (entry
->restricted
)
228 err
= grub_auth_check_authentication (entry
->users
);
233 grub_errno
= GRUB_ERR_NONE
;
237 errs_before
= grub_err_printed_errors
;
239 chosen
= grub_env_get ("chosen");
240 def
= grub_env_get ("default");
244 grub_env_context_open ();
245 menu
= grub_zalloc (sizeof (*menu
));
248 grub_env_set_menu (menu
);
250 grub_env_set ("timeout", "0");
253 for (ptr
= entry
->id
; *ptr
; ptr
++)
254 sz
+= (*ptr
== '>') ? 2 : 1;
257 oldchosen
= grub_strdup (chosen
);
263 olddefault
= grub_strdup (def
);
269 sz
+= grub_strlen (chosen
);
271 buf
= grub_malloc (sz
);
279 optr
= grub_stpcpy (optr
, chosen
);
282 for (ptr
= entry
->id
; *ptr
; ptr
++)
289 grub_env_set ("chosen", buf
);
290 grub_env_export ("chosen");
294 for (ptr
= def
; ptr
&& *ptr
; ptr
++)
296 if (ptr
[0] == '>' && ptr
[1] == '>')
305 if (ptr
&& ptr
[0] && ptr
[1])
306 grub_env_set ("default", ptr
+ 1);
308 grub_env_unset ("default");
310 grub_script_execute_new_scope (entry
->sourcecode
, entry
->argc
, entry
->args
);
312 if (errs_before
!= grub_err_printed_errors
)
313 grub_wait_after_message ();
315 errs_before
= grub_err_printed_errors
;
317 if (grub_errno
== GRUB_ERR_NONE
&& grub_loader_is_loaded ())
318 /* Implicit execution of boot, only if something is loaded. */
319 grub_command_execute ("boot", 0, 0);
321 if (errs_before
!= grub_err_printed_errors
)
322 grub_wait_after_message ();
326 if (menu
&& menu
->size
)
328 grub_show_menu (menu
, 1, auto_boot
);
329 grub_normal_free_menu (menu
);
331 grub_env_context_close ();
334 grub_env_set ("chosen", oldchosen
);
336 grub_env_unset ("chosen");
338 grub_env_set ("default", olddefault
);
340 grub_env_unset ("default");
341 grub_env_unset ("timeout");
344 /* Execute ENTRY from the menu MENU, falling back to entries specified
345 in the environment variable "fallback" if it fails. CALLBACK is a
346 pointer to a struct of function pointers which are used to allow the
347 caller provide feedback to the user. */
349 grub_menu_execute_with_fallback (grub_menu_t menu
,
350 grub_menu_entry_t entry
,
352 grub_menu_execute_callback_t callback
,
357 callback
->notify_booting (entry
, callback_data
);
359 grub_menu_execute_entry (entry
, 1);
361 /* Deal with fallback entries. */
362 while ((fallback_entry
= get_and_remove_first_entry_number ("fallback"))
366 grub_errno
= GRUB_ERR_NONE
;
368 entry
= grub_menu_get_entry (menu
, fallback_entry
);
369 callback
->notify_fallback (entry
, callback_data
);
370 grub_menu_execute_entry (entry
, 1);
371 /* If the function call to execute the entry returns at all, then this is
372 taken to indicate a boot failure. For menu entries that do something
373 other than actually boot an operating system, this could assume
374 incorrectly that something failed. */
378 callback
->notify_failure (callback_data
);
381 static struct grub_menu_viewer
*viewers
;
384 menu_set_chosen_entry (int entry
)
386 struct grub_menu_viewer
*cur
;
387 for (cur
= viewers
; cur
; cur
= cur
->next
)
388 cur
->set_chosen_entry (entry
, cur
->data
);
392 menu_print_timeout (int timeout
)
394 struct grub_menu_viewer
*cur
;
395 for (cur
= viewers
; cur
; cur
= cur
->next
)
396 cur
->print_timeout (timeout
, cur
->data
);
402 struct grub_menu_viewer
*cur
, *next
;
403 for (cur
= viewers
; cur
; cur
= next
)
406 cur
->fini (cur
->data
);
413 menu_init (int entry
, grub_menu_t menu
, int nested
)
415 struct grub_term_output
*term
;
418 FOR_ACTIVE_TERM_OUTPUTS(term
)
419 if (term
->fullscreen
)
421 if (grub_env_get ("theme"))
423 if (!grub_gfxmenu_try_hook
)
425 grub_dl_load ("gfxmenu");
428 if (grub_gfxmenu_try_hook
)
431 err
= grub_gfxmenu_try_hook (entry
, menu
, nested
);
439 grub_error (GRUB_ERR_BAD_MODULE
,
440 N_("module `%s' isn't loaded"),
443 grub_wait_after_message ();
445 grub_errno
= GRUB_ERR_NONE
;
450 FOR_ACTIVE_TERM_OUTPUTS(term
)
454 if (grub_strcmp (term
->name
, "gfxterm") == 0 && gfxmenu
)
457 err
= grub_menu_try_text (term
, entry
, menu
, nested
);
461 grub_errno
= GRUB_ERR_NONE
;
468 struct grub_menu_viewer
*cur
;
469 for (cur
= viewers
; cur
; cur
= cur
->next
)
470 cur
->clear_timeout (cur
->data
);
474 grub_menu_register_viewer (struct grub_menu_viewer
*viewer
)
476 viewer
->next
= viewers
;
481 menuentry_eq (const char *id
, const char *spec
)
483 const char *ptr1
, *ptr2
;
488 if (*ptr2
== '>' && ptr2
[1] != '>' && *ptr1
== 0)
490 if (*ptr2
== '>' && ptr2
[1] != '>')
504 /* Get the entry number from the variable NAME. */
506 get_entry_number (grub_menu_t menu
, const char *name
)
511 val
= grub_env_get (name
);
517 entry
= (int) grub_strtoul (val
, 0, 0);
519 if (grub_errno
== GRUB_ERR_BAD_NUMBER
)
521 /* See if the variable matches the title of a menu entry. */
522 grub_menu_entry_t e
= menu
->entry_list
;
525 grub_errno
= GRUB_ERR_NONE
;
529 if (menuentry_eq (e
->title
, val
)
530 || menuentry_eq (e
->id
, val
))
542 if (grub_errno
!= GRUB_ERR_NONE
)
544 grub_errno
= GRUB_ERR_NONE
;
553 /* Check whether a second has elapsed since the last tick. If so, adjust
554 the timer and return 1; otherwise, return 0. */
556 has_second_elapsed (grub_uint64_t
*saved_time
)
558 grub_uint64_t current_time
;
560 current_time
= grub_get_time_ms ();
561 if (current_time
- *saved_time
>= 1000)
563 *saved_time
= current_time
;
571 print_countdown (struct grub_term_coordinate
*pos
, int n
)
573 grub_term_restore_pos (pos
);
574 /* NOTE: Do not remove the trailing space characters.
575 They are required to clear the line. */
576 grub_printf ("%d ", n
);
580 #define GRUB_MENU_PAGE_SIZE 10
582 /* Show the menu and handle menu entry selection. Returns the menu entry
583 index that should be executed or -1 if no entry should be executed (e.g.,
584 Esc pressed to exit a sub-menu or switching menu viewers).
585 If the return value is not -1, then *AUTO_BOOT is nonzero iff the menu
586 entry to be executed is a result of an automatic default selection because
589 run_menu (grub_menu_t menu
, int nested
, int *auto_boot
)
592 grub_uint64_t saved_time
;
593 int default_entry
,current_entry
;
595 enum timeout_style timeout_style
;
597 default_entry
= get_entry_number (menu
, "default");
599 if (g_ventoy_suppress_esc
)
601 else if (g_ventoy_last_entry
>= 0 && g_ventoy_last_entry
< menu
->size
) {
602 default_entry
= g_ventoy_last_entry
;
604 /* If DEFAULT_ENTRY is not within the menu entries, fall back to
606 else if (default_entry
< 0 || default_entry
>= menu
->size
)
609 timeout
= grub_menu_get_timeout ();
611 /* If there is no timeout, the "countdown" and "hidden" styles result in
612 the system doing nothing and providing no or very little indication
613 why. Technically this is what the user asked for, but it's not very
614 useful and likely to be a source of confusion, so we disallow this. */
615 grub_env_unset ("timeout_style");
617 timeout_style
= get_timeout_style ();
619 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
620 || timeout_style
== TIMEOUT_STYLE_HIDDEN
)
622 static struct grub_term_coordinate
*pos
;
625 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
&& timeout
)
627 pos
= grub_term_save_pos ();
628 print_countdown (pos
, timeout
);
631 /* Enter interruptible sleep until Escape or a menu hotkey is pressed,
632 or the timeout expires. */
633 saved_time
= grub_get_time_ms ();
638 key
= grub_getkey_noblock ();
639 if (key
!= GRUB_TERM_NO_KEY
)
641 entry
= get_entry_index_by_hotkey (menu
, key
);
645 if (key
== GRUB_TERM_ESC
)
651 if (timeout
> 0 && has_second_elapsed (&saved_time
))
654 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
)
655 print_countdown (pos
, timeout
);
659 /* We will fall through to auto-booting the default entry. */
663 grub_env_unset ("timeout");
664 grub_env_unset ("timeout_style");
672 /* If timeout is 0, drawing is pointless (and ugly). */
676 return default_entry
;
679 current_entry
= default_entry
;
682 menu_init (current_entry
, menu
, nested
);
684 /* Initialize the time. */
685 saved_time
= grub_get_time_ms ();
687 timeout
= grub_menu_get_timeout ();
690 menu_print_timeout (timeout
);
697 timeout
= grub_menu_get_timeout ();
699 if (grub_normal_exit_level
)
702 if (timeout
> 0 && has_second_elapsed (&saved_time
))
705 grub_menu_set_timeout (timeout
);
706 menu_print_timeout (timeout
);
711 grub_env_unset ("timeout");
714 return default_entry
;
717 c
= grub_getkey_noblock ();
719 /* Negative values are returned on error. */
720 if ((c
!= GRUB_TERM_NO_KEY
) && (c
> 0))
724 grub_env_unset ("timeout");
725 grub_env_unset ("fallback");
731 case GRUB_TERM_KEY_HOME
:
732 case GRUB_TERM_CTRL
| 'a':
734 menu_set_chosen_entry (current_entry
);
737 case GRUB_TERM_KEY_END
:
738 case GRUB_TERM_CTRL
| 'e':
739 current_entry
= menu
->size
- 1;
740 menu_set_chosen_entry (current_entry
);
743 case GRUB_TERM_KEY_UP
:
744 case GRUB_TERM_CTRL
| 'p':
746 if (current_entry
> 0)
748 menu_set_chosen_entry (current_entry
);
751 case GRUB_TERM_CTRL
| 'n':
752 case GRUB_TERM_KEY_DOWN
:
754 if (current_entry
< menu
->size
- 1)
756 menu_set_chosen_entry (current_entry
);
759 case GRUB_TERM_CTRL
| 'g':
760 case GRUB_TERM_KEY_PPAGE
:
761 if (current_entry
< GRUB_MENU_PAGE_SIZE
)
764 current_entry
-= GRUB_MENU_PAGE_SIZE
;
765 menu_set_chosen_entry (current_entry
);
768 case GRUB_TERM_CTRL
| 'c':
769 case GRUB_TERM_KEY_NPAGE
:
770 if (current_entry
+ GRUB_MENU_PAGE_SIZE
< menu
->size
)
771 current_entry
+= GRUB_MENU_PAGE_SIZE
;
773 current_entry
= menu
->size
- 1;
774 menu_set_chosen_entry (current_entry
);
779 // case GRUB_TERM_KEY_RIGHT:
780 case GRUB_TERM_CTRL
| 'f':
783 return current_entry
;
786 if (nested
&& 0 == g_ventoy_suppress_esc
)
795 grub_cmdline_run (1, 0);
801 grub_menu_entry_t e
= grub_menu_get_entry (menu
, current_entry
);
803 grub_menu_entry_run (e
);
807 case GRUB_TERM_KEY_F2
:
809 if (0 == g_ventoy_fn_mutex
) {
810 cmdstr
= grub_env_get("VTOY_F2_CMD");
814 g_ventoy_fn_mutex
= 1;
815 grub_script_execute_sourcecode(cmdstr
);
816 g_ventoy_fn_mutex
= 0;
821 case GRUB_TERM_KEY_F3
:
823 if (0 == g_ventoy_fn_mutex
) {
824 cmdstr
= grub_env_get("VTOY_F3_CMD");
828 grub_script_execute_sourcecode(cmdstr
);
833 case GRUB_TERM_KEY_F4
:
835 if (0 == g_ventoy_fn_mutex
) {
836 cmdstr
= grub_env_get("VTOY_F4_CMD");
840 g_ventoy_fn_mutex
= 1;
841 grub_script_execute_sourcecode(cmdstr
);
842 g_ventoy_fn_mutex
= 0;
847 case GRUB_TERM_KEY_F5
:
849 if (0 == g_ventoy_fn_mutex
) {
850 cmdstr
= grub_env_get("VTOY_F5_CMD");
854 g_ventoy_fn_mutex
= 1;
855 grub_script_execute_sourcecode(cmdstr
);
856 g_ventoy_fn_mutex
= 0;
861 case GRUB_TERM_KEY_F6
:
863 if (0 == g_ventoy_fn_mutex
) {
864 cmdstr
= grub_env_get("VTOY_F6_CMD");
868 g_ventoy_fn_mutex
= 1;
869 grub_script_execute_sourcecode(cmdstr
);
870 g_ventoy_fn_mutex
= 0;
875 case GRUB_TERM_KEY_F7
:
877 if (g_ventoy_terminal_output
== 0)
879 grub_script_execute_sourcecode("terminal_output console");
880 g_ventoy_terminal_output
= 1;
884 grub_script_execute_sourcecode("terminal_output gfxterm");
885 g_ventoy_terminal_output
= 0;
888 case GRUB_TERM_KEY_F1
:
891 g_ventoy_memdisk_mode
= 1 - g_ventoy_memdisk_mode
;
892 g_ventoy_menu_refresh
= 1;
895 case (GRUB_TERM_CTRL
| 'i'):
897 g_ventoy_iso_raw
= 1 - g_ventoy_iso_raw
;
898 g_ventoy_menu_refresh
= 1;
901 case (GRUB_TERM_CTRL
| 'r'):
903 g_ventoy_grub2_mode
= 1 - g_ventoy_grub2_mode
;
904 g_ventoy_menu_refresh
= 1;
907 case (GRUB_TERM_CTRL
| 'u'):
909 g_ventoy_iso_uefi_drv
= 1 - g_ventoy_iso_uefi_drv
;
910 g_ventoy_menu_refresh
= 1;
917 entry
= get_entry_index_by_hotkey (menu
, c
);
930 /* Never reach here. */
933 /* Callback invoked immediately before a menu entry is executed. */
935 notify_booting (grub_menu_entry_t entry
,
936 void *userdata
__attribute__((unused
)))
939 grub_printf_ (N_("Booting `%s'"), entry
->title
);
940 grub_printf ("\n\n");
943 /* Callback invoked when a default menu entry executed because of a timeout
944 has failed and an attempt will be made to execute the next fallback
947 notify_fallback (grub_menu_entry_t entry
,
948 void *userdata
__attribute__((unused
)))
951 grub_printf_ (N_("Falling back to `%s'"), entry
->title
);
952 grub_printf ("\n\n");
953 grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS
);
956 /* Callback invoked when a menu entry has failed and there is no remaining
957 fallback entry to attempt. */
959 notify_execution_failure (void *userdata
__attribute__((unused
)))
961 if (grub_errno
!= GRUB_ERR_NONE
)
964 grub_errno
= GRUB_ERR_NONE
;
967 grub_printf_ (N_("Failed to boot both default and fallback entries.\n"));
968 grub_wait_after_message ();
971 /* Callbacks used by the text menu to provide user feedback when menu entries
973 static struct grub_menu_execute_callback execution_callback
=
975 .notify_booting
= notify_booting
,
976 .notify_fallback
= notify_fallback
,
977 .notify_failure
= notify_execution_failure
981 show_menu (grub_menu_t menu
, int nested
, int autobooted
)
984 def
= grub_env_get("VTOY_DEFAULT_IMAGE");
992 boot_entry
= run_menu (menu
, nested
, &auto_boot
);
996 if (auto_boot
&& def
&& grub_strcmp(def
, "VTOY_EXIT") == 0) {
1000 if (autobooted
== 0 && auto_boot
== 0) {
1001 g_ventoy_last_entry
= boot_entry
;
1002 if (g_ventoy_menu_esc
)
1006 e
= grub_menu_get_entry (menu
, boot_entry
);
1008 continue; /* Menu is empty. */
1010 if (2 == e
->argc
&& e
->args
&& e
->args
[1] && grub_strncmp(e
->args
[1], "VTOY_RET", 8) == 0)
1016 grub_menu_execute_with_fallback (menu
, e
, autobooted
,
1017 &execution_callback
, 0);
1019 grub_menu_execute_entry (e
, 0);
1023 if (2 == e
->argc
&& e
->args
&& e
->args
[1] && grub_strncmp(e
->args
[1], "VTOY_RUN_RET", 12) == 0)
1027 return GRUB_ERR_NONE
;
1031 grub_show_menu (grub_menu_t menu
, int nested
, int autoboot
)
1033 grub_err_t err1
, err2
;
1037 err1
= show_menu (menu
, nested
, autoboot
);
1039 grub_print_error ();
1041 if (grub_normal_exit_level
)
1044 err2
= grub_auth_check_authentication (NULL
);
1047 grub_print_error ();
1048 grub_errno
= GRUB_ERR_NONE
;