cc-test.sh: add test for -q
[s-mailx.git] / cmd2.c
blobb5e68a5baaf272fdd30f5b2d23c9b392a918be71
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ More user commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 #include <sys/wait.h>
46 static int save1(char *str, int domark, char const *cmd,
47 struct ignoretab *ignore, int convert,
48 int sender_record, int domove);
49 static char * snarf(char *linebuf, bool_t *flag, bool_t usembox);
50 static int delm(int *msgvec);
51 #ifdef HAVE_DEBUG
52 static void clob1(int n);
53 #endif
54 static int ignore1(char **list, struct ignoretab *tab, char const *which);
55 static int igshow(struct ignoretab *tab, char const *which);
56 static int igcomp(const void *l, const void *r);
57 static void unignore_one(const char *name, struct ignoretab *tab);
58 static int unignore1(char **list, struct ignoretab *tab,
59 char const *which);
62 * If any arguments were given, go to the next applicable argument
63 * following dot, otherwise, go to the next applicable message.
64 * If given as first command with no arguments, print first message.
66 FL int
67 next(void *v)
69 int *msgvec = v;
70 struct message *mp;
71 int *ip, *ip2;
72 int list[2], mdot;
74 if (*msgvec != 0) {
77 * If some messages were supplied, find the
78 * first applicable one following dot using
79 * wrap around.
82 mdot = dot - &message[0] + 1;
85 * Find the first message in the supplied
86 * message list which follows dot.
89 for (ip = msgvec; *ip != 0; ip++) {
90 #ifdef _CRAY
92 * Work around an optimizer bug in Cray Standard C Version 4.0.3 (057126).
93 * Otherwise, SIGFPE is received when mb.mb_threaded != 0.
95 #pragma _CRI suppress ip
96 #endif /* _CRAY */
97 if (mb.mb_threaded ? message[*ip-1].m_threadpos >
98 dot->m_threadpos :
99 *ip > mdot)
100 break;
102 if (*ip == 0)
103 ip = msgvec;
104 ip2 = ip;
105 do {
106 mp = &message[*ip2 - 1];
107 if ((mp->m_flag & (MDELETED|MHIDDEN)) == 0) {
108 setdot(mp);
109 goto hitit;
111 if (*ip2 != 0)
112 ip2++;
113 if (*ip2 == 0)
114 ip2 = msgvec;
115 } while (ip2 != ip);
116 printf(tr(21, "No messages applicable\n"));
117 return(1);
121 * If this is the first command, select message 1.
122 * Note that this must exist for us to get here at all.
125 if (!sawcom) {
126 if (msgCount == 0)
127 goto ateof;
128 goto hitit;
132 * Just find the next good message after dot, no
133 * wraparound.
136 if (mb.mb_threaded == 0) {
137 for (mp = dot + did_print_dot; mp < &message[msgCount]; mp++)
138 if ((mp->m_flag & (MDELETED|MSAVED|MHIDDEN)) == 0)
139 break;
140 } else {
141 mp = dot;
142 if (did_print_dot)
143 mp = next_in_thread(mp);
144 while (mp && mp->m_flag & (MDELETED|MSAVED|MHIDDEN))
145 mp = next_in_thread(mp);
147 if (mp == NULL || mp >= &message[msgCount]) {
148 ateof:
149 printf(tr(22, "At EOF\n"));
150 return(0);
152 setdot(mp);
153 hitit:
155 * Print dot.
158 list[0] = dot - &message[0] + 1;
159 list[1] = 0;
160 return(type(list));
164 * Save a message in a file. Mark the message as saved
165 * so we can discard when the user quits.
167 FL int
168 save(void *v)
170 char *str = v;
172 return save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
175 FL int
176 Save(void *v)
178 char *str = v;
180 return save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
184 * Copy a message to a file without affected its saved-ness
186 FL int
187 copycmd(void *v)
189 char *str = v;
191 return save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
194 FL int
195 Copycmd(void *v)
197 char *str = v;
199 return save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
203 * Move a message to a file.
205 FL int
206 cmove(void *v)
208 char *str = v;
210 return save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
213 FL int
214 cMove(void *v)
216 char *str = v;
218 return save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
222 * Decrypt and copy a message to a file.
224 FL int
225 cdecrypt(void *v)
227 char *str = v;
229 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
232 FL int
233 cDecrypt(void *v)
235 char *str = v;
237 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
241 * Save/copy the indicated messages at the end of the passed file name.
242 * If mark is true, mark the message "saved."
244 static int
245 save1(char *str, int domark, char const *cmd, struct ignoretab *ignoret,
246 int convert, int sender_record, int domove)
248 off_t mstats[2], tstats[2];
249 struct stat st;
250 int newfile = 0, compressed = 0, success = 1, last = 0, *msgvec, *ip;
251 struct message *mp;
252 char *file = NULL, *cp, *cq;
253 char const *disp = "";
254 FILE *obuf;
255 enum protocol prot;
256 bool_t f;
258 /*LINTED*/
259 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
260 if (sender_record) {
261 for (cp = str; *cp && blankchar(*cp); cp++)
263 f = (*cp != '\0');
264 } else {
265 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
266 return(1);
268 if (!f) {
269 *msgvec = first(0, MMNORM);
270 if (*msgvec == 0) {
271 if (inhook)
272 return 0;
273 printf(tr(23, "No messages to %s.\n"), cmd);
274 return(1);
276 msgvec[1] = 0;
277 } else if (getmsglist(str, msgvec, 0) < 0)
278 return(1);
279 if (*msgvec == 0) {
280 if (inhook)
281 return 0;
282 printf("No applicable messages.\n");
283 return 1;
285 if (sender_record) {
286 if ((cp = nameof(&message[*msgvec - 1], 0)) == NULL) {
287 printf(tr(24,
288 "Cannot determine message sender to %s.\n"),
289 cmd);
290 return 1;
292 for (cq = cp; *cq && *cq != '@'; cq++);
293 *cq = '\0';
294 if (value("outfolder")) {
295 size_t sz = strlen(cp) + 1;
296 file = salloc(sz + 1);
297 file[0] = '+';
298 memcpy(file + 1, cp, sz);
299 } else
300 file = cp;
302 if ((file = expand(file)) == NULL)
303 return (1);
304 prot = which_protocol(file);
305 if (prot != PROTO_IMAP) {
306 if (access(file, 0) >= 0) {
307 newfile = 0;
308 disp = tr(25, "[Appended]");
309 } else {
310 newfile = 1;
311 disp = tr(26, "[New file]");
314 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "a+") :
315 Zopen(file, "a+", &compressed)) == NULL) {
316 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "wx") :
317 Zopen(file, "wx", &compressed)) == NULL) {
318 perror(file);
319 return(1);
321 } else {
322 if (compressed) {
323 newfile = 0;
324 disp = tr(25, "[Appended]");
326 if (!newfile && fstat(fileno(obuf), &st) &&
327 S_ISREG(st.st_mode) &&
328 fseek(obuf, -2L, SEEK_END) == 0) {
329 char buf[2];
330 int prependnl = 0;
332 switch (fread(buf, sizeof *buf, 2, obuf)) {
333 case 2:
334 if (buf[1] != '\n') {
335 prependnl = 1;
336 break;
338 /*FALLTHRU*/
339 case 1:
340 if (buf[0] != '\n')
341 prependnl = 1;
342 break;
343 default:
344 if (ferror(obuf)) {
345 perror(file);
346 return(1);
348 prependnl = 0;
350 fflush(obuf);
351 if (prependnl) {
352 putc('\n', obuf);
353 fflush(obuf);
358 tstats[0] = tstats[1] = 0;
359 imap_created_mailbox = 0;
360 srelax_hold();
361 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
362 mp = &message[*ip - 1];
363 if (prot == PROTO_IMAP &&
364 ignoret[0].i_count == 0 &&
365 ignoret[1].i_count == 0
366 #ifdef HAVE_IMAP /* TODO revisit */
367 && imap_thisaccount(file)
368 #endif
370 #ifdef HAVE_IMAP
371 if (imap_copy(mp, *ip, file) == STOP)
372 #endif
373 goto jferr;
374 #ifdef HAVE_IMAP
375 mstats[0] = -1;
376 mstats[1] = mp->m_xsize;
377 #endif
378 } else if (sendmp(mp, obuf, ignoret, NULL,
379 convert, mstats) < 0) {
380 perror(file);
381 goto jferr;
383 srelax();
384 touch(mp);
385 if (domark)
386 mp->m_flag |= MSAVED;
387 if (domove) {
388 mp->m_flag |= MDELETED|MSAVED;
389 last = *ip;
391 tstats[0] += mstats[0];
392 tstats[1] += mstats[1];
394 fflush(obuf);
395 if (ferror(obuf)) {
396 perror(file);
397 jferr:
398 success = 0;
400 if (Fclose(obuf) != 0)
401 success = 0;
402 srelax_rele();
404 if (success) {
405 if (prot == PROTO_IMAP || prot == PROTO_MAILDIR) {
406 disp = (
407 #ifdef HAVE_IMAP
408 ((prot == PROTO_IMAP) && disconnected(file))
409 ? "[Queued]" :
410 #endif
411 (imap_created_mailbox ? "[New file]"
412 : "[Appended]"));
414 printf("\"%s\" %s ", file, disp);
415 if (tstats[0] >= 0)
416 printf("%lu", (long)tstats[0]);
417 else
418 printf(tr(27, "binary"));
419 printf("/%lu\n", (long)tstats[1]);
420 } else if (domark) {
421 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
422 mp = &message[*ip - 1];
423 mp->m_flag &= ~MSAVED;
425 } else if (domove) {
426 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
427 mp = &message[*ip - 1];
428 mp->m_flag &= ~(MSAVED|MDELETED);
431 if (domove && last && success) {
432 setdot(&message[last-1]);
433 last = first(0, MDELETED);
434 setdot(&message[last ? last-1 : 0]);
436 return(success == 0);
440 * Write the indicated messages at the end of the passed
441 * file name, minus header and trailing blank line.
442 * This is the MIME save function.
444 FL int
445 cwrite(void *v)
447 char *str = v;
449 if (str == NULL || *str == '\0')
450 str = savestr("/dev/null");
451 return (save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0));
455 * Snarf the file from the end of the command line and
456 * return a pointer to it. If there is no file attached,
457 * return the mbox file. Put a null in front of the file
458 * name so that the message list processing won't see it,
459 * unless the file name is the only thing on the line, in
460 * which case, return 0 in the reference flag variable.
462 static char *
463 snarf(char *linebuf, bool_t *flag, bool_t usembox)
465 char *cp;
467 if ((cp = laststring(linebuf, flag, 0)) == NULL) {
468 if (usembox) {
469 *flag = FAL0;
470 cp = expand("&");
471 } else
472 fprintf(stderr, tr(28, "No file specified.\n"));
474 return (cp);
478 * Delete messages.
480 FL int
481 delete(void *v)
483 int *msgvec = v;
484 delm(msgvec);
485 return 0;
489 * Delete messages, then type the new dot.
491 FL int
492 deltype(void *v)
494 int *msgvec = v;
495 int list[2];
496 int lastdot;
498 lastdot = dot - &message[0] + 1;
499 if (delm(msgvec) >= 0) {
500 list[0] = dot - &message[0] + 1;
501 if (list[0] > lastdot) {
502 touch(dot);
503 list[1] = 0;
504 return(type(list));
506 printf(tr(29, "At EOF\n"));
507 } else
508 printf(tr(30, "No more messages\n"));
509 return(0);
513 * Delete the indicated messages.
514 * Set dot to some nice place afterwards.
515 * Internal interface.
517 static int
518 delm(int *msgvec)
520 struct message *mp;
521 int *ip;
522 int last;
524 last = 0;
525 for (ip = msgvec; *ip != 0; ip++) {
526 mp = &message[*ip - 1];
527 touch(mp);
528 mp->m_flag |= MDELETED|MTOUCH;
529 mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
530 last = *ip;
532 if (last != 0) {
533 setdot(&message[last-1]);
534 last = first(0, MDELETED);
535 if (last != 0) {
536 setdot(&message[last-1]);
537 return(0);
539 else {
540 setdot(&message[0]);
541 return(-1);
546 * Following can't happen -- it keeps lint happy
549 return(-1);
553 * Undelete the indicated messages.
555 FL int
556 undeletecmd(void *v)
558 int *msgvec = v;
559 struct message *mp;
560 int *ip;
562 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
563 mp = &message[*ip - 1];
564 touch(mp);
565 setdot(mp);
566 if (mp->m_flag & (MDELETED|MSAVED))
567 mp->m_flag &= ~(MDELETED|MSAVED);
568 else
569 mp->m_flag &= ~MDELETED;
570 #ifdef HAVE_IMAP
571 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
572 imap_undelete(mp, *ip);
573 #endif
575 return 0;
578 #ifdef HAVE_DEBUG
580 * Interactively dump core on "core"
582 /*ARGSUSED*/
583 FL int
584 core(void *v)
586 int pid;
587 # ifdef WCOREDUMP
588 extern int wait_status;
589 # endif
590 (void)v;
592 switch (pid = fork()) {
593 case -1:
594 perror("fork");
595 return (1);
596 case 0:
597 abort();
598 _exit(1);
600 (void)printf(tr(31, "Okie dokie"));
601 (void)fflush(stdout);
602 (void)wait_child(pid);
603 # ifdef WCOREDUMP
604 if (WCOREDUMP(wait_status))
605 (void)printf(tr(32, " -- Core dumped.\n"));
606 else
607 (void)printf(tr(33, " -- Can't dump core.\n"));
608 # endif
609 return (0);
612 static void
613 clob1(int n)
615 char buf[512], *cp;
617 if (n <= 0)
618 return;
619 for (cp = buf; PTRCMP(cp, <, buf + 512); ++cp)
620 *cp = (char)0xFF;
621 clob1(n - 1);
625 * Clobber as many bytes of stack as the user requests.
627 FL int
628 clobber(void *v)
630 char **argv = v;
631 int times;
633 if (argv[0] == 0)
634 times = 1;
635 else
636 times = (atoi(argv[0]) + 511) / 512;
637 clob1(times);
638 return (0);
640 #endif /* HAVE_DEBUG */
643 * Add the given header fields to the retained list.
644 * If no arguments, print the current list of retained fields.
646 FL int
647 retfield(void *v)
649 char **list = v;
651 return ignore1(list, ignore + 1, "retained");
655 * Add the given header fields to the ignored list.
656 * If no arguments, print the current list of ignored fields.
658 FL int
659 igfield(void *v)
661 char **list = v;
663 return ignore1(list, ignore, "ignored");
666 FL int
667 saveretfield(void *v)
669 char **list = v;
671 return ignore1(list, saveignore + 1, "retained");
674 FL int
675 saveigfield(void *v)
677 char **list = v;
679 return ignore1(list, saveignore, "ignored");
682 FL int
683 fwdretfield(void *v)
685 char **list = v;
687 return ignore1(list, fwdignore + 1, "retained");
690 FL int
691 fwdigfield(void *v)
693 char **list = v;
695 return ignore1(list, fwdignore, "ignored");
698 static int
699 ignore1(char **list, struct ignoretab *tab, char const *which)
701 int h;
702 struct ignore *igp;
703 char **ap;
705 if (*list == NULL)
706 return igshow(tab, which);
707 for (ap = list; *ap != 0; ap++) {
708 char *field;
709 size_t sz;
711 sz = strlen(*ap);
712 field = ac_alloc(sz + 1);
713 i_strcpy(field, *ap, sz + 1);
714 field[sz]='\0';
715 if (member(field, tab)) {
716 ac_free(field);
717 continue;
719 h = hash(field);
720 igp = (struct ignore *)scalloc(1, sizeof (struct ignore));
721 sz = strlen(field) + 1;
722 igp->i_field = smalloc(sz);
723 memcpy(igp->i_field, field, sz);
724 igp->i_link = tab->i_head[h];
725 tab->i_head[h] = igp;
726 tab->i_count++;
727 ac_free(field);
729 return 0;
733 * Print out all currently retained fields.
735 static int
736 igshow(struct ignoretab *tab, char const *which)
738 int h;
739 struct ignore *igp;
740 char **ap, **ring;
742 if (tab->i_count == 0) {
743 printf(tr(34, "No fields currently being %s.\n"), which);
744 return 0;
746 /*LINTED*/
747 ring = (char **)salloc((tab->i_count + 1) * sizeof (char *));
748 ap = ring;
749 for (h = 0; h < HSHSIZE; h++)
750 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
751 *ap++ = igp->i_field;
752 *ap = 0;
753 qsort(ring, tab->i_count, sizeof (char *), igcomp);
754 for (ap = ring; *ap != 0; ap++)
755 printf("%s\n", *ap);
756 return 0;
760 * Compare two names for sorting ignored field list.
762 static int
763 igcomp(const void *l, const void *r)
765 return (strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r)));
768 FL int
769 unignore(void *v)
771 return unignore1((char **)v, ignore, "ignored");
774 FL int
775 unretain(void *v)
777 return unignore1((char **)v, ignore + 1, "retained");
780 FL int
781 unsaveignore(void *v)
783 return unignore1((char **)v, saveignore, "ignored");
786 FL int
787 unsaveretain(void *v)
789 return unignore1((char **)v, saveignore + 1, "retained");
792 FL int
793 unfwdignore(void *v)
795 return unignore1((char **)v, fwdignore, "ignored");
798 FL int
799 unfwdretain(void *v)
801 return unignore1((char **)v, fwdignore + 1, "retained");
804 static void
805 unignore_one(const char *name, struct ignoretab *tab)
807 struct ignore *ip, *iq = NULL;
808 int h = hash(name);
810 for (ip = tab->i_head[h]; ip; ip = ip->i_link) {
811 if (asccasecmp(ip->i_field, name) == 0) {
812 free(ip->i_field);
813 if (iq != NULL)
814 iq->i_link = ip->i_link;
815 else
816 tab->i_head[h] = ip->i_link;
817 free(ip);
818 tab->i_count--;
819 break;
821 iq = ip;
825 static int
826 unignore1(char **list, struct ignoretab *tab, char const *which)
828 if (tab->i_count == 0) {
829 printf(tr(34, "No fields currently being %s.\n"), which);
830 return 0;
832 while (*list)
833 unignore_one(*list++, tab);
834 return 0;