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