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