1 #ifndef lint 2 static char *sccsid = "@(#)lex.c 2.17 (Berkeley) 04/18/85"; 3 #endif 4 5 #include "rcv.h" 6 #include <sys/stat.h> 7 8 /* 9 * Mail -- a mail program 10 * 11 * Lexical processing of commands. 12 */ 13 14 char *prompt = "& "; 15 16 /* 17 * Set up editing on the given file name. 18 * If isedit is true, we are considered to be editing the file, 19 * otherwise we are reading our mail which has signficance for 20 * mbox and so forth. 21 */ 22 23 setfile(name, isedit) 24 char *name; 25 { 26 FILE *ibuf; 27 int i; 28 struct stat stb; 29 static int shudclob; 30 static char efile[128]; 31 extern char tempMesg[]; 32 33 if ((ibuf = fopen(name, "r")) == NULL) 34 return(-1); 35 if (!edit) { 36 if (fstat(fileno(ibuf), &stb) < 0) { 37 perror(name); 38 exit(1); 39 } 40 if (stb.st_size == 0) 41 return(-1); 42 } 43 44 /* 45 * Looks like all will be well. We must now relinquish our 46 * hold on the current set of stuff. Must hold signals 47 * while we are reading the new file, else we will ruin 48 * the message[] data structure. 49 */ 50 51 holdsigs(); 52 if (shudclob) { 53 if (edit) 54 edstop(); 55 else 56 quit(); 57 } 58 59 /* 60 * Copy the messages into /tmp 61 * and set pointers. 62 */ 63 64 readonly = 0; 65 if ((i = open(name, 1)) < 0) 66 readonly++; 67 else 68 close(i); 69 if (shudclob) { 70 fclose(itf); 71 fclose(otf); 72 } 73 shudclob = 1; 74 edit = isedit; 75 strncpy(efile, name, 128); 76 editfile = efile; 77 if (name != mailname) 78 strcpy(mailname, name); 79 mailsize = fsize(ibuf); 80 if ((otf = fopen(tempMesg, "w")) == NULL) { 81 perror(tempMesg); 82 exit(1); 83 } 84 if ((itf = fopen(tempMesg, "r")) == NULL) { 85 perror(tempMesg); 86 exit(1); 87 } 88 remove(tempMesg); 89 setptr(ibuf); 90 setmsize(msgCount); 91 fclose(ibuf); 92 relsesigs(); 93 sawcom = 0; 94 return(0); 95 } 96 97 /* 98 * Interpret user commands one by one. If standard input is not a tty, 99 * print no prompt. 100 */ 101 102 int *msgvec; 103 104 commands() 105 { 106 int eofloop, shudprompt, stop(); 107 register int n; 108 char linebuf[LINESIZE]; 109 int hangup(), contin(); 110 111 # ifdef VMUNIX 112 sigset(SIGCONT, SIG_DFL); 113 # endif VMUNIX 114 if (rcvmode && !sourcing) { 115 if (sigset(SIGINT, SIG_IGN) != SIG_IGN) 116 sigset(SIGINT, stop); 117 if (sigset(SIGHUP, SIG_IGN) != SIG_IGN) 118 sigset(SIGHUP, hangup); 119 } 120 shudprompt = intty && !sourcing; 121 for (;;) { 122 setexit(); 123 124 /* 125 * Print the prompt, if needed. Clear out 126 * string space, and flush the output. 127 */ 128 129 if (!rcvmode && !sourcing) 130 return; 131 eofloop = 0; 132 top: 133 if (shudprompt) { 134 printf(prompt); 135 fflush(stdout); 136 # ifdef VMUNIX 137 sigset(SIGCONT, contin); 138 # endif VMUNIX 139 } else 140 fflush(stdout); 141 sreset(); 142 143 /* 144 * Read a line of commands from the current input 145 * and handle end of file specially. 146 */ 147 148 n = 0; 149 for (;;) { 150 if (readline(input, &linebuf[n]) <= 0) { 151 if (n != 0) 152 break; 153 if (loading) 154 return; 155 if (sourcing) { 156 unstack(); 157 goto more; 158 } 159 if (value("ignoreeof") != NOSTR && shudprompt) { 160 if (++eofloop < 25) { 161 printf("Use \"quit\" to quit.\n"); 162 goto top; 163 } 164 } 165 if (edit) 166 edstop(); 167 return; 168 } 169 if ((n = strlen(linebuf)) == 0) 170 break; 171 n--; 172 if (linebuf[n] != '\\') 173 break; 174 linebuf[n++] = ' '; 175 } 176 # ifdef VMUNIX 177 sigset(SIGCONT, SIG_DFL); 178 # endif VMUNIX 179 if (execute(linebuf, 0)) 180 return; 181 more: ; 182 } 183 } 184 185 /* 186 * Execute a single command. If the command executed 187 * is "quit," then return non-zero so that the caller 188 * will know to return back to main, if he cares. 189 * Contxt is non-zero if called while composing mail. 190 */ 191 192 execute(linebuf, contxt) 193 char linebuf[]; 194 { 195 char word[LINESIZE]; 196 char *arglist[MAXARGC]; 197 struct cmd *com; 198 register char *cp, *cp2; 199 register int c; 200 int muvec[2]; 201 int edstop(), e; 202 203 /* 204 * Strip the white space away from the beginning 205 * of the command, then scan out a word, which 206 * consists of anything except digits and white space. 207 * 208 * Handle ! escapes differently to get the correct 209 * lexical conventions. 210 */ 211 212 cp = linebuf; 213 while (any(*cp, " \t")) 214 cp++; 215 if (*cp == '!') { 216 if (sourcing) { 217 printf("Can't \"!\" while sourcing\n"); 218 unstack(); 219 return(0); 220 } 221 shell(cp+1); 222 return(0); 223 } 224 cp2 = word; 225 while (*cp && !any(*cp, " \t0123456789$^.:/-+*'\"")) 226 *cp2++ = *cp++; 227 *cp2 = '\0'; 228 229 /* 230 * Look up the command; if not found, bitch. 231 * Normally, a blank command would map to the 232 * first command in the table; while sourcing, 233 * however, we ignore blank lines to eliminate 234 * confusion. 235 */ 236 237 if (sourcing && equal(word, "")) 238 return(0); 239 com = lex(word); 240 if (com == NONE) { 241 printf("Unknown command: \"%s\"\n", word); 242 if (loading) 243 return(1); 244 if (sourcing) 245 unstack(); 246 return(0); 247 } 248 249 /* 250 * See if we should execute the command -- if a conditional 251 * we always execute it, otherwise, check the state of cond. 252 */ 253 254 if ((com->c_argtype & F) == 0) 255 if (cond == CRCV && !rcvmode || cond == CSEND && rcvmode) 256 return(0); 257 258 /* 259 * Special case so that quit causes a return to 260 * main, who will call the quit code directly. 261 * If we are in a source file, just unstack. 262 */ 263 264 if (com->c_func == edstop && sourcing) { 265 if (loading) 266 return(1); 267 unstack(); 268 return(0); 269 } 270 if (!edit && com->c_func == edstop) { 271 sigset(SIGINT, SIG_IGN); 272 return(1); 273 } 274 275 /* 276 * Process the arguments to the command, depending 277 * on the type he expects. Default to an error. 278 * If we are sourcing an interactive command, it's 279 * an error. 280 */ 281 282 if (!rcvmode && (com->c_argtype & M) == 0) { 283 printf("May not execute \"%s\" while sending\n", 284 com->c_name); 285 if (loading) 286 return(1); 287 if (sourcing) 288 unstack(); 289 return(0); 290 } 291 if (sourcing && com->c_argtype & I) { 292 printf("May not execute \"%s\" while sourcing\n", 293 com->c_name); 294 if (loading) 295 return(1); 296 unstack(); 297 return(0); 298 } 299 if (readonly && com->c_argtype & W) { 300 printf("May not execute \"%s\" -- message file is read only\n", 301 com->c_name); 302 if (loading) 303 return(1); 304 if (sourcing) 305 unstack(); 306 return(0); 307 } 308 if (contxt && com->c_argtype & R) { 309 printf("Cannot recursively invoke \"%s\"\n", com->c_name); 310 return(0); 311 } 312 e = 1; 313 switch (com->c_argtype & ~(F|P|I|M|T|W|R)) { 314 case MSGLIST: 315 /* 316 * A message list defaulting to nearest forward 317 * legal message. 318 */ 319 if (msgvec == 0) { 320 printf("Illegal use of \"message list\"\n"); 321 return(-1); 322 } 323 if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0) 324 break; 325 if (c == 0) { 326 *msgvec = first(com->c_msgflag, 327 com->c_msgmask); 328 msgvec[1] = NULL; 329 } 330 if (*msgvec == NULL) { 331 printf("No applicable messages\n"); 332 break; 333 } 334 e = (*com->c_func)(msgvec); 335 break; 336 337 case NDMLIST: 338 /* 339 * A message list with no defaults, but no error 340 * if none exist. 341 */ 342 if (msgvec == 0) { 343 printf("Illegal use of \"message list\"\n"); 344 return(-1); 345 } 346 if (getmsglist(cp, msgvec, com->c_msgflag) < 0) 347 break; 348 e = (*com->c_func)(msgvec); 349 break; 350 351 case STRLIST: 352 /* 353 * Just the straight string, with 354 * leading blanks removed. 355 */ 356 while (any(*cp, " \t")) 357 cp++; 358 e = (*com->c_func)(cp); 359 break; 360 361 case RAWLIST: 362 /* 363 * A vector of strings, in shell style. 364 */ 365 if ((c = getrawlist(cp, arglist)) < 0) 366 break; 367 if (c < com->c_minargs) { 368 printf("%s requires at least %d arg(s)\n", 369 com->c_name, com->c_minargs); 370 break; 371 } 372 if (c > com->c_maxargs) { 373 printf("%s takes no more than %d arg(s)\n", 374 com->c_name, com->c_maxargs); 375 break; 376 } 377 e = (*com->c_func)(arglist); 378 break; 379 380 case NOLIST: 381 /* 382 * Just the constant zero, for exiting, 383 * eg. 384 */ 385 e = (*com->c_func)(0); 386 break; 387 388 default: 389 panic("Unknown argtype"); 390 } 391 392 /* 393 * Exit the current source file on 394 * error. 395 */ 396 397 if (e && loading) 398 return(1); 399 if (e && sourcing) 400 unstack(); 401 if (com->c_func == edstop) 402 return(1); 403 if (value("autoprint") != NOSTR && com->c_argtype & P) 404 if ((dot->m_flag & MDELETED) == 0) { 405 muvec[0] = dot - &message[0] + 1; 406 muvec[1] = 0; 407 type(muvec); 408 } 409 if (!sourcing && (com->c_argtype & T) == 0) 410 sawcom = 1; 411 return(0); 412 } 413 414 /* 415 * When we wake up after ^Z, reprint the prompt. 416 */ 417 contin(s) 418 { 419 420 printf(prompt); 421 fflush(stdout); 422 } 423 424 /* 425 * Branch here on hangup signal and simulate quit. 426 */ 427 hangup() 428 { 429 430 holdsigs(); 431 if (edit) { 432 if (setexit()) 433 exit(0); 434 edstop(); 435 } 436 else 437 quit(); 438 exit(0); 439 } 440 441 /* 442 * Set the size of the message vector used to construct argument 443 * lists to message list functions. 444 */ 445 446 setmsize(sz) 447 { 448 449 if (msgvec != (int *) 0) 450 cfree(msgvec); 451 msgvec = (int *) calloc((unsigned) (sz + 1), sizeof *msgvec); 452 } 453 454 /* 455 * Find the correct command in the command table corresponding 456 * to the passed command "word" 457 */ 458 459 struct cmd * 460 lex(word) 461 char word[]; 462 { 463 register struct cmd *cp; 464 extern struct cmd cmdtab[]; 465 466 for (cp = &cmdtab[0]; cp->c_name != NOSTR; cp++) 467 if (isprefix(word, cp->c_name)) 468 return(cp); 469 return(NONE); 470 } 471 472 /* 473 * Determine if as1 is a valid prefix of as2. 474 * Return true if yep. 475 */ 476 477 isprefix(as1, as2) 478 char *as1, *as2; 479 { 480 register char *s1, *s2; 481 482 s1 = as1; 483 s2 = as2; 484 while (*s1++ == *s2) 485 if (*s2++ == '\0') 486 return(1); 487 return(*--s1 == '\0'); 488 } 489 490 /* 491 * The following gets called on receipt of a rubout. This is 492 * to abort printout of a command, mainly. 493 * Dispatching here when command() is inactive crashes rcv. 494 * Close all open files except 0, 1, 2, and the temporary. 495 * The special call to getuserid() is needed so it won't get 496 * annoyed about losing its open file. 497 * Also, unstack all source files. 498 */ 499 500 int inithdr; /* am printing startup headers */ 501 502 #ifdef _NFILE 503 static 504 _fwalk(function) 505 register int (*function)(); 506 { 507 register FILE *iop; 508 509 for (iop = _iob; iop < _iob + _NFILE; iop++) 510 (*function)(iop); 511 } 512 #endif 513 514 static 515 xclose(iop) 516 register FILE *iop; 517 { 518 if (iop == stdin || iop == stdout || 519 iop == stderr || iop == itf || iop == otf) 520 return; 521 522 if (iop != pipef) 523 fclose(iop); 524 else { 525 pclose(pipef); 526 pipef = NULL; 527 } 528 } 529 530 stop(s) 531 { 532 register FILE *fp; 533 534 # ifndef VMUNIX 535 s = SIGINT; 536 # endif VMUNIX 537 noreset = 0; 538 if (!inithdr) 539 sawcom++; 540 inithdr = 0; 541 while (sourcing) 542 unstack(); 543 getuserid((char *) -1); 544 545 /* 546 * Walk through all the open FILEs, applying xclose() to them 547 */ 548 _fwalk(xclose); 549 550 if (image >= 0) { 551 close(image); 552 image = -1; 553 } 554 fprintf(stderr, "Interrupt\n"); 555 # ifndef VMUNIX 556 signal(s, stop); 557 # endif 558 reset(0); 559 } 560 561 /* 562 * Announce the presence of the current Mail version, 563 * give the message count, and print a header listing. 564 */ 565 566 char *greeting = "Mail version %s. Type ? for help.\n"; 567 568 announce(pr) 569 { 570 int vec[2], mdot; 571 extern char *version; 572 573 if (pr && value("quiet") == NOSTR) 574 printf(greeting, version); 575 mdot = newfileinfo(); 576 vec[0] = mdot; 577 vec[1] = 0; 578 dot = &message[mdot - 1]; 579 if (msgCount > 0 && !noheader) { 580 inithdr++; 581 headers(vec); 582 inithdr = 0; 583 } 584 } 585 586 /* 587 * Announce information about the file we are editing. 588 * Return a likely place to set dot. 589 */ 590 newfileinfo() 591 { 592 register struct message *mp; 593 register int u, n, mdot, d, s; 594 char fname[BUFSIZ], zname[BUFSIZ], *ename; 595 596 for (mp = &message[0]; mp < &message[msgCount]; mp++) 597 if (mp->m_flag & MNEW) 598 break; 599 if (mp >= &message[msgCount]) 600 for (mp = &message[0]; mp < &message[msgCount]; mp++) 601 if ((mp->m_flag & MREAD) == 0) 602 break; 603 if (mp < &message[msgCount]) 604 mdot = mp - &message[0] + 1; 605 else 606 mdot = 1; 607 s = d = 0; 608 for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) { 609 if (mp->m_flag & MNEW) 610 n++; 611 if ((mp->m_flag & MREAD) == 0) 612 u++; 613 if (mp->m_flag & MDELETED) 614 d++; 615 if (mp->m_flag & MSAVED) 616 s++; 617 } 618 ename = mailname; 619 if (getfold(fname) >= 0) { 620 strcat(fname, "/"); 621 if (strncmp(fname, mailname, strlen(fname)) == 0) { 622 sprintf(zname, "+%s", mailname + strlen(fname)); 623 ename = zname; 624 } 625 } 626 printf("\"%s\": ", ename); 627 if (msgCount == 1) 628 printf("1 message"); 629 else 630 printf("%d messages", msgCount); 631 if (n > 0) 632 printf(" %d new", n); 633 if (u-n > 0) 634 printf(" %d unread", u); 635 if (d > 0) 636 printf(" %d deleted", d); 637 if (s > 0) 638 printf(" %d saved", s); 639 if (readonly) 640 printf(" [Read only]"); 641 printf("\n"); 642 return(mdot); 643 } 644 645 strace() {} 646 647 /* 648 * Print the current version number. 649 */ 650 651 pversion(e) 652 { 653 printf("Version %s\n", version); 654 return(0); 655 } 656 657 /* 658 * Load a file of user definitions. 659 */ 660 load(name) 661 char *name; 662 { 663 register FILE *in, *oldin; 664 665 if ((in = fopen(name, "r")) == NULL) 666 return; 667 oldin = input; 668 input = in; 669 loading = 1; 670 sourcing = 1; 671 commands(); 672 loading = 0; 673 sourcing = 0; 674 input = oldin; 675 fclose(in); 676 } 677