menu.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. /* menu.c - General supporting functionality for menus. */
  2. /*
  3. * GRUB -- GRand Unified Bootloader
  4. * Copyright (C) 2003,2004,2005,2006,2007,2008,2009,2010 Free Software Foundation, Inc.
  5. *
  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.
  10. *
  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.
  15. *
  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/>.
  18. */
  19. #include <grub/normal_menu.h>
  20. #include <grub/misc.h>
  21. #include <grub/loader.h>
  22. #include <grub/mm.h>
  23. #include <grub/time.h>
  24. #include <grub/env.h>
  25. #include <grub/menu_viewer.h>
  26. #include <grub/command.h>
  27. #include <grub/parser.h>
  28. #include <grub/auth.h>
  29. #include <grub/i18n.h>
  30. #include <grub/term.h>
  31. #include <grub/lib.h>
  32. #include <grub/normal.h>
  33. GRUB_EXPORT(grub_menu_get_timeout);
  34. GRUB_EXPORT(grub_menu_set_timeout);
  35. GRUB_EXPORT(grub_menu_execute_entry);
  36. GRUB_EXPORT(grub_menu_execute_with_fallback);
  37. GRUB_EXPORT(grub_menu_register_viewer);
  38. GRUB_EXPORT(grub_gfxmenu_try_hook);
  39. GRUB_EXPORT(grub_menu_get_entry);
  40. /* Time to delay after displaying an error message about a default/fallback
  41. entry failing to boot. */
  42. #define DEFAULT_ENTRY_ERROR_DELAY_MS 2500
  43. grub_err_t (*grub_gfxmenu_try_hook) (int entry, grub_menu_t menu,
  44. int nested) = NULL;
  45. /* Get a menu entry by its index in the entry list. */
  46. grub_menu_entry_t
  47. grub_menu_get_entry (grub_menu_t menu, int no)
  48. {
  49. grub_menu_entry_t e;
  50. for (e = menu->entry_list; e && no > 0; e = e->next, no--)
  51. ;
  52. return e;
  53. }
  54. /* Return the current timeout. If the variable "timeout" is not set or
  55. invalid, return -1. */
  56. int
  57. grub_menu_get_timeout (void)
  58. {
  59. char *val;
  60. int timeout;
  61. val = grub_env_get ("timeout");
  62. if (! val)
  63. return -1;
  64. grub_error_push ();
  65. timeout = (int) grub_strtoul (val, 0, 0);
  66. /* If the value is invalid, unset the variable. */
  67. if (grub_errno != GRUB_ERR_NONE)
  68. {
  69. grub_env_unset ("timeout");
  70. grub_errno = GRUB_ERR_NONE;
  71. timeout = -1;
  72. }
  73. grub_error_pop ();
  74. return timeout;
  75. }
  76. /* Set current timeout in the variable "timeout". */
  77. void
  78. grub_menu_set_timeout (int timeout)
  79. {
  80. /* Ignore TIMEOUT if it is zero, because it will be unset really soon. */
  81. if (timeout > 0)
  82. {
  83. char buf[16];
  84. grub_snprintf (buf, sizeof (buf), "%d", timeout);
  85. grub_env_set ("timeout", buf);
  86. }
  87. }
  88. /* Get the first entry number from the value of the environment variable NAME,
  89. which is a space-separated list of non-negative integers. The entry number
  90. which is returned is stripped from the value of NAME. If no entry number
  91. can be found, -1 is returned. */
  92. static int
  93. get_and_remove_first_entry_number (const char *name)
  94. {
  95. char *val;
  96. char *tail;
  97. int entry;
  98. val = grub_env_get (name);
  99. if (! val)
  100. return -1;
  101. grub_error_push ();
  102. entry = (int) grub_strtoul (val, &tail, 0);
  103. if (grub_errno == GRUB_ERR_NONE)
  104. {
  105. /* Skip whitespace to find the next digit. */
  106. while (*tail && grub_isspace (*tail))
  107. tail++;
  108. grub_env_set (name, tail);
  109. }
  110. else
  111. {
  112. grub_env_unset (name);
  113. grub_errno = GRUB_ERR_NONE;
  114. entry = -1;
  115. }
  116. grub_error_pop ();
  117. return entry;
  118. }
  119. /* Run a menu entry. */
  120. void
  121. grub_menu_execute_entry(grub_menu_entry_t entry)
  122. {
  123. grub_err_t err = GRUB_ERR_NONE;
  124. if (entry->restricted)
  125. err = grub_normal_check_authentication (entry->users);
  126. if (err)
  127. {
  128. grub_print_error ();
  129. grub_errno = GRUB_ERR_NONE;
  130. return;
  131. }
  132. grub_env_set ("chosen", entry->title);
  133. grub_parser_execute ((char *) entry->sourcecode);
  134. if (grub_errno == GRUB_ERR_NONE && grub_loader_is_loaded ())
  135. /* Implicit execution of boot, only if something is loaded. */
  136. grub_command_execute ("boot", 0, 0);
  137. }
  138. /* Execute ENTRY from the menu MENU, falling back to entries specified
  139. in the environment variable "fallback" if it fails. CALLBACK is a
  140. pointer to a struct of function pointers which are used to allow the
  141. caller provide feedback to the user. */
  142. void
  143. grub_menu_execute_with_fallback (grub_menu_t menu,
  144. grub_menu_entry_t entry,
  145. grub_menu_execute_callback_t callback,
  146. void *callback_data)
  147. {
  148. int fallback_entry;
  149. callback->notify_booting (entry, callback_data);
  150. grub_menu_execute_entry (entry);
  151. /* Deal with fallback entries. */
  152. while ((fallback_entry = get_and_remove_first_entry_number ("fallback"))
  153. >= 0)
  154. {
  155. grub_print_error ();
  156. grub_errno = GRUB_ERR_NONE;
  157. entry = grub_menu_get_entry (menu, fallback_entry);
  158. callback->notify_fallback (entry, callback_data);
  159. grub_menu_execute_entry (entry);
  160. /* If the function call to execute the entry returns at all, then this is
  161. taken to indicate a boot failure. For menu entries that do something
  162. other than actually boot an operating system, this could assume
  163. incorrectly that something failed. */
  164. }
  165. callback->notify_failure (callback_data);
  166. }
  167. static struct grub_menu_viewer *viewers;
  168. static void
  169. menu_set_chosen_entry (int entry)
  170. {
  171. struct grub_menu_viewer *cur;
  172. for (cur = viewers; cur; cur = cur->next)
  173. cur->set_chosen_entry (entry, cur->data);
  174. }
  175. static void
  176. menu_print_timeout (int timeout)
  177. {
  178. struct grub_menu_viewer *cur;
  179. for (cur = viewers; cur; cur = cur->next)
  180. cur->print_timeout (timeout, cur->data);
  181. }
  182. static void
  183. menu_fini (void)
  184. {
  185. struct grub_menu_viewer *cur, *next;
  186. for (cur = viewers; cur; cur = next)
  187. {
  188. next = cur->next;
  189. cur->fini (cur->data);
  190. grub_free (cur);
  191. }
  192. viewers = NULL;
  193. }
  194. static void
  195. menu_init (int entry, grub_menu_t menu, int nested)
  196. {
  197. struct grub_term_output *term;
  198. FOR_ACTIVE_TERM_OUTPUTS(term)
  199. {
  200. grub_err_t err;
  201. if (grub_gfxmenu_try_hook && grub_strcmp (term->name, "gfxterm") == 0)
  202. {
  203. err = grub_gfxmenu_try_hook (entry, menu, nested);
  204. if(!err)
  205. continue;
  206. grub_print_error ();
  207. grub_errno = GRUB_ERR_NONE;
  208. }
  209. err = grub_menu_try_text (term, entry, menu, nested);
  210. if(!err)
  211. continue;
  212. grub_print_error ();
  213. grub_errno = GRUB_ERR_NONE;
  214. }
  215. }
  216. static void
  217. clear_timeout (void)
  218. {
  219. struct grub_menu_viewer *cur;
  220. for (cur = viewers; cur; cur = cur->next)
  221. cur->clear_timeout (cur->data);
  222. }
  223. void
  224. grub_menu_register_viewer (struct grub_menu_viewer *viewer)
  225. {
  226. viewer->next = viewers;
  227. viewers = viewer;
  228. }
  229. /* Get the entry number from the variable NAME. */
  230. static int
  231. get_entry_number (grub_menu_t menu, const char *name)
  232. {
  233. char *val;
  234. int entry;
  235. val = grub_env_get (name);
  236. if (! val)
  237. return -1;
  238. grub_error_push ();
  239. entry = (int) grub_strtoul (val, 0, 0);
  240. if (grub_errno == GRUB_ERR_BAD_NUMBER)
  241. {
  242. /* See if the variable matches the title of a menu entry. */
  243. grub_menu_entry_t e = menu->entry_list;
  244. int i;
  245. grub_errno = GRUB_ERR_NONE;
  246. for (i = 0; e; i++)
  247. {
  248. if (grub_strcmp (e->title, val) == 0)
  249. {
  250. entry = i;
  251. break;
  252. }
  253. e = e->next;
  254. }
  255. if (! e)
  256. entry = -1;
  257. }
  258. if (grub_errno != GRUB_ERR_NONE)
  259. {
  260. grub_errno = GRUB_ERR_NONE;
  261. entry = -1;
  262. }
  263. grub_error_pop ();
  264. return entry;
  265. }
  266. #define GRUB_MENU_PAGE_SIZE 10
  267. /* Show the menu and handle menu entry selection. Returns the menu entry
  268. index that should be executed or -1 if no entry should be executed (e.g.,
  269. Esc pressed to exit a sub-menu or switching menu viewers).
  270. If the return value is not -1, then *AUTO_BOOT is nonzero iff the menu
  271. entry to be executed is a result of an automatic default selection because
  272. of the timeout. */
  273. static int
  274. run_menu (grub_menu_t menu, int nested, int *auto_boot)
  275. {
  276. grub_uint64_t saved_time;
  277. int default_entry, current_entry;
  278. int timeout;
  279. default_entry = get_entry_number (menu, "default");
  280. /* If DEFAULT_ENTRY is not within the menu entries, fall back to
  281. the first entry. */
  282. if (default_entry < 0 || default_entry >= menu->size)
  283. default_entry = 0;
  284. /* If timeout is 0, drawing is pointless (and ugly). */
  285. if (grub_menu_get_timeout () == 0)
  286. {
  287. *auto_boot = 1;
  288. return default_entry;
  289. }
  290. current_entry = default_entry;
  291. /* Initialize the time. */
  292. saved_time = grub_get_time_ms ();
  293. refresh:
  294. menu_init (current_entry, menu, nested);
  295. timeout = grub_menu_get_timeout ();
  296. if (timeout > 0)
  297. menu_print_timeout (timeout);
  298. else
  299. clear_timeout ();
  300. while (1)
  301. {
  302. int c;
  303. timeout = grub_menu_get_timeout ();
  304. if (grub_normal_exit_level)
  305. return -1;
  306. if (timeout > 0)
  307. {
  308. grub_uint64_t current_time;
  309. current_time = grub_get_time_ms ();
  310. if (current_time - saved_time >= 1000)
  311. {
  312. timeout--;
  313. grub_menu_set_timeout (timeout);
  314. saved_time = current_time;
  315. menu_print_timeout (timeout);
  316. }
  317. }
  318. if (timeout == 0)
  319. {
  320. grub_env_unset ("timeout");
  321. *auto_boot = 1;
  322. menu_fini ();
  323. return default_entry;
  324. }
  325. if (grub_checkkey () >= 0 || timeout < 0)
  326. {
  327. c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
  328. if (timeout >= 0)
  329. {
  330. grub_env_unset ("timeout");
  331. grub_env_unset ("fallback");
  332. clear_timeout ();
  333. }
  334. switch (c)
  335. {
  336. case GRUB_TERM_HOME:
  337. current_entry = 0;
  338. menu_set_chosen_entry (current_entry);
  339. break;
  340. case GRUB_TERM_END:
  341. current_entry = menu->size - 1;
  342. menu_set_chosen_entry (current_entry);
  343. break;
  344. case GRUB_TERM_UP:
  345. case '^':
  346. if (current_entry > 0)
  347. current_entry--;
  348. menu_set_chosen_entry (current_entry);
  349. break;
  350. case GRUB_TERM_DOWN:
  351. case 'v':
  352. if (current_entry < menu->size - 1)
  353. current_entry++;
  354. menu_set_chosen_entry (current_entry);
  355. break;
  356. case GRUB_TERM_PPAGE:
  357. if (current_entry < GRUB_MENU_PAGE_SIZE)
  358. current_entry = 0;
  359. else
  360. current_entry -= GRUB_MENU_PAGE_SIZE;
  361. menu_set_chosen_entry (current_entry);
  362. break;
  363. case GRUB_TERM_NPAGE:
  364. if (current_entry + GRUB_MENU_PAGE_SIZE < menu->size)
  365. current_entry += GRUB_MENU_PAGE_SIZE;
  366. else
  367. current_entry = menu->size - 1;
  368. menu_set_chosen_entry (current_entry);
  369. break;
  370. case '\n':
  371. case '\r':
  372. case 6:
  373. menu_fini ();
  374. *auto_boot = 0;
  375. return current_entry;
  376. case '\e':
  377. if (nested)
  378. {
  379. menu_fini ();
  380. return -1;
  381. }
  382. break;
  383. case 'c':
  384. menu_fini ();
  385. grub_normal_cmdline_run (1);
  386. goto refresh;
  387. case 'e':
  388. menu_fini ();
  389. {
  390. grub_menu_entry_t e = grub_menu_get_entry (menu, current_entry);
  391. if (e)
  392. grub_menu_entry_run (e);
  393. }
  394. goto refresh;
  395. default:
  396. {
  397. grub_menu_entry_t entry;
  398. int i;
  399. for (i = 0, entry = menu->entry_list; i < menu->size;
  400. i++, entry = entry->next)
  401. if (entry->hotkey == c)
  402. {
  403. menu_fini ();
  404. *auto_boot = 0;
  405. return i;
  406. }
  407. }
  408. break;
  409. }
  410. }
  411. }
  412. /* Never reach here. */
  413. return -1;
  414. }
  415. /* Callback invoked immediately before a menu entry is executed. */
  416. static void
  417. notify_booting (grub_menu_entry_t entry,
  418. void *userdata __attribute__((unused)))
  419. {
  420. grub_printf (" ");
  421. grub_printf_ (N_("Booting \'%s\'"), entry->title);
  422. grub_printf ("\n\n");
  423. }
  424. /* Callback invoked when a default menu entry executed because of a timeout
  425. has failed and an attempt will be made to execute the next fallback
  426. entry, ENTRY. */
  427. static void
  428. notify_fallback (grub_menu_entry_t entry,
  429. void *userdata __attribute__((unused)))
  430. {
  431. grub_printf ("\n ");
  432. grub_printf_ (N_("Falling back to \'%s\'"), entry->title);
  433. grub_printf ("\n\n");
  434. grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS);
  435. }
  436. /* Callback invoked when a menu entry has failed and there is no remaining
  437. fallback entry to attempt. */
  438. static void
  439. notify_execution_failure (void *userdata __attribute__((unused)))
  440. {
  441. if (grub_errno != GRUB_ERR_NONE)
  442. {
  443. grub_print_error ();
  444. grub_errno = GRUB_ERR_NONE;
  445. }
  446. grub_printf ("\n ");
  447. grub_printf_ (N_("Failed to boot both default and fallback entries.\n"));
  448. grub_wait_after_message ();
  449. }
  450. /* Callbacks used by the text menu to provide user feedback when menu entries
  451. are executed. */
  452. static struct grub_menu_execute_callback execution_callback =
  453. {
  454. .notify_booting = notify_booting,
  455. .notify_fallback = notify_fallback,
  456. .notify_failure = notify_execution_failure
  457. };
  458. static grub_err_t
  459. show_menu (grub_menu_t menu, int nested)
  460. {
  461. while (1)
  462. {
  463. int boot_entry;
  464. grub_menu_entry_t e;
  465. int auto_boot;
  466. boot_entry = run_menu (menu, nested, &auto_boot);
  467. if (boot_entry < 0)
  468. break;
  469. e = grub_menu_get_entry (menu, boot_entry);
  470. if (! e)
  471. continue; /* Menu is empty. */
  472. grub_cls ();
  473. if (auto_boot)
  474. {
  475. grub_menu_execute_with_fallback (menu, e, &execution_callback, 0);
  476. }
  477. else
  478. {
  479. int lines_before = grub_normal_get_line_counter ();
  480. grub_errno = GRUB_ERR_NONE;
  481. grub_menu_execute_entry (e);
  482. grub_print_error ();
  483. grub_errno = GRUB_ERR_NONE;
  484. if (lines_before != grub_normal_get_line_counter ())
  485. grub_wait_after_message ();
  486. }
  487. }
  488. return GRUB_ERR_NONE;
  489. }
  490. grub_err_t
  491. grub_normal_show_menu (grub_menu_t menu, int nested)
  492. {
  493. grub_err_t err1, err2;
  494. while (1)
  495. {
  496. err1 = show_menu (menu, nested);
  497. grub_print_error ();
  498. if (grub_normal_exit_level)
  499. break;
  500. err2 = grub_normal_check_authentication (NULL);
  501. if (err2)
  502. {
  503. grub_print_error ();
  504. grub_errno = GRUB_ERR_NONE;
  505. continue;
  506. }
  507. break;
  508. }
  509. return err1;
  510. }