mk-okey-map.pl: extend for new variable handling..
[s-mailx.git] / cmd2.c
blobcc21a4552305b482a2fe16b5f55b1195c0d8da00
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 newfile = 0, 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, f;
83 NYD_ENTER;
85 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
86 if (sender_record) {
87 for (cp = str; *cp != '\0' && spacechar(*cp); ++cp)
89 f = (*cp != '\0');
90 } else {
91 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
92 goto jleave;
93 while(spacechar(*file))
94 ++file;
95 if (*file == '|') {
96 ++file;
97 if ((shell = ok_vlook(SHELL)) == NULL)
98 shell = XSHELL;
102 if (!f) {
103 *msgvec = first(0, MMNORM);
104 msgvec[1] = 0;
105 } else if (getmsglist(str, msgvec, 0) < 0)
106 goto jleave;
107 if (*msgvec == 0) {
108 if (pstate & (PS_HOOK_MASK | PS_ROBOT)) {
109 success = TRU1;
110 goto jleave;
112 printf(_("No messages to %s.\n"), cmd);
113 goto jleave;
116 if (sender_record) {
117 if ((cp = nameof(message + *msgvec - 1, 0)) == NULL) {
118 printf(_("Cannot determine message sender to %s.\n"), cmd);
119 goto jleave;
122 for (cq = cp; *cq != '\0' && *cq != '@'; cq++)
124 *cq = '\0';
125 if (ok_blook(outfolder)) {
126 size_t sz = strlen(cp) +1;
127 file = salloc(sz + 1);
128 file[0] = '+';
129 memcpy(file + 1, cp, sz);
130 } else
131 file = cp;
134 /* Pipe target is special TODO hacked in later, normalize flow! */
135 if (shell != NULL) {
136 if ((obuf = Popen(file, "w", shell, NULL, 1)) == NULL) {
137 int esave = errno;
138 n_perr(file, esave);
139 errno = esave;
140 goto jleave;
142 disp = _("[Piped]");
143 goto jsend;
146 if ((file = expand(file)) == NULL)
147 goto jleave;
148 if (access(file, F_OK) >= 0) {
149 newfile = 0;
150 disp = _("[Appended]");
151 } else {
152 newfile = 1;
153 disp = _("[New file]");
156 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "a+") : Zopen(file, "a+"));
157 if (obuf == NULL) {
158 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "wx") : Zopen(file, "wx"));
159 if (obuf == NULL) {
160 n_perr(file, 0);
161 goto jleave;
163 } else {
164 if (!newfile && !fstat(fileno(obuf), &st) && S_ISREG(st.st_mode) &&
165 fseek(obuf, -2L, SEEK_END) == 0) {
166 char buf[2];
167 int prependnl = 0;
169 switch (fread(buf, sizeof *buf, 2, obuf)) {
170 case 2:
171 if (buf[1] != '\n') {
172 prependnl = 1;
173 break;
175 /* FALLTHRU */
176 case 1:
177 if (buf[0] != '\n')
178 prependnl = 1;
179 break;
180 default:
181 if (ferror(obuf)) {
182 n_perr(file, 0);
183 goto jleave;
185 prependnl = 0;
188 fflush(obuf);
189 if (prependnl) {
190 putc('\n', obuf);
191 fflush(obuf);
196 jsend:
197 success = TRU1;
198 tstats[0] = tstats[1] = 0;
200 srelax_hold();
201 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
202 ++ip) {
203 mp = message + *ip - 1;
204 if (sendmp(mp, obuf, ignoret, NULL, convert, mstats) < 0) {
205 success = FAL0;
206 goto jferr;
208 srelax();
210 touch(mp);
211 if (domark)
212 mp->m_flag |= MSAVED;
213 if (domove) {
214 mp->m_flag |= MDELETED | MSAVED;
215 last = *ip;
218 tstats[0] += mstats[0];
219 tstats[1] += mp->m_lines;/* TODO won't work, need target! v15!! */
221 srelax_rele();
223 fflush(obuf);
224 if (ferror(obuf)) {
225 jferr:
226 n_perr(file, 0);
227 if (!success)
228 srelax_rele();
229 success = FAL0;
231 if (shell != NULL) {
232 if (!Pclose(obuf, TRU1))
233 success = FAL0;
234 } else if (Fclose(obuf) != 0)
235 success = FAL0;
237 if (success) {
238 printf("\"%s\" %s %" /*PRIu64 "/%"*/ PRIu64 " bytes\n",
239 file, disp, /*tstats[1], TODO v15: lines written */ tstats[0]);
240 } else if (domark) {
241 newfile = ~MSAVED;
242 goto jiterand;
243 } else if (domove) {
244 newfile = ~(MSAVED | MDELETED);
245 jiterand:
246 for (ip = msgvec; *ip != 0 &&
247 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
248 mp = message + *ip - 1;
249 mp->m_flag &= newfile;
253 if (domove && last && success) {
254 setdot(message + last - 1);
255 last = first(0, MDELETED);
256 setdot(message + (last != 0 ? last - 1 : 0));
258 jleave:
259 NYD_LEAVE;
260 return (success == FAL0);
263 static char *
264 snarf(char *linebuf, bool_t *flag, bool_t usembox)
266 char *cp;
267 NYD_ENTER;
269 if ((cp = laststring(linebuf, flag, TRU1)) == NULL) {
270 if (usembox) {
271 *flag = FAL0;
272 cp = expand("&");
273 } else
274 n_err(_("No file specified\n"));
276 NYD_LEAVE;
277 return cp;
280 static int
281 delm(int *msgvec)
283 struct message *mp;
284 int rv = -1, *ip, last;
285 NYD_ENTER;
287 last = 0;
288 for (ip = msgvec; *ip != 0; ++ip) {
289 mp = message + *ip - 1;
290 touch(mp);
291 mp->m_flag |= MDELETED | MTOUCH;
292 mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX);
293 last = *ip;
295 if (last != 0) {
296 setdot(message + last - 1);
297 last = first(0, MDELETED);
298 if (last != 0) {
299 setdot(message + last - 1);
300 rv = 0;
301 } else {
302 setdot(message);
305 NYD_LEAVE;
306 return rv;
309 static int
310 ignore1(char **list, struct ignoretab *tab, char const *which)
312 int h;
313 struct ignored *igp;
314 char **ap;
315 NYD_ENTER;
317 if (*list == NULL) {
318 h = igshow(tab, which);
319 goto jleave;
322 for (ap = list; *ap != 0; ++ap) {
323 char *field;
324 size_t sz;
326 sz = strlen(*ap) +1;
327 field = ac_alloc(sz);
328 i_strcpy(field, *ap, sz);
329 if (member(field, tab))
330 goto jnext;
332 h = hash(field);
333 igp = scalloc(1, sizeof *igp);
334 sz = strlen(field) +1;
335 igp->i_field = smalloc(sz);
336 memcpy(igp->i_field, field, sz);
337 igp->i_link = tab->i_head[h];
338 tab->i_head[h] = igp;
339 ++tab->i_count;
340 jnext:
341 ac_free(field);
343 h = 0;
344 jleave:
345 NYD_LEAVE;
346 return h;
349 static int
350 igshow(struct ignoretab *tab, char const *which)
352 int h;
353 struct ignored *igp;
354 char **ap, **ring;
355 NYD_ENTER;
357 if (tab->i_count == 0) {
358 printf(_("No fields currently being %s.\n"), which);
359 goto jleave;
362 ring = salloc((tab->i_count + 1) * sizeof *ring);
363 ap = ring;
364 for (h = 0; h < HSHSIZE; ++h)
365 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
366 *ap++ = igp->i_field;
367 *ap = 0;
369 qsort(ring, tab->i_count, sizeof *ring, igcomp);
371 for (ap = ring; *ap != NULL; ++ap)
372 printf("%s\n", *ap);
373 jleave:
374 NYD_LEAVE;
375 return 0;
378 static int
379 igcomp(void const *l, void const *r)
381 int rv;
382 NYD_ENTER;
384 rv = strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r));
385 NYD_LEAVE;
386 return rv;
389 static int
390 _unignore(char **list, struct ignoretab *tab, char const *which)
392 char *field;
393 NYD_ENTER;
395 if (tab->i_count == 0)
396 printf(_("No fields currently being %s.\n"), which);
397 else
398 while ((field = *list++) != NULL)
399 if (field[0] == '*' && field[1] == '\0') {
400 __unign_all(tab);
401 break;
402 } else
403 __unign_one(tab, field);
404 NYD_LEAVE;
405 return 0;
408 static void
409 __unign_all(struct ignoretab *tab)
411 size_t i;
412 struct ignored *n, *x;
413 NYD_ENTER;
415 for (i = 0; i < NELEM(tab->i_head); ++i)
416 for (n = tab->i_head[i]; n != NULL; n = x) {
417 x = n->i_link;
418 free(n->i_field);
419 free(n);
421 memset(tab, 0, sizeof *tab);
422 NYD_LEAVE;
425 static void
426 __unign_one(struct ignoretab *tab, char const *name)
428 struct ignored *ip, *iq;
429 int h;
430 NYD_ENTER;
432 h = hash(name);
433 for (iq = NULL, ip = tab->i_head[h]; ip != NULL; ip = ip->i_link) {
434 if (!asccasecmp(ip->i_field, name)) {
435 free(ip->i_field);
436 if (iq != NULL)
437 iq->i_link = ip->i_link;
438 else
439 tab->i_head[h] = ip->i_link;
440 free(ip);
441 --tab->i_count;
442 break;
444 iq = ip;
446 NYD_LEAVE;
449 FL int
450 c_next(void *v)
452 int list[2], *ip, *ip2, mdot, *msgvec = v, rv = 1;
453 struct message *mp;
454 NYD_ENTER;
456 if (*msgvec != 0) {
457 /* If some messages were supplied, find the first applicable one
458 * following dot using wrap around */
459 mdot = (int)PTR2SIZE(dot - message + 1);
461 /* Find first message in supplied message list which follows dot */
462 for (ip = msgvec; *ip != 0; ++ip) {
463 if ((mb.mb_threaded ? message[*ip - 1].m_threadpos > dot->m_threadpos
464 : *ip > mdot))
465 break;
467 if (*ip == 0)
468 ip = msgvec;
469 ip2 = ip;
470 do {
471 mp = message + *ip2 - 1;
472 if (!(mp->m_flag & MMNDEL)) {
473 setdot(mp);
474 goto jhitit;
476 if (*ip2 != 0)
477 ++ip2;
478 if (*ip2 == 0)
479 ip2 = msgvec;
480 } while (ip2 != ip);
481 printf(_("No messages applicable\n"));
482 goto jleave;
485 /* If this is the first command, select message 1. Note that this must
486 * exist for us to get here at all */
487 if (!(pstate & PS_SAW_COMMAND)) {
488 if (msgCount == 0)
489 goto jateof;
490 goto jhitit;
493 /* Just find the next good message after dot, no wraparound */
494 if (mb.mb_threaded == 0) {
495 for (mp = dot + !!(pstate & PS_DID_PRINT_DOT);
496 PTRCMP(mp, <, message + msgCount); ++mp)
497 if (!(mp->m_flag & MMNORM))
498 break;
499 } else {
500 /* TODO The threading code had some bugs that caused crashes.
501 * TODO The last thing (before the deep look) happens here,
502 * TODO so let's not trust PS_DID_PRINT_DOT but check & hope it fixes */
503 if ((mp = dot) != NULL && (pstate & PS_DID_PRINT_DOT))
504 mp = next_in_thread(mp);
505 while (mp != NULL && (mp->m_flag & MMNORM))
506 mp = next_in_thread(mp);
508 if (mp == NULL || PTRCMP(mp, >=, message + msgCount)) {
509 jateof:
510 printf(_("At EOF\n"));
511 rv = 0;
512 goto jleave;
514 setdot(mp);
516 /* Print dot */
517 jhitit:
518 list[0] = (int)PTR2SIZE(dot - message + 1);
519 list[1] = 0;
520 rv = c_type(list);
521 jleave:
522 NYD_LEAVE;
523 return rv;
526 FL int
527 c_dotmove(void *v)
529 char const *args;
530 int msgvec[2], rv;
531 NYD_ENTER;
533 if (*(args = v) == '\0' || args[1] != '\0') {
534 jerr:
535 n_err(_("Synopsis: dotmove: up <-> or down <+> by one message\n"));
536 rv = 1;
537 } else switch (args[0]) {
538 case '-':
539 case '+':
540 if (msgCount == 0) {
541 printf(_("At EOF\n"));
542 rv = 0;
543 } else if (getmsglist(UNCONST(/*TODO*/ args), msgvec, 0) > 0) {
544 setdot(message + msgvec[0] - 1);
545 msgvec[1] = 0;
546 rv = c_headers(msgvec);
547 } else
548 rv = 1;
549 break;
550 default:
551 goto jerr;
553 NYD_LEAVE;
554 return rv;
557 FL int
558 c_save(void *v)
560 char *str = v;
561 int rv;
562 NYD_ENTER;
564 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
565 NYD_LEAVE;
566 return rv;
569 FL int
570 c_Save(void *v)
572 char *str = v;
573 int rv;
574 NYD_ENTER;
576 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
577 NYD_LEAVE;
578 return rv;
581 FL int
582 c_copy(void *v)
584 char *str = v;
585 int rv;
586 NYD_ENTER;
588 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
589 NYD_LEAVE;
590 return rv;
593 FL int
594 c_Copy(void *v)
596 char *str = v;
597 int rv;
598 NYD_ENTER;
600 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
601 NYD_LEAVE;
602 return rv;
605 FL int
606 c_move(void *v)
608 char *str = v;
609 int rv;
610 NYD_ENTER;
612 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
613 NYD_LEAVE;
614 return rv;
617 FL int
618 c_Move(void *v)
620 char *str = v;
621 int rv;
622 NYD_ENTER;
624 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
625 NYD_LEAVE;
626 return rv;
629 FL int
630 c_decrypt(void *v)
632 char *str = v;
633 int rv;
634 NYD_ENTER;
636 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
637 NYD_LEAVE;
638 return rv;
641 FL int
642 c_Decrypt(void *v)
644 char *str = v;
645 int rv;
646 NYD_ENTER;
648 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
649 NYD_LEAVE;
650 return rv;
653 FL int
654 c_write(void *v)
656 char *str = v;
657 int rv;
658 NYD_ENTER;
660 if (str == NULL || *str == '\0')
661 str = savestr("/dev/null");
662 rv = save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0);
663 NYD_LEAVE;
664 return rv;
667 FL int
668 c_delete(void *v)
670 int *msgvec = v;
671 NYD_ENTER;
673 delm(msgvec);
674 NYD_LEAVE;
675 return 0;
678 FL int
679 c_deltype(void *v)
681 int list[2], rv = 0, *msgvec = v, lastdot;
682 NYD_ENTER;
684 lastdot = (int)PTR2SIZE(dot - message + 1);
685 if (delm(msgvec) >= 0) {
686 list[0] = (int)PTR2SIZE(dot - message + 1);
687 if (list[0] > lastdot) {
688 touch(dot);
689 list[1] = 0;
690 rv = c_type(list);
691 goto jleave;
693 printf(_("At EOF\n"));
694 } else
695 printf(_("No more messages\n"));
696 jleave:
697 NYD_LEAVE;
698 return rv;
701 FL int
702 c_undelete(void *v)
704 int *msgvec = v, *ip;
705 struct message *mp;
706 NYD_ENTER;
708 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
709 ++ip) {
710 mp = message + *ip - 1;
711 touch(mp);
712 setdot(mp);
713 if (mp->m_flag & (MDELETED | MSAVED))
714 mp->m_flag &= ~(MDELETED | MSAVED);
715 else
716 mp->m_flag &= ~MDELETED;
718 NYD_LEAVE;
719 return 0;
722 FL int
723 c_retfield(void *v)
725 char **list = v;
726 int rv;
727 NYD_ENTER;
729 rv = ignore1(list, ignore + 1, "retained");
730 NYD_LEAVE;
731 return rv;
734 FL int
735 c_igfield(void *v)
737 char **list = v;
738 int rv;
739 NYD_ENTER;
741 rv = ignore1(list, ignore, "ignored");
742 NYD_LEAVE;
743 return rv;
746 FL int
747 c_saveretfield(void *v)
749 char **list = v;
750 int rv;
751 NYD_ENTER;
753 rv = ignore1(list, saveignore + 1, "retained");
754 NYD_LEAVE;
755 return rv;
758 FL int
759 c_saveigfield(void *v)
761 char **list = v;
762 int rv;
763 NYD_ENTER;
765 rv = ignore1(list, saveignore, "ignored");
766 NYD_LEAVE;
767 return rv;
770 FL int
771 c_fwdretfield(void *v)
773 char **list = v;
774 int rv;
775 NYD_ENTER;
777 rv = ignore1(list, fwdignore + 1, "retained");
778 NYD_LEAVE;
779 return rv;
782 FL int
783 c_fwdigfield(void *v)
785 char **list = v;
786 int rv;
787 NYD_ENTER;
789 rv = ignore1(list, fwdignore, "ignored");
790 NYD_LEAVE;
791 return rv;
794 FL int
795 c_unignore(void *v)
797 int rv;
798 NYD_ENTER;
800 rv = _unignore((char**)v, ignore, "ignored");
801 NYD_LEAVE;
802 return rv;
805 FL int
806 c_unretain(void *v)
808 int rv;
809 NYD_ENTER;
811 rv = _unignore((char**)v, ignore + 1, "retained");
812 NYD_LEAVE;
813 return rv;
816 FL int
817 c_unsaveignore(void *v)
819 int rv;
820 NYD_ENTER;
822 rv = _unignore((char**)v, saveignore, "ignored");
823 NYD_LEAVE;
824 return rv;
827 FL int
828 c_unsaveretain(void *v)
830 int rv;
831 NYD_ENTER;
833 rv = _unignore((char**)v, saveignore + 1, "retained");
834 NYD_LEAVE;
835 return rv;
838 FL int
839 c_unfwdignore(void *v)
841 int rv;
842 NYD_ENTER;
844 rv = _unignore((char**)v, fwdignore, "ignored");
845 NYD_LEAVE;
846 return rv;
849 FL int
850 c_unfwdretain(void *v)
852 int rv;
853 NYD_ENTER;
855 rv = _unignore((char**)v, fwdignore + 1, "retained");
856 NYD_LEAVE;
857 return rv;
860 /* s-it-mode */