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