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