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