lex.c revision 216370
138032Speter/* 238032Speter * Copyright (c) 1980, 1993 338032Speter * The Regents of the University of California. All rights reserved. 438032Speter * 538032Speter * Redistribution and use in source and binary forms, with or without 690792Sgshapiro * modification, are permitted provided that the following conditions 738032Speter * are met: 864562Sgshapiro * 1. Redistributions of source code must retain the above copyright 938032Speter * notice, this list of conditions and the following disclaimer. 1038032Speter * 2. Redistributions in binary form must reproduce the above copyright 1138032Speter * notice, this list of conditions and the following disclaimer in the 1238032Speter * documentation and/or other materials provided with the distribution. 1338032Speter * 4. Neither the name of the University nor the names of its contributors 1438032Speter * may be used to endorse or promote products derived from this software 1566494Sgshapiro * without specific prior written permission. 1638032Speter * 1766494Sgshapiro * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 1866494Sgshapiro * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1966494Sgshapiro * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2066494Sgshapiro * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 2166494Sgshapiro * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2266494Sgshapiro * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2366494Sgshapiro * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2466494Sgshapiro * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2566494Sgshapiro * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2666494Sgshapiro * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2766494Sgshapiro * SUCH DAMAGE. 2866494Sgshapiro */ 2938032Speter 3038032Speter#ifndef lint 3138032Speter#if 0 3238032Speterstatic char sccsid[] = "@(#)lex.c 8.2 (Berkeley) 4/20/95"; 3338032Speter#endif 3438032Speter#endif /* not lint */ 3538032Speter#include <sys/cdefs.h> 3638032Speter__FBSDID("$FreeBSD: head/usr.bin/mail/lex.c 216370 2010-12-11 08:32:16Z joel $"); 3790792Sgshapiro 3880785Sgshapiro#include "rcv.h" 3980785Sgshapiro#include <errno.h> 4080785Sgshapiro#include <fcntl.h> 4180785Sgshapiro#include "extern.h" 4280785Sgshapiro 4380785Sgshapiro/* 4494334Sgshapiro * Mail -- a mail program 4594334Sgshapiro * 4694334Sgshapiro * Lexical processing of commands. 4794334Sgshapiro */ 4894334Sgshapiro 4994334Sgshapirostatic const char *prompt = "& "; 5094334Sgshapiro 5194334Sgshapiroextern const struct cmd cmdtab[]; 5294334Sgshapiroextern const char *version; 5394334Sgshapiro 5494334Sgshapiro/* 5594334Sgshapiro * Set up editing on the given file name. 5694334Sgshapiro * If the first character of name is %, we are considered to be 5794334Sgshapiro * editing the file, otherwise we are reading our mail which has 5894334Sgshapiro * signficance for mbox and so forth. 5994334Sgshapiro * 6094334Sgshapiro * If the -e option is being passed to mail, this function has a 6194334Sgshapiro * tri-state return code: -1 on error, 0 on no mail, 1 if there is 6294334Sgshapiro * mail. 6338032Speter */ 6438032Speterint 6538032Spetersetfile(name) 6638032Speter char *name; 6738032Speter{ 6890792Sgshapiro FILE *ibuf; 6938032Speter int checkmode, i, fd; 7090792Sgshapiro struct stat stb; 7190792Sgshapiro char isedit = *name != '%' || getuserid(myname) != getuid(); 7290792Sgshapiro char *who = name[1] ? name + 1 : myname; 7390792Sgshapiro char tempname[PATHSIZE]; 7438032Speter static int shudclob; 7538032Speter 7638032Speter checkmode = value("checkmode") != NULL; 7738032Speter if ((name = expand(name)) == NULL) 7838032Speter return (-1); 7938032Speter 8038032Speter if ((ibuf = Fopen(name, "r")) == NULL) { 8138032Speter if (!isedit && errno == ENOENT) 8238032Speter goto nomail; 8338032Speter warn("%s", name); 8438032Speter return (-1); 8538032Speter } 8638032Speter 8738032Speter if (fstat(fileno(ibuf), &stb) < 0) { 8838032Speter warn("fstat"); 8938032Speter (void)Fclose(ibuf); 9038032Speter return (-1); 9138032Speter } 9238032Speter 9338032Speter if (S_ISDIR(stb.st_mode) || !S_ISREG(stb.st_mode)) { 9438032Speter (void)Fclose(ibuf); 9538032Speter errno = S_ISDIR(stb.st_mode) ? EISDIR : EINVAL; 9638032Speter warn("%s", name); 9738032Speter return (-1); 9838032Speter } 9938032Speter 10042575Speter /* 10142575Speter * Looks like all will be well. We must now relinquish our 10238032Speter * hold on the current set of stuff. Must hold signals 10338032Speter * while we are reading the new file, else we will ruin 10438032Speter * the message[] data structure. 10538032Speter */ 10638032Speter 10738032Speter holdsigs(); 10838032Speter if (shudclob) 10938032Speter quit(); 11038032Speter 11138032Speter /* 11238032Speter * Copy the messages into /tmp 11338032Speter * and set pointers. 11438032Speter */ 11538032Speter 11638032Speter readonly = 0; 11738032Speter if ((i = open(name, 1)) < 0) 11838032Speter readonly++; 11938032Speter else 12038032Speter (void)close(i); 12138032Speter if (shudclob) { 12238032Speter (void)fclose(itf); 12338032Speter (void)fclose(otf); 12438032Speter } 12538032Speter shudclob = 1; 12638032Speter edit = isedit; 12738032Speter strlcpy(prevfile, mailname, sizeof(prevfile)); 12838032Speter if (name != mailname) 12938032Speter strlcpy(mailname, name, sizeof(mailname)); 13038032Speter mailsize = fsize(ibuf); 13138032Speter (void)snprintf(tempname, sizeof(tempname), 13238032Speter "%s/mail.RxXXXXXXXXXX", tmpdir); 13338032Speter if ((fd = mkstemp(tempname)) == -1 || (otf = fdopen(fd, "w")) == NULL) 13438032Speter err(1, "%s", tempname); 13538032Speter (void)fcntl(fileno(otf), F_SETFD, 1); 13638032Speter if ((itf = fopen(tempname, "r")) == NULL) 13738032Speter err(1, "%s", tempname); 13838032Speter (void)fcntl(fileno(itf), F_SETFD, 1); 13938032Speter (void)rm(tempname); 14038032Speter setptr(ibuf, 0); 14190792Sgshapiro setmsize(msgCount); 14290792Sgshapiro /* 14390792Sgshapiro * New mail may have arrived while we were reading 14490792Sgshapiro * the mail file, so reset mailsize to be where 14590792Sgshapiro * we really are in the file... 14690792Sgshapiro */ 14790792Sgshapiro mailsize = ftello(ibuf); 14890792Sgshapiro (void)Fclose(ibuf); 14938032Speter relsesigs(); 15038032Speter sawcom = 0; 15138032Speter 15238032Speter if ((checkmode || !edit) && msgCount == 0) { 15338032Speternomail: 15438032Speter if (!checkmode) { 15538032Speter fprintf(stderr, "No mail for %s\n", who); 15638032Speter return (-1); 15738032Speter } else 15838032Speter return (0); 15938032Speter } 16038032Speter return (checkmode ? 1 : 0); 16138032Speter} 16238032Speter 16338032Speter/* 16438032Speter * Incorporate any new mail that has arrived since we first 16538032Speter * started reading mail. 16638032Speter */ 16738032Speterint 16838032Speterincfile() 16938032Speter{ 17038032Speter off_t newsize; 17138032Speter int omsgCount = msgCount; 17238032Speter FILE *ibuf; 17338032Speter 17438032Speter ibuf = Fopen(mailname, "r"); 17538032Speter if (ibuf == NULL) 17638032Speter return (-1); 17764562Sgshapiro holdsigs(); 17890792Sgshapiro newsize = fsize(ibuf); 17964562Sgshapiro if (newsize == 0) 18064562Sgshapiro return (-1); /* mail box is now empty??? */ 18138032Speter if (newsize < mailsize) 18238032Speter return (-1); /* mail box has shrunk??? */ 18338032Speter if (newsize == mailsize) 18438032Speter return (0); /* no new mail */ 18542575Speter setptr(ibuf, mailsize); 18638032Speter setmsize(msgCount); 18742575Speter mailsize = ftello(ibuf); 18842575Speter (void)Fclose(ibuf); 18942575Speter relsesigs(); 19042575Speter return (msgCount - omsgCount); 19142575Speter} 19243730Speter 19342575Speterstatic int *msgvec; 19443730Speterstatic int reset_on_stop; /* do a reset() if stopped */ 19543730Speter 19643730Speter/* 19743730Speter * Interpret user commands one by one. If standard input is not a tty, 19843730Speter * print no prompt. 19943730Speter */ 20064562Sgshapirovoid 20143730Spetercommands() 20243730Speter{ 20343730Speter int n, eofloop = 0; 20443730Speter char linebuf[LINESIZE]; 20543730Speter 20643730Speter if (!sourcing) { 20743730Speter if (signal(SIGINT, SIG_IGN) != SIG_IGN) 20843730Speter (void)signal(SIGINT, intr); 20943730Speter if (signal(SIGHUP, SIG_IGN) != SIG_IGN) 21043730Speter (void)signal(SIGHUP, hangup); 21143730Speter (void)signal(SIGTSTP, stop); 21243730Speter (void)signal(SIGTTOU, stop); 21343730Speter (void)signal(SIGTTIN, stop); 21443730Speter } 21543730Speter setexit(); 21664562Sgshapiro for (;;) { 21764562Sgshapiro /* 21864562Sgshapiro * Print the prompt, if needed. Clear out 21943730Speter * string space, and flush the output. 22043730Speter */ 22143730Speter if (!sourcing && value("interactive") != NULL) { 22243730Speter if ((value("autoinc") != NULL) && (incfile() > 0)) 22343730Speter printf("New mail has arrived.\n"); 22443730Speter reset_on_stop = 1; 22543730Speter printf("%s", prompt); 22643730Speter } 22743730Speter (void)fflush(stdout); 22890792Sgshapiro sreset(); 22964562Sgshapiro /* 23064562Sgshapiro * Read a line of commands from the current input 23190792Sgshapiro * and handle end of file specially. 23264562Sgshapiro */ 23364562Sgshapiro n = 0; 23464562Sgshapiro for (;;) { 23564562Sgshapiro if (readline(input, &linebuf[n], LINESIZE - n) < 0) { 23664562Sgshapiro if (n == 0) 23764562Sgshapiro n = -1; 238110560Sgshapiro break; 239110560Sgshapiro } 240110560Sgshapiro if ((n = strlen(linebuf)) == 0) 241110560Sgshapiro break; 242110560Sgshapiro n--; 243110560Sgshapiro if (linebuf[n] != '\\') 244110560Sgshapiro break; 245 linebuf[n++] = ' '; 246 } 247 reset_on_stop = 0; 248 if (n < 0) { 249 /* eof */ 250 if (loading) 251 break; 252 if (sourcing) { 253 unstack(); 254 continue; 255 } 256 if (value("interactive") != NULL && 257 value("ignoreeof") != NULL && 258 ++eofloop < 25) { 259 printf("Use \"quit\" to quit.\n"); 260 continue; 261 } 262 break; 263 } 264 eofloop = 0; 265 if (execute(linebuf, 0)) 266 break; 267 } 268} 269 270/* 271 * Execute a single command. 272 * Command functions return 0 for success, 1 for error, and -1 273 * for abort. A 1 or -1 aborts a load or source. A -1 aborts 274 * the interactive command loop. 275 * Contxt is non-zero if called while composing mail. 276 */ 277int 278execute(linebuf, contxt) 279 char linebuf[]; 280 int contxt; 281{ 282 char word[LINESIZE]; 283 char *arglist[MAXARGC]; 284 const struct cmd *com; 285 char *cp, *cp2; 286 int c, muvec[2]; 287 int e = 1; 288 289 /* 290 * Strip the white space away from the beginning 291 * of the command, then scan out a word, which 292 * consists of anything except digits and white space. 293 * 294 * Handle ! escapes differently to get the correct 295 * lexical conventions. 296 */ 297 298 for (cp = linebuf; isspace((unsigned char)*cp); cp++) 299 ; 300 if (*cp == '!') { 301 if (sourcing) { 302 printf("Can't \"!\" while sourcing\n"); 303 goto out; 304 } 305 shell(cp+1); 306 return (0); 307 } 308 cp2 = word; 309 while (*cp != '\0' && strchr(" \t0123456789$^.:/-+*'\"", *cp) == NULL) 310 *cp2++ = *cp++; 311 *cp2 = '\0'; 312 313 /* 314 * Look up the command; if not found, bitch. 315 * Normally, a blank command would map to the 316 * first command in the table; while sourcing, 317 * however, we ignore blank lines to eliminate 318 * confusion. 319 */ 320 321 if (sourcing && *word == '\0') 322 return (0); 323 com = lex(word); 324 if (com == NULL) { 325 printf("Unknown command: \"%s\"\n", word); 326 goto out; 327 } 328 329 /* 330 * See if we should execute the command -- if a conditional 331 * we always execute it, otherwise, check the state of cond. 332 */ 333 334 if ((com->c_argtype & F) == 0) 335 if ((cond == CRCV && !rcvmode) || (cond == CSEND && rcvmode)) 336 return (0); 337 338 /* 339 * Process the arguments to the command, depending 340 * on the type he expects. Default to an error. 341 * If we are sourcing an interactive command, it's 342 * an error. 343 */ 344 345 if (!rcvmode && (com->c_argtype & M) == 0) { 346 printf("May not execute \"%s\" while sending\n", 347 com->c_name); 348 goto out; 349 } 350 if (sourcing && com->c_argtype & I) { 351 printf("May not execute \"%s\" while sourcing\n", 352 com->c_name); 353 goto out; 354 } 355 if (readonly && com->c_argtype & W) { 356 printf("May not execute \"%s\" -- message file is read only\n", 357 com->c_name); 358 goto out; 359 } 360 if (contxt && com->c_argtype & R) { 361 printf("Cannot recursively invoke \"%s\"\n", com->c_name); 362 goto out; 363 } 364 switch (com->c_argtype & ~(F|P|I|M|T|W|R)) { 365 case MSGLIST: 366 /* 367 * A message list defaulting to nearest forward 368 * legal message. 369 */ 370 if (msgvec == 0) { 371 printf("Illegal use of \"message list\"\n"); 372 break; 373 } 374 if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0) 375 break; 376 if (c == 0) { 377 *msgvec = first(com->c_msgflag, com->c_msgmask); 378 msgvec[1] = 0; 379 } 380 if (*msgvec == 0) { 381 printf("No applicable messages\n"); 382 break; 383 } 384 e = (*com->c_func)(msgvec); 385 break; 386 387 case NDMLIST: 388 /* 389 * A message list with no defaults, but no error 390 * if none exist. 391 */ 392 if (msgvec == 0) { 393 printf("Illegal use of \"message list\"\n"); 394 break; 395 } 396 if (getmsglist(cp, msgvec, com->c_msgflag) < 0) 397 break; 398 e = (*com->c_func)(msgvec); 399 break; 400 401 case STRLIST: 402 /* 403 * Just the straight string, with 404 * leading blanks removed. 405 */ 406 while (isspace((unsigned char)*cp)) 407 cp++; 408 e = (*com->c_func)(cp); 409 break; 410 411 case RAWLIST: 412 /* 413 * A vector of strings, in shell style. 414 */ 415 if ((c = getrawlist(cp, arglist, 416 sizeof(arglist) / sizeof(*arglist))) < 0) 417 break; 418 if (c < com->c_minargs) { 419 printf("%s requires at least %d arg(s)\n", 420 com->c_name, com->c_minargs); 421 break; 422 } 423 if (c > com->c_maxargs) { 424 printf("%s takes no more than %d arg(s)\n", 425 com->c_name, com->c_maxargs); 426 break; 427 } 428 e = (*com->c_func)(arglist); 429 break; 430 431 case NOLIST: 432 /* 433 * Just the constant zero, for exiting, 434 * eg. 435 */ 436 e = (*com->c_func)(0); 437 break; 438 439 default: 440 errx(1, "Unknown argtype"); 441 } 442 443out: 444 /* 445 * Exit the current source file on 446 * error. 447 */ 448 if (e) { 449 if (e < 0) 450 return (1); 451 if (loading) 452 return (1); 453 if (sourcing) 454 unstack(); 455 return (0); 456 } 457 if (com == NULL) 458 return (0); 459 if (value("autoprint") != NULL && com->c_argtype & P) 460 if ((dot->m_flag & MDELETED) == 0) { 461 muvec[0] = dot - &message[0] + 1; 462 muvec[1] = 0; 463 type(muvec); 464 } 465 if (!sourcing && (com->c_argtype & T) == 0) 466 sawcom = 1; 467 return (0); 468} 469 470/* 471 * Set the size of the message vector used to construct argument 472 * lists to message list functions. 473 */ 474void 475setmsize(sz) 476 int sz; 477{ 478 479 if (msgvec != NULL) 480 (void)free(msgvec); 481 msgvec = calloc((unsigned)(sz + 1), sizeof(*msgvec)); 482} 483 484/* 485 * Find the correct command in the command table corresponding 486 * to the passed command "word" 487 */ 488 489__const struct cmd * 490lex(word) 491 char word[]; 492{ 493 const struct cmd *cp; 494 495 /* 496 * ignore trailing chars after `#' 497 * 498 * lines with beginning `#' are comments 499 * spaces before `#' are ignored in execute() 500 */ 501 502 if (*word == '#') 503 *(word+1) = '\0'; 504 505 506 for (cp = &cmdtab[0]; cp->c_name != NULL; cp++) 507 if (isprefix(word, cp->c_name)) 508 return (cp); 509 return (NULL); 510} 511 512/* 513 * Determine if as1 is a valid prefix of as2. 514 * Return true if yep. 515 */ 516int 517isprefix(as1, as2) 518 const char *as1, *as2; 519{ 520 const char *s1, *s2; 521 522 s1 = as1; 523 s2 = as2; 524 while (*s1++ == *s2) 525 if (*s2++ == '\0') 526 return (1); 527 return (*--s1 == '\0'); 528} 529 530/* 531 * The following gets called on receipt of an interrupt. This is 532 * to abort printout of a command, mainly. 533 * Dispatching here when command() is inactive crashes rcv. 534 * Close all open files except 0, 1, 2, and the temporary. 535 * Also, unstack all source files. 536 */ 537 538static int inithdr; /* am printing startup headers */ 539 540/*ARGSUSED*/ 541void 542intr(s) 543 int s; 544{ 545 546 noreset = 0; 547 if (!inithdr) 548 sawcom++; 549 inithdr = 0; 550 while (sourcing) 551 unstack(); 552 553 close_all_files(); 554 555 if (image >= 0) { 556 (void)close(image); 557 image = -1; 558 } 559 fprintf(stderr, "Interrupt\n"); 560 reset(0); 561} 562 563/* 564 * When we wake up after ^Z, reprint the prompt. 565 */ 566void 567stop(s) 568 int s; 569{ 570 sig_t old_action = signal(s, SIG_DFL); 571 sigset_t nset; 572 573 (void)sigemptyset(&nset); 574 (void)sigaddset(&nset, s); 575 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL); 576 (void)kill(0, s); 577 (void)sigprocmask(SIG_BLOCK, &nset, NULL); 578 (void)signal(s, old_action); 579 if (reset_on_stop) { 580 reset_on_stop = 0; 581 reset(0); 582 } 583} 584 585/* 586 * Branch here on hangup signal and simulate "exit". 587 */ 588/*ARGSUSED*/ 589void 590hangup(s) 591 int s; 592{ 593 594 /* nothing to do? */ 595 exit(1); 596} 597 598/* 599 * Announce the presence of the current Mail version, 600 * give the message count, and print a header listing. 601 */ 602void 603announce() 604{ 605 int vec[2], mdot; 606 607 mdot = newfileinfo(0); 608 vec[0] = mdot; 609 vec[1] = 0; 610 dot = &message[mdot - 1]; 611 if (msgCount > 0 && value("noheader") == NULL) { 612 inithdr++; 613 headers(vec); 614 inithdr = 0; 615 } 616} 617 618/* 619 * Announce information about the file we are editing. 620 * Return a likely place to set dot. 621 */ 622int 623newfileinfo(omsgCount) 624 int omsgCount; 625{ 626 struct message *mp; 627 int u, n, mdot, d, s; 628 char fname[PATHSIZE+1], zname[PATHSIZE+1], *ename; 629 630 for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++) 631 if (mp->m_flag & MNEW) 632 break; 633 if (mp >= &message[msgCount]) 634 for (mp = &message[omsgCount]; mp < &message[msgCount]; mp++) 635 if ((mp->m_flag & MREAD) == 0) 636 break; 637 if (mp < &message[msgCount]) 638 mdot = mp - &message[0] + 1; 639 else 640 mdot = omsgCount + 1; 641 s = d = 0; 642 for (mp = &message[0], n = 0, u = 0; mp < &message[msgCount]; mp++) { 643 if (mp->m_flag & MNEW) 644 n++; 645 if ((mp->m_flag & MREAD) == 0) 646 u++; 647 if (mp->m_flag & MDELETED) 648 d++; 649 if (mp->m_flag & MSAVED) 650 s++; 651 } 652 ename = mailname; 653 if (getfold(fname, sizeof(fname) - 1) >= 0) { 654 strcat(fname, "/"); 655 if (strncmp(fname, mailname, strlen(fname)) == 0) { 656 (void)snprintf(zname, sizeof(zname), "+%s", 657 mailname + strlen(fname)); 658 ename = zname; 659 } 660 } 661 printf("\"%s\": ", ename); 662 if (msgCount == 1) 663 printf("1 message"); 664 else 665 printf("%d messages", msgCount); 666 if (n > 0) 667 printf(" %d new", n); 668 if (u-n > 0) 669 printf(" %d unread", u); 670 if (d > 0) 671 printf(" %d deleted", d); 672 if (s > 0) 673 printf(" %d saved", s); 674 if (readonly) 675 printf(" [Read only]"); 676 printf("\n"); 677 return (mdot); 678} 679 680/* 681 * Print the current version number. 682 */ 683 684/*ARGSUSED*/ 685int 686pversion(e) 687 int e; 688{ 689 690 printf("Version %s\n", version); 691 return (0); 692} 693 694/* 695 * Load a file of user definitions. 696 */ 697void 698load(name) 699 char *name; 700{ 701 FILE *in, *oldin; 702 703 if ((in = Fopen(name, "r")) == NULL) 704 return; 705 oldin = input; 706 input = in; 707 loading = 1; 708 sourcing = 1; 709 commands(); 710 loading = 0; 711 sourcing = 0; 712 input = oldin; 713 (void)Fclose(in); 714} 715