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