mk-conf.sh: FIX! Restore usability on SunOS / Solaris..
[s-mailx.git] / cmd2.c
blobf0fe4a70b8f933663e235edf7231867e351ba7ec
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 = "";
81 FILE *obuf;
82 enum protocol prot;
83 bool_t success = FAL0, f;
84 NYD_ENTER;
86 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
87 if (sender_record) {
88 for (cp = str; *cp != '\0' && blankchar(*cp); ++cp)
90 f = (*cp != '\0');
91 } else {
92 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
93 goto jleave;
96 if (!f) {
97 *msgvec = first(0, MMNORM);
98 msgvec[1] = 0;
99 } else if (getmsglist(str, msgvec, 0) < 0)
100 goto jleave;
101 if (*msgvec == 0) {
102 if (pstate & PS_HOOK_MASK) {
103 success = TRU1;
104 goto jleave;
106 printf(_("No messages to %s.\n"), cmd);
107 goto jleave;
110 if (sender_record) {
111 if ((cp = nameof(message + *msgvec - 1, 0)) == NULL) {
112 printf(_("Cannot determine message sender to %s.\n"), cmd);
113 goto jleave;
116 for (cq = cp; *cq != '\0' && *cq != '@'; cq++)
118 *cq = '\0';
119 if (ok_blook(outfolder)) {
120 size_t sz = strlen(cp) +1;
121 file = salloc(sz + 1);
122 file[0] = '+';
123 memcpy(file + 1, cp, sz);
124 } else
125 file = cp;
128 if ((file = expand(file)) == NULL)
129 goto jleave;
130 prot = which_protocol(file);
131 if (prot != PROTO_IMAP) {
132 if (access(file, F_OK) >= 0) {
133 newfile = 0;
134 disp = _("[Appended]");
135 } else {
136 newfile = 1;
137 disp = _("[New file]");
141 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "a+") : Zopen(file, "a+"));
142 if (obuf == NULL) {
143 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "wx") : Zopen(file, "wx"));
144 if (obuf == NULL) {
145 n_perr(file, 0);
146 goto jleave;
148 } else {
149 if (!newfile && fstat(fileno(obuf), &st) && S_ISREG(st.st_mode) &&
150 fseek(obuf, -2L, SEEK_END) == 0) {
151 char buf[2];
152 int prependnl = 0;
154 switch (fread(buf, sizeof *buf, 2, obuf)) {
155 case 2:
156 if (buf[1] != '\n') {
157 prependnl = 1;
158 break;
160 /* FALLTHRU */
161 case 1:
162 if (buf[0] != '\n')
163 prependnl = 1;
164 break;
165 default:
166 if (ferror(obuf)) {
167 n_perr(file, 0);
168 goto jleave;
170 prependnl = 0;
172 fflush(obuf);
173 if (prependnl) {
174 putc('\n', obuf);
175 fflush(obuf);
180 success = TRU1;
181 tstats[0] = tstats[1] = 0;
182 imap_created_mailbox = 0;
184 srelax_hold();
185 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
186 ++ip) {
187 mp = message + *ip - 1;
188 if (prot == PROTO_IMAP && ignoret[0].i_count == 0 &&
189 ignoret[1].i_count == 0
190 #ifdef HAVE_IMAP /* TODO revisit */
191 && imap_thisaccount(file)
192 #endif
194 #ifdef HAVE_IMAP
195 if (imap_copy(mp, *ip, file) == STOP)
196 #endif
198 #ifndef HAVE_IMAP
199 # ifdef ENOSYS
200 errno = ENOSYS;
201 # elif defined EOPNOTSUPP
202 errno = EOPNOTSUPP;
203 # else
204 errno = EINVAL;
205 # endif
206 #endif
207 success = FAL0;
208 goto jferr;
210 #ifdef HAVE_IMAP
211 mstats[0] = mp->m_xsize;
212 #endif
213 } else if (sendmp(mp, obuf, ignoret, NULL, convert, mstats) < 0) {
214 success = FAL0;
215 goto jferr;
217 srelax();
219 touch(mp);
220 if (domark)
221 mp->m_flag |= MSAVED;
222 if (domove) {
223 mp->m_flag |= MDELETED | MSAVED;
224 last = *ip;
227 tstats[0] += mstats[0];
228 tstats[1] += mp->m_lines;/* TODO won't work, need target! v15!! */
230 srelax_rele();
232 fflush(obuf);
233 if (ferror(obuf)) {
234 jferr:
235 n_perr(file, 0);
236 if (!success)
237 srelax_rele();
238 success = FAL0;
240 if (Fclose(obuf) != 0)
241 success = FAL0;
243 if (success) {
244 if (prot == PROTO_IMAP || prot == PROTO_MAILDIR) {
245 disp = (
246 #ifdef HAVE_IMAP
247 ((prot == PROTO_IMAP) && disconnected(file)) ? "[Queued]" :
248 #endif
249 (imap_created_mailbox ? "[New file]" : "[Appended]"));
251 printf("\"%s\" %s %" /*PRIu64 "/%"*/ PRIu64 " bytes\n",
252 file, disp, /*tstats[1], TODO v15: lines written */ tstats[0]);
253 } else if (domark) {
254 newfile = ~MSAVED;
255 goto jiterand;
256 } else if (domove) {
257 newfile = ~(MSAVED | MDELETED);
258 jiterand:
259 for (ip = msgvec; *ip != 0 &&
260 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
261 mp = message + *ip - 1;
262 mp->m_flag &= newfile;
266 if (domove && last && success) {
267 setdot(message + last - 1);
268 last = first(0, MDELETED);
269 setdot(message + (last != 0 ? last - 1 : 0));
271 jleave:
272 NYD_LEAVE;
273 return (success == FAL0);
276 static char *
277 snarf(char *linebuf, bool_t *flag, bool_t usembox)
279 char *cp;
280 NYD_ENTER;
282 if ((cp = laststring(linebuf, flag, FAL0)) == NULL) {
283 if (usembox) {
284 *flag = FAL0;
285 cp = expand("&");
286 } else
287 n_err(_("No file specified\n"));
289 NYD_LEAVE;
290 return cp;
293 static int
294 delm(int *msgvec)
296 struct message *mp;
297 int rv = -1, *ip, last;
298 NYD_ENTER;
300 last = 0;
301 for (ip = msgvec; *ip != 0; ++ip) {
302 mp = message + *ip - 1;
303 touch(mp);
304 mp->m_flag |= MDELETED | MTOUCH;
305 mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX);
306 last = *ip;
308 if (last != 0) {
309 setdot(message + last - 1);
310 last = first(0, MDELETED);
311 if (last != 0) {
312 setdot(message + last - 1);
313 rv = 0;
314 } else {
315 setdot(message);
318 NYD_LEAVE;
319 return rv;
322 static int
323 ignore1(char **list, struct ignoretab *tab, char const *which)
325 int h;
326 struct ignored *igp;
327 char **ap;
328 NYD_ENTER;
330 if (*list == NULL) {
331 h = igshow(tab, which);
332 goto jleave;
335 for (ap = list; *ap != 0; ++ap) {
336 char *field;
337 size_t sz;
339 sz = strlen(*ap) +1;
340 field = ac_alloc(sz);
341 i_strcpy(field, *ap, sz);
342 if (member(field, tab))
343 goto jnext;
345 h = hash(field);
346 igp = scalloc(1, sizeof *igp);
347 sz = strlen(field) +1;
348 igp->i_field = smalloc(sz);
349 memcpy(igp->i_field, field, sz);
350 igp->i_link = tab->i_head[h];
351 tab->i_head[h] = igp;
352 ++tab->i_count;
353 jnext:
354 ac_free(field);
356 h = 0;
357 jleave:
358 NYD_LEAVE;
359 return h;
362 static int
363 igshow(struct ignoretab *tab, char const *which)
365 int h;
366 struct ignored *igp;
367 char **ap, **ring;
368 NYD_ENTER;
370 if (tab->i_count == 0) {
371 printf(_("No fields currently being %s.\n"), which);
372 goto jleave;
375 ring = salloc((tab->i_count + 1) * sizeof *ring);
376 ap = ring;
377 for (h = 0; h < HSHSIZE; ++h)
378 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
379 *ap++ = igp->i_field;
380 *ap = 0;
382 qsort(ring, tab->i_count, sizeof *ring, igcomp);
384 for (ap = ring; *ap != NULL; ++ap)
385 printf("%s\n", *ap);
386 jleave:
387 NYD_LEAVE;
388 return 0;
391 static int
392 igcomp(void const *l, void const *r)
394 int rv;
395 NYD_ENTER;
397 rv = strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r));
398 NYD_LEAVE;
399 return rv;
402 static int
403 _unignore(char **list, struct ignoretab *tab, char const *which)
405 char *field;
406 NYD_ENTER;
408 if (tab->i_count == 0)
409 printf(_("No fields currently being %s.\n"), which);
410 else
411 while ((field = *list++) != NULL)
412 if (field[0] == '*' && field[1] == '\0') {
413 __unign_all(tab);
414 break;
415 } else
416 __unign_one(tab, field);
417 NYD_LEAVE;
418 return 0;
421 static void
422 __unign_all(struct ignoretab *tab)
424 size_t i;
425 struct ignored *n, *x;
426 NYD_ENTER;
428 for (i = 0; i < NELEM(tab->i_head); ++i)
429 for (n = tab->i_head[i]; n != NULL; n = x) {
430 x = n->i_link;
431 free(n->i_field);
432 free(n);
434 memset(tab, 0, sizeof *tab);
435 NYD_LEAVE;
438 static void
439 __unign_one(struct ignoretab *tab, char const *name)
441 struct ignored *ip, *iq;
442 int h;
443 NYD_ENTER;
445 h = hash(name);
446 for (iq = NULL, ip = tab->i_head[h]; ip != NULL; ip = ip->i_link) {
447 if (!asccasecmp(ip->i_field, name)) {
448 free(ip->i_field);
449 if (iq != NULL)
450 iq->i_link = ip->i_link;
451 else
452 tab->i_head[h] = ip->i_link;
453 free(ip);
454 --tab->i_count;
455 break;
457 iq = ip;
459 NYD_LEAVE;
462 FL int
463 c_next(void *v)
465 int list[2], *ip, *ip2, mdot, *msgvec = v, rv = 1;
466 struct message *mp;
467 NYD_ENTER;
469 if (*msgvec != 0) {
470 /* If some messages were supplied, find the first applicable one
471 * following dot using wrap around */
472 mdot = (int)PTR2SIZE(dot - message + 1);
474 /* Find first message in supplied message list which follows dot */
475 for (ip = msgvec; *ip != 0; ++ip) {
476 if ((mb.mb_threaded ? message[*ip - 1].m_threadpos > dot->m_threadpos
477 : *ip > mdot))
478 break;
480 if (*ip == 0)
481 ip = msgvec;
482 ip2 = ip;
483 do {
484 mp = message + *ip2 - 1;
485 if (!(mp->m_flag & MMNDEL)) {
486 setdot(mp);
487 goto jhitit;
489 if (*ip2 != 0)
490 ++ip2;
491 if (*ip2 == 0)
492 ip2 = msgvec;
493 } while (ip2 != ip);
494 printf(_("No messages applicable\n"));
495 goto jleave;
498 /* If this is the first command, select message 1. Note that this must
499 * exist for us to get here at all */
500 if (!(pstate & PS_SAW_COMMAND)) {
501 if (msgCount == 0)
502 goto jateof;
503 goto jhitit;
506 /* Just find the next good message after dot, no wraparound */
507 if (mb.mb_threaded == 0) {
508 for (mp = dot + !!(pstate & PS_DID_PRINT_DOT);
509 PTRCMP(mp, <, message + msgCount); ++mp)
510 if (!(mp->m_flag & MMNORM))
511 break;
512 } else {
513 /* TODO The threading code had some bugs that caused crashes.
514 * TODO The last thing (before the deep look) happens here,
515 * TODO so let's not trust PS_DID_PRINT_DOT but check & hope it fixes */
516 if ((mp = dot) != NULL && (pstate & PS_DID_PRINT_DOT))
517 mp = next_in_thread(mp);
518 while (mp != NULL && (mp->m_flag & MMNORM))
519 mp = next_in_thread(mp);
521 if (mp == NULL || PTRCMP(mp, >=, message + msgCount)) {
522 jateof:
523 printf(_("At EOF\n"));
524 rv = 0;
525 goto jleave;
527 setdot(mp);
529 /* Print dot */
530 jhitit:
531 list[0] = (int)PTR2SIZE(dot - message + 1);
532 list[1] = 0;
533 rv = c_type(list);
534 jleave:
535 NYD_LEAVE;
536 return rv;
539 FL int
540 c_save(void *v)
542 char *str = v;
543 int rv;
544 NYD_ENTER;
546 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
547 NYD_LEAVE;
548 return rv;
551 FL int
552 c_Save(void *v)
554 char *str = v;
555 int rv;
556 NYD_ENTER;
558 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
559 NYD_LEAVE;
560 return rv;
563 FL int
564 c_copy(void *v)
566 char *str = v;
567 int rv;
568 NYD_ENTER;
570 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
571 NYD_LEAVE;
572 return rv;
575 FL int
576 c_Copy(void *v)
578 char *str = v;
579 int rv;
580 NYD_ENTER;
582 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
583 NYD_LEAVE;
584 return rv;
587 FL int
588 c_move(void *v)
590 char *str = v;
591 int rv;
592 NYD_ENTER;
594 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
595 NYD_LEAVE;
596 return rv;
599 FL int
600 c_Move(void *v)
602 char *str = v;
603 int rv;
604 NYD_ENTER;
606 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
607 NYD_LEAVE;
608 return rv;
611 FL int
612 c_decrypt(void *v)
614 char *str = v;
615 int rv;
616 NYD_ENTER;
618 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
619 NYD_LEAVE;
620 return rv;
623 FL int
624 c_Decrypt(void *v)
626 char *str = v;
627 int rv;
628 NYD_ENTER;
630 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
631 NYD_LEAVE;
632 return rv;
635 FL int
636 c_write(void *v)
638 char *str = v;
639 int rv;
640 NYD_ENTER;
642 if (str == NULL || *str == '\0')
643 str = savestr("/dev/null");
644 rv = save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0);
645 NYD_LEAVE;
646 return rv;
649 FL int
650 c_delete(void *v)
652 int *msgvec = v;
653 NYD_ENTER;
655 delm(msgvec);
656 NYD_LEAVE;
657 return 0;
660 FL int
661 c_deltype(void *v)
663 int list[2], rv = 0, *msgvec = v, lastdot;
664 NYD_ENTER;
666 lastdot = (int)PTR2SIZE(dot - message + 1);
667 if (delm(msgvec) >= 0) {
668 list[0] = (int)PTR2SIZE(dot - message + 1);
669 if (list[0] > lastdot) {
670 touch(dot);
671 list[1] = 0;
672 rv = c_type(list);
673 goto jleave;
675 printf(_("At EOF\n"));
676 } else
677 printf(_("No more messages\n"));
678 jleave:
679 NYD_LEAVE;
680 return rv;
683 FL int
684 c_undelete(void *v)
686 int *msgvec = v, *ip;
687 struct message *mp;
688 NYD_ENTER;
690 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
691 ++ip) {
692 mp = message + *ip - 1;
693 touch(mp);
694 setdot(mp);
695 if (mp->m_flag & (MDELETED | MSAVED))
696 mp->m_flag &= ~(MDELETED | MSAVED);
697 else
698 mp->m_flag &= ~MDELETED;
699 #ifdef HAVE_IMAP
700 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
701 imap_undelete(mp, *ip);
702 #endif
704 NYD_LEAVE;
705 return 0;
708 FL int
709 c_retfield(void *v)
711 char **list = v;
712 int rv;
713 NYD_ENTER;
715 rv = ignore1(list, ignore + 1, "retained");
716 NYD_LEAVE;
717 return rv;
720 FL int
721 c_igfield(void *v)
723 char **list = v;
724 int rv;
725 NYD_ENTER;
727 rv = ignore1(list, ignore, "ignored");
728 NYD_LEAVE;
729 return rv;
732 FL int
733 c_saveretfield(void *v)
735 char **list = v;
736 int rv;
737 NYD_ENTER;
739 rv = ignore1(list, saveignore + 1, "retained");
740 NYD_LEAVE;
741 return rv;
744 FL int
745 c_saveigfield(void *v)
747 char **list = v;
748 int rv;
749 NYD_ENTER;
751 rv = ignore1(list, saveignore, "ignored");
752 NYD_LEAVE;
753 return rv;
756 FL int
757 c_fwdretfield(void *v)
759 char **list = v;
760 int rv;
761 NYD_ENTER;
763 rv = ignore1(list, fwdignore + 1, "retained");
764 NYD_LEAVE;
765 return rv;
768 FL int
769 c_fwdigfield(void *v)
771 char **list = v;
772 int rv;
773 NYD_ENTER;
775 rv = ignore1(list, fwdignore, "ignored");
776 NYD_LEAVE;
777 return rv;
780 FL int
781 c_unignore(void *v)
783 int rv;
784 NYD_ENTER;
786 rv = _unignore((char**)v, ignore, "ignored");
787 NYD_LEAVE;
788 return rv;
791 FL int
792 c_unretain(void *v)
794 int rv;
795 NYD_ENTER;
797 rv = _unignore((char**)v, ignore + 1, "retained");
798 NYD_LEAVE;
799 return rv;
802 FL int
803 c_unsaveignore(void *v)
805 int rv;
806 NYD_ENTER;
808 rv = _unignore((char**)v, saveignore, "ignored");
809 NYD_LEAVE;
810 return rv;
813 FL int
814 c_unsaveretain(void *v)
816 int rv;
817 NYD_ENTER;
819 rv = _unignore((char**)v, saveignore + 1, "retained");
820 NYD_LEAVE;
821 return rv;
824 FL int
825 c_unfwdignore(void *v)
827 int rv;
828 NYD_ENTER;
830 rv = _unignore((char**)v, fwdignore, "ignored");
831 NYD_LEAVE;
832 return rv;
835 FL int
836 c_unfwdretain(void *v)
838 int rv;
839 NYD_ENTER;
841 rv = _unignore((char**)v, fwdignore + 1, "retained");
842 NYD_LEAVE;
843 return rv;
846 /* s-it-mode */