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