]> diplodocus.org Git - nmh/blob - uip/mhstoresbr.c
64b69bc00bec3fde6c84c7a83db5f27bafb678a4
[nmh] / uip / mhstoresbr.c
1 /* mhstoresbr.c -- routines to save/store the contents of MIME messages
2 *
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 #include "h/mh.h"
9 #include "sbr/read_switch_multiword.h"
10 #include "sbr/concat.h"
11 #include "sbr/smatch.h"
12 #include "sbr/r1bindex.h"
13 #include "sbr/uprf.h"
14 #include "sbr/getcpy.h"
15 #include "sbr/getfolder.h"
16 #include "sbr/folder_read.h"
17 #include "sbr/folder_free.h"
18 #include "sbr/folder_addmsg.h"
19 #include "sbr/context_find.h"
20 #include "sbr/path.h"
21 #include "sbr/error.h"
22 #include <fcntl.h>
23 #include "h/mts.h"
24 #include "h/tws.h"
25 #include "h/fmt_scan.h"
26 #include "h/mime.h"
27 #include "h/mhparse.h"
28 #include "h/utils.h"
29 #include "mhmisc.h"
30 #include "mhshowsbr.h"
31 #include "sbr/m_maildir.h"
32 #include "sbr/m_mktemp.h"
33
34 enum clobber_policy_t {
35 NMH_CLOBBER_ALWAYS = 0,
36 NMH_CLOBBER_AUTO,
37 NMH_CLOBBER_SUFFIX,
38 NMH_CLOBBER_ASK,
39 NMH_CLOBBER_NEVER
40 };
41
42 static enum clobber_policy_t clobber_policy (const char *) PURE;
43
44 struct mhstoreinfo {
45 CT *cts; /* Top-level list of contents to store. */
46 char *cwd; /* cached current directory */
47 int autosw; /* -auto enabled */
48 int verbosw; /* -verbose enabled */
49 int files_not_clobbered; /* output flag indicating that store failed
50 in order to not clobber an existing file */
51
52 /* The following must never be touched by a caller: they are for
53 internal use by the mhstoresbr functions. */
54 char *dir; /* directory in which to store contents */
55 enum clobber_policy_t clobber_policy; /* -clobber selection */
56 };
57
58 static bool use_param_as_filename(const char *p);
59
60 mhstoreinfo_t
61 mhstoreinfo_create (CT *ct, char *pwd, const char *csw, int asw, int vsw)
62 {
63 mhstoreinfo_t info;
64
65 NEW(info);
66 info->cts = ct;
67 info->cwd = pwd;
68 info->autosw = asw;
69 info->verbosw = vsw;
70 info->files_not_clobbered = 0;
71 info->dir = NULL;
72 info->clobber_policy = clobber_policy (csw);
73
74 return info;
75 }
76
77 void
78 mhstoreinfo_free (mhstoreinfo_t info)
79 {
80 free (info->cwd);
81 free (info->dir);
82 free (info);
83 }
84
85 int
86 mhstoreinfo_files_not_clobbered (const mhstoreinfo_t info)
87 {
88 return info->files_not_clobbered;
89 }
90
91
92 /*
93 * Type for a compare function for qsort. This keeps
94 * the compiler happy.
95 */
96 typedef int (*qsort_comp) (const void *, const void *);
97
98
99 /*
100 * static prototypes
101 */
102 static void store_single_message (CT, mhstoreinfo_t);
103 static int store_switch (CT, mhstoreinfo_t);
104 static int store_generic (CT, mhstoreinfo_t);
105 static int store_application (CT, mhstoreinfo_t);
106 static int store_multi (CT, mhstoreinfo_t);
107 static int store_external (CT, mhstoreinfo_t);
108 static int store_content (CT, mhstoreinfo_t);
109 static int output_content_file (CT, int);
110 static int output_content_folder (char *, char *);
111 static int parse_format_string (CT, char *, char *, int, char *);
112 static void get_storeproc (CT);
113 static char *clobber_check (char *, mhstoreinfo_t);
114
115 /*
116 * Main entry point to store content
117 * from a collection of messages.
118 */
119
120 void
121 store_all_messages (mhstoreinfo_t info)
122 {
123 CT ct, *ctp;
124 char *cp;
125
126 /*
127 * Check for the directory in which to
128 * store any contents.
129 */
130 if ((cp = context_find (nmhstorage)) && *cp)
131 info->dir = mh_xstrdup(cp);
132 else
133 info->dir = getcpy (info->cwd);
134
135 for (ctp = info->cts; *ctp; ctp++) {
136 ct = *ctp;
137 store_single_message (ct, info);
138 }
139
140 flush_errors ();
141 }
142
143
144 /*
145 * Entry point to store the content
146 * in a (single) message
147 */
148
149 static void
150 store_single_message (CT ct, mhstoreinfo_t info)
151 {
152 if (type_ok (ct, 1)) {
153 umask (ct->c_umask);
154 store_switch (ct, info);
155 if (ct->c_fp) {
156 fclose (ct->c_fp);
157 ct->c_fp = NULL;
158 }
159 if (ct->c_ceclosefnx)
160 (*ct->c_ceclosefnx) (ct);
161 }
162 }
163
164
165 /*
166 * Switching routine to store different content types
167 */
168
169 static int
170 store_switch (CT ct, mhstoreinfo_t info)
171 {
172 switch (ct->c_type) {
173 case CT_MULTIPART:
174 return store_multi (ct, info);
175
176 case CT_MESSAGE:
177 switch (ct->c_subtype) {
178 case MESSAGE_EXTERNAL:
179 return store_external (ct, info);
180
181 case MESSAGE_RFC822:
182 default:
183 return store_generic (ct, info);
184 }
185
186 case CT_APPLICATION:
187 default:
188 return store_application (ct, info);
189
190 case CT_TEXT:
191 case CT_AUDIO:
192 case CT_IMAGE:
193 case CT_VIDEO:
194 return store_generic (ct, info);
195 }
196
197 return OK; /* NOT REACHED */
198 }
199
200
201 /*
202 * Generic routine to store a MIME content.
203 * (audio, video, image, text, message/rfc822)
204 */
205
206 static int
207 store_generic (CT ct, mhstoreinfo_t info)
208 {
209 /*
210 * Check if the content specifies a filename.
211 * Don't bother with this for type "message"
212 * (only "message/rfc822" will use store_generic).
213 */
214 if (info->autosw && ct->c_type != CT_MESSAGE)
215 get_storeproc (ct);
216
217 return store_content (ct, info);
218 }
219
220
221 /*
222 * Store content of type "application"
223 */
224
225 static int
226 store_application (CT ct, mhstoreinfo_t info)
227 {
228 CI ci = &ct->c_ctinfo;
229
230 /* Check if the content specifies a filename */
231 if (info->autosw)
232 get_storeproc (ct);
233
234 /*
235 * If storeproc is not defined, and the content is type
236 * "application/octet-stream", we also check for various
237 * attribute/value pairs which specify if this a tar file.
238 */
239 if (!ct->c_storeproc && ct->c_subtype == APPLICATION_OCTETS) {
240 bool tarP = false;
241 bool zP = false;
242 bool gzP = false;
243 char *cp;
244
245 if ((cp = get_param(ci->ci_first_pm, "type", ' ', 1))) {
246 if (strcasecmp (cp, "tar") == 0)
247 tarP = true;
248 }
249
250 /* check for "conversions=compress" attribute */
251 if ((cp = get_param(ci->ci_first_pm, "conversions", ' ', 1)) ||
252 (cp = get_param(ci->ci_first_pm, "x-conversions", ' ', 1))) {
253 if (strcasecmp (cp, "compress") == 0 ||
254 strcasecmp (cp, "x-compress") == 0) {
255 zP = true;
256 }
257 if (strcasecmp (cp, "gzip") == 0 ||
258 strcasecmp (cp, "x-gzip") == 0) {
259 gzP = true;
260 }
261 }
262
263 if (tarP) {
264 ct->c_showproc = add (zP ? "%euncompress | tar tvf -"
265 : (gzP ? "%egzip -dc | tar tvf -"
266 : "%etar tvf -"), NULL);
267 if (!ct->c_storeproc) {
268 if (info->autosw) {
269 ct->c_storeproc = add (zP ? "| uncompress | tar xvpf -"
270 : (gzP ? "| gzip -dc | tar xvpf -"
271 : "| tar xvpf -"), NULL);
272 ct->c_umask = 0022;
273 } else {
274 ct->c_storeproc= add (zP ? "%m%P.tar.Z"
275 : (gzP ? "%m%P.tar.gz"
276 : "%m%P.tar"), NULL);
277 }
278 }
279 }
280 }
281
282 return store_content (ct, info);
283 }
284
285
286 /*
287 * Store the content of a multipart message
288 */
289
290 static int
291 store_multi (CT ct, mhstoreinfo_t info)
292 {
293 int result;
294 struct multipart *m = (struct multipart *) ct->c_ctparams;
295 struct part *part;
296
297 result = NOTOK;
298 for (part = m->mp_parts; part; part = part->mp_next) {
299 CT p = part->mp_part;
300
301 if (part_ok (p) && type_ok (p, 1)) {
302 if (ct->c_storage) {
303 /* Support mhstore -outfile. The MIME parser doesn't
304 load c_storage, so we know that p->c_storage is
305 NULL here. */
306 p->c_storage = mh_xstrdup(ct->c_storage);
307 }
308 result = store_switch (p, info);
309
310 if (result == OK && ct->c_subtype == MULTI_ALTERNATE)
311 break;
312 }
313 }
314
315 return result;
316 }
317
318
319 /*
320 * Store content from a message of type "message/external".
321 */
322
323 static int
324 store_external (CT ct, mhstoreinfo_t info)
325 {
326 int result = NOTOK;
327 struct exbody *e = (struct exbody *) ct->c_ctparams;
328 CT p = e->eb_content;
329
330 if (!type_ok (p, 1))
331 return OK;
332
333 /*
334 * Check if the parameters for the external body
335 * specified a filename.
336 */
337 if (info->autosw) {
338 char *cp;
339
340 if ((cp = e->eb_name) && use_param_as_filename(cp)) {
341 if (!ct->c_storeproc)
342 ct->c_storeproc = mh_xstrdup(cp);
343 if (!p->c_storeproc)
344 p->c_storeproc = mh_xstrdup(cp);
345 }
346 }
347
348 /*
349 * Since we will let the Content structure for the
350 * external body substitute for the current content,
351 * we temporarily change its partno (number inside
352 * multipart), so everything looks right.
353 */
354 p->c_partno = ct->c_partno;
355
356 /* we probably need to check if content is really there */
357 if (ct->c_storage) {
358 /* Support mhstore -outfile. The MIME parser doesn't load
359 c_storage, so we know that p->c_storage is NULL here. */
360 p->c_storage = mh_xstrdup(ct->c_storage);
361 }
362 result = store_switch (p, info);
363
364 p->c_partno = NULL;
365 return result;
366 }
367
368
369 /*
370 * Store contents of a message or message part to
371 * a folder, a file, the standard output, or pass
372 * the contents to a command.
373 */
374
375 static int
376 store_content (CT ct, mhstoreinfo_t info)
377 {
378 bool appending = false;
379 int msgnum = 0;
380 char *cp, buffer[BUFSIZ];
381
382 /*
383 * Get storage formatting string.
384 *
385 * 1) If we have storeproc defined, then use that
386 * 2) Else check for a mhn-store-<type>/<subtype> entry
387 * 3) Else check for a mhn-store-<type> entry
388 * 4) Else if content is "message", use "+" (current folder)
389 * 5) Else use string "%m%P.%s".
390 */
391 if ((cp = ct->c_storeproc) == NULL || *cp == '\0') {
392 CI ci = &ct->c_ctinfo;
393
394 cp = context_find_by_type ("store", ci->ci_type, ci->ci_subtype);
395 if (cp == NULL) {
396 cp = ct->c_type == CT_MESSAGE ? "+" : "%m%P.%s";
397 }
398 }
399
400 if (! ct->c_storage) {
401 /*
402 * Check the beginning of storage formatting string
403 * to see if we are saving content to a folder.
404 */
405 if (*cp == '+' || *cp == '@') {
406 char *tmpfilenam, *folder;
407
408 /* Store content in temporary file for now */
409 if ((tmpfilenam = m_mktemp(invo_name, NULL, NULL)) == NULL) {
410 die("unable to create temporary file in %s",
411 get_temp_dir());
412 }
413 ct->c_storage = mh_xstrdup(tmpfilenam);
414
415 /* Get the folder name */
416 if (cp[1])
417 folder = pluspath (cp);
418 else
419 folder = getfolder (1);
420
421 /* Check if folder exists */
422 create_folder(m_mailpath(folder), 0, exit);
423
424 /* Record the folder name */
425 ct->c_folder = mh_xstrdup(folder);
426
427 if (cp[1])
428 free (folder);
429
430 goto got_filename;
431 }
432
433 /*
434 * Parse and expand the storage formatting string
435 * in `cp' into `buffer'.
436 */
437 parse_format_string (ct, cp, buffer, sizeof(buffer), info->dir);
438
439 /*
440 * If formatting begins with '|' or '!', then pass
441 * content to standard input of a command and return.
442 */
443 if (buffer[0] == '|' || buffer[0] == '!')
444 return show_content_aux (ct, 0, buffer + 1, info->dir, NULL);
445
446 /* record the filename */
447 if ((ct->c_storage = clobber_check (mh_xstrdup(buffer), info)) ==
448 NULL) {
449 return NOTOK;
450 }
451 } else {
452 /* The output filename was explicitly specified, so use it. */
453 if ((ct->c_storage = clobber_check (ct->c_storage, info)) ==
454 NULL) {
455 return NOTOK;
456 }
457 }
458
459 got_filename:
460 /* flush the output stream */
461 fflush (stdout);
462
463 /* Now save or append the content to a file */
464 if (output_content_file (ct, appending) == NOTOK)
465 return NOTOK;
466
467 /*
468 * If necessary, link the file into a folder and remove
469 * the temporary file.
470 */
471 if (ct->c_folder) {
472 msgnum = output_content_folder (ct->c_folder, ct->c_storage);
473 (void) m_unlink (ct->c_storage);
474 if (msgnum == NOTOK)
475 return NOTOK;
476 }
477
478 if (info->verbosw) {
479 /*
480 * Now print out the name/number of the message
481 * that we are storing.
482 */
483 fprintf (stderr, "storing message %s", ct->c_file);
484 if (ct->c_partno)
485 fprintf (stderr, " part %s", ct->c_partno);
486
487 /*
488 * We now print the name of the file, folder, and/or message
489 * to which we are storing the content.
490 */
491 if (ct->c_folder) {
492 fprintf (stderr, " to folder %s as message %d\n", ct->c_folder,
493 msgnum);
494 } else if (!strcmp(ct->c_storage, "-")) {
495 fprintf (stderr, " to stdout\n");
496 } else {
497 int cwdlen = strlen (info->cwd);
498
499 fprintf (stderr, " as file %s\n",
500 !has_prefix(ct->c_storage, info->cwd)
501 || ct->c_storage[cwdlen] != '/'
502 ? ct->c_storage : ct->c_storage + cwdlen + 1);
503 }
504 }
505
506 return OK;
507 }
508
509
510 /*
511 * Output content to a file
512 */
513
514 static int
515 output_content_file (CT ct, int appending)
516 {
517 char *file, buffer[BUFSIZ];
518 long pos, last;
519 FILE *fp;
520
521 /*
522 * If the pathname is absolute, make sure
523 * all the relevant directories exist.
524 */
525 if (strchr(ct->c_storage, '/')
526 && make_intermediates (ct->c_storage) == NOTOK)
527 return NOTOK;
528
529 if (ct->c_encoding != CE_7BIT) {
530 int cc, fd;
531
532 if (!ct->c_ceopenfnx) {
533 inform("don't know how to decode part %s of message %s",
534 ct->c_partno, ct->c_file);
535 return NOTOK;
536 }
537
538 file = appending || !strcmp (ct->c_storage, "-") ? NULL
539 : ct->c_storage;
540 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
541 return NOTOK;
542 if (!strcmp (file, ct->c_storage)) {
543 (*ct->c_ceclosefnx) (ct);
544 return OK;
545 }
546
547 /*
548 * Send to standard output
549 */
550 if (!strcmp (ct->c_storage, "-")) {
551 int gd;
552
553 if ((gd = dup (fileno (stdout))) == NOTOK) {
554 advise ("stdout", "unable to dup");
555 losing:
556 (*ct->c_ceclosefnx) (ct);
557 return NOTOK;
558 }
559 if ((fp = fdopen (gd, appending ? "a" : "w")) == NULL) {
560 advise ("stdout", "unable to fdopen (%d, \"%s\") from", gd,
561 appending ? "a" : "w");
562 close (gd);
563 goto losing;
564 }
565 } else {
566 /*
567 * Open output file
568 */
569 if ((fp = fopen (ct->c_storage, appending ? "a" : "w")) == NULL) {
570 advise (ct->c_storage, "unable to fopen for %s",
571 appending ? "appending" : "writing");
572 goto losing;
573 }
574 }
575
576 for (;;) {
577 switch (cc = read (fd, buffer, sizeof(buffer))) {
578 case NOTOK:
579 advise (file, "error reading content from");
580 break;
581
582 case OK:
583 break;
584
585 default:
586 if ((int) fwrite (buffer, sizeof(*buffer), cc, fp) < cc) {
587 advise ("output_content_file", "fwrite");
588 }
589 continue;
590 }
591 break;
592 }
593
594 (*ct->c_ceclosefnx) (ct);
595
596 if (cc != NOTOK && fflush (fp))
597 advise (ct->c_storage, "error writing to");
598
599 fclose (fp);
600
601 return cc == NOTOK ? NOTOK : OK;
602 }
603
604 if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
605 advise (ct->c_file, "unable to open for reading");
606 return NOTOK;
607 }
608
609 pos = ct->c_begin;
610 last = ct->c_end;
611 fseek (ct->c_fp, pos, SEEK_SET);
612
613 if (!strcmp (ct->c_storage, "-")) {
614 int gd;
615
616 if ((gd = dup (fileno (stdout))) == NOTOK) {
617 advise ("stdout", "unable to dup");
618 return NOTOK;
619 }
620 if ((fp = fdopen (gd, appending ? "a" : "w")) == NULL) {
621 advise ("stdout", "unable to fdopen (%d, \"%s\") from", gd,
622 appending ? "a" : "w");
623 close (gd);
624 return NOTOK;
625 }
626 } else {
627 if ((fp = fopen (ct->c_storage, appending ? "a" : "w")) == NULL) {
628 advise (ct->c_storage, "unable to fopen for %s",
629 appending ? "appending" : "writing");
630 return NOTOK;
631 }
632 }
633
634 while (fgets (buffer, sizeof buffer, ct->c_fp)) {
635 if ((pos += strlen (buffer)) > last) {
636 int diff;
637
638 diff = strlen (buffer) - (pos - last);
639 if (diff >= 0)
640 buffer[diff] = '\0';
641 }
642 fputs (buffer, fp);
643 if (pos >= last)
644 break;
645 }
646
647 if (fflush (fp))
648 advise (ct->c_storage, "error writing to");
649
650 fclose (fp);
651 fclose (ct->c_fp);
652 ct->c_fp = NULL;
653 return OK;
654 }
655
656
657 /*
658 * Add a file to a folder.
659 *
660 * Return the new message number of the file
661 * when added to the folder. Return -1, if
662 * there is an error.
663 */
664
665 static int
666 output_content_folder (char *folder, char *filename)
667 {
668 int msgnum;
669 struct msgs *mp;
670
671 /* Read the folder. */
672 if (!(mp = folder_read(folder, 0))) {
673 inform("unable to read folder %s", folder);
674 return NOTOK;
675 }
676 /* Link file into folder */
677 msgnum = folder_addmsg(&mp, filename, 0, 0, 0, 0, NULL);
678
679 /* free folder structure */
680 folder_free (mp);
681
682 /*
683 * Return msgnum. We are relying on the fact that
684 * msgnum will be -1, if folder_addmsg() had an error.
685 */
686 return msgnum;
687 }
688
689
690 /*
691 * Parse and expand the storage formatting string
692 * pointed to by "cp" into "buffer".
693 */
694
695 static int
696 parse_format_string (CT ct, char *cp, char *buffer, int buflen, char *dir)
697 {
698 int len;
699 char *bp;
700 CI ci = &ct->c_ctinfo;
701
702 /*
703 * If storage string is "-", just copy it, and
704 * return (send content to standard output).
705 */
706 if (cp[0] == '-' && cp[1] == '\0') {
707 strncpy (buffer, cp, buflen - 1);
708 return 0;
709 }
710
711 bp = buffer;
712 bp[0] = '\0';
713
714 /*
715 * If formatting string is a pathname that doesn't
716 * begin with '/', then preface the path with the
717 * appropriate directory.
718 */
719 if (*cp != '/' && *cp != '|' && *cp != '!') {
720 if (!strcmp(dir, "/"))
721 dir = ""; /* Don't start with "//". */
722 snprintf (bp, buflen, "%s/", dir);
723 len = strlen (bp);
724 bp += len;
725 buflen -= len;
726 }
727
728 for (; *cp; cp++) {
729
730 /* We are processing a storage escape */
731 if (*cp == '%') {
732 switch (*++cp) {
733 case 'a':
734 /*
735 * Insert parameters from Content-Type.
736 * This is only valid for '|' commands.
737 */
738 if (buffer[0] != '|' && buffer[0] != '!') {
739 *bp++ = *--cp;
740 *bp = '\0';
741 buflen--;
742 continue;
743 }
744 {
745 PM pm;
746 char *s = "";
747
748 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
749 snprintf (bp, buflen, "%s%s=\"%s\"", s,
750 pm->pm_name, get_param_value(pm, '?'));
751 len = strlen (bp);
752 bp += len;
753 buflen -= len;
754 s = " ";
755 }
756 }
757 break;
758
759 case 'm':
760 /* insert message number */
761 snprintf (bp, buflen, "%s", r1bindex (ct->c_file, '/'));
762 break;
763
764 case 'P':
765 /* insert part number with leading dot */
766 if (ct->c_partno)
767 snprintf (bp, buflen, ".%s", ct->c_partno);
768 break;
769
770 case 'p':
771 /* insert part number without leading dot */
772 if (ct->c_partno)
773 strncpy (bp, ct->c_partno, buflen);
774 break;
775
776 case 't':
777 /* insert content type */
778 strncpy (bp, ci->ci_type, buflen);
779 break;
780
781 case 's':
782 /* insert content subtype */
783 strncpy (bp, ci->ci_subtype, buflen);
784 break;
785
786 case '%':
787 /* insert the character % */
788 goto raw;
789
790 default:
791 *bp++ = *--cp;
792 *bp = '\0';
793 buflen--;
794 continue;
795 }
796
797 /* Advance bp and decrement buflen */
798 len = strlen (bp);
799 bp += len;
800 buflen -= len;
801
802 } else {
803 raw:
804 *bp++ = *cp;
805 *bp = '\0';
806 buflen--;
807 }
808 }
809
810 return 0;
811 }
812
813
814 /*
815 * Check if the content specifies a filename
816 * in its MIME parameters.
817 */
818
819 static void
820 get_storeproc (CT ct)
821 {
822 char *cp;
823 CI ci;
824
825 /*
826 * If the storeproc has already been defined,
827 * we just return (for instance, if this content
828 * is part of a "message/external".
829 */
830 if (ct->c_storeproc)
831 return;
832
833 /*
834 * If there's a Content-Disposition header and it has a filename,
835 * use that (RFC-2183).
836 */
837 if (ct->c_dispo) {
838 if ((cp = get_param(ct->c_dispo_first, "filename", '_', 0)) &&
839 use_param_as_filename(cp)) {
840 ct->c_storeproc = mh_xstrdup(cp);
841 free(cp);
842 return;
843 }
844 free(cp);
845 }
846
847 /*
848 * Check the attribute/value pairs, for the attribute "name".
849 * If found, do a few sanity checks and copy the value into
850 * the storeproc.
851 */
852 ci = &ct->c_ctinfo;
853 if ((cp = get_param(ci->ci_first_pm, "name", '_', 0)) &&
854 use_param_as_filename(cp)) {
855 ct->c_storeproc = mh_xstrdup(cp);
856
857 }
858 free(cp);
859 }
860
861
862 /******************************************************************************/
863 /* -clobber support */
864
865 static enum clobber_policy_t
866 clobber_policy (const char *value)
867 {
868 if (value == NULL || ! strcasecmp (value, "always")) {
869 return NMH_CLOBBER_ALWAYS;
870 }
871 if (! strcasecmp (value, "auto")) {
872 return NMH_CLOBBER_AUTO;
873 }
874 if (! strcasecmp (value, "suffix")) {
875 return NMH_CLOBBER_SUFFIX;
876 }
877 if (! strcasecmp (value, "ask")) {
878 return NMH_CLOBBER_ASK;
879 }
880 if (! strcasecmp (value, "never")) {
881 return NMH_CLOBBER_NEVER;
882 }
883
884 die("invalid argument, %s, to clobber", value);
885 }
886
887
888 static char *
889 next_version (char *file, enum clobber_policy_t clobber_policy)
890 {
891 const size_t max_versions = 1000000;
892 /* 8 = log max_versions + one for - or . + one for null terminator */
893 const size_t buflen = strlen (file) + 8;
894 char *buffer = mh_xmalloc (buflen);
895 size_t version;
896
897 char *extension = NULL;
898 if (clobber_policy == NMH_CLOBBER_AUTO &&
899 ((extension = strrchr (file, '.')) != NULL)) {
900 *extension++ = '\0';
901 }
902
903 for (version = 1; version < max_versions; ++version) {
904 int fd;
905
906 switch (clobber_policy) {
907 case NMH_CLOBBER_AUTO: {
908 snprintf (buffer, buflen, "%s-%ld%s%s", file, (long) version,
909 extension == NULL ? "" : ".",
910 extension == NULL ? "" : extension);
911 break;
912 }
913
914 case NMH_CLOBBER_SUFFIX:
915 snprintf (buffer, buflen, "%s.%ld", file, (long) version);
916 break;
917
918 default:
919 /* Should never get here. */
920 inform("will not overwrite %s, invalid clobber policy", buffer);
921 free (buffer);
922 return NULL;
923 }
924
925 /* Actually (try to) create the file here to avoid a race
926 condition on file naming + creation. This won't solve the
927 problem with old NFS that doesn't support O_EXCL, though.
928 Let the umask strip off permissions from 0666 as desired.
929 That's what fopen () would do if it was creating the file. */
930 if ((fd = open (buffer, O_CREAT | O_EXCL,
931 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
932 S_IROTH | S_IWOTH)) >= 0) {
933 close (fd);
934 break;
935 }
936 }
937
938 free (file);
939
940 if (version >= max_versions) {
941 inform("will not overwrite %s, too many versions", buffer);
942 free (buffer);
943 buffer = NULL;
944 }
945
946 return buffer;
947 }
948
949
950 static char *
951 clobber_check (char *original_file, mhstoreinfo_t info)
952 {
953 /* clobber policy return value
954 * -------------- ------------
955 * -always original_file
956 * -auto original_file-<digits>.extension
957 * -suffix original_file.<digits>
958 * -ask original_file, 0, or another filename/path
959 * -never 0
960 */
961
962 char *file;
963 char *cwd = NULL;
964 bool check_again;
965
966 if (! strcmp (original_file, "-")) {
967 return original_file;
968 }
969
970 if (info->clobber_policy == NMH_CLOBBER_ASK) {
971 /* Save cwd for possible use in loop below. */
972 char *slash;
973
974 cwd = mh_xstrdup(original_file);
975 slash = strrchr (cwd, '/');
976
977 if (slash) {
978 *slash = '\0';
979 } else {
980 /* original_file isn't a full path, which should only happen if
981 it is -. */
982 free (cwd);
983 cwd = NULL;
984 }
985 }
986
987 do {
988 struct stat st;
989
990 file = original_file;
991 check_again = false;
992
993 switch (info->clobber_policy) {
994 case NMH_CLOBBER_ALWAYS:
995 break;
996
997 case NMH_CLOBBER_SUFFIX:
998 case NMH_CLOBBER_AUTO:
999 if (stat (file, &st) == OK) {
1000 if ((file = next_version (original_file, info->clobber_policy)) ==
1001 NULL) {
1002 ++info->files_not_clobbered;
1003 }
1004 }
1005 break;
1006
1007 case NMH_CLOBBER_ASK:
1008 if (stat (file, &st) == OK) {
1009 enum answers { NMH_YES, NMH_NO, NMH_RENAME };
1010 static struct swit answer[4] = {
1011 { "yes", 0, NMH_YES },
1012 { "no", 0, NMH_NO },
1013 { "rename", 0, NMH_RENAME },
1014 { NULL, 0, 0 } };
1015 char **ans;
1016
1017 if (isatty (fileno (stdin))) {
1018 char *prompt =
1019 concat ("Overwrite \"", file, "\" [y/n/rename]? ", NULL);
1020 ans = read_switch_multiword (prompt, answer);
1021 free (prompt);
1022 } else {
1023 /* Overwrite, that's what nmh used to do. And warn. */
1024 inform("-clobber ask but no tty, so overwrite %s", file);
1025 break;
1026 }
1027
1028 switch ((enum answers) smatch (*ans, answer)) {
1029 case NMH_YES:
1030 break;
1031 case NMH_NO:
1032 free (file);
1033 file = NULL;
1034 ++info->files_not_clobbered;
1035 break;
1036 case NMH_RENAME: {
1037 char buf[PATH_MAX];
1038 fputs("Enter filename or full path of the new file: ", stdout);
1039 if (fgets (buf, sizeof buf, stdin) == NULL ||
1040 buf[0] == '\0') {
1041 file = NULL;
1042 ++info->files_not_clobbered;
1043 } else {
1044 trim_suffix_c(buf, '\n');
1045 }
1046
1047 free (file);
1048
1049 if (buf[0] == '/') {
1050 /* Full path, use it. */
1051 file = mh_xstrdup(buf);
1052 } else {
1053 /* Relative path. */
1054 file = cwd ? concat (cwd, "/", buf, NULL) : mh_xstrdup(buf);
1055 }
1056
1057 check_again = true;
1058 break;
1059 }
1060 }
1061 }
1062 break;
1063
1064 case NMH_CLOBBER_NEVER:
1065 if (stat (file, &st) == OK) {
1066 /* Keep count of files that would have been clobbered,
1067 and return that as process exit status. */
1068 inform("will not overwrite %s with -clobber never", file);
1069 free (file);
1070 file = NULL;
1071 ++info->files_not_clobbered;
1072 }
1073 break;
1074 }
1075
1076 original_file = file;
1077 } while (check_again);
1078
1079 free (cwd);
1080
1081 return file;
1082 }
1083
1084 static bool
1085 use_param_as_filename(const char *p)
1086 {
1087 /* Preserve result of original test that considered an empty string
1088 * OK. */
1089 return !*p || (!strchr("/.|!", *p) && !strchr(p, '%'));
1090 }
1091
1092 /* -clobber support */
1093 /******************************************************************************/