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