Add n_signal(), our future (v15) sigaction(2) call-in
[s-mailx.git] / cmd2.c
blobc39d84161b41a38392710ff35739f3413acc9599
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 - 2015 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. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE cmd2
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* Save/copy the indicated messages at the end of the passed file name.
43 * If mark is true, mark the message "saved" */
44 static int save1(char *str, int domark, char const *cmd,
45 struct ignoretab *ignoret, int convert, int sender_record,
46 int domove);
48 /* Snarf the file from the end of the command line and return a pointer to it.
49 * If there is no file attached, return the mbox file. Put a null in front of
50 * the file name so that the message list processing won't see it, unless the
51 * file name is the only thing on the line, in which case, return 0 in the
52 * reference flag variable */
53 static char * snarf(char *linebuf, bool_t *flag, bool_t usembox);
55 /* Delete the indicated messages. Set dot to some nice place afterwards */
56 static int delm(int *msgvec);
58 static int ignore1(char **list, struct ignoretab *tab, char const *which);
60 /* Print out all currently retained fields */
61 static int igshow(struct ignoretab *tab, char const *which);
63 /* Compare two names for sorting ignored field list */
64 static int igcomp(void const *l, void const *r);
66 /* */
67 static int _unignore(char **list, struct ignoretab *tab, char const *which);
68 static void __unign_all(struct ignoretab *tab);
69 static void __unign_one(struct ignoretab *tab, char const *name);
71 static int
72 save1(char *str, int domark, char const *cmd, struct ignoretab *ignoret,
73 int convert, int sender_record, int domove)
75 ui64_t mstats[1], tstats[2];
76 struct stat st;
77 int last = 0, *msgvec, *ip;
78 struct message *mp;
79 char *file = NULL, *cp, *cq;
80 char const *disp = "", *shell = NULL;
81 FILE *obuf;
82 bool_t success = FAL0, isflag;
83 NYD_ENTER;
85 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
86 if (sender_record) {
87 for (cp = str; *cp != '\0' && spacechar(*cp); ++cp)
89 isflag = (*cp != '\0');
90 } else {
91 if ((file = snarf(str, &isflag, convert != SEND_TOFILE)) == NULL)
92 goto jleave;
93 while(spacechar(*file))
94 ++file;
95 if (*file == '|') {
96 ++file;
97 shell = ok_vlook(SHELL);
101 if (!isflag) {
102 *msgvec = first(0, MMNORM);
103 msgvec[1] = 0;
104 } else if (getmsglist(str, msgvec, 0) < 0)
105 goto jleave;
106 if (*msgvec == 0) {
107 if (pstate & (PS_HOOK_MASK | PS_ROBOT)) {
108 success = TRU1;
109 goto jleave;
111 printf(_("No messages to %s.\n"), cmd);
112 goto jleave;
115 if (sender_record) {
116 if ((cp = nameof(message + *msgvec - 1, 0)) == NULL) {
117 printf(_("Cannot determine message sender to %s.\n"), cmd);
118 goto jleave;
121 for (cq = cp; *cq != '\0' && *cq != '@'; cq++)
123 *cq = '\0';
124 if (ok_blook(outfolder)) {
125 size_t sz = strlen(cp) +1;
126 file = salloc(sz + 1);
127 file[0] = '+';
128 memcpy(file + 1, cp, sz);
129 } else
130 file = cp;
133 /* Pipe target is special TODO hacked in later, normalize flow! */
134 if (shell != NULL) {
135 if ((obuf = Popen(file, "w", shell, NULL, 1)) == NULL) {
136 int esave = errno;
138 n_perr(file, esave);
139 errno = esave;
140 goto jleave;
142 isflag = FAL0;
143 disp = _("[Piped]");
144 goto jsend;
147 if ((file = expand(file)) == NULL)
148 goto jleave;
150 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "a+") : Zopen(file, "a+"));
151 if (obuf == NULL) {
152 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "wx") : Zopen(file, "wx"));
153 if (obuf == NULL) {
154 n_perr(file, 0);
155 goto jleave;
157 isflag = TRU1;
158 disp = _("[New file]");
159 } else {
160 isflag = FAL0;
161 disp = _("[Appended]");
164 /* TODO RETURN check, but be aware of protocols: v15: Mailbox->lock()! */
165 n_file_lock(fileno(obuf), FLT_WRITE, 0,0, UIZ_MAX);
167 if (!isflag && !fstat(fileno(obuf), &st) && S_ISREG(st.st_mode) &&
168 fseek(obuf, -2L, SEEK_END) == 0) {
169 char buf[2];
170 int prependnl = 0;
172 switch (fread(buf, sizeof *buf, 2, obuf)) {
173 case 2:
174 if (buf[1] != '\n') {
175 prependnl = 1;
176 break;
178 /* FALLTHRU */
179 case 1:
180 if (buf[0] != '\n')
181 prependnl = 1;
182 break;
183 default:
184 if (ferror(obuf)) {
185 n_perr(file, 0);
186 goto jleave;
188 prependnl = 0;
191 fflush(obuf);
192 if (prependnl) {
193 putc('\n', obuf);
194 fflush(obuf);
198 jsend:
199 success = TRU1;
200 tstats[0] = tstats[1] = 0;
202 srelax_hold();
203 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
204 ++ip) {
205 mp = message + *ip - 1;
206 if (sendmp(mp, obuf, ignoret, NULL, convert, mstats) < 0) {
207 success = FAL0;
208 goto jferr;
210 srelax();
212 touch(mp);
213 if (domark)
214 mp->m_flag |= MSAVED;
215 if (domove) {
216 mp->m_flag |= MDELETED | MSAVED;
217 last = *ip;
220 tstats[0] += mstats[0];
221 tstats[1] += mp->m_lines;/* TODO won't work, need target! v15!! */
223 srelax_rele();
225 fflush(obuf);
226 if (ferror(obuf)) {
227 jferr:
228 n_perr(file, 0);
229 if (!success)
230 srelax_rele();
231 success = FAL0;
233 if (shell != NULL) {
234 if (!Pclose(obuf, TRU1))
235 success = FAL0;
236 } else if (Fclose(obuf) != 0)
237 success = FAL0;
239 if (success) {
240 printf("\"%s\" %s %" /*PRIu64 "/%"*/ PRIu64 " bytes\n",
241 file, disp, /*tstats[1], TODO v15: lines written */ tstats[0]);
242 } else if (domark) {
243 for (ip = msgvec; *ip != 0 &&
244 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
245 mp = message + *ip - 1;
246 mp->m_flag &= ~MSAVED;
248 } else if (domove) {
249 for (ip = msgvec; *ip != 0 &&
250 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
251 mp = message + *ip - 1;
252 mp->m_flag &= ~(MSAVED | MDELETED);
256 if (domove && last && success) {
257 setdot(message + last - 1);
258 last = first(0, MDELETED);
259 setdot(message + (last != 0 ? last - 1 : 0));
261 jleave:
262 NYD_LEAVE;
263 return (success == FAL0);
266 static char *
267 snarf(char *linebuf, bool_t *flag, bool_t usembox)
269 char *cp;
270 NYD_ENTER;
272 if ((cp = laststring(linebuf, flag, TRU1)) == NULL) {
273 if (usembox) {
274 *flag = FAL0;
275 cp = expand("&");
276 } else
277 n_err(_("No file specified\n"));
279 NYD_LEAVE;
280 return cp;
283 static int
284 delm(int *msgvec)
286 struct message *mp;
287 int rv = -1, *ip, last;
288 NYD_ENTER;
290 last = 0;
291 for (ip = msgvec; *ip != 0; ++ip) {
292 mp = message + *ip - 1;
293 touch(mp);
294 mp->m_flag |= MDELETED | MTOUCH;
295 mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX);
296 last = *ip;
298 if (last != 0) {
299 setdot(message + last - 1);
300 last = first(0, MDELETED);
301 if (last != 0) {
302 setdot(message + last - 1);
303 rv = 0;
304 } else {
305 setdot(message);
308 NYD_LEAVE;
309 return rv;
312 static int
313 ignore1(char **list, struct ignoretab *tab, char const *which)
315 int h;
316 struct ignored *igp;
317 char **ap;
318 NYD_ENTER;
320 if (*list == NULL) {
321 h = igshow(tab, which);
322 goto jleave;
325 for (ap = list; *ap != 0; ++ap) {
326 char *field;
327 size_t sz;
329 sz = strlen(*ap) +1;
330 field = ac_alloc(sz);
331 i_strcpy(field, *ap, sz);
332 if (member(field, tab))
333 goto jnext;
335 h = hash(field);
336 igp = scalloc(1, sizeof *igp);
337 sz = strlen(field) +1;
338 igp->i_field = smalloc(sz);
339 memcpy(igp->i_field, field, sz);
340 igp->i_link = tab->i_head[h];
341 tab->i_head[h] = igp;
342 ++tab->i_count;
343 jnext:
344 ac_free(field);
346 h = 0;
347 jleave:
348 NYD_LEAVE;
349 return h;
352 static int
353 igshow(struct ignoretab *tab, char const *which)
355 int h;
356 struct ignored *igp;
357 char **ap, **ring;
358 NYD_ENTER;
360 if (tab->i_count == 0) {
361 printf(_("No fields currently being %s.\n"), which);
362 goto jleave;
365 ring = salloc((tab->i_count + 1) * sizeof *ring);
366 ap = ring;
367 for (h = 0; h < HSHSIZE; ++h)
368 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
369 *ap++ = igp->i_field;
370 *ap = 0;
372 qsort(ring, tab->i_count, sizeof *ring, igcomp);
374 for (ap = ring; *ap != NULL; ++ap)
375 printf("%s\n", *ap);
376 jleave:
377 NYD_LEAVE;
378 return 0;
381 static int
382 igcomp(void const *l, void const *r)
384 int rv;
385 NYD_ENTER;
387 rv = strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r));
388 NYD_LEAVE;
389 return rv;
392 static int
393 _unignore(char **list, struct ignoretab *tab, char const *which)
395 char *field;
396 NYD_ENTER;
398 if (tab->i_count == 0)
399 printf(_("No fields currently being %s.\n"), which);
400 else
401 while ((field = *list++) != NULL)
402 if (field[0] == '*' && field[1] == '\0') {
403 __unign_all(tab);
404 break;
405 } else
406 __unign_one(tab, field);
407 NYD_LEAVE;
408 return 0;
411 static void
412 __unign_all(struct ignoretab *tab)
414 size_t i;
415 struct ignored *n, *x;
416 NYD_ENTER;
418 for (i = 0; i < NELEM(tab->i_head); ++i)
419 for (n = tab->i_head[i]; n != NULL; n = x) {
420 x = n->i_link;
421 free(n->i_field);
422 free(n);
424 memset(tab, 0, sizeof *tab);
425 NYD_LEAVE;
428 static void
429 __unign_one(struct ignoretab *tab, char const *name)
431 struct ignored *ip, *iq;
432 int h;
433 NYD_ENTER;
435 h = hash(name);
436 for (iq = NULL, ip = tab->i_head[h]; ip != NULL; ip = ip->i_link) {
437 if (!asccasecmp(ip->i_field, name)) {
438 free(ip->i_field);
439 if (iq != NULL)
440 iq->i_link = ip->i_link;
441 else
442 tab->i_head[h] = ip->i_link;
443 free(ip);
444 --tab->i_count;
445 break;
447 iq = ip;
449 NYD_LEAVE;
452 FL int
453 c_next(void *v)
455 int list[2], *ip, *ip2, mdot, *msgvec = v, rv = 1;
456 struct message *mp;
457 NYD_ENTER;
459 if (*msgvec != 0) {
460 /* If some messages were supplied, find the first applicable one
461 * following dot using wrap around */
462 mdot = (int)PTR2SIZE(dot - message + 1);
464 /* Find first message in supplied message list which follows dot */
465 for (ip = msgvec; *ip != 0; ++ip) {
466 if ((mb.mb_threaded ? message[*ip - 1].m_threadpos > dot->m_threadpos
467 : *ip > mdot))
468 break;
470 if (*ip == 0)
471 ip = msgvec;
472 ip2 = ip;
473 do {
474 mp = message + *ip2 - 1;
475 if (!(mp->m_flag & MMNDEL)) {
476 setdot(mp);
477 goto jhitit;
479 if (*ip2 != 0)
480 ++ip2;
481 if (*ip2 == 0)
482 ip2 = msgvec;
483 } while (ip2 != ip);
484 printf(_("No messages applicable\n"));
485 goto jleave;
488 /* If this is the first command, select message 1. Note that this must
489 * exist for us to get here at all */
490 if (!(pstate & PS_SAW_COMMAND)) {
491 if (msgCount == 0)
492 goto jateof;
493 goto jhitit;
496 /* Just find the next good message after dot, no wraparound */
497 if (mb.mb_threaded == 0) {
498 for (mp = dot + !!(pstate & PS_DID_PRINT_DOT);
499 PTRCMP(mp, <, message + msgCount); ++mp)
500 if (!(mp->m_flag & MMNORM))
501 break;
502 } else {
503 /* TODO The threading code had some bugs that caused crashes.
504 * TODO The last thing (before the deep look) happens here,
505 * TODO so let's not trust PS_DID_PRINT_DOT but check & hope it fixes */
506 if ((mp = dot) != NULL && (pstate & PS_DID_PRINT_DOT))
507 mp = next_in_thread(mp);
508 while (mp != NULL && (mp->m_flag & MMNORM))
509 mp = next_in_thread(mp);
511 if (mp == NULL || PTRCMP(mp, >=, message + msgCount)) {
512 jateof:
513 printf(_("At EOF\n"));
514 rv = 0;
515 goto jleave;
517 setdot(mp);
519 /* Print dot */
520 jhitit:
521 list[0] = (int)PTR2SIZE(dot - message + 1);
522 list[1] = 0;
523 rv = c_type(list);
524 jleave:
525 NYD_LEAVE;
526 return rv;
529 FL int
530 c_dotmove(void *v)
532 char const *args;
533 int msgvec[2], rv;
534 NYD_ENTER;
536 if (*(args = v) == '\0' || args[1] != '\0') {
537 jerr:
538 n_err(_("Synopsis: dotmove: up <-> or down <+> by one message\n"));
539 rv = 1;
540 } else switch (args[0]) {
541 case '-':
542 case '+':
543 if (msgCount == 0) {
544 printf(_("At EOF\n"));
545 rv = 0;
546 } else if (getmsglist(UNCONST(/*TODO*/ args), msgvec, 0) > 0) {
547 setdot(message + msgvec[0] - 1);
548 msgvec[1] = 0;
549 rv = c_headers(msgvec);
550 } else
551 rv = 1;
552 break;
553 default:
554 goto jerr;
556 NYD_LEAVE;
557 return rv;
560 FL int
561 c_save(void *v)
563 char *str = v;
564 int rv;
565 NYD_ENTER;
567 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
568 NYD_LEAVE;
569 return rv;
572 FL int
573 c_Save(void *v)
575 char *str = v;
576 int rv;
577 NYD_ENTER;
579 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
580 NYD_LEAVE;
581 return rv;
584 FL int
585 c_copy(void *v)
587 char *str = v;
588 int rv;
589 NYD_ENTER;
591 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
592 NYD_LEAVE;
593 return rv;
596 FL int
597 c_Copy(void *v)
599 char *str = v;
600 int rv;
601 NYD_ENTER;
603 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
604 NYD_LEAVE;
605 return rv;
608 FL int
609 c_move(void *v)
611 char *str = v;
612 int rv;
613 NYD_ENTER;
615 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
616 NYD_LEAVE;
617 return rv;
620 FL int
621 c_Move(void *v)
623 char *str = v;
624 int rv;
625 NYD_ENTER;
627 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
628 NYD_LEAVE;
629 return rv;
632 FL int
633 c_decrypt(void *v)
635 char *str = v;
636 int rv;
637 NYD_ENTER;
639 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
640 NYD_LEAVE;
641 return rv;
644 FL int
645 c_Decrypt(void *v)
647 char *str = v;
648 int rv;
649 NYD_ENTER;
651 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
652 NYD_LEAVE;
653 return rv;
656 FL int
657 c_write(void *v)
659 char *str = v;
660 int rv;
661 NYD_ENTER;
663 if (str == NULL || *str == '\0')
664 str = savestr("/dev/null");
665 rv = save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0);
666 NYD_LEAVE;
667 return rv;
670 FL int
671 c_delete(void *v)
673 int *msgvec = v;
674 NYD_ENTER;
676 delm(msgvec);
677 NYD_LEAVE;
678 return 0;
681 FL int
682 c_deltype(void *v)
684 int list[2], rv = 0, *msgvec = v, lastdot;
685 NYD_ENTER;
687 lastdot = (int)PTR2SIZE(dot - message + 1);
688 if (delm(msgvec) >= 0) {
689 list[0] = (int)PTR2SIZE(dot - message + 1);
690 if (list[0] > lastdot) {
691 touch(dot);
692 list[1] = 0;
693 rv = c_type(list);
694 goto jleave;
696 printf(_("At EOF\n"));
697 } else
698 printf(_("No more messages\n"));
699 jleave:
700 NYD_LEAVE;
701 return rv;
704 FL int
705 c_undelete(void *v)
707 int *msgvec = v, *ip;
708 struct message *mp;
709 NYD_ENTER;
711 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
712 ++ip) {
713 mp = message + *ip - 1;
714 touch(mp);
715 setdot(mp);
716 if (mp->m_flag & (MDELETED | MSAVED))
717 mp->m_flag &= ~(MDELETED | MSAVED);
718 else
719 mp->m_flag &= ~MDELETED;
721 NYD_LEAVE;
722 return 0;
725 FL int
726 c_retfield(void *v)
728 char **list = v;
729 int rv;
730 NYD_ENTER;
732 rv = ignore1(list, ignore + 1, "retained");
733 NYD_LEAVE;
734 return rv;
737 FL int
738 c_igfield(void *v)
740 char **list = v;
741 int rv;
742 NYD_ENTER;
744 rv = ignore1(list, ignore, "ignored");
745 NYD_LEAVE;
746 return rv;
749 FL int
750 c_saveretfield(void *v)
752 char **list = v;
753 int rv;
754 NYD_ENTER;
756 rv = ignore1(list, saveignore + 1, "retained");
757 NYD_LEAVE;
758 return rv;
761 FL int
762 c_saveigfield(void *v)
764 char **list = v;
765 int rv;
766 NYD_ENTER;
768 rv = ignore1(list, saveignore, "ignored");
769 NYD_LEAVE;
770 return rv;
773 FL int
774 c_fwdretfield(void *v)
776 char **list = v;
777 int rv;
778 NYD_ENTER;
780 rv = ignore1(list, fwdignore + 1, "retained");
781 NYD_LEAVE;
782 return rv;
785 FL int
786 c_fwdigfield(void *v)
788 char **list = v;
789 int rv;
790 NYD_ENTER;
792 rv = ignore1(list, fwdignore, "ignored");
793 NYD_LEAVE;
794 return rv;
797 FL int
798 c_unignore(void *v)
800 int rv;
801 NYD_ENTER;
803 rv = _unignore((char**)v, ignore, "ignored");
804 NYD_LEAVE;
805 return rv;
808 FL int
809 c_unretain(void *v)
811 int rv;
812 NYD_ENTER;
814 rv = _unignore((char**)v, ignore + 1, "retained");
815 NYD_LEAVE;
816 return rv;
819 FL int
820 c_unsaveignore(void *v)
822 int rv;
823 NYD_ENTER;
825 rv = _unignore((char**)v, saveignore, "ignored");
826 NYD_LEAVE;
827 return rv;
830 FL int
831 c_unsaveretain(void *v)
833 int rv;
834 NYD_ENTER;
836 rv = _unignore((char**)v, saveignore + 1, "retained");
837 NYD_LEAVE;
838 return rv;
841 FL int
842 c_unfwdignore(void *v)
844 int rv;
845 NYD_ENTER;
847 rv = _unignore((char**)v, fwdignore, "ignored");
848 NYD_LEAVE;
849 return rv;
852 FL int
853 c_unfwdretain(void *v)
855 int rv;
856 NYD_ENTER;
858 rv = _unignore((char**)v, fwdignore + 1, "retained");
859 NYD_LEAVE;
860 return rv;
863 /* s-it-mode */