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