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