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 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