NetBSD/usr.bin/mail/lex.c
christos ca13337dfe From Anon Ymous:
- Remove all longjmp(3) calls from signal handlers.  Instead, we post
to an internal signal queue and check that periodically.  All signal
related code is now in sig.c, except for the SIGCHLD handler which
remains in popen.c as it is intimately tied to routines there.

- Handle SIGPIPE in type1() regardless of mime support, or else the
handler in execute() will prevent our error code from being returned
resulting in 'sawcom' not being set on the first command as it should.
This only affected the initial behavior of the "next" command without
mime support.

- Add the 'T' flag to many commands in cmdtab.c that should not look
like the first command.  E.g., start mail on a mailbox with multiple
messages, run "set foo", then "next", and watch the second message get
displayed rather than the first as is the case without the first "set"
command.

- Add file descriptor and file handle leak detection.  Enabled by
DEBUG_FILE_LEAK.  This will likely disappear in the future.

- Fix a long standing (since import in 1993) longjmp() bug in
edstop(): the jmpbuf was invalid when quit() is called at the end of
main.

- Fix a long standing bug (since import in 1993) in snarf() where it
didn't strip whitespace correctly if the line consisted only of
whitespace.

- Lint cleanup.

- New Feature: "Header" command.  This allows miscellaneous header
fields to be added to the header, e.g., "X-Organization:" or
"Reply-To:" fields.

- New Feature: "page-also" variable.  This allows the specification of
additional commands to page.  It is more flexible than "crt".

- Document the "pager-off" variable: if set, it disables paging
entirely.
2009-04-10 13:08:24 +00:00

1112 lines
22 KiB
C

