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 127 static int 128 command_help(int argc, char *argv[]) 129 { 130 char buf[81]; /* XXX buffer size? */ 131 int hfd, matched, doindex; 132 char *topic, *subtopic, *t, *s, *d; 133 134 /* page the help text from our load path */ 135 snprintf(buf, sizeof(buf), "%s/boot/loader.help", getenv("loaddev")); 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 if ((err = putenv(argv[1])) != 0) { 308 command_errmsg = strerror(err); 309 return (CMD_ERROR); 310 } 311 } 312 return (CMD_OK); 313 } 314 315 COMMAND_SET(unset, "unset", "unset a variable", command_unset); 316 317 static int 318 command_unset(int argc, char *argv[]) 319 { 320 int err; 321 322 if (argc != 2) { 323 command_errmsg = "wrong number of arguments"; 324 return (CMD_ERROR); 325 } else { 326 if ((err = unsetenv(argv[1])) != 0) { 327 command_errmsg = strerror(err); 328 return (CMD_ERROR); 329 } 330 } 331 return (CMD_OK); 332 } 333 334 COMMAND_SET(echo, "echo", "echo arguments", command_echo); 335 336 static int 337 command_echo(int argc, char *argv[]) 338 { 339 char *s; 340 int nl, ch; 341 342 nl = 0; 343 optind = 1; 344 optreset = 1; 345 while ((ch = getopt(argc, argv, "n")) != -1) { 346 switch (ch) { 347 case 'n': 348 nl = 1; 349 break; 350 case '?': 351 default: 352 /* getopt has already reported an error */ 353 return (CMD_OK); 354 } 355 } 356 argv += (optind); 357 argc -= (optind); 358 359 s = unargv(argc, argv); 360 if (s != NULL) { 361 printf("%s", s); 362 free(s); 363 } 364 if (!nl) 365 printf("\n"); 366 return (CMD_OK); 367 } 368 369 /* 370 * A passable emulation of the sh(1) command of the same name. 371 */ 372 373 COMMAND_SET(read, "read", "read input from the terminal", command_read); 374 375 static int 376 command_read(int argc, char *argv[]) 377 { 378 char *prompt; 379 int timeout; 380 time_t when; 381 char *cp; 382 char *name; 383 char buf[256]; /* XXX size? */ 384 int c; 385 386 timeout = -1; 387 prompt = NULL; 388 optind = 1; 389 optreset = 1; 390 while ((c = getopt(argc, argv, "p:t:")) != -1) { 391 switch (c) { 392 case 'p': 393 prompt = optarg; 394 break; 395 case 't': 396 timeout = strtol(optarg, &cp, 0); 397 if (cp == optarg) { 398 snprintf(command_errbuf, 399 sizeof(command_errbuf), 400 "bad timeout '%s'", optarg); 401 return (CMD_ERROR); 402 } 403 break; 404 default: 405 return (CMD_OK); 406 } 407 } 408 409 argv += (optind); 410 argc -= (optind); 411 name = (argc > 0) ? argv[0]: NULL; 412 413 if (prompt != NULL) 414 printf("%s", prompt); 415 if (timeout >= 0) { 416 when = time(NULL) + timeout; 417 while (!ischar()) 418 if (time(NULL) >= when) 419 return (CMD_OK); /* is timeout an error? */ 420 } 421 422 ngets(buf, sizeof(buf)); 423 424 if (name != NULL) 425 setenv(name, buf, 1); 426 return (CMD_OK); 427 } 428 429 /* 430 * File pager 431 */ 432 COMMAND_SET(more, "more", "show contents of a file", command_more); 433 434 static int 435 command_more(int argc, char *argv[]) 436 { 437 int i; 438 int res; 439 char line[80]; 440 441 res = 0; 442 pager_open(); 443 for (i = 1; (i < argc) && (res == 0); i++) { 444 snprintf(line, sizeof(line), "*** FILE %s BEGIN ***\n", 445 argv[i]); 446 if (pager_output(line)) 447 break; 448 res = page_file(argv[i]); 449 if (!res) { 450 snprintf(line, sizeof(line), "*** FILE %s END ***\n", 451 argv[i]); 452 res = pager_output(line); 453 } 454 } 455 pager_close(); 456 457 if (res == 0) 458 return (CMD_OK); 459 else 460 return (CMD_ERROR); 461 } 462 463 static int 464 page_file(char *filename) 465 { 466 int result; 467 468 result = pager_file(filename); 469 470 if (result == -1) { 471 snprintf(command_errbuf, sizeof(command_errbuf), 472 "error showing %s", filename); 473 } 474 475 return (result); 476 } 477 478 /* 479 * List all disk-like devices 480 */ 481 COMMAND_SET(lsdev, "lsdev", "list all devices", command_lsdev); 482 483 static int 484 command_lsdev(int argc, char *argv[]) 485 { 486 int verbose, ch, i; 487 char line[80]; 488 489 verbose = 0; 490 optind = 1; 491 optreset = 1; 492 while ((ch = getopt(argc, argv, "v")) != -1) { 493 switch (ch) { 494 case 'v': 495 verbose = 1; 496 break; 497 case '?': 498 default: 499 /* getopt has already reported an error */ 500 return (CMD_OK); 501 } 502 } 503 argv += (optind); 504 argc -= (optind); 505 506 pager_open(); 507 for (i = 0; devsw[i] != NULL; i++) { 508 if (devsw[i]->dv_print != NULL) { 509 if (devsw[i]->dv_print(verbose)) 510 break; 511 } else { 512 snprintf(line, sizeof(line), "%s: (unknown)\n", 513 devsw[i]->dv_name); 514 if (pager_output(line)) 515 break; 516 } 517 } 518 pager_close(); 519 return (CMD_OK); 520 } 521