1 /* $NetBSD: lex.c,v 1.37 2009/04/10 13:08:25 christos Exp $ */
4 * Copyright (c) 1980, 1993
5 * The Regents of the University of California. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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
32 #include <sys/cdefs.h>
35 static char sccsid
[] = "@(#)lex.c 8.2 (Berkeley) 4/20/95";
37 __RCSID("$NetBSD: lex.c,v 1.37 2009/04/10 13:08:25 christos Exp $");
54 * Mail -- a mail program
56 * Lexical processing of commands.
59 static const char *prompt
= DEFAULT_PROMPT
;
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. */
65 #ifdef DEBUG_FILE_LEAK
71 extern struct glue __sglue
;
73 static int open_fd_cnt
;
74 static int open_fp_cnt
;
85 for (gp
= &__sglue
; gp
; gp
= gp
->next
) {
86 for (fp
= gp
->iobs
, n
= gp
->niobs
; --n
>= 0; fp
++)
100 maxfd
= fcntl(0, F_MAXFD
);
107 for (fd
= 0; fd
<= maxfd
; fd
++) {
110 if (fstat(fd
, &sb
) != -1)
112 else if (errno
!= EBADF
113 #ifdef BROKEN_CLONE_STAT /* see PRs 37878 and 37550 */
114 && errno
!= EOPNOTSUPP
117 warn("fstat(%d): errno=%d", fd
, errno
);
125 open_fd_cnt
= fds_count();
126 open_fp_cnt
= file_count();
130 file_leak_check(void)
132 if (open_fp_cnt
!= file_count() ||
133 open_fd_cnt
!= fds_count()) {
134 (void)printf("FILE LEAK WARNING: "
136 "fd-count: %d (%d) max-fd: %d\n",
137 file_count(), open_fp_cnt
,
138 fds_count(), open_fd_cnt
,
142 #endif /* DEBUG_FILE_LEAK */
145 * Set the size of the message vector used to construct argument
146 * lists to message list functions.
153 msgvec
= ecalloc((size_t) (sz
+ 1), sizeof(*msgvec
));
157 * Set up editing on the given file name.
158 * If the first character of name is %, we are considered to be
159 * editing the file, otherwise we are reading our mail which has
160 * signficance for mbox and so forth.
163 setfile(const char *name
)
168 char isedit
= *name
!= '%' || getuserid(myname
) != (int)getuid();
169 const char *who
= name
[1] ? name
+ 1 : myname
;
171 char tempname
[PATHSIZE
];
173 if ((name
= expand(name
)) == NULL
)
176 if ((ibuf
= Fopen(name
, "r")) == NULL
) {
177 if (!isedit
&& errno
== ENOENT
)
183 if (fstat(fileno(ibuf
), &stb
) < 0) {
189 switch (stb
.st_mode
& S_IFMT
) {
207 * Looks like all will be well. We must now relinquish our
208 * hold on the current set of stuff. Must hold signals
209 * while we are reading the new file, else we will ruin
210 * the message[] data structure.
219 * Copy the messages into /tmp
224 if ((i
= open(name
, O_WRONLY
)) < 0)
234 (void)strcpy(prevfile
, mailname
);
235 if (name
!= mailname
)
236 (void)strcpy(mailname
, name
);
237 mailsize
= fsize(ibuf
);
238 (void)snprintf(tempname
, sizeof(tempname
),
239 "%s/mail.RxXXXXXXXXXX", tmpdir
);
240 if ((fd
= mkstemp(tempname
)) == -1 ||
241 (otf
= fdopen(fd
, "w")) == NULL
)
242 err(1, "%s", tempname
);
243 (void)fcntl(fileno(otf
), F_SETFD
, FD_CLOEXEC
);
244 if ((itf
= fopen(tempname
, "r")) == NULL
)
245 err(1, "%s", tempname
);
246 (void)fcntl(fileno(itf
), F_SETFD
, FD_CLOEXEC
);
248 setptr(ibuf
, (off_t
)0);
249 setmsize(get_abs_msgCount());
251 * New mail may have arrived while we were reading
252 * the mail file, so reset mailsize to be where
253 * we really are in the file...
255 mailsize
= ftell(ibuf
);
260 if (!edit
&& get_abs_msgCount() == 0) {
262 (void)fprintf(stderr
, "No mail for %s\n", who
);
269 * Incorporate any new mail that has arrived since we first
270 * started reading mail.
280 omsgCount
= get_abs_msgCount();
282 ibuf
= Fopen(mailname
, "r");
287 newsize
= fsize(ibuf
);
288 if (newsize
== 0 || /* mail box is now empty??? */
289 newsize
< mailsize
) { /* mail box has shrunk??? */
293 if (newsize
== mailsize
) {
294 rval
= 0; /* no new mail */
297 setptr(ibuf
, mailsize
); /* read in new mail */
298 setmsize(get_abs_msgCount()); /* get the new message count */
299 mailsize
= ftell(ibuf
);
300 rval
= get_abs_msgCount() - omsgCount
;
309 * Return a pointer to the comment character, respecting quoting as
310 * done in getrawlist(). The comment character is ignored inside
314 comment_char(char *line
)
319 for (p
= line
; *p
; p
++) {
320 if (quotec
!= '\0') {
324 else if (*p
== '"' || *p
== '\'')
326 else if (*p
== COMMENT_CHAR
)
333 * Signal handler is hooked by setup_piping().
334 * Respond to a broken pipe signal --
335 * probably caused by quitting more.
337 static jmp_buf pipestop
;
341 lex_brokpipe(int signo
)
344 longjmp(pipestop
, signo
);
348 * Check the command line for any requested piping or redirection,
349 * depending on the value of 'c'. If "enable-pipes" is set, search
350 * the command line (cp) for the first occurrence of the character 'c'
351 * that is not in a quote or (parenthese) group.
359 if (cp
== NULL
|| value(ENAME_ENABLE_PIPES
) == NULL
)
364 for (/*EMPTY*/; *cp
!= '\0'; cp
++) {
369 (cp
[1] == quotec
|| cp
[1] == '\\'))
398 do_paging(const char *cmd
, int c_pipe
)
402 if (value(ENAME_PAGER_OFF
) != NULL
)
405 if (c_pipe
& C_PIPE_PAGER
)
408 if (c_pipe
& C_PIPE_CRT
&& value(ENAME_CRT
) != NULL
)
411 if ((cp
= value(ENAME_PAGE_ALSO
)) == NULL
)
414 if ((p
= strcasestr(cp
, cmd
)) == NULL
)
417 if (p
!= cp
&& p
[-1] != ',' && !is_WSP(p
[-1]))
422 return (*p
== '\0' || *p
== ',' || is_WSP(*p
));
426 * Setup any pipe or redirection that the command line indicates.
427 * If none, then setup the pager unless "pager-off" is defined.
429 static FILE *fp_stop
= NULL
;
430 static int oldfd1
= -1;
431 static sig_t old_sigpipe
;
434 setup_piping(const char *cmd
, char *cmdline
, int c_pipe
)
442 last_file
= last_registered_file(0);
445 if ((cp
= shellpr(cmdline
)) != NULL
) {
452 if ((fout
= Popen(cp
, "w")) == NULL
) {
453 warn("Popen: %s", cp
);
460 mode
= *cp
== '>' ? "a" : "w";
465 if ((fout
= Fopen(cp
, mode
)) == NULL
) {
466 warn("Fopen: %s", cp
);
472 else if (do_paging(cmd
, c_pipe
)) {
474 pager
= value(ENAME_PAGER
);
475 if (pager
== NULL
|| *pager
== '\0')
478 if ((fout
= Popen(pager
, "w")) == NULL
) {
479 warn("Popen: %s", pager
);
485 old_sigpipe
= sig_signal(SIGPIPE
, lex_brokpipe
);
486 (void)fflush(stdout
);
487 if ((oldfd1
= dup(STDOUT_FILENO
)) == -1)
488 err(EXIT_FAILURE
, "dup failed");
489 if (dup2(fileno(fout
), STDOUT_FILENO
) == -1)
490 err(EXIT_FAILURE
, "dup2 failed");
497 * This will close any piping started by setup_piping().
503 struct sigaction osa
;
506 (void)fflush(stdout
);
507 if (fileno(stdout
) != oldfd1
&&
508 dup2(oldfd1
, STDOUT_FILENO
) == -1)
509 err(EXIT_FAILURE
, "dup2 failed");
511 (void)sig_ignore(SIGPIPE
, &osa
, &oset
);
513 close_top_files(fp_stop
);
518 (void)sig_signal(SIGPIPE
, old_sigpipe
);
519 (void)sig_restore(SIGPIPE
, &osa
, &oset
);
525 * Determine if as1 is a valid prefix of as2.
526 * Return true if yep.
529 isprefix(char *as1
, const char *as2
)
539 return *--s1
== '\0';
543 * Find the correct command in the command table corresponding
544 * to the passed command "word"
546 PUBLIC
const struct cmd
*
549 const struct cmd
*cp
;
551 for (cp
= &cmdtab
[0]; cp
->c_name
!= NULL
; cp
++)
552 if (isprefix(word
, cp
->c_name
))
558 get_cmdname(char *buf
)
564 for (cp
= buf
; *cp
; cp
++)
565 if (strchr(" \t0123456789$^.:/-+*'\">|", *cp
) != NULL
)
567 /* XXX - Don't miss the pipe command! */
568 if (cp
== buf
&& *cp
== '|')
572 (void)strlcpy(cmd
, buf
, len
);
577 * Execute a single command.
578 * Command functions return 0 for success, 1 for error, and -1
579 * for abort. A 1 or -1 aborts a load or source. A -1 aborts
580 * the interactive command loop.
581 * execute_contxt_e is in extern.h.
584 execute(char linebuf
[], enum execute_contxt_e contxt
)
587 char *arglist
[MAXARGC
];
588 const struct cmd
* volatile com
= NULL
;
595 * Strip the white space away from the beginning
596 * of the command, then scan out a word, which
597 * consists of anything except digits and white space.
599 * Handle ! escapes differently to get the correct
600 * lexical conventions.
603 cp
= skip_space(linebuf
);
606 (void)printf("Can't \"!\" while sourcing\n");
613 word
= get_cmdname(cp
);
617 * Look up the command; if not found, bitch.
618 * Normally, a blank command would map to the
619 * first command in the table; while sourcing,
620 * however, we ignore blank lines to eliminate
624 if (sourcing
&& *word
== '\0')
628 (void)printf("Unknown command: \"%s\"\n", word
);
633 * See if we should execute the command -- if a conditional
634 * we always execute it, otherwise, check the state of cond.
637 if ((com
->c_argtype
& F
) == 0 && (cond
& CSKIP
))
641 * Process the arguments to the command, depending
642 * on the type he expects. Default to an error.
643 * If we are sourcing an interactive command, it's
647 if (mailmode
== mm_sending
&& (com
->c_argtype
& M
) == 0) {
648 (void)printf("May not execute \"%s\" while sending\n",
652 if (sourcing
&& com
->c_argtype
& I
) {
653 (void)printf("May not execute \"%s\" while sourcing\n",
657 if (readonly
&& com
->c_argtype
& W
) {
658 (void)printf("May not execute \"%s\" -- message file is read only\n",
662 if (contxt
== ec_composing
&& com
->c_argtype
& R
) {
663 (void)printf("Cannot recursively invoke \"%s\"\n", com
->c_name
);
667 if (!sourcing
&& com
->c_pipe
&& value(ENAME_INTERACTIVE
) != NULL
) {
670 if (setjmp(pipestop
))
673 if (setup_piping(com
->c_name
, cp
, com
->c_pipe
) == -1)
676 switch (com
->c_argtype
& ARGTYPE_MASK
) {
679 * A message list defaulting to nearest forward
683 (void)printf("Illegal use of \"message list\"\n");
686 if ((c
= getmsglist(cp
, msgvec
, com
->c_msgflag
)) < 0)
689 *msgvec
= first(com
->c_msgflag
, com
->c_msgmask
);
693 (void)printf("No applicable messages\n");
696 e
= (*com
->c_func
)(msgvec
);
701 * A message list with no defaults, but no error
705 (void)printf("Illegal use of \"message list\"\n");
708 if (getmsglist(cp
, msgvec
, com
->c_msgflag
) < 0)
710 e
= (*com
->c_func
)(msgvec
);
715 * Just the straight string, with
716 * leading blanks removed.
719 e
= (*com
->c_func
)(cp
);
724 * A vector of strings, in shell style.
726 if ((c
= getrawlist(cp
, arglist
, (int)__arraycount(arglist
))) < 0)
728 if (c
< com
->c_minargs
) {
729 (void)printf("%s requires at least %d arg(s)\n",
730 com
->c_name
, com
->c_minargs
);
733 if (c
> com
->c_maxargs
) {
734 (void)printf("%s takes no more than %d arg(s)\n",
735 com
->c_name
, com
->c_maxargs
);
738 e
= (*com
->c_func
)(arglist
);
743 * Just the constant zero, for exiting,
746 e
= (*com
->c_func
)(0);
750 errx(1, "Unknown argtype");
757 * Exit the current source file on
769 else if (com
!= NULL
) {
770 if (contxt
!= ec_autoprint
&& com
->c_argtype
& P
&&
771 value(ENAME_AUTOPRINT
) != NULL
&&
772 (dot
->m_flag
& MDELETED
) == 0)
773 (void)execute(__UNCONST("print ."), ec_autoprint
);
774 if (!sourcing
&& (com
->c_argtype
& T
) == 0)
782 * The following gets called on receipt of an interrupt. This is
783 * to abort printout of a command, mainly.
784 * Dispatching here when commands() is inactive crashes rcv.
785 * Close all open files except 0, 1, 2, and the temporary.
786 * Also, unstack all source files.
806 (void)fprintf(stderr
, "Interrupt\n");
807 longjmp(jmpbuf
, signo
);
811 * Branch here on hangup signal and simulate "exit".
815 lex_hangup(int s __unused
)
823 * When we wake up after ^Z, reprint the prompt.
825 * NOTE: EditLine deals with the prompt and job control, so with it
826 * this does nothing, i.e., reset_on_stop == 0.
834 longjmp(jmpbuf
, signo
);
839 * Interpret user commands one by one. If standard input is not a tty,
846 char linebuf
[LINESIZE
];
849 #ifdef DEBUG_FILE_LEAK
857 (void)sig_signal(SIGINT
, lex_intr
);
858 (void)sig_signal(SIGHUP
, lex_hangup
);
859 (void)sig_signal(SIGTSTP
, lex_stop
);
860 (void)sig_signal(SIGTTOU
, lex_stop
);
861 (void)sig_signal(SIGTTIN
, lex_stop
);
865 (void)setjmp(jmpbuf
); /* "reset" location if we got an interrupt */
867 eofloop
= 0; /* initialize this after a possible longjmp */
870 (void)fflush(stdout
);
873 * Print the prompt, if needed. Clear out
874 * string space, and flush the output.
876 if (!sourcing
&& value(ENAME_INTERACTIVE
) != NULL
) {
877 if ((prompt
= value(ENAME_PROMPT
)) == NULL
)
878 prompt
= DEFAULT_PROMPT
;
879 prompt
= smsgprintf(prompt
, dot
);
880 if ((value(ENAME_AUTOINC
) != NULL
) && (incfile() > 0))
881 (void)printf("New mail has arrived.\n");
884 reset_on_stop
= 1; /* enable job control longjmp */
885 (void)printf("%s", prompt
);
888 #ifdef DEBUG_FILE_LEAK
892 * Read a line of commands from the current input
893 * and handle end of file specially.
902 line
= my_gets(&elm
.command
, prompt
, NULL
);
908 (void)strlcpy(linebuf
, line
, sizeof(linebuf
));
911 if (readline(input
, &linebuf
[n
], LINESIZE
- n
, 0) < 0) {
917 #else /* USE_EDITLINE */
918 if (readline(input
, &linebuf
[n
], LINESIZE
- n
, reset_on_stop
) < 0) {
923 #endif /* USE_EDITLINE */
925 setscreensize(); /* so we can resize window */
927 if (sourcing
) { /* allow comments in source files */
929 if ((ptr
= comment_char(linebuf
)) != NULL
)
932 if ((n
= (int)strlen(linebuf
)) == 0)
935 if (linebuf
[n
] != '\\')
941 reset_on_stop
= 0; /* disable job control longjmp */
953 if (value(ENAME_INTERACTIVE
) != NULL
&&
954 (p
= value(ENAME_IGNOREEOF
)) != NULL
&&
955 ++eofloop
< (*p
== '\0' ? 25 : atoi(p
))) {
956 (void)printf("Use \"quit\" to quit.\n");
962 if (execute(linebuf
, ec_normal
))
968 * Announce information about the file we are editing.
969 * Return a likely place to set dot.
972 newfileinfo(int omsgCount
)
975 int d
, n
, s
, t
, u
, mdot
;
976 char fname
[PATHSIZE
];
980 * Figure out where to set the 'dot'. Use the first new or
983 for (mp
= get_abs_message(omsgCount
+ 1); mp
;
984 mp
= next_abs_message(mp
))
985 if (mp
->m_flag
& MNEW
)
989 for (mp
= get_abs_message(omsgCount
+ 1); mp
;
990 mp
= next_abs_message(mp
))
991 if ((mp
->m_flag
& MREAD
) == 0)
994 mdot
= get_msgnum(mp
);
996 mdot
= omsgCount
+ 1;
997 #ifdef THREAD_SUPPORT
999 * See if the message is in the current thread.
1001 if (mp
!= NULL
&& get_message(1) != NULL
&& get_message(mdot
) != mp
)
1005 * Scan the message array counting the new, unread, deleted,
1006 * and saved messages.
1008 d
= n
= s
= t
= u
= 0;
1009 for (mp
= get_abs_message(1); mp
; mp
= next_abs_message(mp
)) {
1010 if (mp
->m_flag
& MNEW
)
1012 if ((mp
->m_flag
& MREAD
) == 0)
1014 if (mp
->m_flag
& MDELETED
)
1016 if (mp
->m_flag
& MSAVED
)
1018 if (mp
->m_flag
& MTAGGED
)
1022 if (getfold(fname
, sizeof(fname
)) >= 0) {
1023 char zname
[PATHSIZE
];
1026 if (l
< sizeof(fname
) - 1)
1028 if (strncmp(fname
, mailname
, l
) == 0) {
1029 (void)snprintf(zname
, sizeof(zname
), "+%s",
1035 * Display the statistics.
1037 (void)printf("\"%s\": ", ename
);
1039 int cnt
= get_abs_msgCount();
1040 (void)printf("%d message%s", cnt
, cnt
== 1 ? "" : "s");
1043 (void)printf(" %d new", n
);
1045 (void)printf(" %d unread", u
);
1047 (void)printf(" %d tagged", t
);
1049 (void)printf(" %d deleted", d
);
1051 (void)printf(" %d saved", s
);
1053 (void)printf(" [Read only]");
1060 * Announce the presence of the current Mail version,
1061 * give the message count, and print a header listing.
1068 mdot
= newfileinfo(0);
1071 if ((dot
= get_message(mdot
)) == NULL
)
1072 dot
= get_abs_message(1); /* make sure we get something! */
1073 if (get_abs_msgCount() > 0 && value(ENAME_NOHEADER
) == NULL
) {
1081 * Print the current version number.
1086 pversion(void *v __unused
)
1088 (void)printf("Version %s\n", version
);
1093 * Load a file of user definitions.
1096 load(const char *name
)
1100 if ((in
= Fopen(name
, "r")) == NULL
)