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