sendout.c:__savemail(): try to file_lock() *record*
[s-mailx.git] / cmd2.c
blob752a10a7b6e1da0fa4321588a8f4794ca7d05a0d
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 shell = ok_vlook(SHELL);
101 if (!f) {
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;
137 n_perr(file, esave);
138 errno = esave;
139 goto jleave;
141 disp = _("[Piped]");
142 goto jsend;
145 if ((file = expand(file)) == NULL)
146 goto jleave;
147 if (access(file, F_OK) >= 0) {
148 newfile = 0;
149 disp = _("[Appended]");
150 } else {
151 newfile = 1;
152 disp = _("[New file]");
155 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "a+") : Zopen(file, "a+"));
156 if (obuf == NULL) {
157 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "wx") : Zopen(file, "wx"));
158 if (obuf == NULL) {
159 n_perr(file, 0);
160 goto jleave;
162 } else {
163 if (!newfile && !fstat(fileno(obuf), &st) && S_ISREG(st.st_mode) &&
164 fseek(obuf, -2L, SEEK_END) == 0) {
165 char buf[2];
166 int prependnl = 0;
168 switch (fread(buf, sizeof *buf, 2, obuf)) {
169 case 2:
170 if (buf[1] != '\n') {
171 prependnl = 1;
172 break;
174 /* FALLTHRU */
175 case 1:
176 if (buf[0] != '\n')
177 prependnl = 1;
178 break;
179 default:
180 if (ferror(obuf)) {
181 n_perr(file, 0);
182 goto jleave;
184 prependnl = 0;
187 fflush(obuf);
188 if (prependnl) {
189 putc('\n', obuf);
190 fflush(obuf);
195 jsend:
196 success = TRU1;
197 tstats[0] = tstats[1] = 0;
199 srelax_hold();
200 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
201 ++ip) {
202 mp = message + *ip - 1;
203 if (sendmp(mp, obuf, ignoret, NULL, convert, mstats) < 0) {
204 success = FAL0;
205 goto jferr;
207 srelax();
209 touch(mp);
210 if (domark)
211 mp->m_flag |= MSAVED;
212 if (domove) {
213 mp->m_flag |= MDELETED | MSAVED;
214 last = *ip;
217 tstats[0] += mstats[0];
218 tstats[1] += mp->m_lines;/* TODO won't work, need target! v15!! */
220 srelax_rele();
222 fflush(obuf);
223 if (ferror(obuf)) {
224 jferr:
225 n_perr(file, 0);
226 if (!success)
227 srelax_rele();
228 success = FAL0;
230 if (shell != NULL) {
231 if (!Pclose(obuf, TRU1))
232 success = FAL0;
233 } else if (Fclose(obuf) != 0)
234 success = FAL0;
236 if (success) {
237 printf("\"%s\" %s %" /*PRIu64 "/%"*/ PRIu64 " bytes\n",
238 file, disp, /*tstats[1], TODO v15: lines written */ tstats[0]);
239 } else if (domark) {
240 newfile = ~MSAVED;
241 goto jiterand;
242 } else if (domove) {
243 newfile = ~(MSAVED | MDELETED);
244 jiterand:
245 for (ip = msgvec; *ip != 0 &&
246 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
247 mp = message + *ip - 1;
248 mp->m_flag &= newfile;
252 if (domove && last && success) {
253 setdot(message + last - 1);
254 last = first(0, MDELETED);
255 setdot(message + (last != 0 ? last - 1 : 0));
257 jleave:
258 NYD_LEAVE;
259 return (success == FAL0);
262 static char *
263 snarf(char *linebuf, bool_t *flag, bool_t usembox)
265 char *cp;
266 NYD_ENTER;
268 if ((cp = laststring(linebuf, flag, TRU1)) == NULL) {
269 if (usembox) {
270 *flag = FAL0;
271 cp = expand("&");
272 } else
273 n_err(_("No file specified\n"));
275 NYD_LEAVE;
276 return cp;
279 static int
280 delm(int *msgvec)
282 struct message *mp;
283 int rv = -1, *ip, last;
284 NYD_ENTER;
286 last = 0;
287 for (ip = msgvec; *ip != 0; ++ip) {
288 mp = message + *ip - 1;
289 touch(mp);
290 mp->m_flag |= MDELETED | MTOUCH;
291 mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX);
292 last = *ip;
294 if (last != 0) {
295 setdot(message + last - 1);
296 last = first(0, MDELETED);
297 if (last != 0) {
298 setdot(message + last - 1);
299 rv = 0;
300 } else {
301 setdot(message);
304 NYD_LEAVE;
305 return rv;
308 static int
309 ignore1(char **list, struct ignoretab *tab, char const *which)
311 int h;
312 struct ignored *igp;
313 char **ap;
314 NYD_ENTER;
316 if (*list == NULL) {
317 h = igshow(tab, which);
318 goto jleave;
321 for (ap = list; *ap != 0; ++ap) {
322 char *field;
323 size_t sz;
325 sz = strlen(*ap) +1;
326 field = ac_alloc(sz);
327 i_strcpy(field, *ap, sz);
328 if (member(field, tab))
329 goto jnext;
331 h = hash(field);
332 igp = scalloc(1, sizeof *igp);
333 sz = strlen(field) +1;
334 igp->i_field = smalloc(sz);
335 memcpy(igp->i_field, field, sz);
336 igp->i_link = tab->i_head[h];
337 tab->i_head[h] = igp;
338 ++tab->i_count;
339 jnext:
340 ac_free(field);
342 h = 0;
343 jleave:
344 NYD_LEAVE;
345 return h;
348 static int
349 igshow(struct ignoretab *tab, char const *which)
351 int h;
352 struct ignored *igp;
353 char **ap, **ring;
354 NYD_ENTER;
356 if (tab->i_count == 0) {
357 printf(_("No fields currently being %s.\n"), which);
358 goto jleave;
361 ring = salloc((tab->i_count + 1) * sizeof *ring);
362 ap = ring;
363 for (h = 0; h < HSHSIZE; ++h)
364 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
365 *ap++ = igp->i_field;
366 *ap = 0;
368 qsort(ring, tab->i_count, sizeof *ring, igcomp);
370 for (ap = ring; *ap != NULL; ++ap)
371 printf("%s\n", *ap);
372 jleave:
373 NYD_LEAVE;
374 return 0;
377 static int
378 igcomp(void const *l, void const *r)
380 int rv;
381 NYD_ENTER;
383 rv = strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r));
384 NYD_LEAVE;
385 return rv;
388 static int
389 _unignore(char **list, struct ignoretab *tab, char const *which)
391 char *field;
392 NYD_ENTER;
394 if (tab->i_count == 0)
395 printf(_("No fields currently being %s.\n"), which);
396 else
397 while ((field = *list++) != NULL)
398 if (field[0] == '*' && field[1] == '\0') {
399 __unign_all(tab);
400 break;
401 } else
402 __unign_one(tab, field);
403 NYD_LEAVE;
404 return 0;
407 static void
408 __unign_all(struct ignoretab *tab)
410 size_t i;
411 struct ignored *n, *x;
412 NYD_ENTER;
414 for (i = 0; i < NELEM(tab->i_head); ++i)
415 for (n = tab->i_head[i]; n != NULL; n = x) {
416 x = n->i_link;
417 free(n->i_field);
418 free(n);
420 memset(tab, 0, sizeof *tab);
421 NYD_LEAVE;
424 static void
425 __unign_one(struct ignoretab *tab, char const *name)
427 struct ignored *ip, *iq;
428 int h;
429 NYD_ENTER;
431 h = hash(name);
432 for (iq = NULL, ip = tab->i_head[h]; ip != NULL; ip = ip->i_link) {
433 if (!asccasecmp(ip->i_field, name)) {
434 free(ip->i_field);
435 if (iq != NULL)
436 iq->i_link = ip->i_link;
437 else
438 tab->i_head[h] = ip->i_link;
439 free(ip);
440 --tab->i_count;
441 break;
443 iq = ip;
445 NYD_LEAVE;
448 FL int
449 c_next(void *v)
451 int list[2], *ip, *ip2, mdot, *msgvec = v, rv = 1;
452 struct message *mp;
453 NYD_ENTER;
455 if (*msgvec != 0) {
456 /* If some messages were supplied, find the first applicable one
457 * following dot using wrap around */
458 mdot = (int)PTR2SIZE(dot - message + 1);
460 /* Find first message in supplied message list which follows dot */
461 for (ip = msgvec; *ip != 0; ++ip) {
462 if ((mb.mb_threaded ? message[*ip - 1].m_threadpos > dot->m_threadpos
463 : *ip > mdot))
464 break;
466 if (*ip == 0)
467 ip = msgvec;
468 ip2 = ip;
469 do {
470 mp = message + *ip2 - 1;
471 if (!(mp->m_flag & MMNDEL)) {
472 setdot(mp);
473 goto jhitit;
475 if (*ip2 != 0)
476 ++ip2;
477 if (*ip2 == 0)
478 ip2 = msgvec;
479 } while (ip2 != ip);
480 printf(_("No messages applicable\n"));
481 goto jleave;
484 /* If this is the first command, select message 1. Note that this must
485 * exist for us to get here at all */
486 if (!(pstate & PS_SAW_COMMAND)) {
487 if (msgCount == 0)
488 goto jateof;
489 goto jhitit;
492 /* Just find the next good message after dot, no wraparound */
493 if (mb.mb_threaded == 0) {
494 for (mp = dot + !!(pstate & PS_DID_PRINT_DOT);
495 PTRCMP(mp, <, message + msgCount); ++mp)
496 if (!(mp->m_flag & MMNORM))
497 break;
498 } else {
499 /* TODO The threading code had some bugs that caused crashes.
500 * TODO The last thing (before the deep look) happens here,
501 * TODO so let's not trust PS_DID_PRINT_DOT but check & hope it fixes */
502 if ((mp = dot) != NULL && (pstate & PS_DID_PRINT_DOT))
503 mp = next_in_thread(mp);
504 while (mp != NULL && (mp->m_flag & MMNORM))
505 mp = next_in_thread(mp);
507 if (mp == NULL || PTRCMP(mp, >=, message + msgCount)) {
508 jateof:
509 printf(_("At EOF\n"));
510 rv = 0;
511 goto jleave;
513 setdot(mp);
515 /* Print dot */
516 jhitit:
517 list[0] = (int)PTR2SIZE(dot - message + 1);
518 list[1] = 0;
519 rv = c_type(list);
520 jleave:
521 NYD_LEAVE;
522 return rv;
525 FL int
526 c_dotmove(void *v)
528 char const *args;
529 int msgvec[2], rv;
530 NYD_ENTER;
532 if (*(args = v) == '\0' || args[1] != '\0') {
533 jerr:
534 n_err(_("Synopsis: dotmove: up <-> or down <+> by one message\n"));
535 rv = 1;
536 } else switch (args[0]) {
537 case '-':
538 case '+':
539 if (msgCount == 0) {
540 printf(_("At EOF\n"));
541 rv = 0;
542 } else if (getmsglist(UNCONST(/*TODO*/ args), msgvec, 0) > 0) {
543 setdot(message + msgvec[0] - 1);
544 msgvec[1] = 0;
545 rv = c_headers(msgvec);
546 } else
547 rv = 1;
548 break;
549 default:
550 goto jerr;
552 NYD_LEAVE;
553 return rv;
556 FL int
557 c_save(void *v)
559 char *str = v;
560 int rv;
561 NYD_ENTER;
563 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
564 NYD_LEAVE;
565 return rv;
568 FL int
569 c_Save(void *v)
571 char *str = v;
572 int rv;
573 NYD_ENTER;
575 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
576 NYD_LEAVE;
577 return rv;
580 FL int
581 c_copy(void *v)
583 char *str = v;
584 int rv;
585 NYD_ENTER;
587 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
588 NYD_LEAVE;
589 return rv;
592 FL int
593 c_Copy(void *v)
595 char *str = v;
596 int rv;
597 NYD_ENTER;
599 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
600 NYD_LEAVE;
601 return rv;
604 FL int
605 c_move(void *v)
607 char *str = v;
608 int rv;
609 NYD_ENTER;
611 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
612 NYD_LEAVE;
613 return rv;
616 FL int
617 c_Move(void *v)
619 char *str = v;
620 int rv;
621 NYD_ENTER;
623 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
624 NYD_LEAVE;
625 return rv;
628 FL int
629 c_decrypt(void *v)
631 char *str = v;
632 int rv;
633 NYD_ENTER;
635 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
636 NYD_LEAVE;
637 return rv;
640 FL int
641 c_Decrypt(void *v)
643 char *str = v;
644 int rv;
645 NYD_ENTER;
647 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
648 NYD_LEAVE;
649 return rv;
652 FL int
653 c_write(void *v)
655 char *str = v;
656 int rv;
657 NYD_ENTER;
659 if (str == NULL || *str == '\0')
660 str = savestr("/dev/null");
661 rv = save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0);
662 NYD_LEAVE;
663 return rv;
666 FL int
667 c_delete(void *v)
669 int *msgvec = v;
670 NYD_ENTER;
672 delm(msgvec);
673 NYD_LEAVE;
674 return 0;
677 FL int
678 c_deltype(void *v)
680 int list[2], rv = 0, *msgvec = v, lastdot;
681 NYD_ENTER;
683 lastdot = (int)PTR2SIZE(dot - message + 1);
684 if (delm(msgvec) >= 0) {
685 list[0] = (int)PTR2SIZE(dot - message + 1);
686 if (list[0] > lastdot) {
687 touch(dot);
688 list[1] = 0;
689 rv = c_type(list);
690 goto jleave;
692 printf(_("At EOF\n"));
693 } else
694 printf(_("No more messages\n"));
695 jleave:
696 NYD_LEAVE;
697 return rv;
700 FL int
701 c_undelete(void *v)
703 int *msgvec = v, *ip;
704 struct message *mp;
705 NYD_ENTER;
707 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
708 ++ip) {
709 mp = message + *ip - 1;
710 touch(mp);
711 setdot(mp);
712 if (mp->m_flag & (MDELETED | MSAVED))
713 mp->m_flag &= ~(MDELETED | MSAVED);
714 else
715 mp->m_flag &= ~MDELETED;
717 NYD_LEAVE;
718 return 0;
721 FL int
722 c_retfield(void *v)
724 char **list = v;
725 int rv;
726 NYD_ENTER;
728 rv = ignore1(list, ignore + 1, "retained");
729 NYD_LEAVE;
730 return rv;
733 FL int
734 c_igfield(void *v)
736 char **list = v;
737 int rv;
738 NYD_ENTER;
740 rv = ignore1(list, ignore, "ignored");
741 NYD_LEAVE;
742 return rv;
745 FL int
746 c_saveretfield(void *v)
748 char **list = v;
749 int rv;
750 NYD_ENTER;
752 rv = ignore1(list, saveignore + 1, "retained");
753 NYD_LEAVE;
754 return rv;
757 FL int
758 c_saveigfield(void *v)
760 char **list = v;
761 int rv;
762 NYD_ENTER;
764 rv = ignore1(list, saveignore, "ignored");
765 NYD_LEAVE;
766 return rv;
769 FL int
770 c_fwdretfield(void *v)
772 char **list = v;
773 int rv;
774 NYD_ENTER;
776 rv = ignore1(list, fwdignore + 1, "retained");
777 NYD_LEAVE;
778 return rv;
781 FL int
782 c_fwdigfield(void *v)
784 char **list = v;
785 int rv;
786 NYD_ENTER;
788 rv = ignore1(list, fwdignore, "ignored");
789 NYD_LEAVE;
790 return rv;
793 FL int
794 c_unignore(void *v)
796 int rv;
797 NYD_ENTER;
799 rv = _unignore((char**)v, ignore, "ignored");
800 NYD_LEAVE;
801 return rv;
804 FL int
805 c_unretain(void *v)
807 int rv;
808 NYD_ENTER;
810 rv = _unignore((char**)v, ignore + 1, "retained");
811 NYD_LEAVE;
812 return rv;
815 FL int
816 c_unsaveignore(void *v)
818 int rv;
819 NYD_ENTER;
821 rv = _unignore((char**)v, saveignore, "ignored");
822 NYD_LEAVE;
823 return rv;
826 FL int
827 c_unsaveretain(void *v)
829 int rv;
830 NYD_ENTER;
832 rv = _unignore((char**)v, saveignore + 1, "retained");
833 NYD_LEAVE;
834 return rv;
837 FL int
838 c_unfwdignore(void *v)
840 int rv;
841 NYD_ENTER;
843 rv = _unignore((char**)v, fwdignore, "ignored");
844 NYD_LEAVE;
845 return rv;
848 FL int
849 c_unfwdretain(void *v)
851 int rv;
852 NYD_ENTER;
854 rv = _unignore((char**)v, fwdignore + 1, "retained");
855 NYD_LEAVE;
856 return rv;
859 /* s-it-mode */