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