]> git.proxmox.com Git - grub2.git/blob - grub-core/normal/main.c
* grub-core/normal/menu.c (grub_wait_after_message): Add a 10 second
[grub2.git] / grub-core / normal / main.c
1 /* main.c - the normal mode main routine */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2000,2001,2002,2003,2005,2006,2007,2008,2009 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
20 #include <grub/kernel.h>
21 #include <grub/normal.h>
22 #include <grub/dl.h>
23 #include <grub/misc.h>
24 #include <grub/file.h>
25 #include <grub/mm.h>
26 #include <grub/term.h>
27 #include <grub/env.h>
28 #include <grub/parser.h>
29 #include <grub/reader.h>
30 #include <grub/menu_viewer.h>
31 #include <grub/auth.h>
32 #include <grub/i18n.h>
33 #include <grub/charset.h>
34 #include <grub/script_sh.h>
35
36 #define GRUB_DEFAULT_HISTORY_SIZE 50
37
38 static int nested_level = 0;
39 int grub_normal_exit_level = 0;
40
41 /* Read a line from the file FILE. */
42 char *
43 grub_file_getline (grub_file_t file)
44 {
45 char c;
46 int pos = 0;
47 int literal = 0;
48 char *cmdline;
49 int max_len = 64;
50
51 /* Initially locate some space. */
52 cmdline = grub_malloc (max_len);
53 if (! cmdline)
54 return 0;
55
56 while (1)
57 {
58 if (grub_file_read (file, &c, 1) != 1)
59 break;
60
61 /* Skip all carriage returns. */
62 if (c == '\r')
63 continue;
64
65 /* Replace tabs with spaces. */
66 if (c == '\t')
67 c = ' ';
68
69 /* The previous is a backslash, then... */
70 if (literal)
71 {
72 /* If it is a newline, replace it with a space and continue. */
73 if (c == '\n')
74 {
75 c = ' ';
76
77 /* Go back to overwrite the backslash. */
78 if (pos > 0)
79 pos--;
80 }
81
82 literal = 0;
83 }
84
85 if (c == '\\')
86 literal = 1;
87
88 if (pos == 0)
89 {
90 if (! grub_isspace (c))
91 cmdline[pos++] = c;
92 }
93 else
94 {
95 if (pos >= max_len)
96 {
97 char *old_cmdline = cmdline;
98 max_len = max_len * 2;
99 cmdline = grub_realloc (cmdline, max_len);
100 if (! cmdline)
101 {
102 grub_free (old_cmdline);
103 return 0;
104 }
105 }
106
107 if (c == '\n')
108 break;
109
110 cmdline[pos++] = c;
111 }
112 }
113
114 cmdline[pos] = '\0';
115
116 /* If the buffer is empty, don't return anything at all. */
117 if (pos == 0)
118 {
119 grub_free (cmdline);
120 cmdline = 0;
121 }
122
123 return cmdline;
124 }
125
126 static void
127 free_menu (grub_menu_t menu)
128 {
129 grub_menu_entry_t entry = menu->entry_list;
130
131 while (entry)
132 {
133 grub_menu_entry_t next_entry = entry->next;
134
135 grub_free ((void *) entry->title);
136 grub_free ((void *) entry->sourcecode);
137 entry = next_entry;
138 }
139
140 grub_free (menu);
141 grub_env_unset_menu ();
142 }
143
144 static void
145 free_menu_entry_classes (struct grub_menu_entry_class *head)
146 {
147 /* Free all the classes. */
148 while (head)
149 {
150 struct grub_menu_entry_class *next;
151
152 grub_free (head->name);
153 next = head->next;
154 grub_free (head);
155 head = next;
156 }
157 }
158
159 static struct
160 {
161 char *name;
162 int key;
163 } hotkey_aliases[] =
164 {
165 {"backspace", '\b'},
166 {"tab", '\t'},
167 {"delete", GRUB_TERM_DC}
168 };
169
170 /* Add a menu entry to the current menu context (as given by the environment
171 variable data slot `menu'). As the configuration file is read, the script
172 parser calls this when a menu entry is to be created. */
173 grub_err_t
174 grub_normal_add_menu_entry (int argc, const char **args,
175 const char *sourcecode)
176 {
177 const char *menutitle = 0;
178 const char *menusourcecode;
179 grub_menu_t menu;
180 grub_menu_entry_t *last;
181 int failed = 0;
182 int i;
183 struct grub_menu_entry_class *classes_head; /* Dummy head node for list. */
184 struct grub_menu_entry_class *classes_tail;
185 char *users = NULL;
186 int hotkey = 0;
187
188 /* Allocate dummy head node for class list. */
189 classes_head = grub_zalloc (sizeof (struct grub_menu_entry_class));
190 if (! classes_head)
191 return grub_errno;
192 classes_tail = classes_head;
193
194 menu = grub_env_get_menu ();
195 if (! menu)
196 return grub_error (GRUB_ERR_MENU, "no menu context");
197
198 last = &menu->entry_list;
199
200 menusourcecode = grub_strdup (sourcecode);
201 if (! menusourcecode)
202 return grub_errno;
203
204 /* Parse menu arguments. */
205 for (i = 0; i < argc; i++)
206 {
207 /* Capture arguments. */
208 if (grub_strncmp ("--", args[i], 2) == 0 && i + 1 < argc)
209 {
210 const char *arg = &args[i][2];
211
212 /* Handle menu class. */
213 if (grub_strcmp(arg, "class") == 0)
214 {
215 char *class_name;
216 struct grub_menu_entry_class *new_class;
217
218 i++;
219 class_name = grub_strdup (args[i]);
220 if (! class_name)
221 {
222 failed = 1;
223 break;
224 }
225
226 /* Create a new class and add it at the tail of the list. */
227 new_class = grub_zalloc (sizeof (struct grub_menu_entry_class));
228 if (! new_class)
229 {
230 grub_free (class_name);
231 failed = 1;
232 break;
233 }
234 /* Fill in the new class node. */
235 new_class->name = class_name;
236 /* Link the tail to it, and make it the new tail. */
237 classes_tail->next = new_class;
238 classes_tail = new_class;
239 continue;
240 }
241 else if (grub_strcmp(arg, "users") == 0)
242 {
243 i++;
244 users = grub_strdup (args[i]);
245 if (! users)
246 {
247 failed = 1;
248 break;
249 }
250
251 continue;
252 }
253 else if (grub_strcmp(arg, "hotkey") == 0)
254 {
255 unsigned j;
256
257 i++;
258 if (args[i][1] == 0)
259 {
260 hotkey = args[i][0];
261 continue;
262 }
263
264 for (j = 0; j < ARRAY_SIZE (hotkey_aliases); j++)
265 if (grub_strcmp (args[i], hotkey_aliases[j].name) == 0)
266 {
267 hotkey = hotkey_aliases[j].key;
268 break;
269 }
270
271 if (j < ARRAY_SIZE (hotkey_aliases))
272 continue;
273
274 failed = 1;
275 grub_error (GRUB_ERR_MENU,
276 "Invalid hotkey: '%s'.", args[i]);
277 break;
278 }
279 else
280 {
281 /* Handle invalid argument. */
282 failed = 1;
283 grub_error (GRUB_ERR_MENU,
284 "invalid argument for menuentry: %s", args[i]);
285 break;
286 }
287 }
288
289 /* Capture title. */
290 if (! menutitle)
291 {
292 menutitle = grub_strdup (args[i]);
293 }
294 else
295 {
296 failed = 1;
297 grub_error (GRUB_ERR_MENU,
298 "too many titles for menuentry: %s", args[i]);
299 break;
300 }
301 }
302
303 /* Validate arguments. */
304 if ((! failed) && (! menutitle))
305 {
306 grub_error (GRUB_ERR_MENU, "menuentry is missing title");
307 failed = 1;
308 }
309
310 /* If argument parsing failed, free any allocated resources. */
311 if (failed)
312 {
313 free_menu_entry_classes (classes_head);
314 grub_free ((void *) menutitle);
315 grub_free ((void *) menusourcecode);
316
317 /* Here we assume that grub_error has been used to specify failure details. */
318 return grub_errno;
319 }
320
321 /* Add the menu entry at the end of the list. */
322 while (*last)
323 last = &(*last)->next;
324
325 *last = grub_zalloc (sizeof (**last));
326 if (! *last)
327 {
328 free_menu_entry_classes (classes_head);
329 grub_free ((void *) menutitle);
330 grub_free ((void *) menusourcecode);
331 return grub_errno;
332 }
333
334 (*last)->title = menutitle;
335 (*last)->hotkey = hotkey;
336 (*last)->classes = classes_head;
337 if (users)
338 (*last)->restricted = 1;
339 (*last)->users = users;
340 (*last)->sourcecode = menusourcecode;
341
342 menu->size++;
343
344 return GRUB_ERR_NONE;
345 }
346
347 static grub_menu_t
348 read_config_file (const char *config)
349 {
350 grub_file_t file;
351
352 auto grub_err_t getline (char **line, int cont);
353 grub_err_t getline (char **line, int cont __attribute__ ((unused)))
354 {
355 while (1)
356 {
357 char *buf;
358
359 *line = buf = grub_file_getline (file);
360 if (! buf)
361 return grub_errno;
362
363 if (buf[0] == '#')
364 grub_free (*line);
365 else
366 break;
367 }
368
369 return GRUB_ERR_NONE;
370 }
371
372 grub_menu_t newmenu;
373
374 newmenu = grub_env_get_menu ();
375 if (! newmenu)
376 {
377 newmenu = grub_zalloc (sizeof (*newmenu));
378 if (! newmenu)
379 return 0;
380
381 grub_env_set_menu (newmenu);
382 }
383
384 /* Try to open the config file. */
385 file = grub_file_open (config);
386 if (! file)
387 return 0;
388
389 while (1)
390 {
391 char *line;
392
393 /* Print an error, if any. */
394 grub_print_error ();
395 grub_errno = GRUB_ERR_NONE;
396
397 if ((getline (&line, 0)) || (! line))
398 break;
399
400 grub_normal_parse_line (line, getline);
401 grub_free (line);
402 }
403
404 grub_file_close (file);
405
406 return newmenu;
407 }
408
409 /* Initialize the screen. */
410 void
411 grub_normal_init_page (struct grub_term_output *term)
412 {
413 int msg_len;
414 int posx;
415 const char *msg = _("GNU GRUB version %s");
416 char *msg_formatted;
417 grub_uint32_t *unicode_msg;
418 grub_uint32_t *last_position;
419
420 grub_term_cls (term);
421
422 msg_formatted = grub_xasprintf (msg, PACKAGE_VERSION);
423 if (!msg_formatted)
424 return;
425
426 msg_len = grub_utf8_to_ucs4_alloc (msg_formatted,
427 &unicode_msg, &last_position);
428 grub_free (msg_formatted);
429
430 if (msg_len < 0)
431 {
432 return;
433 }
434
435 posx = grub_getstringwidth (unicode_msg, last_position, term);
436 posx = (grub_term_width (term) - posx) / 2;
437 grub_term_gotoxy (term, posx, 1);
438
439 grub_print_ucs4 (unicode_msg, last_position, 0, 0, term);
440 grub_putcode ('\n', term);
441 grub_putcode ('\n', term);
442 grub_free (unicode_msg);
443 }
444
445 static void
446 read_lists (const char *val)
447 {
448 if (! grub_no_autoload)
449 {
450 read_command_list (val);
451 read_fs_list (val);
452 read_crypto_list (val);
453 read_terminal_list (val);
454 }
455 }
456
457 static char *
458 read_lists_hook (struct grub_env_var *var __attribute__ ((unused)),
459 const char *val)
460 {
461 read_lists (val);
462 return val ? grub_strdup (val) : NULL;
463 }
464
465 /* Read the config file CONFIG and execute the menu interface or
466 the command line interface if BATCH is false. */
467 void
468 grub_normal_execute (const char *config, int nested, int batch)
469 {
470 grub_menu_t menu = 0;
471 const char *prefix;
472
473 if (! nested)
474 {
475 prefix = grub_env_get ("prefix");
476 read_lists (prefix);
477 grub_register_variable_hook ("prefix", NULL, read_lists_hook);
478 grub_command_execute ("parser.grub", 0, 0);
479 }
480
481 if (config)
482 {
483 menu = read_config_file (config);
484
485 /* Ignore any error. */
486 grub_errno = GRUB_ERR_NONE;
487 }
488
489 if (! batch)
490 {
491 if (menu && menu->size)
492 {
493 grub_show_menu (menu, nested);
494 if (nested)
495 free_menu (menu);
496 }
497 }
498 }
499
500 /* This starts the normal mode. */
501 void
502 grub_enter_normal_mode (const char *config)
503 {
504 nested_level++;
505 grub_normal_execute (config, 0, 0);
506 grub_cmdline_run (0);
507 nested_level--;
508 if (grub_normal_exit_level)
509 grub_normal_exit_level--;
510 }
511
512 /* Enter normal mode from rescue mode. */
513 static grub_err_t
514 grub_cmd_normal (struct grub_command *cmd __attribute__ ((unused)),
515 int argc, char *argv[])
516 {
517 if (argc == 0)
518 {
519 /* Guess the config filename. It is necessary to make CONFIG static,
520 so that it won't get broken by longjmp. */
521 char *config;
522 const char *prefix;
523
524 prefix = grub_env_get ("prefix");
525 if (prefix)
526 {
527 config = grub_xasprintf ("%s/grub.cfg", prefix);
528 if (! config)
529 goto quit;
530
531 grub_enter_normal_mode (config);
532 grub_free (config);
533 }
534 else
535 grub_enter_normal_mode (0);
536 }
537 else
538 grub_enter_normal_mode (argv[0]);
539
540 quit:
541 return 0;
542 }
543
544 /* Exit from normal mode to rescue mode. */
545 static grub_err_t
546 grub_cmd_normal_exit (struct grub_command *cmd __attribute__ ((unused)),
547 int argc __attribute__ ((unused)),
548 char *argv[] __attribute__ ((unused)))
549 {
550 if (nested_level <= grub_normal_exit_level)
551 return grub_error (GRUB_ERR_BAD_ARGUMENT, "not in normal environment");
552 grub_normal_exit_level++;
553 return GRUB_ERR_NONE;
554 }
555
556 static grub_err_t
557 grub_normal_reader_init (int nested)
558 {
559 struct grub_term_output *term;
560 const char *msg = _("Minimal BASH-like line editing is supported. For "
561 "the first word, TAB lists possible command completions. Anywhere "
562 "else TAB lists possible device or file completions. %s");
563 const char *msg_esc = _("ESC at any time exits.");
564 char *msg_formatted;
565
566 msg_formatted = grub_xasprintf (msg, nested ? msg_esc : "");
567 if (!msg_formatted)
568 return grub_errno;
569
570 FOR_ACTIVE_TERM_OUTPUTS(term)
571 {
572 grub_normal_init_page (term);
573 grub_term_setcursor (term, 1);
574
575 grub_print_message_indented (msg_formatted, 3, STANDARD_MARGIN, term);
576 grub_putcode ('\n', term);
577 grub_putcode ('\n', term);
578 }
579 grub_free (msg_formatted);
580
581 return 0;
582 }
583
584 static grub_err_t
585 grub_normal_read_line_real (char **line, int cont, int nested)
586 {
587 const char *prompt;
588
589 if (cont)
590 prompt = ">";
591 else
592 prompt = "grub>";
593
594 if (!prompt)
595 return grub_errno;
596
597 while (1)
598 {
599 *line = grub_cmdline_get (prompt);
600 if (*line)
601 break;
602
603 if (cont || nested)
604 {
605 grub_free (*line);
606 *line = 0;
607 return grub_errno;
608 }
609 }
610
611 return 0;
612 }
613
614 static grub_err_t
615 grub_normal_read_line (char **line, int cont)
616 {
617 return grub_normal_read_line_real (line, cont, 0);
618 }
619
620 void
621 grub_cmdline_run (int nested)
622 {
623 grub_err_t err = GRUB_ERR_NONE;
624
625 err = grub_auth_check_authentication (NULL);
626
627 if (err)
628 {
629 grub_print_error ();
630 grub_errno = GRUB_ERR_NONE;
631 return;
632 }
633
634 grub_normal_reader_init (nested);
635
636 while (1)
637 {
638 char *line;
639
640 if (grub_normal_exit_level)
641 break;
642
643 /* Print an error, if any. */
644 grub_print_error ();
645 grub_errno = GRUB_ERR_NONE;
646
647 grub_normal_read_line_real (&line, 0, nested);
648 if (! line)
649 break;
650
651 grub_normal_parse_line (line, grub_normal_read_line);
652 grub_free (line);
653 }
654 }
655
656 static char *
657 grub_env_write_pager (struct grub_env_var *var __attribute__ ((unused)),
658 const char *val)
659 {
660 grub_set_more ((*val == '1'));
661 return grub_strdup (val);
662 }
663
664 /* clear */
665 static grub_err_t
666 grub_mini_cmd_clear (struct grub_command *cmd __attribute__ ((unused)),
667 int argc __attribute__ ((unused)),
668 char *argv[] __attribute__ ((unused)))
669 {
670 grub_cls ();
671 return 0;
672 }
673
674 static grub_command_t cmd_clear;
675
676 static void (*grub_xputs_saved) (const char *str);
677
678 GRUB_MOD_INIT(normal)
679 {
680 grub_context_init ();
681 grub_script_init ();
682
683 grub_xputs_saved = grub_xputs;
684 grub_xputs = grub_xputs_normal;
685
686 /* Normal mode shouldn't be unloaded. */
687 if (mod)
688 grub_dl_ref (mod);
689
690 cmd_clear =
691 grub_register_command ("clear", grub_mini_cmd_clear,
692 0, N_("Clear the screen."));
693
694 grub_set_history (GRUB_DEFAULT_HISTORY_SIZE);
695
696 grub_register_variable_hook ("pager", 0, grub_env_write_pager);
697
698 /* Register a command "normal" for the rescue mode. */
699 grub_register_command ("normal", grub_cmd_normal,
700 0, N_("Enter normal mode."));
701 grub_register_command ("normal_exit", grub_cmd_normal_exit,
702 0, N_("Exit from normal mode."));
703
704 /* Reload terminal colors when these variables are written to. */
705 grub_register_variable_hook ("color_normal", NULL, grub_env_write_color_normal);
706 grub_register_variable_hook ("color_highlight", NULL, grub_env_write_color_highlight);
707
708 /* Preserve hooks after context changes. */
709 grub_env_export ("color_normal");
710 grub_env_export ("color_highlight");
711
712 /* Set default color names. */
713 grub_env_set ("color_normal", "white/black");
714 grub_env_set ("color_highlight", "black/white");
715 }
716
717 GRUB_MOD_FINI(normal)
718 {
719 grub_context_fini ();
720 grub_script_fini ();
721
722 grub_xputs = grub_xputs_saved;
723
724 grub_set_history (0);
725 grub_register_variable_hook ("pager", 0, 0);
726 grub_fs_autoload_hook = 0;
727 grub_unregister_command (cmd_clear);
728 }