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;
45 int g_ventoy_terminal_output
= 0;
47 /* Time to delay after displaying an error message about a default/fallback
48 entry failing to boot. */
49 #define DEFAULT_ENTRY_ERROR_DELAY_MS 2500
51 grub_err_t (*grub_gfxmenu_try_hook
) (int entry
, grub_menu_t menu
,
56 TIMEOUT_STYLE_COUNTDOWN
,
60 struct timeout_style_name
{
62 enum timeout_style style
;
63 } timeout_style_names
[] = {
64 {"menu", TIMEOUT_STYLE_MENU
},
65 {"countdown", TIMEOUT_STYLE_COUNTDOWN
},
66 {"hidden", TIMEOUT_STYLE_HIDDEN
},
70 /* Wait until the user pushes any key so that the user
71 can see what happened. */
73 grub_wait_after_message (void)
75 grub_uint64_t endtime
;
77 grub_printf_ (N_("Press any key to continue..."));
80 endtime
= grub_get_time_ms () + 10000;
82 while (grub_get_time_ms () < endtime
83 && grub_getkey_noblock () == GRUB_TERM_NO_KEY
);
88 /* Get a menu entry by its index in the entry list. */
90 grub_menu_get_entry (grub_menu_t menu
, int no
)
94 for (e
= menu
->entry_list
; e
&& no
> 0; e
= e
->next
, no
--)
100 /* Get the index of a menu entry associated with a given hotkey, or -1. */
102 get_entry_index_by_hotkey (grub_menu_t menu
, int hotkey
)
104 grub_menu_entry_t entry
;
107 for (i
= 0, entry
= menu
->entry_list
; i
< menu
->size
;
108 i
++, entry
= entry
->next
)
109 if (entry
->hotkey
== hotkey
)
115 /* Return the timeout style. If the variable "timeout_style" is not set or
116 invalid, default to TIMEOUT_STYLE_MENU. */
117 static enum timeout_style
118 get_timeout_style (void)
121 struct timeout_style_name
*style_name
;
123 val
= grub_env_get ("timeout_style");
125 return TIMEOUT_STYLE_MENU
;
127 for (style_name
= timeout_style_names
; style_name
->name
; style_name
++)
128 if (grub_strcmp (style_name
->name
, val
) == 0)
129 return style_name
->style
;
131 return TIMEOUT_STYLE_MENU
;
134 /* Return the current timeout. If the variable "timeout" is not set or
135 invalid, return -1. */
137 grub_menu_get_timeout (void)
142 val
= grub_env_get ("timeout");
148 timeout
= (int) grub_strtoul (val
, 0, 0);
150 /* If the value is invalid, unset the variable. */
151 if (grub_errno
!= GRUB_ERR_NONE
)
153 grub_env_unset ("timeout");
154 grub_errno
= GRUB_ERR_NONE
;
163 /* Set current timeout in the variable "timeout". */
165 grub_menu_set_timeout (int timeout
)
167 /* Ignore TIMEOUT if it is zero, because it will be unset really soon. */
172 grub_snprintf (buf
, sizeof (buf
), "%d", timeout
);
173 grub_env_set ("timeout", buf
);
177 /* Get the first entry number from the value of the environment variable NAME,
178 which is a space-separated list of non-negative integers. The entry number
179 which is returned is stripped from the value of NAME. If no entry number
180 can be found, -1 is returned. */
182 get_and_remove_first_entry_number (const char *name
)
188 val
= grub_env_get (name
);
194 entry
= (int) grub_strtoul (val
, &tail
, 0);
196 if (grub_errno
== GRUB_ERR_NONE
)
198 /* Skip whitespace to find the next digit. */
199 while (*tail
&& grub_isspace (*tail
))
201 grub_env_set (name
, tail
);
205 grub_env_unset (name
);
206 grub_errno
= GRUB_ERR_NONE
;
215 /* Run a menu entry. */
217 grub_menu_execute_entry(grub_menu_entry_t entry
, int auto_boot
)
219 grub_err_t err
= GRUB_ERR_NONE
;
221 grub_menu_t menu
= NULL
;
222 char *optr
, *buf
, *oldchosen
= NULL
, *olddefault
= NULL
;
223 const char *ptr
, *chosen
, *def
;
226 if (entry
->restricted
)
227 err
= grub_auth_check_authentication (entry
->users
);
232 grub_errno
= GRUB_ERR_NONE
;
236 errs_before
= grub_err_printed_errors
;
238 chosen
= grub_env_get ("chosen");
239 def
= grub_env_get ("default");
243 grub_env_context_open ();
244 menu
= grub_zalloc (sizeof (*menu
));
247 grub_env_set_menu (menu
);
249 grub_env_set ("timeout", "0");
252 for (ptr
= entry
->id
; *ptr
; ptr
++)
253 sz
+= (*ptr
== '>') ? 2 : 1;
256 oldchosen
= grub_strdup (chosen
);
262 olddefault
= grub_strdup (def
);
268 sz
+= grub_strlen (chosen
);
270 buf
= grub_malloc (sz
);
278 optr
= grub_stpcpy (optr
, chosen
);
281 for (ptr
= entry
->id
; *ptr
; ptr
++)
288 grub_env_set ("chosen", buf
);
289 grub_env_export ("chosen");
293 for (ptr
= def
; ptr
&& *ptr
; ptr
++)
295 if (ptr
[0] == '>' && ptr
[1] == '>')
304 if (ptr
&& ptr
[0] && ptr
[1])
305 grub_env_set ("default", ptr
+ 1);
307 grub_env_unset ("default");
309 grub_script_execute_new_scope (entry
->sourcecode
, entry
->argc
, entry
->args
);
311 if (errs_before
!= grub_err_printed_errors
)
312 grub_wait_after_message ();
314 errs_before
= grub_err_printed_errors
;
316 if (grub_errno
== GRUB_ERR_NONE
&& grub_loader_is_loaded ())
317 /* Implicit execution of boot, only if something is loaded. */
318 grub_command_execute ("boot", 0, 0);
320 if (errs_before
!= grub_err_printed_errors
)
321 grub_wait_after_message ();
325 if (menu
&& menu
->size
)
327 grub_show_menu (menu
, 1, auto_boot
);
328 grub_normal_free_menu (menu
);
330 grub_env_context_close ();
333 grub_env_set ("chosen", oldchosen
);
335 grub_env_unset ("chosen");
337 grub_env_set ("default", olddefault
);
339 grub_env_unset ("default");
340 grub_env_unset ("timeout");
343 /* Execute ENTRY from the menu MENU, falling back to entries specified
344 in the environment variable "fallback" if it fails. CALLBACK is a
345 pointer to a struct of function pointers which are used to allow the
346 caller provide feedback to the user. */
348 grub_menu_execute_with_fallback (grub_menu_t menu
,
349 grub_menu_entry_t entry
,
351 grub_menu_execute_callback_t callback
,
356 callback
->notify_booting (entry
, callback_data
);
358 grub_menu_execute_entry (entry
, 1);
360 /* Deal with fallback entries. */
361 while ((fallback_entry
= get_and_remove_first_entry_number ("fallback"))
365 grub_errno
= GRUB_ERR_NONE
;
367 entry
= grub_menu_get_entry (menu
, fallback_entry
);
368 callback
->notify_fallback (entry
, callback_data
);
369 grub_menu_execute_entry (entry
, 1);
370 /* If the function call to execute the entry returns at all, then this is
371 taken to indicate a boot failure. For menu entries that do something
372 other than actually boot an operating system, this could assume
373 incorrectly that something failed. */
377 callback
->notify_failure (callback_data
);
380 static struct grub_menu_viewer
*viewers
;
383 menu_set_chosen_entry (int entry
)
385 struct grub_menu_viewer
*cur
;
386 for (cur
= viewers
; cur
; cur
= cur
->next
)
387 cur
->set_chosen_entry (entry
, cur
->data
);
391 menu_print_timeout (int timeout
)
393 struct grub_menu_viewer
*cur
;
394 for (cur
= viewers
; cur
; cur
= cur
->next
)
395 cur
->print_timeout (timeout
, cur
->data
);
401 struct grub_menu_viewer
*cur
, *next
;
402 for (cur
= viewers
; cur
; cur
= next
)
405 cur
->fini (cur
->data
);
412 menu_init (int entry
, grub_menu_t menu
, int nested
)
414 struct grub_term_output
*term
;
417 FOR_ACTIVE_TERM_OUTPUTS(term
)
418 if (term
->fullscreen
)
420 if (grub_env_get ("theme"))
422 if (!grub_gfxmenu_try_hook
)
424 grub_dl_load ("gfxmenu");
427 if (grub_gfxmenu_try_hook
)
430 err
= grub_gfxmenu_try_hook (entry
, menu
, nested
);
438 grub_error (GRUB_ERR_BAD_MODULE
,
439 N_("module `%s' isn't loaded"),
442 grub_wait_after_message ();
444 grub_errno
= GRUB_ERR_NONE
;
449 FOR_ACTIVE_TERM_OUTPUTS(term
)
453 if (grub_strcmp (term
->name
, "gfxterm") == 0 && gfxmenu
)
456 err
= grub_menu_try_text (term
, entry
, menu
, nested
);
460 grub_errno
= GRUB_ERR_NONE
;
467 struct grub_menu_viewer
*cur
;
468 for (cur
= viewers
; cur
; cur
= cur
->next
)
469 cur
->clear_timeout (cur
->data
);
473 grub_menu_register_viewer (struct grub_menu_viewer
*viewer
)
475 viewer
->next
= viewers
;
480 menuentry_eq (const char *id
, const char *spec
)
482 const char *ptr1
, *ptr2
;
487 if (*ptr2
== '>' && ptr2
[1] != '>' && *ptr1
== 0)
489 if (*ptr2
== '>' && ptr2
[1] != '>')
503 /* Get the entry number from the variable NAME. */
505 get_entry_number (grub_menu_t menu
, const char *name
)
510 val
= grub_env_get (name
);
516 entry
= (int) grub_strtoul (val
, 0, 0);
518 if (grub_errno
== GRUB_ERR_BAD_NUMBER
)
520 /* See if the variable matches the title of a menu entry. */
521 grub_menu_entry_t e
= menu
->entry_list
;
524 grub_errno
= GRUB_ERR_NONE
;
528 if (menuentry_eq (e
->title
, val
)
529 || menuentry_eq (e
->id
, val
))
541 if (grub_errno
!= GRUB_ERR_NONE
)
543 grub_errno
= GRUB_ERR_NONE
;
552 /* Check whether a second has elapsed since the last tick. If so, adjust
553 the timer and return 1; otherwise, return 0. */
555 has_second_elapsed (grub_uint64_t
*saved_time
)
557 grub_uint64_t current_time
;
559 current_time
= grub_get_time_ms ();
560 if (current_time
- *saved_time
>= 1000)
562 *saved_time
= current_time
;
570 print_countdown (struct grub_term_coordinate
*pos
, int n
)
572 grub_term_restore_pos (pos
);
573 /* NOTE: Do not remove the trailing space characters.
574 They are required to clear the line. */
575 grub_printf ("%d ", n
);
579 #define GRUB_MENU_PAGE_SIZE 10
581 /* Show the menu and handle menu entry selection. Returns the menu entry
582 index that should be executed or -1 if no entry should be executed (e.g.,
583 Esc pressed to exit a sub-menu or switching menu viewers).
584 If the return value is not -1, then *AUTO_BOOT is nonzero iff the menu
585 entry to be executed is a result of an automatic default selection because
588 run_menu (grub_menu_t menu
, int nested
, int *auto_boot
)
591 grub_uint64_t saved_time
;
592 int default_entry
,current_entry
;
594 enum timeout_style timeout_style
;
596 default_entry
= get_entry_number (menu
, "default");
598 if (g_ventoy_suppress_esc
)
600 else if (g_ventoy_last_entry
>= 0 && g_ventoy_last_entry
< menu
->size
) {
601 default_entry
= g_ventoy_last_entry
;
603 /* If DEFAULT_ENTRY is not within the menu entries, fall back to
605 else if (default_entry
< 0 || default_entry
>= menu
->size
)
608 timeout
= grub_menu_get_timeout ();
610 /* If there is no timeout, the "countdown" and "hidden" styles result in
611 the system doing nothing and providing no or very little indication
612 why. Technically this is what the user asked for, but it's not very
613 useful and likely to be a source of confusion, so we disallow this. */
614 grub_env_unset ("timeout_style");
616 timeout_style
= get_timeout_style ();
618 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
619 || timeout_style
== TIMEOUT_STYLE_HIDDEN
)
621 static struct grub_term_coordinate
*pos
;
624 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
&& timeout
)
626 pos
= grub_term_save_pos ();
627 print_countdown (pos
, timeout
);
630 /* Enter interruptible sleep until Escape or a menu hotkey is pressed,
631 or the timeout expires. */
632 saved_time
= grub_get_time_ms ();
637 key
= grub_getkey_noblock ();
638 if (key
!= GRUB_TERM_NO_KEY
)
640 entry
= get_entry_index_by_hotkey (menu
, key
);
644 if (key
== GRUB_TERM_ESC
)
650 if (timeout
> 0 && has_second_elapsed (&saved_time
))
653 if (timeout_style
== TIMEOUT_STYLE_COUNTDOWN
)
654 print_countdown (pos
, timeout
);
658 /* We will fall through to auto-booting the default entry. */
662 grub_env_unset ("timeout");
663 grub_env_unset ("timeout_style");
671 /* If timeout is 0, drawing is pointless (and ugly). */
675 return default_entry
;
678 current_entry
= default_entry
;
681 menu_init (current_entry
, menu
, nested
);
683 /* Initialize the time. */
684 saved_time
= grub_get_time_ms ();
686 timeout
= grub_menu_get_timeout ();
689 menu_print_timeout (timeout
);
696 timeout
= grub_menu_get_timeout ();
698 if (grub_normal_exit_level
)
701 if (timeout
> 0 && has_second_elapsed (&saved_time
))
704 grub_menu_set_timeout (timeout
);
705 menu_print_timeout (timeout
);
710 grub_env_unset ("timeout");
713 return default_entry
;
716 c
= grub_getkey_noblock ();
718 /* Negative values are returned on error. */
719 if ((c
!= GRUB_TERM_NO_KEY
) && (c
> 0))
723 grub_env_unset ("timeout");
724 grub_env_unset ("fallback");
730 case GRUB_TERM_KEY_HOME
:
731 case GRUB_TERM_CTRL
| 'a':
733 menu_set_chosen_entry (current_entry
);
736 case GRUB_TERM_KEY_END
:
737 case GRUB_TERM_CTRL
| 'e':
738 current_entry
= menu
->size
- 1;
739 menu_set_chosen_entry (current_entry
);
742 case GRUB_TERM_KEY_UP
:
743 case GRUB_TERM_CTRL
| 'p':
745 if (current_entry
> 0)
747 menu_set_chosen_entry (current_entry
);
750 case GRUB_TERM_CTRL
| 'n':
751 case GRUB_TERM_KEY_DOWN
:
753 if (current_entry
< menu
->size
- 1)
755 menu_set_chosen_entry (current_entry
);
758 case GRUB_TERM_CTRL
| 'g':
759 case GRUB_TERM_KEY_PPAGE
:
760 if (current_entry
< GRUB_MENU_PAGE_SIZE
)
763 current_entry
-= GRUB_MENU_PAGE_SIZE
;
764 menu_set_chosen_entry (current_entry
);
767 case GRUB_TERM_CTRL
| 'c':
768 case GRUB_TERM_KEY_NPAGE
:
769 if (current_entry
+ GRUB_MENU_PAGE_SIZE
< menu
->size
)
770 current_entry
+= GRUB_MENU_PAGE_SIZE
;
772 current_entry
= menu
->size
- 1;
773 menu_set_chosen_entry (current_entry
);
778 // case GRUB_TERM_KEY_RIGHT:
779 case GRUB_TERM_CTRL
| 'f':
782 return current_entry
;
785 if (nested
&& 0 == g_ventoy_suppress_esc
)
794 grub_cmdline_run (1, 0);
800 grub_menu_entry_t e
= grub_menu_get_entry (menu
, current_entry
);
802 grub_menu_entry_run (e
);
806 case GRUB_TERM_KEY_F2
:
808 if (0 == g_ventoy_fn_mutex
) {
809 cmdstr
= grub_env_get("VTOY_F2_CMD");
813 g_ventoy_fn_mutex
= 1;
814 grub_script_execute_sourcecode(cmdstr
);
815 g_ventoy_fn_mutex
= 0;
820 case GRUB_TERM_KEY_F3
:
822 if (0 == g_ventoy_fn_mutex
) {
823 cmdstr
= grub_env_get("VTOY_F3_CMD");
827 grub_script_execute_sourcecode(cmdstr
);
832 case GRUB_TERM_KEY_F4
:
834 if (0 == g_ventoy_fn_mutex
) {
835 cmdstr
= grub_env_get("VTOY_F4_CMD");
839 g_ventoy_fn_mutex
= 1;
840 grub_script_execute_sourcecode(cmdstr
);
841 g_ventoy_fn_mutex
= 0;
846 case GRUB_TERM_KEY_F5
:
848 if (0 == g_ventoy_fn_mutex
) {
849 cmdstr
= grub_env_get("VTOY_F5_CMD");
853 g_ventoy_fn_mutex
= 1;
854 grub_script_execute_sourcecode(cmdstr
);
855 g_ventoy_fn_mutex
= 0;
860 case GRUB_TERM_KEY_F6
:
862 if (0 == g_ventoy_fn_mutex
) {
863 cmdstr
= grub_env_get("VTOY_F6_CMD");
867 g_ventoy_fn_mutex
= 1;
868 grub_script_execute_sourcecode(cmdstr
);
869 g_ventoy_fn_mutex
= 0;
874 case GRUB_TERM_KEY_F7
:
876 if (g_ventoy_terminal_output
== 0)
878 grub_script_execute_sourcecode("terminal_output console");
879 g_ventoy_terminal_output
= 1;
883 grub_script_execute_sourcecode("terminal_output gfxterm");
884 g_ventoy_terminal_output
= 0;
887 case GRUB_TERM_KEY_F1
:
890 g_ventoy_memdisk_mode
= 1 - g_ventoy_memdisk_mode
;
891 g_ventoy_menu_refresh
= 1;
894 case (GRUB_TERM_CTRL
| 'i'):
896 g_ventoy_iso_raw
= 1 - g_ventoy_iso_raw
;
897 g_ventoy_menu_refresh
= 1;
900 case (GRUB_TERM_CTRL
| 'u'):
902 g_ventoy_iso_uefi_drv
= 1 - g_ventoy_iso_uefi_drv
;
903 g_ventoy_menu_refresh
= 1;
910 entry
= get_entry_index_by_hotkey (menu
, c
);
923 /* Never reach here. */
926 /* Callback invoked immediately before a menu entry is executed. */
928 notify_booting (grub_menu_entry_t entry
,
929 void *userdata
__attribute__((unused
)))
932 grub_printf_ (N_("Booting `%s'"), entry
->title
);
933 grub_printf ("\n\n");
936 /* Callback invoked when a default menu entry executed because of a timeout
937 has failed and an attempt will be made to execute the next fallback
940 notify_fallback (grub_menu_entry_t entry
,
941 void *userdata
__attribute__((unused
)))
944 grub_printf_ (N_("Falling back to `%s'"), entry
->title
);
945 grub_printf ("\n\n");
946 grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS
);
949 /* Callback invoked when a menu entry has failed and there is no remaining
950 fallback entry to attempt. */
952 notify_execution_failure (void *userdata
__attribute__((unused
)))
954 if (grub_errno
!= GRUB_ERR_NONE
)
957 grub_errno
= GRUB_ERR_NONE
;
960 grub_printf_ (N_("Failed to boot both default and fallback entries.\n"));
961 grub_wait_after_message ();
964 /* Callbacks used by the text menu to provide user feedback when menu entries
966 static struct grub_menu_execute_callback execution_callback
=
968 .notify_booting
= notify_booting
,
969 .notify_fallback
= notify_fallback
,
970 .notify_failure
= notify_execution_failure
974 show_menu (grub_menu_t menu
, int nested
, int autobooted
)
977 def
= grub_env_get("VTOY_DEFAULT_IMAGE");
985 boot_entry
= run_menu (menu
, nested
, &auto_boot
);
989 if (auto_boot
&& def
&& grub_strcmp(def
, "VTOY_EXIT") == 0) {
993 if (autobooted
== 0 && auto_boot
== 0) {
994 g_ventoy_last_entry
= boot_entry
;
995 if (g_ventoy_menu_esc
)
999 e
= grub_menu_get_entry (menu
, boot_entry
);
1001 continue; /* Menu is empty. */
1003 if (2 == e
->argc
&& e
->args
&& e
->args
[1] && grub_strncmp(e
->args
[1], "VTOY_RET", 8) == 0)
1009 grub_menu_execute_with_fallback (menu
, e
, autobooted
,
1010 &execution_callback
, 0);
1012 grub_menu_execute_entry (e
, 0);
1016 if (2 == e
->argc
&& e
->args
&& e
->args
[1] && grub_strncmp(e
->args
[1], "VTOY_RUN_RET", 12) == 0)
1020 return GRUB_ERR_NONE
;
1024 grub_show_menu (grub_menu_t menu
, int nested
, int autoboot
)
1026 grub_err_t err1
, err2
;
1030 err1
= show_menu (menu
, nested
, autoboot
);
1032 grub_print_error ();
1034 if (grub_normal_exit_level
)
1037 err2
= grub_auth_check_authentication (NULL
);
1040 grub_print_error ();
1041 grub_errno
= GRUB_ERR_NONE
;