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 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 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 *topic = *subtopic = *desc = NULL; 76 cp = line + 2; 77 while((cp != NULL) && (*cp != 0)) { 78 ep = strchr(cp, ' '); 79 if ((*cp == 'T') && (*topic == NULL)) { 80 if (ep != NULL) 81 *ep++ = 0; 82 *topic = strdup(cp + 1); 83 } else if ((*cp == 'S') && (*subtopic == NULL)) { 84 if (ep != NULL) 85 *ep++ = 0; 86 *subtopic = strdup(cp + 1); 87 } else if (*cp == 'D') { 88 *desc = strdup(cp + 1); 89 ep = NULL; 90 } 91 cp = ep; 92 } 93 if (*topic == NULL) { 94 if (*subtopic != NULL) 95 free(*subtopic); 96 if (*desc != NULL) 97 free(*desc); 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, use '?' to list commands\n"); 138 return(CMD_OK); 139 } 140 141 /* pick up request from arguments */ 142 topic = subtopic = NULL; 143 switch(argc) { 144 case 3: 145 subtopic = strdup(argv[2]); 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"); 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) /* 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) && !strcmp(subtopic, s))) { 180 /* exact match, print text */ 181 while((fgetstr(buf, 80, hfd) >= 0) && (buf[0] != '#')) { 182 if (pager_output(buf)) 183 break; 184 if (pager_output("\n")) 185 break; 186 } 187 } else if ((subtopic == NULL) && (s != NULL)) { 188 /* topic match, list subtopics */ 189 if (help_emitsummary(t, s, d)) 190 break; 191 } 192 } 193 free(t); 194 free(s); 195 free(d); 196 } 197 pager_close(); 198 close(hfd); 199 if (!matched) { 200 snprintf(command_errbuf, sizeof(command_errbuf), 201 "no help available for '%s'", topic); 202 free(topic); 203 if (subtopic) 204 free(subtopic); 205 return(CMD_ERROR); 206 } 207 free(topic); 208 if (subtopic) 209 free(subtopic); 210 return(CMD_OK); 211 } 212 213 214 COMMAND_SET(commandlist, "?", "list commands", command_commandlist); 215 216 /* 217 * Please note: although we use the pager for the list of commands, 218 * this routine is called from the ? FORTH function which then 219 * unconditionally prints some commands. This will lead to anomalous 220 * behavior. There's no 'pager_output' binding to FORTH to allow 221 * things to work right, so I'm documenting the bug rather than 222 * fixing it. 223 */ 224 static int 225 command_commandlist(int argc, char *argv[]) 226 { 227 struct bootblk_command **cmdp; 228 int res; 229 char name[20]; 230 231 res = 0; 232 pager_open(); 233 res = pager_output("Available commands:\n"); 234 SET_FOREACH(cmdp, Xcommand_set) { 235 if (res) 236 break; 237 if (((*cmdp)->c_name != NULL) && ((*cmdp)->c_desc != NULL)) { 238 sprintf(name, " %-15s ", (*cmdp)->c_name); 239 pager_output(name); 240 pager_output((*cmdp)->c_desc); 241 res = pager_output("\n"); 242 } 243 } 244 pager_close(); 245 return(CMD_OK); 246 } 247 248 /* 249 * XXX set/show should become set/echo if we have variable 250 * substitution happening. 251 */ 252 253 COMMAND_SET(show, "show", "show variable(s)", command_show); 254 255 static int 256 command_show(int argc, char *argv[]) 257 { 258 struct env_var *ev; 259 char *cp; 260 261 if (argc < 2) { 262 /* 263 * With no arguments, print everything. 264 */ 265 pager_open(); 266 for (ev = environ; ev != NULL; ev = ev->ev_next) { 267 pager_output(ev->ev_name); 268 cp = getenv(ev->ev_name); 269 if (cp != NULL) { 270 pager_output("="); 271 pager_output(cp); 272 } 273 if (pager_output("\n")) 274 break; 275 } 276 pager_close(); 277 } else { 278 if ((cp = getenv(argv[1])) != NULL) { 279 printf("%s\n", cp); 280 } else { 281 snprintf(command_errbuf, sizeof(command_errbuf), 282 "variable '%s' not found", argv[1]); 283 return(CMD_ERROR); 284 } 285 } 286 return(CMD_OK); 287 } 288 289 COMMAND_SET(set, "set", "set a variable", command_set); 290 291 static int 292 command_set(int argc, char *argv[]) 293 { 294 int err; 295 296 if (argc != 2) { 297 command_errmsg = "wrong number of arguments"; 298 return(CMD_ERROR); 299 } else { 300 if ((err = putenv(argv[1])) != 0) { 301 command_errmsg = strerror(err); 302 return(CMD_ERROR); 303 } 304 } 305 return(CMD_OK); 306 } 307 308 COMMAND_SET(unset, "unset", "unset a variable", command_unset); 309 310 static int 311 command_unset(int argc, char *argv[]) 312 { 313 int err; 314 315 if (argc != 2) { 316 command_errmsg = "wrong number of arguments"; 317 return(CMD_ERROR); 318 } else { 319 if ((err = unsetenv(argv[1])) != 0) { 320 command_errmsg = strerror(err); 321 return(CMD_ERROR); 322 } 323 } 324 return(CMD_OK); 325 } 326 327 COMMAND_SET(echo, "echo", "echo arguments", command_echo); 328 329 static int 330 command_echo(int argc, char *argv[]) 331 { 332 char *s; 333 int nl, ch; 334 335 nl = 0; 336 optind = 1; 337 optreset = 1; 338 while ((ch = getopt(argc, argv, "n")) != -1) { 339 switch(ch) { 340 case 'n': 341 nl = 1; 342 break; 343 case '?': 344 default: 345 /* getopt has already reported an error */ 346 return(CMD_OK); 347 } 348 } 349 argv += (optind); 350 argc -= (optind); 351 352 s = unargv(argc, argv); 353 if (s != NULL) { 354 printf("%s", s); 355 free(s); 356 } 357 if (!nl) 358 printf("\n"); 359 return(CMD_OK); 360 } 361 362 /* 363 * A passable emulation of the sh(1) command of the same name. 364 */ 365 366 COMMAND_SET(read, "read", "read input from the terminal", command_read); 367 368 static int 369 command_read(int argc, char *argv[]) 370 { 371 char *prompt; 372 int timeout; 373 time_t when; 374 char *cp; 375 char *name; 376 char buf[256]; /* XXX size? */ 377 int c; 378 379 timeout = -1; 380 prompt = NULL; 381 optind = 1; 382 optreset = 1; 383 while ((c = getopt(argc, argv, "p:t:")) != -1) { 384 switch(c) { 385 386 case 'p': 387 prompt = optarg; 388 break; 389 case 't': 390 timeout = strtol(optarg, &cp, 0); 391 if (cp == optarg) { 392 snprintf(command_errbuf, sizeof(command_errbuf), 393 "bad timeout '%s'", optarg); 394 return(CMD_ERROR); 395 } 396 break; 397 default: 398 return(CMD_OK); 399 } 400 } 401 402 argv += (optind); 403 argc -= (optind); 404 name = (argc > 0) ? argv[0]: NULL; 405 406 if (prompt != NULL) 407 printf("%s", prompt); 408 if (timeout >= 0) { 409 when = time(NULL) + timeout; 410 while (!ischar()) 411 if (time(NULL) >= when) 412 return(CMD_OK); /* is timeout an error? */ 413 } 414 415 ngets(buf, sizeof(buf)); 416 417 if (name != NULL) 418 setenv(name, buf, 1); 419 return(CMD_OK); 420 } 421 422 /* 423 * File pager 424 */ 425 COMMAND_SET(more, "more", "show contents of a file", command_more); 426 427 static int 428 command_more(int argc, char *argv[]) 429 { 430 int i; 431 int res; 432 char line[80]; 433 434 res=0; 435 pager_open(); 436 for (i = 1; (i < argc) && (res == 0); i++) { 437 sprintf(line, "*** FILE %s BEGIN ***\n", argv[i]); 438 if (pager_output(line)) 439 break; 440 res = page_file(argv[i]); 441 if (!res) { 442 sprintf(line, "*** FILE %s END ***\n", argv[i]); 443 res = pager_output(line); 444 } 445 } 446 pager_close(); 447 448 if (res == 0) 449 return CMD_OK; 450 else 451 return CMD_ERROR; 452 } 453 454 static int 455 page_file(char *filename) 456 { 457 int result; 458 459 result = pager_file(filename); 460 461 if (result == -1) { 462 snprintf(command_errbuf, sizeof(command_errbuf), 463 "error showing %s", filename); 464 } 465 466 return result; 467 } 468 469 /* 470 * List all disk-like devices 471 */ 472 COMMAND_SET(lsdev, "lsdev", "list all devices", command_lsdev); 473 474 static int 475 command_lsdev(int argc, char *argv[]) 476 { 477 int verbose, ch, i; 478 char line[80]; 479 480 verbose = 0; 481 optind = 1; 482 optreset = 1; 483 while ((ch = getopt(argc, argv, "v")) != -1) { 484 switch(ch) { 485 case 'v': 486 verbose = 1; 487 break; 488 case '?': 489 default: 490 /* getopt has already reported an error */ 491 return(CMD_OK); 492 } 493 } 494 argv += (optind); 495 argc -= (optind); 496 497 pager_open(); 498 for (i = 0; devsw[i] != NULL; i++) { 499 if (devsw[i]->dv_print != NULL){ 500 if (devsw[i]->dv_print(verbose)) 501 break; 502 } else { 503 sprintf(line, "%s: (unknown)\n", devsw[i]->dv_name); 504 if (pager_output(line)) 505 break; 506 } 507 } 508 pager_close(); 509 return(CMD_OK); 510 } 511 512