1 /* $NetBSD: lex.c,v 1.41 2012/04/29 23:50:22 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. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 #if 0 35 static char sccsid[] = "@(#)lex.c 8.2 (Berkeley) 4/20/95"; 36 #else 37 __RCSID("$NetBSD: lex.c,v 1.41 2012/04/29 23:50:22 christos Exp $"); 38 #endif 39 #endif /* not lint */ 40 41 #include <assert.h> 42 #include <util.h> 43 44 #include "rcv.h" 45 #include "extern.h" 46 #ifdef USE_EDITLINE 47 #include "complete.h" 48 #endif 49 #include "format.h" 50 #include "sig.h" 51 #include "thread.h" 52 53 /* 54 * Mail -- a mail program 55 * 56 * Lexical processing of commands. 57 */ 58 59 static const char *prompt = DEFAULT_PROMPT; 60 static int *msgvec; 61 static int inithdr; /* Am printing startup headers. */ 62 static jmp_buf jmpbuf; /* The reset jmpbuf */ 63 static int reset_on_stop; /* To do job control longjmp. */ 64 65 #ifdef DEBUG_FILE_LEAK 66 struct glue { 67 struct glue *next; 68 int niobs; 69 FILE *iobs; 70 }; 71 extern struct glue __sglue; 72 73 static int open_fd_cnt; 74 static int open_fp_cnt; 75 76 static int 77 file_count(void) 78 { 79 struct glue *gp; 80 FILE *fp; 81 int n; 82 int cnt; 83 84 cnt = 0; 85 for (gp = &__sglue; gp; gp = gp->next) { 86 for (fp = gp->iobs, n = gp->niobs; --n >= 0; fp++) 87 if (fp->_flags) 88 cnt++; 89 } 90 return cnt; 91 } 92 93 static int 94 fds_count(void) 95 { 96 int maxfd; 97 int cnt; 98 int fd; 99 100 maxfd = fcntl(0, F_MAXFD); 101 if (maxfd == -1) { 102 warn("fcntl"); 103 return -1; 104 } 105 106 cnt = 0; 107 for (fd = 0; fd <= maxfd; fd++) { 108 struct stat sb; 109 110 if (fstat(fd, &sb) != -1) 111 cnt++; 112 else if (errno != EBADF 113 #ifdef BROKEN_CLONE_STAT /* see PRs 37878 and 37550 */ 114 && errno != EOPNOTSUPP 115 #endif 116 ) 117 warn("fstat(%d): errno=%d", fd, errno); 118 } 119 return cnt; 120 } 121 122 static void 123 file_leak_init(void) 124 { 125 open_fd_cnt = fds_count(); 126 open_fp_cnt = file_count(); 127 } 128 129 static void 130 file_leak_check(void) 131 { 132 if (open_fp_cnt != file_count() || 133 open_fd_cnt != fds_count()) { 134 (void)printf("FILE LEAK WARNING: " 135 "fp-count: %d (%d) " 136 "fd-count: %d (%d) max-fd: %d\n", 137 file_count(), open_fp_cnt, 138 fds_count(), open_fd_cnt, 139 fcntl(0, F_MAXFD)); 140 } 141 } 142 #endif /* DEBUG_FILE_LEAK */ 143 144 /* 145 * Set the size of the message vector used to construct argument 146 * lists to message list functions. 147 */ 148 static void 149 setmsize(int sz) 150 { 151 if (msgvec != 0) 152 free(msgvec); 153 msgvec = ecalloc((size_t) (sz + 1), sizeof(*msgvec)); 154 } 155 156 /* 157 * Set up editing on the given file name. 158 * If the first character of name is %, we are considered to be 159 * editing the file, otherwise we are reading our mail which has 160 * signficance for mbox and so forth. 161 */ 162 PUBLIC int 163 setfile(const char *name) 164 { 165 FILE *ibuf; 166 int i, fd; 167 struct stat stb; 168 char isedit = *name != '%' || getuserid(myname) != (int)getuid(); 169 const char *who = name[1] ? name + 1 : myname; 170 static int shudclob; 171 char tempname[PATHSIZE]; 172 173 if ((name = expand(name)) == NULL) 174 return -1; 175 176 if ((ibuf = Fopen(name, "re")) == NULL) { 177 if (!isedit && errno == ENOENT) 178 goto nomail; 179 warn("Can't open `%s'", name); 180 return -1; 181 } 182 183 if (fstat(fileno(ibuf), &stb) < 0) { 184 warn("fstat"); 185 (void)Fclose(ibuf); 186 return -1; 187 } 188 189 switch (stb.st_mode & S_IFMT) { 190 case S_IFDIR: 191 (void)Fclose(ibuf); 192 errno = EISDIR; 193 warn("%s", name); 194 return -1; 195 196 case S_IFREG: 197 break; 198 199 default: 200 (void)Fclose(ibuf); 201 errno = EINVAL; 202 warn("%s", name); 203 return -1; 204 } 205 206 /* 207 * Looks like all will be well. We must now relinquish our 208 * hold on the current set of stuff. Must hold signals 209 * while we are reading the new file, else we will ruin 210 * the message[] data structure. 211 */ 212 213 sig_check(); 214 sig_hold(); 215 if (shudclob) 216 quit(jmpbuf); 217 218 /* 219 * Copy the messages into /tmp 220 * and set pointers. 221 */ 222 223 readonly = 0; 224 if ((i = open(name, O_WRONLY)) < 0) 225 readonly++; 226 else 227 (void)close(i); 228 if (shudclob) { 229 (void)fclose(itf); 230 (void)fclose(otf); 231 } 232 shudclob = 1; 233 edit = isedit; 234 (void)strcpy(prevfile, mailname); 235 if (name != mailname) 236 (void)strcpy(mailname, name); 237 mailsize = fsize(ibuf); 238 (void)snprintf(tempname, sizeof(tempname), 239 "%s/mail.RxXXXXXXXXXX", tmpdir); 240 if ((fd = mkstemp(tempname)) == -1 || 241 (otf = fdopen(fd, "we")) == NULL) 242 err(EXIT_FAILURE, "Can't create tmp file `%s'", tempname); 243 if ((itf = fopen(tempname, "re")) == NULL) 244 err(EXIT_FAILURE, "Can't create tmp file `%s'", tempname); 245 (void)rm(tempname); 246 setptr(ibuf, (off_t)0); 247 setmsize(get_abs_msgCount()); 248 /* 249 * New mail may have arrived while we were reading 250 * the mail file, so reset mailsize to be where 251 * we really are in the file... 252 */ 253 mailsize = ftell(ibuf); 254 (void)Fclose(ibuf); 255 sig_release(); 256 sig_check(); 257 sawcom = 0; 258 if (!edit && get_abs_msgCount() == 0) { 259 nomail: 260 (void)fprintf(stderr, "No mail for %s\n", who); 261 return -1; 262 } 263 return 0; 264 } 265 266 /* 267 * Incorporate any new mail that has arrived since we first 268 * started reading mail. 269 */ 270 PUBLIC int 271 incfile(void) 272 { 273 off_t newsize; 274 int omsgCount; 275 FILE *ibuf; 276 int rval; 277 278 omsgCount = get_abs_msgCount(); 279 280 ibuf = Fopen(mailname, "re"); 281 if (ibuf == NULL) 282 return -1; 283 sig_check(); 284 sig_hold(); 285 newsize = fsize(ibuf); 286 if (newsize == 0 || /* mail box is now empty??? */ 287 newsize < mailsize) { /* mail box has shrunk??? */ 288 rval = -1; 289 goto done; 290 } 291 if (newsize == mailsize) { 292 rval = 0; /* no new mail */ 293 goto done; 294 } 295 setptr(ibuf, mailsize); /* read in new mail */ 296 setmsize(get_abs_msgCount()); /* get the new message count */ 297 mailsize = ftell(ibuf); 298 rval = get_abs_msgCount() - omsgCount; 299 done: 300 (void)Fclose(ibuf); 301 sig_release(); 302 sig_check(); 303 return rval; 304 } 305 306 /* 307 * Return a pointer to the comment character, respecting quoting as 308 * done in getrawlist(). The comment character is ignored inside 309 * quotes. 310 */ 311 static char * 312 comment_char(char *line) 313 { 314 char *p; 315 char quotec; 316 quotec = '\0'; 317 for (p = line; *p; p++) { 318 if (quotec != '\0') { 319 if (*p == quotec) 320 quotec = '\0'; 321 } 322 else if (*p == '"' || *p == '\'') 323 quotec = *p; 324 else if (*p == COMMENT_CHAR) 325 return p; 326 } 327 return NULL; 328 } 329 330 /* 331 * Signal handler is hooked by setup_piping(). 332 * Respond to a broken pipe signal -- 333 * probably caused by quitting more. 334 */ 335 static jmp_buf pipestop; 336 337 /*ARGSUSED*/ 338 __dead static void 339 lex_brokpipe(int signo) 340 { 341 342 longjmp(pipestop, signo); 343 } 344 345 /* 346 * Check the command line for any requested piping or redirection, 347 * depending on the value of 'c'. If "enable-pipes" is set, search 348 * the command line (cp) for the first occurrence of the character 'c' 349 * that is not in a quote or (parenthese) group. 350 */ 351 PUBLIC char * 352 shellpr(char *cp) 353 { 354 int quotec; 355 int level; 356 357 if (cp == NULL || value(ENAME_ENABLE_PIPES) == NULL) 358 return NULL; 359 360 level = 0; 361 quotec = 0; 362 for (/*EMPTY*/; *cp != '\0'; cp++) { 363 if (quotec) { 364 if (*cp == quotec) 365 quotec = 0; 366 if (*cp == '\\' && 367 (cp[1] == quotec || cp[1] == '\\')) 368 cp++; 369 } 370 else { 371 switch (*cp) { 372 case '|': 373 case '>': 374 if (level == 0) 375 return cp; 376 break; 377 case '(': 378 level++; 379 break; 380 case ')': 381 level--; 382 break; 383 case '"': 384 case '\'': 385 quotec = *cp; 386 break; 387 default: 388 break; 389 } 390 } 391 } 392 return NULL; 393 } 394 395 static int 396 do_paging(const char *cmd, int c_pipe) 397 { 398 char *cp, *p; 399 400 if (value(ENAME_PAGER_OFF) != NULL) 401 return 0; 402 403 if (c_pipe & C_PIPE_PAGER) 404 return 1; 405 406 if (c_pipe & C_PIPE_CRT && value(ENAME_CRT) != NULL) 407 return 1; 408 409 if ((cp = value(ENAME_PAGE_ALSO)) == NULL) 410 return 0; 411 412 if ((p = strcasestr(cp, cmd)) == NULL) 413 return 0; 414 415 if (p != cp && p[-1] != ',' && !is_WSP(p[-1])) 416 return 0; 417 418 p += strlen(cmd); 419 420 return (*p == '\0' || *p == ',' || is_WSP(*p)); 421 } 422 423 /* 424 * Setup any pipe or redirection that the command line indicates. 425 * If none, then setup the pager unless "pager-off" is defined. 426 */ 427 static FILE *fp_stop = NULL; 428 static int oldfd1 = -1; 429 static sig_t old_sigpipe; 430 431 static int 432 setup_piping(const char *cmd, char *cmdline, int c_pipe) 433 { 434 FILE *fout; 435 FILE *last_file; 436 char *cp; 437 438 sig_check(); 439 440 last_file = last_registered_file(0); 441 442 fout = NULL; 443 if ((cp = shellpr(cmdline)) != NULL) { 444 char c; 445 c = *cp; 446 *cp = '\0'; 447 cp++; 448 449 if (c == '|') { 450 if ((fout = Popen(cp, "we")) == NULL) { 451 warn("Popen: %s", cp); 452 return -1; 453 } 454 } 455 else { 456 const char *mode; 457 assert(c == '>'); 458 mode = *cp == '>' ? "ae" : "we"; 459 if (*cp == '>') 460 cp++; 461 462 cp = skip_WSP(cp); 463 if ((fout = Fopen(cp, mode)) == NULL) { 464 warn("Fopen: %s", cp); 465 return -1; 466 } 467 } 468 469 } 470 else if (do_paging(cmd, c_pipe)) { 471 const char *pager; 472 pager = value(ENAME_PAGER); 473 if (pager == NULL || *pager == '\0') 474 pager = _PATH_MORE; 475 476 if ((fout = Popen(pager, "we")) == NULL) { 477 warn("Popen: %s", pager); 478 return -1; 479 } 480 } 481 482 if (fout) { 483 old_sigpipe = sig_signal(SIGPIPE, lex_brokpipe); 484 (void)fflush(stdout); 485 if ((oldfd1 = dup(STDOUT_FILENO)) == -1) 486 err(EXIT_FAILURE, "dup failed"); 487 if (dup2(fileno(fout), STDOUT_FILENO) == -1) 488 err(EXIT_FAILURE, "dup2 failed"); 489 fp_stop = last_file; 490 } 491 return 0; 492 } 493 494 /* 495 * This will close any piping started by setup_piping(). 496 */ 497 static void 498 close_piping(void) 499 { 500 sigset_t oset; 501 struct sigaction osa; 502 503 if (oldfd1 != -1) { 504 (void)fflush(stdout); 505 if (fileno(stdout) != oldfd1 && 506 dup2(oldfd1, STDOUT_FILENO) == -1) 507 err(EXIT_FAILURE, "dup2 failed"); 508 509 (void)sig_ignore(SIGPIPE, &osa, &oset); 510 511 close_top_files(fp_stop); 512 fp_stop = NULL; 513 (void)close(oldfd1); 514 oldfd1 = -1; 515 516 (void)sig_signal(SIGPIPE, old_sigpipe); 517 (void)sig_restore(SIGPIPE, &osa, &oset); 518 } 519 sig_check(); 520 } 521 522 /* 523 * Determine if as1 is a valid prefix of as2. 524 * Return true if yep. 525 */ 526 static int 527 isprefix(char *as1, const char *as2) 528 { 529 char *s1; 530 const char *s2; 531 532 s1 = as1; 533 s2 = as2; 534 while (*s1++ == *s2) 535 if (*s2++ == '\0') 536 return 1; 537 return *--s1 == '\0'; 538 } 539 540 /* 541 * Find the correct command in the command table corresponding 542 * to the passed command "word" 543 */ 544 PUBLIC const struct cmd * 545 lex(char word[]) 546 { 547 const struct cmd *cp; 548 549 for (cp = &cmdtab[0]; cp->c_name != NULL; cp++) 550 if (isprefix(word, cp->c_name)) 551 return cp; 552 return NULL; 553 } 554 555 PUBLIC char * 556 get_cmdname(char *buf) 557 { 558 char *cp; 559 char *cmd; 560 size_t len; 561 562 for (cp = buf; *cp; cp++) 563 if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL) 564 break; 565 /* XXX - Don't miss the pipe command! */ 566 if (cp == buf && *cp == '|') 567 cp++; 568 len = cp - buf + 1; 569 cmd = salloc(len); 570 (void)strlcpy(cmd, buf, len); 571 return cmd; 572 } 573 574 /* 575 * Execute a single command. 576 * Command functions return 0 for success, 1 for error, and -1 577 * for abort. A 1 or -1 aborts a load or source. A -1 aborts 578 * the interactive command loop. 579 * execute_contxt_e is in extern.h. 580 */ 581 PUBLIC int 582 execute(char linebuf[], enum execute_contxt_e contxt) 583 { 584 char *word; 585 char *arglist[MAXARGC]; 586 const struct cmd * volatile com = NULL; 587 char *volatile cp; 588 int retval; 589 int c; 590 int e = 1; 591 592 /* 593 * Strip the white space away from the beginning 594 * of the command, then scan out a word, which 595 * consists of anything except digits and white space. 596 * 597 * Handle ! escapes differently to get the correct 598 * lexical conventions. 599 */ 600 601 cp = skip_space(linebuf); 602 if (*cp == '!') { 603 if (sourcing) { 604 (void)printf("Can't \"!\" while sourcing\n"); 605 goto out; 606 } 607 (void)shell(cp + 1); 608 return 0; 609 } 610 611 word = get_cmdname(cp); 612 cp += strlen(word); 613 614 /* 615 * Look up the command; if not found, bitch. 616 * Normally, a blank command would map to the 617 * first command in the table; while sourcing, 618 * however, we ignore blank lines to eliminate 619 * confusion. 620 */ 621 622 if (sourcing && *word == '\0') 623 return 0; 624 com = lex(word); 625 if (com == NULL) { 626 (void)printf("Unknown command: \"%s\"\n", word); 627 goto out; 628 } 629 630 /* 631 * See if we should execute the command -- if a conditional 632 * we always execute it, otherwise, check the state of cond. 633 */ 634 635 if ((com->c_argtype & F) == 0 && (cond & CSKIP)) 636 return 0; 637 638 /* 639 * Process the arguments to the command, depending 640 * on the type he expects. Default to an error. 641 * If we are sourcing an interactive command, it's 642 * an error. 643 */ 644 645 if (mailmode == mm_sending && (com->c_argtype & M) == 0) { 646 (void)printf("May not execute \"%s\" while sending\n", 647 com->c_name); 648 goto out; 649 } 650 if (sourcing && com->c_argtype & I) { 651 (void)printf("May not execute \"%s\" while sourcing\n", 652 com->c_name); 653 goto out; 654 } 655 if (readonly && com->c_argtype & W) { 656 (void)printf("May not execute \"%s\" -- message file is read only\n", 657 com->c_name); 658 goto out; 659 } 660 if (contxt == ec_composing && com->c_argtype & R) { 661 (void)printf("Cannot recursively invoke \"%s\"\n", com->c_name); 662 goto out; 663 } 664 665 if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) { 666 667 sig_check(); 668 if (setjmp(pipestop)) 669 goto out; 670 671 if (setup_piping(com->c_name, cp, com->c_pipe) == -1) 672 goto out; 673 } 674 switch (com->c_argtype & ARGTYPE_MASK) { 675 case MSGLIST: 676 /* 677 * A message list defaulting to nearest forward 678 * legal message. 679 */ 680 if (msgvec == 0) { 681 (void)printf("Illegal use of \"message list\"\n"); 682 break; 683 } 684 if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0) 685 break; 686 if (c == 0) { 687 *msgvec = first(com->c_msgflag, com->c_msgmask); 688 msgvec[1] = 0; 689 } 690 if (*msgvec == 0) { 691 (void)printf("No applicable messages\n"); 692 break; 693 } 694 e = (*com->c_func)(msgvec); 695 break; 696 697 case NDMLIST: 698 /* 699 * A message list with no defaults, but no error 700 * if none exist. 701 */ 702 if (msgvec == 0) { 703 (void)printf("Illegal use of \"message list\"\n"); 704 break; 705 } 706 if (getmsglist(cp, msgvec, com->c_msgflag) < 0) 707 break; 708 e = (*com->c_func)(msgvec); 709 break; 710 711 case STRLIST: 712 /* 713 * Just the straight string, with 714 * leading blanks removed. 715 */ 716 cp = skip_space(cp); 717 e = (*com->c_func)(cp); 718 break; 719 720 case RAWLIST: 721 /* 722 * A vector of strings, in shell style. 723 */ 724 if ((c = getrawlist(cp, arglist, (int)__arraycount(arglist))) < 0) 725 break; 726 if (c < com->c_minargs) { 727 (void)printf("%s requires at least %d arg(s)\n", 728 com->c_name, com->c_minargs); 729 break; 730 } 731 if (c > com->c_maxargs) { 732 (void)printf("%s takes no more than %d arg(s)\n", 733 com->c_name, com->c_maxargs); 734 break; 735 } 736 e = (*com->c_func)(arglist); 737 break; 738 739 case NOLIST: 740 /* 741 * Just the constant zero, for exiting, 742 * eg. 743 */ 744 e = (*com->c_func)(0); 745 break; 746 747 default: 748 errx(EXIT_FAILURE, "Unknown argtype"); 749 } 750 751 out: 752 close_piping(); 753 754 /* 755 * Exit the current source file on 756 * error. 757 */ 758 retval = 0; 759 if (e) { 760 if (e < 0) 761 retval = 1; 762 else if (loading) 763 retval = 1; 764 else if (sourcing) 765 (void)unstack(); 766 } 767 else if (com != NULL) { 768 if (contxt != ec_autoprint && com->c_argtype & P && 769 value(ENAME_AUTOPRINT) != NULL && 770 (dot->m_flag & MDELETED) == 0) 771 (void)execute(__UNCONST("print ."), ec_autoprint); 772 if (!sourcing && (com->c_argtype & T) == 0) 773 sawcom = 1; 774 } 775 sig_check(); 776 return retval; 777 } 778 779 /* 780 * The following gets called on receipt of an interrupt. This is 781 * to abort printout of a command, mainly. 782 * Dispatching here when commands() is inactive crashes rcv. 783 * Close all open files except 0, 1, 2, and the temporary. 784 * Also, unstack all source files. 785 */ 786 __dead static void 787 lex_intr(int signo) 788 { 789 790 noreset = 0; 791 if (!inithdr) 792 sawcom++; 793 inithdr = 0; 794 while (sourcing) 795 (void)unstack(); 796 797 close_piping(); 798 close_all_files(); 799 800 if (image >= 0) { 801 (void)close(image); 802 image = -1; 803 } 804 (void)fprintf(stderr, "Interrupt\n"); 805 longjmp(jmpbuf, signo); 806 } 807 808 /* 809 * Branch here on hangup signal and simulate "exit". 810 */ 811 /*ARGSUSED*/ 812 __dead static void 813 lex_hangup(int s __unused) 814 { 815 816 /* nothing to do? */ 817 exit(EXIT_FAILURE); 818 } 819 820 /* 821 * When we wake up after ^Z, reprint the prompt. 822 * 823 * NOTE: EditLine deals with the prompt and job control, so with it 824 * this does nothing, i.e., reset_on_stop == 0. 825 */ 826 static void 827 lex_stop(int signo) 828 { 829 830 if (reset_on_stop) { 831 reset_on_stop = 0; 832 longjmp(jmpbuf, signo); 833 } 834 } 835 836 /* 837 * Interpret user commands one by one. If standard input is not a tty, 838 * print no prompt. 839 */ 840 PUBLIC void 841 commands(void) 842 { 843 int n; 844 char linebuf[LINESIZE]; 845 int eofloop; 846 847 #ifdef DEBUG_FILE_LEAK 848 file_leak_init(); 849 #endif 850 851 if (!sourcing) { 852 sig_check(); 853 854 sig_hold(); 855 (void)sig_signal(SIGINT, lex_intr); 856 (void)sig_signal(SIGHUP, lex_hangup); 857 (void)sig_signal(SIGTSTP, lex_stop); 858 (void)sig_signal(SIGTTOU, lex_stop); 859 (void)sig_signal(SIGTTIN, lex_stop); 860 sig_release(); 861 } 862 863 (void)setjmp(jmpbuf); /* "reset" location if we got an interrupt */ 864 865 eofloop = 0; /* initialize this after a possible longjmp */ 866 for (;;) { 867 sig_check(); 868 (void)fflush(stdout); 869 sreset(); 870 /* 871 * Print the prompt, if needed. Clear out 872 * string space, and flush the output. 873 */ 874 if (!sourcing && value(ENAME_INTERACTIVE) != NULL) { 875 if ((prompt = value(ENAME_PROMPT)) == NULL) 876 prompt = DEFAULT_PROMPT; 877 prompt = smsgprintf(prompt, dot); 878 if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0)) 879 (void)printf("New mail has arrived.\n"); 880 881 #ifndef USE_EDITLINE 882 reset_on_stop = 1; /* enable job control longjmp */ 883 (void)printf("%s", prompt); 884 #endif 885 } 886 #ifdef DEBUG_FILE_LEAK 887 file_leak_check(); 888 #endif 889 /* 890 * Read a line of commands from the current input 891 * and handle end of file specially. 892 */ 893 n = 0; 894 for (;;) { 895 sig_check(); 896 #ifdef USE_EDITLINE 897 if (!sourcing) { 898 char *line; 899 900 line = my_gets(&elm.command, prompt, NULL); 901 if (line == NULL) { 902 if (n == 0) 903 n = -1; 904 break; 905 } 906 (void)strlcpy(linebuf, line, sizeof(linebuf)); 907 } 908 else { 909 if (readline(input, &linebuf[n], LINESIZE - n, 0) < 0) { 910 if (n == 0) 911 n = -1; 912 break; 913 } 914 } 915 #else /* USE_EDITLINE */ 916 if (readline(input, &linebuf[n], LINESIZE - n, reset_on_stop) < 0) { 917 if (n == 0) 918 n = -1; 919 break; 920 } 921 #endif /* USE_EDITLINE */ 922 if (!sourcing) 923 setscreensize(); /* so we can resize window */ 924 925 if (sourcing) { /* allow comments in source files */ 926 char *ptr; 927 if ((ptr = comment_char(linebuf)) != NULL) 928 *ptr = '\0'; 929 } 930 if ((n = (int)strlen(linebuf)) == 0) 931 break; 932 n--; 933 if (linebuf[n] != '\\') 934 break; 935 linebuf[n++] = ' '; 936 } 937 #ifndef USE_EDITLINE 938 sig_check(); 939 reset_on_stop = 0; /* disable job control longjmp */ 940 #endif 941 if (n < 0) { 942 char *p; 943 944 /* eof */ 945 if (loading) 946 break; 947 if (sourcing) { 948 (void)unstack(); 949 continue; 950 } 951 if (value(ENAME_INTERACTIVE) != NULL && 952 (p = value(ENAME_IGNOREEOF)) != NULL && 953 ++eofloop < (*p == '\0' ? 25 : atoi(p))) { 954 (void)printf("Use \"quit\" to quit.\n"); 955 continue; 956 } 957 break; 958 } 959 eofloop = 0; 960 if (execute(linebuf, ec_normal)) 961 break; 962 } 963 } 964 965 /* 966 * Announce information about the file we are editing. 967 * Return a likely place to set dot. 968 */ 969 PUBLIC int 970 newfileinfo(int omsgCount) 971 { 972 struct message *mp; 973 int d, n, s, t, u, mdot; 974 char fname[PATHSIZE]; 975 char *ename; 976 977 /* 978 * Figure out where to set the 'dot'. Use the first new or 979 * unread message. 980 */ 981 for (mp = get_abs_message(omsgCount + 1); mp; 982 mp = next_abs_message(mp)) 983 if (mp->m_flag & MNEW) 984 break; 985 986 if (mp == NULL) 987 for (mp = get_abs_message(omsgCount + 1); mp; 988 mp = next_abs_message(mp)) 989 if ((mp->m_flag & MREAD) == 0) 990 break; 991 if (mp != NULL) 992 mdot = get_msgnum(mp); 993 else 994 mdot = omsgCount + 1; 995 #ifdef THREAD_SUPPORT 996 /* 997 * See if the message is in the current thread. 998 */ 999 if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp) 1000 mdot = 0; 1001 #endif 1002 /* 1003 * Scan the message array counting the new, unread, deleted, 1004 * and saved messages. 1005 */ 1006 d = n = s = t = u = 0; 1007 for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) { 1008 if (mp->m_flag & MNEW) 1009 n++; 1010 if ((mp->m_flag & MREAD) == 0) 1011 u++; 1012 if (mp->m_flag & MDELETED) 1013 d++; 1014 if (mp->m_flag & MSAVED) 1015 s++; 1016 if (mp->m_flag & MTAGGED) 1017 t++; 1018 } 1019 ename = mailname; 1020 if (getfold(fname, sizeof(fname)) >= 0) { 1021 char zname[PATHSIZE]; 1022 size_t l; 1023 l = strlen(fname); 1024 if (l < sizeof(fname) - 1) 1025 fname[l++] = '/'; 1026 if (strncmp(fname, mailname, l) == 0) { 1027 (void)snprintf(zname, sizeof(zname), "+%s", 1028 mailname + l); 1029 ename = zname; 1030 } 1031 } 1032 /* 1033 * Display the statistics. 1034 */ 1035 (void)printf("\"%s\": ", ename); 1036 { 1037 int cnt = get_abs_msgCount(); 1038 (void)printf("%d message%s", cnt, cnt == 1 ? "" : "s"); 1039 } 1040 if (n > 0) 1041 (void)printf(" %d new", n); 1042 if (u-n > 0) 1043 (void)printf(" %d unread", u); 1044 if (t > 0) 1045 (void)printf(" %d tagged", t); 1046 if (d > 0) 1047 (void)printf(" %d deleted", d); 1048 if (s > 0) 1049 (void)printf(" %d saved", s); 1050 if (readonly) 1051 (void)printf(" [Read only]"); 1052 (void)printf("\n"); 1053 1054 return mdot; 1055 } 1056 1057 /* 1058 * Announce the presence of the current Mail version, 1059 * give the message count, and print a header listing. 1060 */ 1061 PUBLIC void 1062 announce(void) 1063 { 1064 int vec[2], mdot; 1065 1066 mdot = newfileinfo(0); 1067 vec[0] = mdot; 1068 vec[1] = 0; 1069 if ((dot = get_message(mdot)) == NULL) 1070 dot = get_abs_message(1); /* make sure we get something! */ 1071 if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) { 1072 inithdr++; 1073 (void)headers(vec); 1074 inithdr = 0; 1075 } 1076 } 1077 1078 /* 1079 * Print the current version number. 1080 */ 1081 1082 /*ARGSUSED*/ 1083 PUBLIC int 1084 pversion(void *v __unused) 1085 { 1086 (void)printf("Version %s\n", version); 1087 return 0; 1088 } 1089 1090 /* 1091 * Load a file of user definitions. 1092 */ 1093 PUBLIC void 1094 load(const char *name) 1095 { 1096 FILE *in, *oldin; 1097 1098 if ((in = Fopen(name, "re")) == NULL) 1099 return; 1100 oldin = input; 1101 input = in; 1102 loading = 1; 1103 sourcing = 1; 1104 commands(); 1105 loading = 0; 1106 sourcing = 0; 1107 input = oldin; 1108 (void)Fclose(in); 1109 } 1110