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[20]; 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), " %-15s ", 242 (*cmdp)->c_name); 243 pager_output(name); 244 pager_output((*cmdp)->c_desc); 245 res = pager_output("\n"); 246 } 247 } 248 pager_close(); 249 return (CMD_OK); 250 } 251 252 /* 253 * XXX set/show should become set/echo if we have variable 254 * substitution happening. 255 */ 256 257 COMMAND_SET(show, "show", "show variable(s)", command_show); 258 259 static int 260 command_show(int argc, char *argv[]) 261 { 262 struct env_var *ev; 263 char *cp; 264 265 if (argc < 2) { 266 /* 267 * With no arguments, print everything. 268 */ 269 pager_open(); 270 for (ev = environ; ev != NULL; ev = ev->ev_next) { 271 pager_output(ev->ev_name); 272 cp = getenv(ev->ev_name); 273 if (cp != NULL) { 274 pager_output("="); 275 pager_output(cp); 276 } 277 if (pager_output("\n")) 278 break; 279 } 280 pager_close(); 281 } else { 282 if ((cp = getenv(argv[1])) != NULL) { 283 printf("%s\n", cp); 284 } else { 285 snprintf(command_errbuf, sizeof(command_errbuf), 286 "variable '%s' not found", argv[1]); 287 return (CMD_ERROR); 288 } 289 } 290 return (CMD_OK); 291 } 292 293 COMMAND_SET(set, "set", "set a variable", command_set); 294 295 static int 296 command_set(int argc, char *argv[]) 297 { 298 int err; 299 300 if (argc != 2) { 301 command_errmsg = "wrong number of arguments"; 302 return (CMD_ERROR); 303 } else { 304 #ifdef LOADER_VERIEXEC 305 /* 306 * Impose restrictions if input is not verified 307 */ 308 const char *restricted[] = { 309 "boot", 310 "init", 311 "loader.ve.", 312 "rootfs", 313 "secur", 314 "vfs.", 315 NULL, 316 }; 317 const char **cp; 318 int ves; 319 320 ves = ve_status_get(-1); 321 if (ves == VE_UNVERIFIED_OK) { 322 #ifdef LOADER_VERIEXEC_TESTING 323 printf("Checking: %s\n", argv[1]); 324 #endif 325 for (cp = restricted; *cp; cp++) { 326 if (strncmp(argv[1], *cp, strlen(*cp)) == 0) { 327 printf("Ignoring restricted variable: %s\n", 328 argv[1]); 329 return (CMD_OK); 330 } 331 } 332 } 333 #endif 334 if ((err = putenv(argv[1])) != 0) { 335 command_errmsg = strerror(err); 336 return (CMD_ERROR); 337 } 338 } 339 return (CMD_OK); 340 } 341 342 COMMAND_SET(unset, "unset", "unset a variable", command_unset); 343 344 static int 345 command_unset(int argc, char *argv[]) 346 { 347 int err; 348 349 if (argc != 2) { 350 command_errmsg = "wrong number of arguments"; 351 return (CMD_ERROR); 352 } else { 353 if ((err = unsetenv(argv[1])) != 0) { 354 command_errmsg = strerror(err); 355 return (CMD_ERROR); 356 } 357 } 358 return (CMD_OK); 359 } 360 361 COMMAND_SET(echo, "echo", "echo arguments", command_echo); 362 363 static int 364 command_echo(int argc, char *argv[]) 365 { 366 char *s; 367 int nl, ch; 368 369 nl = 0; 370 optind = 1; 371 optreset = 1; 372 while ((ch = getopt(argc, argv, "n")) != -1) { 373 switch (ch) { 374 case 'n': 375 nl = 1; 376 break; 377 case '?': 378 default: 379 /* getopt has already reported an error */ 380 return (CMD_OK); 381 } 382 } 383 argv += (optind); 384 argc -= (optind); 385 386 s = unargv(argc, argv); 387 if (s != NULL) { 388 printf("%s", s); 389 free(s); 390 } 391 if (!nl) 392 printf("\n"); 393 return (CMD_OK); 394 } 395 396 /* 397 * A passable emulation of the sh(1) command of the same name. 398 */ 399 400 COMMAND_SET(read, "read", "read input from the terminal", command_read); 401 402 static int 403 command_read(int argc, char *argv[]) 404 { 405 char *prompt; 406 int timeout; 407 time_t when; 408 char *cp; 409 char *name; 410 char buf[256]; /* XXX size? */ 411 int c; 412 413 timeout = -1; 414 prompt = NULL; 415 optind = 1; 416 optreset = 1; 417 while ((c = getopt(argc, argv, "p:t:")) != -1) { 418 switch (c) { 419 case 'p': 420 prompt = optarg; 421 break; 422 case 't': 423 timeout = strtol(optarg, &cp, 0); 424 if (cp == optarg) { 425 snprintf(command_errbuf, 426 sizeof(command_errbuf), 427 "bad timeout '%s'", optarg); 428 return (CMD_ERROR); 429 } 430 break; 431 default: 432 return (CMD_OK); 433 } 434 } 435 436 argv += (optind); 437 argc -= (optind); 438 name = (argc > 0) ? argv[0]: NULL; 439 440 if (prompt != NULL) 441 printf("%s", prompt); 442 if (timeout >= 0) { 443 when = time(NULL) + timeout; 444 while (!ischar()) 445 if (time(NULL) >= when) 446 return (CMD_OK); /* is timeout an error? */ 447 } 448 449 ngets(buf, sizeof(buf)); 450 451 if (name != NULL) 452 setenv(name, buf, 1); 453 return (CMD_OK); 454 } 455 456 /* 457 * File pager 458 */ 459 COMMAND_SET(more, "more", "show contents of a file", command_more); 460 461 static int 462 command_more(int argc, char *argv[]) 463 { 464 int i; 465 int res; 466 char line[80]; 467 468 res = 0; 469 pager_open(); 470 for (i = 1; (i < argc) && (res == 0); i++) { 471 snprintf(line, sizeof(line), "*** FILE %s BEGIN ***\n", 472 argv[i]); 473 if (pager_output(line)) 474 break; 475 res = page_file(argv[i]); 476 if (!res) { 477 snprintf(line, sizeof(line), "*** FILE %s END ***\n", 478 argv[i]); 479 res = pager_output(line); 480 } 481 } 482 pager_close(); 483 484 return (CMD_OK); 485 } 486 487 static int 488 page_file(char *filename) 489 { 490 int result; 491 492 result = pager_file(filename); 493 494 if (result == -1) { 495 snprintf(command_errbuf, sizeof(command_errbuf), 496 "error showing %s", filename); 497 } 498 499 return (result); 500 } 501 502 /* 503 * List all disk-like devices 504 */ 505 COMMAND_SET(lsdev, "lsdev", "list all devices", command_lsdev); 506 507 static int 508 command_lsdev(int argc, char *argv[]) 509 { 510 int verbose, ch, i; 511 char line[80]; 512 513 verbose = 0; 514 optind = 1; 515 optreset = 1; 516 while ((ch = getopt(argc, argv, "v")) != -1) { 517 switch (ch) { 518 case 'v': 519 verbose = 1; 520 break; 521 case '?': 522 default: 523 /* getopt has already reported an error */ 524 return (CMD_OK); 525 } 526 } 527 argv += (optind); 528 argc -= (optind); 529 530 pager_open(); 531 for (i = 0; devsw[i] != NULL; i++) { 532 if (devsw[i]->dv_print != NULL) { 533 if (devsw[i]->dv_print(verbose)) 534 break; 535 } else { 536 snprintf(line, sizeof(line), "%s: (unknown)\n", 537 devsw[i]->dv_name); 538 if (pager_output(line)) 539 break; 540 } 541 } 542 pager_close(); 543 return (CMD_OK); 544 } 545 546 static int 547 command_readtest(int argc, char *argv[]) 548 { 549 int fd; 550 time_t start, end; 551 char buf[512]; 552 ssize_t rv, count = 0; 553 554 if (argc != 2) { 555 snprintf(command_errbuf, sizeof(command_errbuf), 556 "Usage: readtest <filename>"); 557 return (CMD_ERROR); 558 } 559 560 start = getsecs(); 561 if ((fd = open(argv[1], O_RDONLY)) < 0) { 562 snprintf(command_errbuf, sizeof(command_errbuf), 563 "can't open '%s'", argv[1]); 564 return (CMD_ERROR); 565 } 566 while ((rv = read(fd, buf, sizeof(buf))) > 0) 567 count += rv; 568 end = getsecs(); 569 570 printf("Received %zd bytes during %jd seconds\n", count, (intmax_t)end - start); 571 close(fd); 572 return (CMD_OK); 573 } 574 575 COMMAND_SET(readtest, "readtest", "Time a file read", command_readtest); 576 577 static int 578 command_quit(int argc, char *argv[]) 579 { 580 exit(0); 581 return (CMD_OK); 582 } 583 584 COMMAND_SET(quit, "quit", "exit the loader", command_quit); 585