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