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