/* $NetBSD: lex.c,v 1.37 2009/04/10 13:08:25 christos Exp $ */
/*
* Copyright (c) 1980, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)lex.c 8.2 (Berkeley) 4/20/95";
#else
__RCSID("$NetBSD: lex.c,v 1.37 2009/04/10 13:08:25 christos Exp $");
#endif
#endif /* not lint */
#include <assert.h>
#include <util.h>
#include "rcv.h"
#include "extern.h"
#ifdef USE_EDITLINE
#include "complete.h"
#endif
#include "format.h"
#include "sig.h"
#include "thread.h"
/*
* Mail -- a mail program
*
* Lexical processing of commands.
*/
static const char *prompt = DEFAULT_PROMPT;
static int *msgvec;
static int inithdr; /* Am printing startup headers. */
static jmp_buf jmpbuf; /* The reset jmpbuf */
static int reset_on_stop; /* To do job control longjmp. */
#ifdef DEBUG_FILE_LEAK
struct glue {
struct glue *next;
int niobs;
FILE *iobs;
};
extern struct glue __sglue;
static int open_fd_cnt;
static int open_fp_cnt;
static int
file_count(void)
{
struct glue *gp;
FILE *fp;
int n;
int cnt;
cnt = 0;
for (gp = &__sglue; gp; gp = gp->next) {
for (fp = gp->iobs, n = gp->niobs; --n >= 0; fp++)
if (fp->_flags)
cnt++;
}
return cnt;
}
static int
fds_count(void)
{
int maxfd;
int cnt;
int fd;
maxfd = fcntl(0, F_MAXFD);
if (maxfd == -1) {
warn("fcntl");
return -1;
}
cnt = 0;
for (fd = 0; fd <= maxfd; fd++) {
struct stat sb;
if (fstat(fd, &sb) != -1)
cnt++;
else if (errno != EBADF
#ifdef BROKEN_CLONE_STAT /* see PRs 37878 and 37550 */
&& errno != EOPNOTSUPP
#endif
)
warn("fstat(%d): errno=%d", fd, errno);
}
return cnt;
}
static void
file_leak_init(void)
{
open_fd_cnt = fds_count();
open_fp_cnt = file_count();
}
static void
file_leak_check(void)
{
if (open_fp_cnt != file_count() ||
open_fd_cnt != fds_count()) {
(void)printf("FILE LEAK WARNING: "
"fp-count: %d (%d) "
"fd-count: %d (%d) max-fd: %d\n",
file_count(), open_fp_cnt,
fds_count(), open_fd_cnt,
fcntl(0, F_MAXFD));
}
}
#endif /* DEBUG_FILE_LEAK */
/*
* Set the size of the message vector used to construct argument
* lists to message list functions.
*/
static void
setmsize(int sz)
{
if (msgvec != 0)
free(msgvec);
msgvec = ecalloc((size_t) (sz + 1), sizeof(*msgvec));
}
/*
* Set up editing on the given file name.
* If the first character of name is %, we are considered to be
* editing the file, otherwise we are reading our mail which has
* signficance for mbox and so forth.
*/
PUBLIC int
setfile(const char *name)
{
FILE *ibuf;
int i, fd;
struct stat stb;
char isedit = *name != '%' || getuserid(myname) != (int)getuid();
const char *who = name[1] ? name + 1 : myname;
static int shudclob;
char tempname[PATHSIZE];
if ((name = expand(name)) == NULL)
return -1;
if ((ibuf = Fopen(name, "r")) == NULL) {
if (!isedit && errno == ENOENT)
goto nomail;
warn("%s", name);
return -1;
}
if (fstat(fileno(ibuf), &stb) < 0) {
warn("fstat");
(void)Fclose(ibuf);
return -1;
}
switch (stb.st_mode & S_IFMT) {
case S_IFDIR:
(void)Fclose(ibuf);
errno = EISDIR;
warn("%s", name);
return -1;
case S_IFREG:
break;
default:
(void)Fclose(ibuf);
errno = EINVAL;
warn("%s", name);
return -1;
}
/*
* Looks like all will be well. We must now relinquish our
* hold on the current set of stuff. Must hold signals
* while we are reading the new file, else we will ruin
* the message[] data structure.
*/
sig_check();
sig_hold();
if (shudclob)
quit(jmpbuf);
/*
* Copy the messages into /tmp
* and set pointers.
*/
readonly = 0;
if ((i = open(name, O_WRONLY)) < 0)
readonly++;
else
(void)close(i);
if (shudclob) {
(void)fclose(itf);
(void)fclose(otf);
}
shudclob = 1;
edit = isedit;
(void)strcpy(prevfile, mailname);
if (name != mailname)
(void)strcpy(mailname, name);
mailsize = fsize(ibuf);
(void)snprintf(tempname, sizeof(tempname),
"%s/mail.RxXXXXXXXXXX", tmpdir);
if ((fd = mkstemp(tempname)) == -1 ||
(otf = fdopen(fd, "w")) == NULL)
err(1, "%s", tempname);
(void)fcntl(fileno(otf), F_SETFD, FD_CLOEXEC);
if ((itf = fopen(tempname, "r")) == NULL)
err(1, "%s", tempname);
(void)fcntl(fileno(itf), F_SETFD, FD_CLOEXEC);
(void)rm(tempname);
setptr(ibuf, (off_t)0);
setmsize(get_abs_msgCount());
/*
* New mail may have arrived while we were reading
* the mail file, so reset mailsize to be where
* we really are in the file...
*/
mailsize = ftell(ibuf);
(void)Fclose(ibuf);
sig_release();
sig_check();
sawcom = 0;
if (!edit && get_abs_msgCount() == 0) {
nomail:
(void)fprintf(stderr, "No mail for %s\n", who);
return -1;
}
return 0;
}
/*
* Incorporate any new mail that has arrived since we first
* started reading mail.
*/
PUBLIC int
incfile(void)
{
off_t newsize;
int omsgCount;
FILE *ibuf;
int rval;
omsgCount = get_abs_msgCount();
ibuf = Fopen(mailname, "r");
if (ibuf == NULL)
return -1;
sig_check();
sig_hold();
newsize = fsize(ibuf);
if (newsize == 0 || /* mail box is now empty??? */
newsize < mailsize) { /* mail box has shrunk??? */
rval = -1;
goto done;
}
if (newsize == mailsize) {
rval = 0; /* no new mail */
goto done;
}
setptr(ibuf, mailsize); /* read in new mail */
setmsize(get_abs_msgCount()); /* get the new message count */
mailsize = ftell(ibuf);
rval = get_abs_msgCount() - omsgCount;
done:
(void)Fclose(ibuf);
sig_release();
sig_check();
return rval;
}
/*
* Return a pointer to the comment character, respecting quoting as
* done in getrawlist(). The comment character is ignored inside
* quotes.
*/
static char *
comment_char(char *line)
{
char *p;
char quotec;
quotec = '\0';
for (p = line; *p; p++) {
if (quotec != '\0') {
if (*p == quotec)
quotec = '\0';
}
else if (*p == '"' || *p == '\'')
quotec = *p;
else if (*p == COMMENT_CHAR)
return p;
}
return NULL;
}
/*
* Signal handler is hooked by setup_piping().
* Respond to a broken pipe signal --
* probably caused by quitting more.
*/
static jmp_buf pipestop;
/*ARGSUSED*/
static void
lex_brokpipe(int signo)
{
longjmp(pipestop, signo);
}
/*
* Check the command line for any requested piping or redirection,
* depending on the value of 'c'. If "enable-pipes" is set, search
* the command line (cp) for the first occurrence of the character 'c'
* that is not in a quote or (parenthese) group.
*/
PUBLIC char *
shellpr(char *cp)
{
int quotec;
int level;
if (cp == NULL || value(ENAME_ENABLE_PIPES) == NULL)
return NULL;
level = 0;
quotec = 0;
for (/*EMPTY*/; *cp != '\0'; cp++) {
if (quotec) {
if (*cp == quotec)
quotec = 0;
if (*cp == '\\' &&
(cp[1] == quotec || cp[1] == '\\'))
cp++;
}
else {
switch (*cp) {
case '|':
case '>':
if (level == 0)
return cp;
break;
case '(':
level++;
break;
case ')':
level--;
break;
case '"':
case '\'':
quotec = *cp;
break;
default:
break;
}
}
}
return NULL;
}
static int
do_paging(const char *cmd, int c_pipe)
{
char *cp, *p;
if (value(ENAME_PAGER_OFF) != NULL)
return 0;
if (c_pipe & C_PIPE_PAGER)
return 1;
if (c_pipe & C_PIPE_CRT && value(ENAME_CRT) != NULL)
return 1;
if ((cp = value(ENAME_PAGE_ALSO)) == NULL)
return 0;
if ((p = strcasestr(cp, cmd)) == NULL)
return 0;
if (p != cp && p[-1] != ',' && !is_WSP(p[-1]))
return 0;
p += strlen(cmd);
return (*p == '\0' || *p == ',' || is_WSP(*p));
}
/*
* Setup any pipe or redirection that the command line indicates.
* If none, then setup the pager unless "pager-off" is defined.
*/
static FILE *fp_stop = NULL;
static int oldfd1 = -1;
static sig_t old_sigpipe;
static int
setup_piping(const char *cmd, char *cmdline, int c_pipe)
{
FILE *fout;
FILE *last_file;
char *cp;
sig_check();
last_file = last_registered_file(0);
fout = NULL;
if ((cp = shellpr(cmdline)) != NULL) {
char c;
c = *cp;
*cp = '\0';
cp++;
if (c == '|') {
if ((fout = Popen(cp, "w")) == NULL) {
warn("Popen: %s", cp);
return -1;
}
}
else {
const char *mode;
assert(c == '>');
mode = *cp == '>' ? "a" : "w";
if (*cp == '>')
cp++;
cp = skip_WSP(cp);
if ((fout = Fopen(cp, mode)) == NULL) {
warn("Fopen: %s", cp);
return -1;
}
}
}
else if (do_paging(cmd, c_pipe)) {
const char *pager;
pager = value(ENAME_PAGER);
if (pager == NULL || *pager == '\0')
pager = _PATH_MORE;
if ((fout = Popen(pager, "w")) == NULL) {
warn("Popen: %s", pager);
return -1;
}
}
if (fout) {
old_sigpipe = sig_signal(SIGPIPE, lex_brokpipe);
(void)fflush(stdout);
if ((oldfd1 = dup(STDOUT_FILENO)) == -1)
err(EXIT_FAILURE, "dup failed");
if (dup2(fileno(fout), STDOUT_FILENO) == -1)
err(EXIT_FAILURE, "dup2 failed");
fp_stop = last_file;
}
return 0;
}
/*
* This will close any piping started by setup_piping().
*/
static void
close_piping(void)
{
sigset_t oset;
struct sigaction osa;
if (oldfd1 != -1) {
(void)fflush(stdout);
if (fileno(stdout) != oldfd1 &&
dup2(oldfd1, STDOUT_FILENO) == -1)
err(EXIT_FAILURE, "dup2 failed");
(void)sig_ignore(SIGPIPE, &osa, &oset);
close_top_files(fp_stop);
fp_stop = NULL;
(void)close(oldfd1);
oldfd1 = -1;
(void)sig_signal(SIGPIPE, old_sigpipe);
(void)sig_restore(SIGPIPE, &osa, &oset);
}
sig_check();
}
/*
* Determine if as1 is a valid prefix of as2.
* Return true if yep.
*/
static int
isprefix(char *as1, const char *as2)
{
char *s1;
const char *s2;
s1 = as1;
s2 = as2;
while (*s1++ == *s2)
if (*s2++ == '\0')
return 1;
return *--s1 == '\0';
}
/*
* Find the correct command in the command table corresponding
* to the passed command "word"
*/
PUBLIC const struct cmd *
lex(char word[])
{
const struct cmd *cp;
for (cp = &cmdtab[0]; cp->c_name != NULL; cp++)
if (isprefix(word, cp->c_name))
return cp;
return NULL;
}
PUBLIC char *
get_cmdname(char *buf)
{
char *cp;
char *cmd;
size_t len;
for (cp = buf; *cp; cp++)
if (strchr(" \t0123456789$^.:/-+*'\">|", *cp) != NULL)
break;
/* XXX - Don't miss the pipe command! */
if (cp == buf && *cp == '|')
cp++;
len = cp - buf + 1;
cmd = salloc(len);
(void)strlcpy(cmd, buf, len);
return cmd;
}
/*
* Execute a single command.
* Command functions return 0 for success, 1 for error, and -1
* for abort. A 1 or -1 aborts a load or source. A -1 aborts
* the interactive command loop.
* execute_contxt_e is in extern.h.
*/
PUBLIC int
execute(char linebuf[], enum execute_contxt_e contxt)
{
char *word;
char *arglist[MAXARGC];
const struct cmd *com = NULL;
char *volatile cp;
int retval;
int c;
int e = 1;
/*
* Strip the white space away from the beginning
* of the command, then scan out a word, which
* consists of anything except digits and white space.
*
* Handle ! escapes differently to get the correct
* lexical conventions.
*/
cp = skip_space(linebuf);
if (*cp == '!') {
if (sourcing) {
(void)printf("Can't \"!\" while sourcing\n");
goto out;
}
(void)shell(cp + 1);
return 0;
}
word = get_cmdname(cp);
cp += strlen(word);
/*
* Look up the command; if not found, bitch.
* Normally, a blank command would map to the
* first command in the table; while sourcing,
* however, we ignore blank lines to eliminate
* confusion.
*/
if (sourcing && *word == '\0')
return 0;
com = lex(word);
if (com == NULL) {
(void)printf("Unknown command: \"%s\"\n", word);
goto out;
}
/*
* See if we should execute the command -- if a conditional
* we always execute it, otherwise, check the state of cond.
*/
if ((com->c_argtype & F) == 0 && (cond & CSKIP))
return 0;
/*
* Process the arguments to the command, depending
* on the type he expects. Default to an error.
* If we are sourcing an interactive command, it's
* an error.
*/
if (mailmode == mm_sending && (com->c_argtype & M) == 0) {
(void)printf("May not execute \"%s\" while sending\n",
com->c_name);
goto out;
}
if (sourcing && com->c_argtype & I) {
(void)printf("May not execute \"%s\" while sourcing\n",
com->c_name);
goto out;
}
if (readonly && com->c_argtype & W) {
(void)printf("May not execute \"%s\" -- message file is read only\n",
com->c_name);
goto out;
}
if (contxt == ec_composing && com->c_argtype & R) {
(void)printf("Cannot recursively invoke \"%s\"\n", com->c_name);
goto out;
}
if (!sourcing && com->c_pipe && value(ENAME_INTERACTIVE) != NULL) {
sig_check();
if (setjmp(pipestop))
goto out;
if (setup_piping(com->c_name, cp, com->c_pipe) == -1)
goto out;
}
switch (com->c_argtype & ARGTYPE_MASK) {
case MSGLIST:
/*
* A message list defaulting to nearest forward
* legal message.
*/
if (msgvec == 0) {
(void)printf("Illegal use of \"message list\"\n");
break;
}
if ((c = getmsglist(cp, msgvec, com->c_msgflag)) < 0)
break;
if (c == 0) {
*msgvec = first(com->c_msgflag, com->c_msgmask);
msgvec[1] = 0;
}
if (*msgvec == 0) {
(void)printf("No applicable messages\n");
break;
}
e = (*com->c_func)(msgvec);
break;
case NDMLIST:
/*
* A message list with no defaults, but no error
* if none exist.
*/
if (msgvec == 0) {
(void)printf("Illegal use of \"message list\"\n");
break;
}
if (getmsglist(cp, msgvec, com->c_msgflag) < 0)
break;
e = (*com->c_func)(msgvec);
break;
case STRLIST:
/*
* Just the straight string, with
* leading blanks removed.
*/
cp = skip_space(cp);
e = (*com->c_func)(cp);
break;
case RAWLIST:
/*
* A vector of strings, in shell style.
*/
if ((c = getrawlist(cp, arglist, (int)__arraycount(arglist))) < 0)
break;
if (c < com->c_minargs) {
(void)printf("%s requires at least %d arg(s)\n",
com->c_name, com->c_minargs);
break;
}
if (c > com->c_maxargs) {
(void)printf("%s takes no more than %d arg(s)\n",
com->c_name, com->c_maxargs);
break;
}
e = (*com->c_func)(arglist);
break;
case NOLIST:
/*
* Just the constant zero, for exiting,
* eg.
*/
e = (*com->c_func)(0);
break;
default:
errx(1, "Unknown argtype");
}
out:
close_piping();
/*
* Exit the current source file on
* error.
*/
retval = 0;
if (e) {
if (e < 0)
retval = 1;
else if (loading)
retval = 1;
else if (sourcing)
(void)unstack();
}
else if (com != NULL) {
if (contxt != ec_autoprint && com->c_argtype & P &&
value(ENAME_AUTOPRINT) != NULL &&
(dot->m_flag & MDELETED) == 0)
(void)execute(__UNCONST("print ."), ec_autoprint);
if (!sourcing && (com->c_argtype & T) == 0)
sawcom = 1;
}
sig_check();
return retval;
}
/*
* The following gets called on receipt of an interrupt. This is
* to abort printout of a command, mainly.
* Dispatching here when commands() is inactive crashes rcv.
* Close all open files except 0, 1, 2, and the temporary.
* Also, unstack all source files.
*/
static void
lex_intr(int signo)
{
noreset = 0;
if (!inithdr)
sawcom++;
inithdr = 0;
while (sourcing)
(void)unstack();
close_piping();
close_all_files();
if (image >= 0) {
(void)close(image);
image = -1;
}
(void)fprintf(stderr, "Interrupt\n");
longjmp(jmpbuf, signo);
}
/*
* Branch here on hangup signal and simulate "exit".
*/
/*ARGSUSED*/
static void
lex_hangup(int s __unused)
{
/* nothing to do? */
exit(EXIT_FAILURE);
}
/*
* When we wake up after ^Z, reprint the prompt.
*
* NOTE: EditLine deals with the prompt and job control, so with it
* this does nothing, i.e., reset_on_stop == 0.
*/
static void
lex_stop(int signo)
{
if (reset_on_stop) {
reset_on_stop = 0;
longjmp(jmpbuf, signo);
}
}
/*
* Interpret user commands one by one. If standard input is not a tty,
* print no prompt.
*/
PUBLIC void
commands(void)
{
int n;
char linebuf[LINESIZE];
int eofloop;
#ifdef DEBUG_FILE_LEAK
file_leak_init();
#endif
if (!sourcing) {
sig_check();
sig_hold();
(void)sig_signal(SIGINT, lex_intr);
(void)sig_signal(SIGHUP, lex_hangup);
(void)sig_signal(SIGTSTP, lex_stop);
(void)sig_signal(SIGTTOU, lex_stop);
(void)sig_signal(SIGTTIN, lex_stop);
sig_release();
}
(void)setjmp(jmpbuf); /* "reset" location if we got an interrupt */
eofloop = 0; /* initialize this after a possible longjmp */
for (;;) {
sig_check();
(void)fflush(stdout);
sreset();
/*
* Print the prompt, if needed. Clear out
* string space, and flush the output.
*/
if (!sourcing && value(ENAME_INTERACTIVE) != NULL) {
if ((prompt = value(ENAME_PROMPT)) == NULL)
prompt = DEFAULT_PROMPT;
prompt = smsgprintf(prompt, dot);
if ((value(ENAME_AUTOINC) != NULL) && (incfile() > 0))
(void)printf("New mail has arrived.\n");
#ifndef USE_EDITLINE
reset_on_stop = 1; /* enable job control longjmp */
(void)printf("%s", prompt);
#endif
}
#ifdef DEBUG_FILE_LEAK
file_leak_check();
#endif
/*
* Read a line of commands from the current input
* and handle end of file specially.
*/
n = 0;
for (;;) {
sig_check();
#ifdef USE_EDITLINE
if (!sourcing) {
char *line;
line = my_gets(&elm.command, prompt, NULL);
if (line == NULL) {
if (n == 0)
n = -1;
break;
}
(void)strlcpy(linebuf, line, sizeof(linebuf));
}
else {
if (readline(input, &linebuf[n], LINESIZE - n, 0) < 0) {
if (n == 0)
n = -1;
break;
}
}
#else /* USE_EDITLINE */
if (readline(input, &linebuf[n], LINESIZE - n, reset_on_stop) < 0) {
if (n == 0)
n = -1;
break;
}
#endif /* USE_EDITLINE */
if (!sourcing)
setscreensize(); /* so we can resize window */
if (sourcing) { /* allow comments in source files */
char *ptr;
if ((ptr = comment_char(linebuf)) != NULL)
*ptr = '\0';
}
if ((n = (int)strlen(linebuf)) == 0)
break;
n--;
if (linebuf[n] != '\\')
break;
linebuf[n++] = ' ';
}
#ifndef USE_EDITLINE
sig_check();
reset_on_stop = 0; /* disable job control longjmp */
#endif
if (n < 0) {
char *p;
/* eof */
if (loading)
break;
if (sourcing) {
(void)unstack();
continue;
}
if (value(ENAME_INTERACTIVE) != NULL &&
(p = value(ENAME_IGNOREEOF)) != NULL &&
++eofloop < (*p == '\0' ? 25 : atoi(p))) {
(void)printf("Use \"quit\" to quit.\n");
continue;
}
break;
}
eofloop = 0;
if (execute(linebuf, ec_normal))
break;
}
}
/*
* Announce information about the file we are editing.
* Return a likely place to set dot.
*/
PUBLIC int
newfileinfo(int omsgCount)
{
struct message *mp;
int d, n, s, t, u, mdot;
char fname[PATHSIZE];
char *ename;
/*
* Figure out where to set the 'dot'. Use the first new or
* unread message.
*/
for (mp = get_abs_message(omsgCount + 1); mp;
mp = next_abs_message(mp))
if (mp->m_flag & MNEW)
break;
if (mp == NULL)
for (mp = get_abs_message(omsgCount + 1); mp;
mp = next_abs_message(mp))
if ((mp->m_flag & MREAD) == 0)
break;
if (mp != NULL)
mdot = get_msgnum(mp);
else
mdot = omsgCount + 1;
#ifdef THREAD_SUPPORT
/*
* See if the message is in the current thread.
*/
if (mp != NULL && get_message(1) != NULL && get_message(mdot) != mp)
mdot = 0;
#endif
/*
* Scan the message array counting the new, unread, deleted,
* and saved messages.
*/
d = n = s = t = u = 0;
for (mp = get_abs_message(1); mp; mp = next_abs_message(mp)) {
if (mp->m_flag & MNEW)
n++;
if ((mp->m_flag & MREAD) == 0)
u++;
if (mp->m_flag & MDELETED)
d++;
if (mp->m_flag & MSAVED)
s++;
if (mp->m_flag & MTAGGED)
t++;
}
ename = mailname;
if (getfold(fname, sizeof(fname)) >= 0) {
char zname[PATHSIZE];
size_t l;
l = strlen(fname);
if (l < sizeof(fname) - 1)
fname[l++] = '/';
if (strncmp(fname, mailname, l) == 0) {
(void)snprintf(zname, sizeof(zname), "+%s",
mailname + l);
ename = zname;
}
}
/*
* Display the statistics.
*/
(void)printf("\"%s\": ", ename);
{
int cnt = get_abs_msgCount();
(void)printf("%d message%s", cnt, cnt == 1 ? "" : "s");
}
if (n > 0)
(void)printf(" %d new", n);
if (u-n > 0)
(void)printf(" %d unread", u);
if (t > 0)
(void)printf(" %d tagged", t);
if (d > 0)
(void)printf(" %d deleted", d);
if (s > 0)
(void)printf(" %d saved", s);
if (readonly)
(void)printf(" [Read only]");
(void)printf("\n");
return mdot;
}
/*
* Announce the presence of the current Mail version,
* give the message count, and print a header listing.
*/
PUBLIC void
announce(void)
{
int vec[2], mdot;
mdot = newfileinfo(0);
vec[0] = mdot;
vec[1] = 0;
if ((dot = get_message(mdot)) == NULL)
dot = get_abs_message(1); /* make sure we get something! */
if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) {
inithdr++;
(void)headers(vec);
inithdr = 0;
}
}
/*
* Print the current version number.
*/
/*ARGSUSED*/
PUBLIC int
pversion(void *v __unused)
{
(void)printf("Version %s\n", version);
return 0;
}
/*
* Load a file of user definitions.
*/
PUBLIC void
load(const char *name)
{
FILE *in, *oldin;
if ((in = Fopen(name, "r")) == NULL)
return;
oldin = input;
input = in;
loading = 1;
sourcing = 1;
commands();
loading = 0;
sourcing = 0;
input = oldin;
(void)Fclose(in);
}