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