1 /* $NetBSD: lex.c,v 1.39 2010/01/12 14:45:31 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.39 2010/01/12 14:45:31 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, "r")) == 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, "w")) == NULL) 242 err(EXIT_FAILURE, "Can't create tmp file `%s'", tempname); 243 (void)fcntl(fileno(otf), F_SETFD, FD_CLOEXEC); 244 if ((itf = fopen(tempname, "r")) == NULL) 245 err(EXIT_FAILURE, "Can't create tmp file `%s'", tempname); 246 (void)fcntl(fileno(itf), F_SETFD, FD_CLOEXEC); 247 (void)rm(tempname); 248 setptr(ibuf, (off_t)0); 249 setmsize(get_abs_msgCount()); 250 /* 251 * New mail may have arrived while we were reading 252 * the mail file, so reset mailsize to be where 253 * we really are in the file... 254 */ 255 mailsize = ftell(ibuf); 256 (void)Fclose(ibuf); 257 sig_release(); 258 sig_check(); 259 sawcom = 0; 260 if (!edit && get_abs_msgCount() == 0) { 261 nomail: 262 (void)fprintf(stderr, "No mail for %s\n", who); 263 return -1; 264 } 265 return 0; 266 } 267 268 /* 269 * Incorporate any new mail that has arrived since we first 270 * started reading mail. 271 */ 272 PUBLIC int 273 incfile(void) 274 { 275 off_t newsize; 276 int omsgCount; 277 FILE *ibuf; 278 int rval; 279 280 omsgCount = get_abs_msgCount(); 281 282 ibuf = Fopen(mailname, "r"); 283 if (ibuf == NULL) 284 return -1; 285 sig_check(); 286 sig_hold(); 287 newsize = fsize(ibuf); 288 if (newsize == 0 || /* mail box is now empty??? */ 289 newsize < mailsize) { /* mail box has shrunk??? */ 290 rval = -1; 291 goto done; 292 } 293 if (newsize == mailsize) { 294 rval = 0; /* no new mail */ 295 goto done; 296 } 297 setptr(ibuf, mailsize); /* read in new mail */ 298 setmsize(get_abs_msgCount()); /* get the new message count */ 299 mailsize = ftell(ibuf); 300 rval = get_abs_msgCount() - omsgCount; 301 done: 302 (void)Fclose(ibuf); 303 sig_release(); 304 sig_check(); 305 return rval; 306 } 307 308 /* 309 * Return a pointer to the comment character, respecting quoting as 310 * done in getrawlist(). The comment character is ignored inside 311 * quotes. 312 */ 313 static char * 314 comment_char(char *line) 315 { 316 char *p; 317 char quotec; 318 quotec = '\0'; 319 for (p = line; *p; p++) { 320 if (quotec != '\0') { 321 if (*p == quotec) 322 quotec = '\0'; 323 } 324 else if (*p == '"' || *p == '\'') 325 quotec = *p; 326 else if (*p == COMMENT_CHAR) 327 return p; 328 } 329 return NULL; 330 } 331 332 /* 333 * Signal handler is hooked by setup_piping(). 334 * Respond to a broken pipe signal -- 335 * probably caused by quitting more. 336 */ 337 static jmp_buf pipestop; 338 339 /*ARGSUSED*/ 340 static void 341 lex_brokpipe(int signo) 342 { 343 344 longjmp(pipestop, signo); 345 } 346 347 /* 348 * Check the command line for any requested piping or redirection, 349 * depending on the value of 'c'. If "enable-pipes" is set, search 350 * the command line (cp) for the first occurrence of the character 'c' 351 * that is not in a quote or (parenthese) group. 352 */ 353 PUBLIC char * 354 shellpr(char *cp) 355 { 356 int quotec; 357 int level; 358 359 if (cp == NULL || value(ENAME_ENABLE_PIPES) == NULL) 360 return NULL; 361 362 level = 0; 363 quotec = 0; 364 for (/*EMPTY*/; *cp != '\0'; cp++) { 365 if (quotec) { 366 if (*cp == quotec) 367 quotec = 0; 368 if (*cp == '\\' && 369 (cp[1] == quotec || cp[1] == '\\')) 370 cp++; 371 } 372 else { 373 switch (*cp) { 374 case '|': 375 case '>': 376 if (level == 0) 377 return cp; 378 break; 379 case '(': 380 level++; 381 break; 382 case ')': 383 level--; 384 break; 385 case '"': 386 case '\'': 387 quotec = *cp; 388 break; 389 default: 390 break; 391 } 392 } 393 } 394 return NULL; 395 } 396 397 static int 398 do_paging(const char *cmd, int c_pipe) 399 { 400 char *cp, *p; 401 402 if (value(ENAME_PAGER_OFF) != NULL) 403 return 0; 404 405 if (c_pipe & C_PIPE_PAGER) 406 return 1; 407 408 if (c_pipe & C_PIPE_CRT && value(ENAME_CRT) != NULL) 409 return 1; 410 411 if ((cp = value(ENAME_PAGE_ALSO)) == NULL) 412 return 0; 413 414 if ((p = strcasestr(cp, cmd)) == NULL) 415 return 0; 416 417 if (p != cp && p[-1] != ',' && !is_WSP(p[-1])) 418 return 0; 419 420 p += strlen(cmd); 421 422 return (*p == '\0' || *p == ',' || is_WSP(*p)); 423 } 424 425 /* 426 * Setup any pipe or redirection that the command line indicates. 427 * If none, then setup the pager unless "pager-off" is defined. 428 */ 429 static FILE *fp_stop = NULL; 430 static int oldfd1 = -1; 431 static sig_t old_sigpipe; 432 433 static int 434 setup_piping(const char *cmd, char *cmdline, int c_pipe) 435 { 436 FILE *fout; 437 FILE *last_file; 438 char *cp; 439 440 sig_check(); 441 442 last_file = last_registered_file(0); 443 444 fout = NULL; 445 if ((cp = shellpr(cmdline)) != NULL) { 446 char c; 447 c = *cp; 448 *cp = '\0'; 449 cp++; 450 451 if (c == '|') { 452 if ((fout = Popen(cp, "w")) == NULL) { 453 warn("Popen: %s", cp); 454 return -1; 455 } 456 } 457 else { 458 const char *mode; 459 assert(c == '>'); 460 mode = *cp == '>' ? "a" : "w"; 461 if (*cp == '>') 462 cp++; 463 464 cp = skip_WSP(cp); 465 if ((fout = Fopen(cp, mode)) == NULL) { 466 warn("Fopen: %s", cp); 467 return -1; 468 } 469 } 470 471 } 472 else if (do_paging(cmd, c_pipe)) { 473 const char *pager; 474 pager = value(ENAME_PAGER); 475 if (pager == NULL || *pager == '\0') 476 pager = _PATH_MORE; 477 478 if ((fout = Popen(pager, "w")) == NULL) { 479 warn("Popen: %s", pager); 480 return -1; 481 } 482 } 483 484 if (fout) { 485 old_sigpipe = sig_signal(SIGPIPE, lex_brokpipe); 486 (void)fflush(stdout); 487 if ((oldfd1 = dup(STDOUT_FILENO)) == -1) 488 err(EXIT_FAILURE, "dup failed"); 489 if (dup2(fileno(fout), STDOUT_FILENO) == -1) 490 err(EXIT_FAILURE, "dup2 failed"); 491 fp_stop = last_file; 492 } 493 return 0; 494 } 495 496 /* 497 * This will close any piping started by setup_piping(). 498 */ 499 static void 500 close_piping(void) 501 { 502 sigset_t oset; 503 struct sigaction osa; 504 505 if (oldfd1 != -1) { 506 (void)fflush(stdout); 507 if (fileno(stdout) != oldfd1 && 508 dup2(oldfd1, STDOUT_FILENO) == -1) 509 err(EXIT_FAILURE, "dup2 failed"); 510 511 (void)sig_ignore(SIGPIPE, &osa, &oset); 512 513 close_top_files(fp_stop); 514 fp_stop = NULL; 515 (void)close(oldfd1); 516 oldfd1 = -1; 517 518 (void)sig_signal(SIGPIPE, old_sigpipe); 519 (void)sig_restore(SIGPIPE, &osa, &oset); 520 } 521 sig_check(); 522 } 523 524 /* 525 * Determine if as1 is a valid prefix of as2. 526 * Return true if yep. 527 */ 528 static int 529 isprefix(char *as1, const char *as2) 530 { 531 char *s1; 532 const char *s2; 533 534 s1 = as1; 535 s2 = as2; 536 while (*s1++ == *s2) 537 if (*s2++ == '\0') 538 return 1; 539 return *--s1 == '\0'; 540 } 541 542 /* 543 * Find the correct command in the command table corresponding 544 * to the passed command "word" 545 */ 546 PUBLIC const struct cmd * 547 lex(char word[]) 548 { 549 const struct cmd *cp; 550 551 for (cp = &cmdtab[0]; cp->c_name != NULL; cp++) 552 if (isprefix(word, cp->c_name)) 553 return cp; 554 return NULL; 555 } 556 557 PUBLIC char * 558 get_cmdname(char *buf) 559 { 560 char *cp; 561 char *cmd; 562 size_t len; 563 564 for (cp = buf; *cp; cp++) 565 if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL) 566 break; 567 /* XXX - Don't miss the pipe command! */ 568 if (cp == buf && *cp == '|') 569 cp++; 570 len = cp - buf + 1; 571 cmd = salloc(len); 572 (void)strlcpy(cmd, buf, len); 573 return cmd; 574 } 575 576 /* 577 * Execute a single command. 578 * Command functions return 0 for success, 1 for error, and -1 579 * for abort. A 1 or -1 aborts a load or source. A -1 aborts 580 * the interactive command loop. 581 * execute_contxt_e is in extern.h. 582 */ 583 PUBLIC int 584 execute(char linebuf[], enum execute_contxt_e contxt) 585 { 586 char *word; 587 char *arglist[MAXARGC]; 588 const struct cmd * volatile com = NULL; 589 char *volatile cp; 590 int retval; 591 int c; 592 int e = 1; 593 594 /* 595 * Strip the white space away from the beginning 596 * of the command, then scan out a word, which 597 * consists of anything except digits and white space. 598 * 599 * Handle ! escapes differently to get the correct 600 * lexical conventions. 601 */ 602 603 cp = skip_space(linebuf); 604 if (*cp == '!') { 605 if (sourcing) { 606 (void)printf("Can't \"!\" while sourcing\n"); 607 goto out; 608 } 609 (void)shell(cp + 1); 610 return 0; 611 } 612 613 word = get_cmdname(cp); 614 cp += strlen(word); 615 616 /* 617 * Look up the command; if not found, bitch. 618 * Normally, a blank command would map to the 619 * first command in the table; while sourcing, 620 * however, we ignore blank lines to eliminate 621 * confusion. 622 */ 623 624 if (sourcing && *word == '\0') 625 return 0; 626 com = lex(word); 627 if (com == NULL) { 628 (void)printf("Unknown command: \"%s\"\n", word); 629 goto out; 630 } 631 632 /* 633 * See if we should execute the command -- if a conditional 634 * we always execute it, otherwise, check the state of cond. 635 */ 636 637 if ((com->c_argtype & F) == 0 && (cond & CSKIP)) 638 return 0; 639 640 /* 641 * Process the arguments to the command, depending 642 * on the type he expects. Default to an error. 643 * If we are sourcing an interactive command, it's 644 * an error. 645 */ 646 647 if (mailmode == mm_sending && (com->c_argtype & M) == 0) { 648 (void)printf("May not execute \"%s\" while sending\n", 649 com->c_name); 650 goto out; 651 } 652 if (sourcing && com->c_argtype & I) { 653 (void)printf("May not execute \"%s\" while sourcing\n", 654 com->c_name); 655 goto out; 656 } 657 if (readonly && com->c_argtype & W) { 658 (void)printf("May not execute \"%s\" -- message file is read only\n", 659 com->c_name); 660 goto out; 661 } 662 if (contxt == ec_composing && com->c_argtype & R) { 663 (void)printf("Cannot recursively invoke \"%s\"\n", com->c_name); 664 goto out; 665 } 666 667 if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) { 668 669 sig_check(); 670 if (setjmp(pipestop)) 671 goto out; 672 673 if (setup_piping(com->c_name, cp, com->c_pipe) == -1) 674 goto out; 675 } 676 switch (com->c_argtype & ARGTYPE_MASK) { 677 case MSGLIST: 678 /* 679 * A message list defaulting to nearest forward 680 * legal message. 681 */ 682 if (msgvec == 0) { 683 (void)printf("Illegal use of \"message list\"\n"); 684 break; 685 } 686 if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0) 687 break; 688 if (c == 0) { 689 *msgvec = first(com->c_msgflag, com->c_msgmask); 690 msgvec[1] = 0; 691 } 692 if (*msgvec == 0) { 693 (void)printf("No applicable messages\n"); 694 break; 695 } 696 e = (*com->c_func)(msgvec); 697 break; 698 699 case NDMLIST: 700 /* 701 * A message list with no defaults, but no error 702 * if none exist. 703 */ 704 if (msgvec == 0) { 705 (void)printf("Illegal use of \"message list\"\n"); 706 break; 707 } 708 if (getmsglist(cp, msgvec, com->c_msgflag) < 0) 709 break; 710 e = (*com->c_func)(msgvec); 711 break; 712 713 case STRLIST: 714 /* 715 * Just the straight string, with 716 * leading blanks removed. 717 */ 718 cp = skip_space(cp); 719 e = (*com->c_func)(cp); 720 break; 721 722 case RAWLIST: 723 /* 724 * A vector of strings, in shell style. 725 */ 726 if ((c = getrawlist(cp, arglist, (int)__arraycount(arglist))) < 0) 727 break; 728 if (c < com->c_minargs) { 729 (void)printf("%s requires at least %d arg(s)\n", 730 com->c_name, com->c_minargs); 731 break; 732 } 733 if (c > com->c_maxargs) { 734 (void)printf("%s takes no more than %d arg(s)\n", 735 com->c_name, com->c_maxargs); 736 break; 737 } 738 e = (*com->c_func)(arglist); 739 break; 740 741 case NOLIST: 742 /* 743 * Just the constant zero, for exiting, 744 * eg. 745 */ 746 e = (*com->c_func)(0); 747 break; 748 749 default: 750 errx(EXIT_FAILURE, "Unknown argtype"); 751 } 752 753 out: 754 close_piping(); 755 756 /* 757 * Exit the current source file on 758 * error. 759 */ 760 retval = 0; 761 if (e) { 762 if (e < 0) 763 retval = 1; 764 else if (loading) 765 retval = 1; 766 else if (sourcing) 767 (void)unstack(); 768 } 769 else if (com != NULL) { 770 if (contxt != ec_autoprint && com->c_argtype & P && 771 value(ENAME_AUTOPRINT) != NULL && 772 (dot->m_flag & MDELETED) == 0) 773 (void)execute(__UNCONST("print ."), ec_autoprint); 774 if (!sourcing && (com->c_argtype & T) == 0) 775 sawcom = 1; 776 } 777 sig_check(); 778 return retval; 779 } 780 781 /* 782 * The following gets called on receipt of an interrupt. This is 783 * to abort printout of a command, mainly. 784 * Dispatching here when commands() is inactive crashes rcv. 785 * Close all open files except 0, 1, 2, and the temporary. 786 * Also, unstack all source files. 787 */ 788 static void 789 lex_intr(int signo) 790 { 791 792 noreset = 0; 793 if (!inithdr) 794 sawcom++; 795 inithdr = 0; 796 while (sourcing) 797 (void)unstack(); 798 799 close_piping(); 800 close_all_files(); 801 802 if (image >= 0) { 803 (void)close(image); 804 image = -1; 805 } 806 (void)fprintf(stderr, "Interrupt\n"); 807 longjmp(jmpbuf, signo); 808 } 809 810 /* 811 * Branch here on hangup signal and simulate "exit". 812 */ 813 /*ARGSUSED*/ 814 static void 815 lex_hangup(int s __unused) 816 { 817 818 /* nothing to do? */ 819 exit(EXIT_FAILURE); 820 } 821 822 /* 823 * When we wake up after ^Z, reprint the prompt. 824 * 825 * NOTE: EditLine deals with the prompt and job control, so with it 826 * this does nothing, i.e., reset_on_stop == 0. 827 */ 828 static void 829 lex_stop(int signo) 830 { 831 832 if (reset_on_stop) { 833 reset_on_stop = 0; 834 longjmp(jmpbuf, signo); 835 } 836 } 837 838 /* 839 * Interpret user commands one by one. If standard input is not a tty, 840 * print no prompt. 841 */ 842 PUBLIC void 843 commands(void) 844 { 845 int n; 846 char linebuf[LINESIZE]; 847 int eofloop; 848 849 #ifdef DEBUG_FILE_LEAK 850 file_leak_init(); 851 #endif 852 853 if (!sourcing) { 854 sig_check(); 855 856 sig_hold(); 857 (void)sig_signal(SIGINT, lex_intr); 858 (void)sig_signal(SIGHUP, lex_hangup); 859 (void)sig_signal(SIGTSTP, lex_stop); 860 (void)sig_signal(SIGTTOU, lex_stop); 861 (void)sig_signal(SIGTTIN, lex_stop); 862 sig_release(); 863 } 864 865 (void)setjmp(jmpbuf); /* "reset" location if we got an interrupt */ 866 867 eofloop = 0; /* initialize this after a possible longjmp */ 868 for (;;) { 869 sig_check(); 870 (void)fflush(stdout); 871 sreset(); 872 /* 873 * Print the prompt, if needed. Clear out 874 * string space, and flush the output. 875 */ 876 if (!sourcing && value(ENAME_INTERACTIVE) != NULL) { 877 if ((prompt = value(ENAME_PROMPT)) == NULL) 878 prompt = DEFAULT_PROMPT; 879 prompt = smsgprintf(prompt, dot); 880 if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0)) 881 (void)printf("New mail has arrived.\n"); 882 883 #ifndef USE_EDITLINE 884 reset_on_stop = 1; /* enable job control longjmp */ 885 (void)printf("%s", prompt); 886 #endif 887 } 888 #ifdef DEBUG_FILE_LEAK 889 file_leak_check(); 890 #endif 891 /* 892 * Read a line of commands from the current input 893 * and handle end of file specially. 894 */ 895 n = 0; 896 for (;;) { 897 sig_check(); 898 #ifdef USE_EDITLINE 899 if (!sourcing) { 900 char *line; 901 902 line = my_gets(&elm.command, prompt, NULL); 903 if (line == NULL) { 904 if (n == 0) 905 n = -1; 906 break; 907 } 908 (void)strlcpy(linebuf, line, sizeof(linebuf)); 909 } 910 else { 911 if (readline(input, &linebuf[n], LINESIZE - n, 0) < 0) { 912 if (n == 0) 913 n = -1; 914 break; 915 } 916 } 917 #else /* USE_EDITLINE */ 918 if (readline(input, &linebuf[n], LINESIZE - n, reset_on_stop) < 0) { 919 if (n == 0) 920 n = -1; 921 break; 922 } 923 #endif /* USE_EDITLINE */ 924 if (!sourcing) 925 setscreensize(); /* so we can resize window */ 926 927 if (sourcing) { /* allow comments in source files */ 928 char *ptr; 929 if ((ptr = comment_char(linebuf)) != NULL) 930 *ptr = '\0'; 931 } 932 if ((n = (int)strlen(linebuf)) == 0) 933 break; 934 n--; 935 if (linebuf[n] != '\\') 936 break; 937 linebuf[n++] = ' '; 938 } 939 #ifndef USE_EDITLINE 940 sig_check(); 941 reset_on_stop = 0; /* disable job control longjmp */ 942 #endif 943 if (n < 0) { 944 char *p; 945 946 /* eof */ 947 if (loading) 948 break; 949 if (sourcing) { 950 (void)unstack(); 951 continue; 952 } 953 if (value(ENAME_INTERACTIVE) != NULL && 954 (p = value(ENAME_IGNOREEOF)) != NULL && 955 ++eofloop < (*p == '\0' ? 25 : atoi(p))) { 956 (void)printf("Use \"quit\" to quit.\n"); 957 continue; 958 } 959 break; 960 } 961 eofloop = 0; 962 if (execute(linebuf, ec_normal)) 963 break; 964 } 965 } 966 967 /* 968 * Announce information about the file we are editing. 969 * Return a likely place to set dot. 970 */ 971 PUBLIC int 972 newfileinfo(int omsgCount) 973 { 974 struct message *mp; 975 int d, n, s, t, u, mdot; 976 char fname[PATHSIZE]; 977 char *ename; 978 979 /* 980 * Figure out where to set the 'dot'. Use the first new or 981 * unread message. 982 */ 983 for (mp = get_abs_message(omsgCount + 1); mp; 984 mp = next_abs_message(mp)) 985 if (mp->m_flag & MNEW) 986 break; 987 988 if (mp == NULL) 989 for (mp = get_abs_message(omsgCount + 1); mp; 990 mp = next_abs_message(mp)) 991 if ((mp->m_flag & MREAD) == 0) 992 break; 993 if (mp != NULL) 994 mdot = get_msgnum(mp); 995 else 996 mdot = omsgCount + 1; 997 #ifdef THREAD_SUPPORT 998 /* 999 * See if the message is in the current thread. 1000 */ 1001 if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp) 1002 mdot = 0; 1003 #endif 1004 /* 1005 * Scan the message array counting the new, unread, deleted, 1006 * and saved messages. 1007 */ 1008 d = n = s = t = u = 0; 1009 for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) { 1010 if (mp->m_flag & MNEW) 1011 n++; 1012 if ((mp->m_flag & MREAD) == 0) 1013 u++; 1014 if (mp->m_flag & MDELETED) 1015 d++; 1016 if (mp->m_flag & MSAVED) 1017 s++; 1018 if (mp->m_flag & MTAGGED) 1019 t++; 1020 } 1021 ename = mailname; 1022 if (getfold(fname, sizeof(fname)) >= 0) { 1023 char zname[PATHSIZE]; 1024 size_t l; 1025 l = strlen(fname); 1026 if (l < sizeof(fname) - 1) 1027 fname[l++] = '/'; 1028 if (strncmp(fname, mailname, l) == 0) { 1029 (void)snprintf(zname, sizeof(zname), "+%s", 1030 mailname + l); 1031 ename = zname; 1032 } 1033 } 1034 /* 1035 * Display the statistics. 1036 */ 1037 (void)printf("\"%s\": ", ename); 1038 { 1039 int cnt = get_abs_msgCount(); 1040 (void)printf("%d message%s", cnt, cnt == 1 ? "" : "s"); 1041 } 1042 if (n > 0) 1043 (void)printf(" %d new", n); 1044 if (u-n > 0) 1045 (void)printf(" %d unread", u); 1046 if (t > 0) 1047 (void)printf(" %d tagged", t); 1048 if (d > 0) 1049 (void)printf(" %d deleted", d); 1050 if (s > 0) 1051 (void)printf(" %d saved", s); 1052 if (readonly) 1053 (void)printf(" [Read only]"); 1054 (void)printf("\n"); 1055 1056 return mdot; 1057 } 1058 1059 /* 1060 * Announce the presence of the current Mail version, 1061 * give the message count, and print a header listing. 1062 */ 1063 PUBLIC void 1064 announce(void) 1065 { 1066 int vec[2], mdot; 1067 1068 mdot = newfileinfo(0); 1069 vec[0] = mdot; 1070 vec[1] = 0; 1071 if ((dot = get_message(mdot)) == NULL) 1072 dot = get_abs_message(1); /* make sure we get something! */ 1073 if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) { 1074 inithdr++; 1075 (void)headers(vec); 1076 inithdr = 0; 1077 } 1078 } 1079 1080 /* 1081 * Print the current version number. 1082 */ 1083 1084 /*ARGSUSED*/ 1085 PUBLIC int 1086 pversion(void *v __unused) 1087 { 1088 (void)printf("Version %s\n", version); 1089 return 0; 1090 } 1091 1092 /* 1093 * Load a file of user definitions. 1094 */ 1095 PUBLIC void 1096 load(const char *name) 1097 { 1098 FILE *in, *oldin; 1099 1100 if ((in = Fopen(name, "r")) == NULL) 1101 return; 1102 oldin = input; 1103 input = in; 1104 loading = 1; 1105 sourcing = 1; 1106 commands(); 1107 loading = 0; 1108 sourcing = 0; 1109 input = oldin; 1110 (void)Fclose(in); 1111 } 1112