1 /*- 2 * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 #include <stand.h> 28 #include <string.h> 29 30 #include "bootstrap.h" 31 32 const char *command_errmsg; 33 /* XXX should have procedural interface for setting, size limit? */ 34 char command_errbuf[COMMAND_ERRBUFSZ]; 35 36 static int page_file(char *filename); 37 38 /* 39 * Help is read from a formatted text file. 40 * 41 * Entries in the file are formatted as 42 43 # Ttopic [Ssubtopic] Ddescription 44 help 45 text 46 here 47 # 48 49 * 50 * Note that for code simplicity's sake, the above format must be followed 51 * exactly. 52 * 53 * Subtopic entries must immediately follow the topic (this is used to 54 * produce the listing of subtopics). 55 * 56 * If no argument(s) are supplied by the user, the help for 'help' is displayed. 57 */ 58 COMMAND_SET(help, "help", "detailed help", command_help); 59 60 static int 61 help_getnext(int fd, char **topic, char **subtopic, char **desc) 62 { 63 char line[81], *cp, *ep; 64 65 /* Make sure we provide sane values. */ 66 *topic = *subtopic = *desc = NULL; 67 for (;;) { 68 if (fgetstr(line, 80, fd) < 0) 69 return (0); 70 71 if (strlen(line) < 3 || line[0] != '#' || line[1] != ' ') 72 continue; 73 74 cp = line + 2; 75 while (cp != NULL && *cp != 0) { 76 ep = strchr(cp, ' '); 77 if (*cp == 'T' && *topic == NULL) { 78 if (ep != NULL) 79 *ep++ = 0; 80 *topic = strdup(cp + 1); 81 } else if (*cp == 'S' && *subtopic == NULL) { 82 if (ep != NULL) 83 *ep++ = 0; 84 *subtopic = strdup(cp + 1); 85 } else if (*cp == 'D') { 86 *desc = strdup(cp + 1); 87 ep = NULL; 88 } 89 cp = ep; 90 } 91 if (*topic == NULL) { 92 free(*subtopic); 93 free(*desc); 94 *subtopic = *desc = NULL; 95 continue; 96 } 97 return (1); 98 } 99 } 100 101 static int 102 help_emitsummary(char *topic, char *subtopic, char *desc) 103 { 104 int i; 105 106 pager_output(" "); 107 pager_output(topic); 108 i = strlen(topic); 109 if (subtopic != NULL) { 110 pager_output(" "); 111 pager_output(subtopic); 112 i += strlen(subtopic) + 1; 113 } 114 if (desc != NULL) { 115 do { 116 pager_output(" "); 117 } while (i++ < 30); 118 pager_output(desc); 119 } 120 return (pager_output("\n")); 121 } 122 123 static int 124 command_help(int argc, char *argv[]) 125 { 126 char buf[81]; /* XXX buffer size? */ 127 int hfd, matched, doindex; 128 char *topic, *subtopic, *t, *s, *d; 129 130 /* page the help text from our load path */ 131 snprintf(buf, sizeof(buf), "%s/boot/%s", getenv("loaddev"), 132 HELP_FILENAME); 133 if ((hfd = open(buf, O_RDONLY)) < 0) { 134 printf("Verbose help not available, " 135 "use '?' to list commands\n"); 136 return (CMD_OK); 137 } 138 139 /* pick up request from arguments */ 140 topic = subtopic = NULL; 141 switch (argc) { 142 case 3: 143 subtopic = strdup(argv[2]); 144 /* FALLTHROUGH */ 145 case 2: 146 topic = strdup(argv[1]); 147 break; 148 case 1: 149 topic = strdup("help"); 150 break; 151 default: 152 command_errmsg = "usage is 'help <topic> [<subtopic>]"; 153 close(hfd); 154 return(CMD_ERROR); 155 } 156 157 /* magic "index" keyword */ 158 doindex = strcmp(topic, "index") == 0? 1 : 0; 159 matched = doindex; 160 161 /* Scan the helpfile looking for help matching the request */ 162 pager_open(); 163 while (help_getnext(hfd, &t, &s, &d)) { 164 165 if (doindex) { /* dink around formatting */ 166 if (help_emitsummary(t, s, d)) 167 break; 168 169 } else if (strcmp(topic, t)) { 170 /* topic mismatch */ 171 if (matched) { 172 /* nothing more on this topic, stop scanning */ 173 break; 174 } 175 } else { 176 /* topic matched */ 177 matched = 1; 178 if ((subtopic == NULL && s == NULL) || 179 (subtopic != NULL && s != NULL && 180 strcmp(subtopic, s) == 0)) { 181 /* exact match, print text */ 182 while (fgetstr(buf, 80, hfd) >= 0 && 183 buf[0] != '#') { 184 if (pager_output(buf)) 185 break; 186 if (pager_output("\n")) 187 break; 188 } 189 } else if (subtopic == NULL && s != NULL) { 190 /* topic match, list subtopics */ 191 if (help_emitsummary(t, s, d)) 192 break; 193 } 194 } 195 free(t); 196 free(s); 197 free(d); 198 t = s = d = NULL; 199 } 200 free(t); 201 free(s); 202 free(d); 203 pager_close(); 204 close(hfd); 205 if (!matched) { 206 snprintf(command_errbuf, sizeof(command_errbuf), 207 "no help available for '%s'", topic); 208 free(topic); 209 free(subtopic); 210 return (CMD_ERROR); 211 } 212 free(topic); 213 free(subtopic); 214 return (CMD_OK); 215 } 216 217 COMMAND_SET(commandlist, "?", "list commands", command_commandlist); 218 219 /* 220 * Please note: although we use the pager for the list of commands, 221 * this routine is called from the ? FORTH function which then 222 * unconditionally prints some commands. This will lead to anomalous 223 * behavior. There's no 'pager_output' binding to FORTH to allow 224 * things to work right, so I'm documenting the bug rather than 225 * fixing it. 226 */ 227 static int 228 command_commandlist(int argc __unused, char *argv[] __unused) 229 { 230 struct bootblk_command **cmdp; 231 int res; 232 char name[23]; 233 234 res = 0; 235 pager_open(); 236 res = pager_output("Available commands:\n"); 237 SET_FOREACH(cmdp, Xcommand_set) { 238 if (res) 239 break; 240 if ((*cmdp)->c_name != NULL && (*cmdp)->c_desc != NULL) { 241 snprintf(name, sizeof(name), " %-20s", 242 (*cmdp)->c_name); 243 pager_output(name); 244 pager_output(" "); 245 pager_output((*cmdp)->c_desc); 246 res = pager_output("\n"); 247 } 248 } 249 pager_close(); 250 return (CMD_OK); 251 } 252 253 /* 254 * XXX set/show should become set/echo if we have variable 255 * substitution happening. 256 */ 257 258 COMMAND_SET(show, "show", "show variable(s)", command_show); 259 260 static int 261 command_show(int argc, char *argv[]) 262 { 263 struct env_var *ev; 264 char *cp; 265 266 if (argc < 2) { 267 /* 268 * With no arguments, print everything. 269 */ 270 pager_open(); 271 for (ev = environ; ev != NULL; ev = ev->ev_next) { 272 pager_output(ev->ev_name); 273 cp = getenv(ev->ev_name); 274 if (cp != NULL) { 275 pager_output("="); 276 pager_output(cp); 277 } 278 if (pager_output("\n")) 279 break; 280 } 281 pager_close(); 282 } else { 283 if ((cp = getenv(argv[1])) != NULL) { 284 printf("%s\n", cp); 285 } else { 286 snprintf(command_errbuf, sizeof(command_errbuf), 287 "variable '%s' not found", argv[1]); 288 return (CMD_ERROR); 289 } 290 } 291 return (CMD_OK); 292 } 293 294 #ifdef LOADER_VERIEXEC 295 static int 296 is_restricted_var(const char *var) 297 { 298 /* 299 * We impose restrictions if input is not verified 300 * allowing for exceptions. 301 * These entries should include the '=' 302 */ 303 const char *allowed[] = { 304 "boot_function=", 305 "boot_phase=", 306 "boot_recover_cli=", 307 "boot_recover_volume=", 308 "boot_safe=", 309 "boot_set=", 310 "boot_single=", 311 "boot_verbose=", 312 NULL, 313 }; 314 const char *restricted[] = { 315 "boot", 316 "init", 317 "loader.ve.", 318 "rootfs", 319 "secur", 320 "vfs.", 321 NULL, 322 }; 323 const char **cp; 324 int ok = -1; 325 326 #ifdef LOADER_VERIEXEC_TESTING 327 printf("Checking: %s\n", var); 328 #endif 329 for (cp = restricted; *cp; cp++) { 330 if (strncmp(var, *cp, strlen(*cp)) == 0) { 331 ok = 0; 332 break; 333 } 334 } 335 if (!ok) { 336 /* 337 * Check for exceptions. 338 * These should match up to '='. 339 */ 340 for (cp = allowed; *cp; cp++) { 341 if (strncmp(var, *cp, strlen(*cp)) == 0) { 342 ok = 1; 343 break; 344 } 345 } 346 } 347 return (ok == 0); 348 } 349 #endif 350 351 COMMAND_SET(set, "set", "set a variable", command_set); 352 353 static int 354 command_set(int argc, char *argv[]) 355 { 356 int err; 357 358 if (argc != 2) { 359 command_errmsg = "wrong number of arguments"; 360 return (CMD_ERROR); 361 } else { 362 #ifdef LOADER_VERIEXEC 363 int ves; 364 365 ves = ve_status_get(-1); 366 if (ves == VE_UNVERIFIED_OK) { 367 if (is_restricted_var(argv[1])) { 368 printf("Ignoring restricted variable: %s\n", 369 argv[1]); 370 return (CMD_OK); 371 } 372 } 373 #endif 374 if ((err = putenv(argv[1])) != 0) { 375 command_errmsg = strerror(err); 376 return (CMD_ERROR); 377 } 378 } 379 return (CMD_OK); 380 } 381 382 COMMAND_SET(unset, "unset", "unset a variable", command_unset); 383 384 static int 385 command_unset(int argc, char *argv[]) 386 { 387 int err; 388 389 if (argc != 2) { 390 command_errmsg = "wrong number of arguments"; 391 return (CMD_ERROR); 392 } else { 393 #ifdef LOADER_VERIEXEC 394 int ves; 395 396 ves = ve_status_get(-1); 397 if (ves == VE_UNVERIFIED_OK) { 398 if (is_restricted_var(argv[1])) { 399 printf("Ignoring restricted variable: %s\n", 400 argv[1]); 401 return (CMD_OK); 402 } 403 } 404 #endif 405 if ((err = unsetenv(argv[1])) != 0) { 406 command_errmsg = strerror(err); 407 return (CMD_ERROR); 408 } 409 } 410 return (CMD_OK); 411 } 412 413 COMMAND_SET(echo, "echo", "echo arguments", command_echo); 414 415 static int 416 command_echo(int argc, char *argv[]) 417 { 418 char *s; 419 int nl, ch; 420 421 nl = 0; 422 optind = 1; 423 optreset = 1; 424 while ((ch = getopt(argc, argv, "n")) != -1) { 425 switch (ch) { 426 case 'n': 427 nl = 1; 428 break; 429 case '?': 430 default: 431 /* getopt has already reported an error */ 432 return (CMD_OK); 433 } 434 } 435 argv += (optind); 436 argc -= (optind); 437 438 s = unargv(argc, argv); 439 if (s != NULL) { 440 printf("%s", s); 441 free(s); 442 } 443 if (!nl) 444 printf("\n"); 445 return (CMD_OK); 446 } 447 448 /* 449 * A passable emulation of the sh(1) command of the same name. 450 */ 451 452 COMMAND_SET(read, "read", "read input from the terminal", command_read); 453 454 static int 455 command_read(int argc, char *argv[]) 456 { 457 char *prompt; 458 int timeout; 459 time_t when; 460 char *cp; 461 char *name; 462 char buf[256]; /* XXX size? */ 463 int c; 464 465 timeout = -1; 466 prompt = NULL; 467 optind = 1; 468 optreset = 1; 469 while ((c = getopt(argc, argv, "p:t:")) != -1) { 470 switch (c) { 471 case 'p': 472 prompt = optarg; 473 break; 474 case 't': 475 timeout = strtol(optarg, &cp, 0); 476 if (cp == optarg) { 477 snprintf(command_errbuf, 478 sizeof(command_errbuf), 479 "bad timeout '%s'", optarg); 480 return (CMD_ERROR); 481 } 482 break; 483 default: 484 return (CMD_OK); 485 } 486 } 487 488 argv += (optind); 489 argc -= (optind); 490 name = (argc > 0) ? argv[0]: NULL; 491 492 if (prompt != NULL) 493 printf("%s", prompt); 494 if (timeout >= 0) { 495 when = time(NULL) + timeout; 496 while (!ischar()) 497 if (time(NULL) >= when) 498 return (CMD_OK); /* is timeout an error? */ 499 } 500 501 ngets(buf, sizeof(buf)); 502 503 if (name != NULL) 504 setenv(name, buf, 1); 505 return (CMD_OK); 506 } 507 508 /* 509 * File pager 510 */ 511 COMMAND_SET(more, "more", "show contents of a file", command_more); 512 513 static int 514 command_more(int argc, char *argv[]) 515 { 516 int i; 517 int res; 518 char line[80]; 519 520 res = 0; 521 pager_open(); 522 for (i = 1; (i < argc) && (res == 0); i++) { 523 snprintf(line, sizeof(line), "*** FILE %s BEGIN ***\n", 524 argv[i]); 525 if (pager_output(line)) 526 break; 527 res = page_file(argv[i]); 528 if (!res) { 529 snprintf(line, sizeof(line), "*** FILE %s END ***\n", 530 argv[i]); 531 res = pager_output(line); 532 } 533 } 534 pager_close(); 535 536 return (CMD_OK); 537 } 538 539 static int 540 page_file(char *filename) 541 { 542 int result; 543 544 result = pager_file(filename); 545 546 if (result == -1) { 547 snprintf(command_errbuf, sizeof(command_errbuf), 548 "error showing %s", filename); 549 } 550 551 return (result); 552 } 553 554 /* 555 * List all disk-like devices 556 */ 557 COMMAND_SET(lsdev, "lsdev", "list all devices", command_lsdev); 558 559 static int 560 command_lsdev(int argc, char *argv[]) 561 { 562 int verbose, ch, i; 563 char line[80]; 564 565 verbose = 0; 566 optind = 1; 567 optreset = 1; 568 while ((ch = getopt(argc, argv, "v")) != -1) { 569 switch (ch) { 570 case 'v': 571 verbose = 1; 572 break; 573 case '?': 574 default: 575 /* getopt has already reported an error */ 576 return (CMD_OK); 577 } 578 } 579 argv += (optind); 580 argc -= (optind); 581 582 pager_open(); 583 for (i = 0; devsw[i] != NULL; i++) { 584 if (devsw[i]->dv_print != NULL) { 585 if (devsw[i]->dv_print(verbose)) 586 break; 587 } else { 588 snprintf(line, sizeof(line), "%s: (unknown)\n", 589 devsw[i]->dv_name); 590 if (pager_output(line)) 591 break; 592 } 593 } 594 pager_close(); 595 return (CMD_OK); 596 } 597 598 static int 599 command_readtest(int argc, char *argv[]) 600 { 601 int fd; 602 time_t start, end; 603 char buf[512]; 604 ssize_t rv, count = 0; 605 606 if (argc != 2) { 607 snprintf(command_errbuf, sizeof(command_errbuf), 608 "Usage: readtest <filename>"); 609 return (CMD_ERROR); 610 } 611 612 start = getsecs(); 613 if ((fd = open(argv[1], O_RDONLY)) < 0) { 614 snprintf(command_errbuf, sizeof(command_errbuf), 615 "can't open '%s'", argv[1]); 616 return (CMD_ERROR); 617 } 618 while ((rv = read(fd, buf, sizeof(buf))) > 0) 619 count += rv; 620 end = getsecs(); 621 622 printf("Received %zd bytes during %jd seconds\n", count, (intmax_t)end - start); 623 close(fd); 624 return (CMD_OK); 625 } 626 627 COMMAND_SET(readtest, "readtest", "Time a file read", command_readtest); 628 629 static int 630 command_quit(int argc, char *argv[]) 631 { 632 exit(0); 633 return (CMD_OK); 634 } 635 636 COMMAND_SET(quit, "quit", "exit the loader", command_quit); 637