1 /* $NetBSD: bootmenu.c,v 1.11 2013/07/28 08:50:09 he Exp $ */ 2 3 /*- 4 * Copyright (c) 2008 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #ifndef SMALL 30 31 #include <sys/types.h> 32 #include <sys/reboot.h> 33 #include <sys/bootblock.h> 34 35 #include <lib/libsa/stand.h> 36 #include <lib/libsa/ufs.h> 37 #include <lib/libkern/libkern.h> 38 39 #include <libi386.h> 40 #include <bootmenu.h> 41 42 #define isnum(c) ((c) >= '0' && (c) <= '9') 43 44 static void docommandchoice(int); 45 46 extern struct x86_boot_params boot_params; 47 extern const char bootprog_name[], bootprog_rev[], bootprog_kernrev[]; 48 49 #define MENUFORMAT_AUTO 0 50 #define MENUFORMAT_NUMBER 1 51 #define MENUFORMAT_LETTER 2 52 53 struct bootconf_def bootconf; 54 55 int 56 atoi(const char *in) 57 { 58 char *c; 59 int ret; 60 61 ret = 0; 62 c = (char *)in; 63 if (*c == '-') 64 c++; 65 for (; isnum(*c); c++) 66 ret = (ret * 10) + (*c - '0'); 67 68 return (*in == '-') ? -ret : ret; 69 } 70 71 /* 72 * This function parses a boot.cfg file in the root of the filesystem 73 * (if present) and populates the global boot configuration. 74 * 75 * The file consists of a number of lines each terminated by \n 76 * The lines are in the format keyword=value. There should not be spaces 77 * around the = sign. 78 * 79 * The recognised keywords are: 80 * banner: text displayed instead of the normal welcome text 81 * menu: Descriptive text:command to use 82 * timeout: Timeout in seconds (overrides that set by installboot) 83 * default: the default menu option to use if Return is pressed 84 * consdev: the console device to use 85 * format: how menu choices are displayed: (a)utomatic, (n)umbers or (l)etters 86 * clear: whether to clear the screen or not 87 * 88 * Example boot.cfg file: 89 * banner=Welcome to NetBSD 90 * banner=Please choose the boot type from the following menu 91 * menu=Boot NetBSD:boot netbsd 92 * menu=Boot into single user mode:boot netbsd -s 93 * menu=:boot hd1a:netbsd -cs 94 * menu=Goto boot comand line:prompt 95 * timeout=10 96 * consdev=com0 97 * default=1 98 */ 99 void 100 parsebootconf(const char *conf) 101 { 102 char *bc, *c; 103 int cmenu, cbanner, len; 104 int fd, err, off; 105 struct stat st; 106 char *next, *key, *value, *v2; 107 108 /* Clear bootconf structure */ 109 memset((void *)&bootconf, 0, sizeof(bootconf)); 110 111 /* Set timeout to configured */ 112 bootconf.timeout = boot_params.bp_timeout; 113 114 /* automatically switch between letter and numbers on menu */ 115 bootconf.menuformat = MENUFORMAT_AUTO; 116 117 fd = open(BOOTCONF, 0); 118 if (fd < 0) 119 return; 120 121 err = fstat(fd, &st); 122 if (err == -1) { 123 close(fd); 124 return; 125 } 126 127 /* 128 * Check the size. A bootconf file is normally only a few 129 * hundred bytes long. If it is much bigger than expected, 130 * don't try to load it. We can't load something big into 131 * an 8086 real mode segment anyway, and in pxeboot this is 132 * probably a case of the loader getting a filename for the 133 * kernel and thinking it is boot.cfg by accident. (The 32k 134 * number is arbitrary but 8086 real mode data segments max 135 * out at 64k.) 136 */ 137 if (st.st_size > 32768) { 138 close(fd); 139 return; 140 } 141 142 bc = alloc(st.st_size + 1); 143 if (bc == NULL) { 144 printf("Could not allocate memory for boot configuration\n"); 145 return; 146 } 147 148 off = 0; 149 do { 150 len = read(fd, bc + off, 1024); 151 if (len <= 0) 152 break; 153 off += len; 154 } while (len > 0); 155 bc[off] = '\0'; 156 157 close(fd); 158 /* bc now contains the whole boot.cfg file */ 159 160 cmenu = 0; 161 cbanner = 0; 162 for (c = bc; *c; c = next) { 163 key = c; 164 /* find end of line */ 165 for (; *c && *c != '\n'; c++) 166 /* zero terminate line on start of comment */ 167 if (*c == '#') 168 *c = 0; 169 /* zero terminate line */ 170 if (*(next = c)) 171 *next++ = 0; 172 /* Look for = separator between key and value */ 173 for (c = key; *c && *c != '='; c++) 174 continue; 175 /* Ignore lines with no key=value pair */ 176 if (*c == '\0') 177 continue; 178 179 /* zero terminate key which points to keyword */ 180 *c++ = 0; 181 value = c; 182 /* Look for end of line (or file) and zero terminate value */ 183 for (; *c && *c != '\n'; c++) 184 continue; 185 *c = 0; 186 187 if (!strncmp(key, "menu", 4)) { 188 /* 189 * Parse "menu=<description>:<command>". If the 190 * description is empty ("menu=:<command>)", 191 * then re-use the command as the description. 192 * Note that the command may contain embedded 193 * colons. 194 */ 195 if (cmenu >= MAXMENU) 196 continue; 197 bootconf.desc[cmenu] = value; 198 for (v2 = value; *v2 && *v2 != ':'; v2++) 199 continue; 200 if (*v2) { 201 *v2++ = 0; 202 bootconf.command[cmenu] = v2; 203 if (! *value) 204 bootconf.desc[cmenu] = v2; 205 cmenu++; 206 } else { 207 /* No delimiter means invalid line */ 208 bootconf.desc[cmenu] = NULL; 209 } 210 } else if (!strncmp(key, "banner", 6)) { 211 if (cbanner < MAXBANNER) 212 bootconf.banner[cbanner++] = value; 213 } else if (!strncmp(key, "timeout", 7)) { 214 if (!isnum(*value)) 215 bootconf.timeout = -1; 216 else 217 bootconf.timeout = atoi(value); 218 } else if (!strncmp(key, "default", 7)) { 219 bootconf.def = atoi(value) - 1; 220 } else if (!strncmp(key, "consdev", 7)) { 221 bootconf.consdev = value; 222 } else if (!strncmp(key, "load", 4)) { 223 module_add(value); 224 } else if (!strncmp(key, "format", 6)) { 225 printf("value:%c\n", *value); 226 switch (*value) { 227 case 'a': 228 case 'A': 229 bootconf.menuformat = MENUFORMAT_AUTO; 230 break; 231 232 case 'n': 233 case 'N': 234 case 'd': 235 case 'D': 236 bootconf.menuformat = MENUFORMAT_NUMBER; 237 break; 238 239 case 'l': 240 case 'L': 241 bootconf.menuformat = MENUFORMAT_LETTER; 242 break; 243 } 244 } else if (!strncmp(key, "clear", 5)) { 245 bootconf.clear = !!atoi(value); 246 } else if (!strncmp(key, "userconf", 8)) { 247 userconf_add(value); 248 } 249 } 250 switch (bootconf.menuformat) { 251 case MENUFORMAT_AUTO: 252 if (cmenu > 9 && bootconf.timeout > 0) 253 bootconf.menuformat = MENUFORMAT_LETTER; 254 else 255 bootconf.menuformat = MENUFORMAT_NUMBER; 256 break; 257 258 case MENUFORMAT_NUMBER: 259 if (cmenu > 9 && bootconf.timeout > 0) 260 cmenu = 9; 261 break; 262 } 263 264 bootconf.nummenu = cmenu; 265 if (bootconf.def < 0) 266 bootconf.def = 0; 267 if (bootconf.def >= cmenu) 268 bootconf.def = cmenu - 1; 269 } 270 271 /* 272 * doboottypemenu will render the menu and parse any user input 273 */ 274 static int 275 getchoicefrominput(char *input, int def) 276 { 277 int choice, usedef; 278 279 choice = -1; 280 usedef = 0; 281 282 if (*input == '\0' || *input == '\r' || *input == '\n') { 283 choice = def; 284 usedef = 1; 285 } else if (*input >= 'A' && *input < bootconf.nummenu + 'A') 286 choice = (*input) - 'A'; 287 else if (*input >= 'a' && *input < bootconf.nummenu + 'a') 288 choice = (*input) - 'a'; 289 else if (isnum(*input)) { 290 choice = atoi(input) - 1; 291 if (choice < 0 || choice >= bootconf.nummenu) 292 choice = -1; 293 } 294 295 if (bootconf.menuformat != MENUFORMAT_LETTER && 296 !isnum(*input) && !usedef) 297 choice = -1; 298 299 return choice; 300 } 301 302 static void 303 docommandchoice(int choice) 304 { 305 char input[80], *ic, *oc; 306 307 ic = bootconf.command[choice]; 308 /* Split command string at ; into separate commands */ 309 do { 310 oc = input; 311 /* Look for ; separator */ 312 for (; *ic && *ic != COMMAND_SEPARATOR; ic++) 313 *oc++ = *ic; 314 if (*input == '\0') 315 continue; 316 /* Strip out any trailing spaces */ 317 oc--; 318 for (; *oc == ' ' && oc > input; oc--); 319 *++oc = '\0'; 320 if (*ic == COMMAND_SEPARATOR) 321 ic++; 322 /* Stop silly command strings like ;;; */ 323 if (*input != '\0') 324 docommand(input); 325 /* Skip leading spaces */ 326 for (; *ic == ' '; ic++); 327 } while (*ic); 328 } 329 330 void 331 bootdefault(void) 332 { 333 int choice; 334 static int entered; 335 336 if (bootconf.nummenu > 0) { 337 if (entered) { 338 printf("default boot twice, skipping...\n"); 339 return; 340 } 341 entered = 1; 342 choice = bootconf.def; 343 printf("command(s): %s\n", bootconf.command[choice]); 344 docommandchoice(choice); 345 } 346 } 347 348 #if defined(__minix) 349 static void 350 showmenu(void) 351 { 352 int choice; 353 354 printf("\n"); 355 /* Display menu */ 356 if (bootconf.menuformat == MENUFORMAT_LETTER) { 357 for (choice = 0; choice < bootconf.nummenu; choice++) 358 printf(" %c. %s\n", choice + 'A', 359 bootconf.desc[choice]); 360 } else { 361 /* Can't use %2d format string with libsa */ 362 for (choice = 0; choice < bootconf.nummenu; choice++) 363 printf(" %s%d. %s\n", 364 (choice < 9) ? " " : "", 365 choice + 1, 366 bootconf.desc[choice]); 367 } 368 } 369 #endif /* defined(__minix) */ 370 371 void 372 doboottypemenu(void) 373 { 374 #if !defined(__minix) 375 int choice; 376 char input[80]; 377 378 printf("\n"); 379 /* Display menu */ 380 if (bootconf.menuformat == MENUFORMAT_LETTER) { 381 for (choice = 0; choice < bootconf.nummenu; choice++) 382 printf(" %c. %s\n", choice + 'A', 383 bootconf.desc[choice]); 384 } else { 385 /* Can't use %2d format string with libsa */ 386 for (choice = 0; choice < bootconf.nummenu; choice++) 387 printf(" %s%d. %s\n", 388 (choice < 9) ? " " : "", 389 choice + 1, 390 bootconf.desc[choice]); 391 } 392 #else 393 int choice, editing; 394 char input[256], *ic, *oc; 395 #endif /* !defined(__minix) */ 396 #if defined(__minix) 397 showmenu(); 398 #endif /* defined(__minix) */ 399 choice = -1; 400 #if defined(__minix) 401 editing = 0; 402 #endif /* defined(__minix) */ 403 for (;;) { 404 input[0] = '\0'; 405 406 if (bootconf.timeout < 0) { 407 if (bootconf.menuformat == MENUFORMAT_LETTER) 408 #if !defined(__minix) 409 printf("\nOption: [%c]:", 410 #else 411 printf("\nOption%s: [%c]:", 412 editing ? " (edit)" : "", 413 #endif /* !defined(__minix) */ 414 bootconf.def + 'A'); 415 else 416 #if !defined(__minix) 417 printf("\nOption: [%d]:", 418 #else 419 printf("\nOption%s: [%d]:", 420 editing ? " (edit)" : "", 421 #endif /* !defined(__minix) */ 422 bootconf.def + 1); 423 424 #if !defined(__minix) 425 gets(input); 426 #else 427 editline(input, sizeof(input), NULL); 428 #endif /* !defined(__minix) */ 429 choice = getchoicefrominput(input, bootconf.def); 430 } else if (bootconf.timeout == 0) 431 choice = bootconf.def; 432 else { 433 printf("\nChoose an option; RETURN for default; " 434 "SPACE to stop countdown.\n"); 435 if (bootconf.menuformat == MENUFORMAT_LETTER) 436 printf("Option %c will be chosen in ", 437 bootconf.def + 'A'); 438 else 439 printf("Option %d will be chosen in ", 440 bootconf.def + 1); 441 input[0] = awaitkey(bootconf.timeout, 1); 442 input[1] = '\0'; 443 choice = getchoicefrominput(input, bootconf.def); 444 /* If invalid key pressed, drop to menu */ 445 if (choice == -1) 446 bootconf.timeout = -1; 447 } 448 if (choice < 0) 449 continue; 450 #if !defined(__minix) 451 if (!strcmp(bootconf.command[choice], "prompt") && 452 ((boot_params.bp_flags & X86_BP_FLAGS_PASSWORD) == 0 || 453 check_password((char *)boot_params.bp_password))) { 454 printf("type \"?\" or \"help\" for help.\n"); 455 bootmenu(); /* does not return */ 456 } else { 457 docommandchoice(choice); 458 } 459 #else 460 ic = bootconf.command[choice]; 461 if (editing) { 462 printf("> "); 463 editline(input, sizeof(input), ic); 464 ic = input; 465 } 466 if (!strcmp(ic, "edit") && 467 ((boot_params.bp_flags & X86_BP_FLAGS_PASSWORD) == 0 || 468 check_password((char *)boot_params.bp_password))) { 469 editing = 1; 470 bootconf.timeout = -1; 471 } else if (!strcmp(ic, "prompt") && 472 ((boot_params.bp_flags & X86_BP_FLAGS_PASSWORD) == 0 || 473 check_password((char *)boot_params.bp_password))) { 474 printf("type \"?\" or \"help\" for help, " 475 "or \"menu\" to return to the menu.\n"); 476 prompt(1); 477 showmenu(); 478 editing = 0; 479 bootconf.timeout = -1; 480 } else { 481 /* Split command string at ; into separate commands */ 482 do { 483 /* 484 * This must support inline editing, since ic 485 * may also point to input. 486 */ 487 oc = input; 488 /* Look for ; separator */ 489 for (; *ic && *ic != COMMAND_SEPARATOR; ic++) 490 *oc++ = *ic; 491 if (*ic == COMMAND_SEPARATOR) 492 ic++; 493 if (oc == input) 494 continue; 495 /* Strip out any trailing spaces */ 496 oc--; 497 for (; *oc == ' ' && oc > input; oc--); 498 *++oc = '\0'; 499 /* Stop silly command strings like ;;; */ 500 if (*input != '\0') 501 docommand(input); 502 /* Skip leading spaces */ 503 for (; *ic == ' '; ic++); 504 } while (*ic); 505 } 506 #endif /* !defined(__minix) */ 507 508 } 509 } 510 511 #endif /* !SMALL */ 512