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