Added -clobber switch to mhstore(1) [Bug #11160].
[mmh] / uip / mhstoresbr.c
index 2e88df2..f5e3347 100644 (file)
@@ -2,8 +2,6 @@
 /*
  * mhstoresbr.c -- routines to save/store the contents of MIME messages
  *
- * $Id$
- *
  * This code is Copyright (c) 2002, by the authors of nmh.  See the
  * COPYRIGHT file in the root directory of the nmh distribution for
  * complete copyright information.
 #include <h/signals.h>
 #include <h/md5.h>
 #include <errno.h>
-#include <setjmp.h>
 #include <signal.h>
 #include <h/mts.h>
 #include <h/tws.h>
 #include <h/mime.h>
 #include <h/mhparse.h>
+#include <h/utils.h>
 
-extern int errno;
 
 /*
  * The list of top-level contents to display
@@ -51,7 +48,6 @@ typedef int (*qsort_comp) (const void *, const void *);
 /* mhmisc.c */
 int part_ok (CT, int);
 int type_ok (CT, int);
-int make_intermediates (char *);
 void flush_errors (void);
 
 /* mhshowsbr.c */
@@ -75,12 +71,11 @@ static int store_external (CT);
 static int ct_compar (CT *, CT *);
 static int store_content (CT, CT);
 static int output_content_file (CT, int);
-static int check_folder (char *);
 static int output_content_folder (char *, char *);
 static int parse_format_string (CT, char *, char *, int, char *);
 static void get_storeproc (CT);
 static int copy_some_headers (FILE *, CT);
-
+static char *clobber_check (char *);
 
 /*
  * Main entry point to store content
@@ -97,9 +92,7 @@ store_all_messages (CT *cts)
      * Check for the directory in which to
      * store any contents.
      */
-    if (autosw)
-       dir = getcpy (cwd);
-    else if ((cp = context_find (nmhstorage)) && *cp)
+    if ((cp = context_find (nmhstorage)) && *cp)
        dir = getcpy (cp);
     else
        dir = getcpy (cwd);
@@ -226,8 +219,8 @@ store_application (CT ct)
 
        for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
            /* check for "type=tar" attribute */
-           if (!strcasecmp (*ap, "type")) {
-               if (strcasecmp (*ep, "tar"))
+           if (!mh_strcasecmp (*ap, "type")) {
+               if (mh_strcasecmp (*ep, "tar"))
                    break;
 
                tarP = 1;
@@ -235,14 +228,14 @@ store_application (CT ct)
            }
 
            /* check for "conversions=compress" attribute */
-           if ((!strcasecmp (*ap, "conversions") || !strcasecmp (*ap, "x-conversions"))
-               && (!strcasecmp (*ep, "compress") || !strcasecmp (*ep, "x-compress"))) {
+           if ((!mh_strcasecmp (*ap, "conversions") || !mh_strcasecmp (*ap, "x-conversions"))
+               && (!mh_strcasecmp (*ep, "compress") || !mh_strcasecmp (*ep, "x-compress"))) {
                zP = 1;
                continue;
            }
            /* check for "conversions=gzip" attribute */
-           if ((!strcasecmp (*ap, "conversions") || !strcasecmp (*ap, "x-conversions"))
-               && (!strcasecmp (*ep, "gzip") || !strcasecmp (*ep, "x-gzip"))) {
+           if ((!mh_strcasecmp (*ap, "conversions") || !mh_strcasecmp (*ap, "x-conversions"))
+               && (!mh_strcasecmp (*ep, "gzip") || !mh_strcasecmp (*ep, "x-gzip"))) {
                gzP = 1;
                continue;
            }
@@ -483,7 +476,7 @@ ct_compar (CT *a, CT *b)
 static int
 store_content (CT ct, CT p)
 {
-    int appending = 0, msgnum;
+    int appending = 0, msgnum = 0;
     int is_partial = 0, first_partial = 0;
     int last_partial = 0;
     char *cp, buffer[BUFSIZ];
@@ -561,18 +554,17 @@ store_content (CT ct, CT p)
        char *tmpfilenam, *folder;
 
        /* Store content in temporary file for now */
-       tmpfilenam = m_scratch ("", invo_name);
+       tmpfilenam = m_mktemp(invo_name, NULL, NULL);
        ct->c_storage = add (tmpfilenam, NULL);
 
        /* Get the folder name */
        if (cp[1])
-           folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
+           folder = pluspath (cp);
        else
            folder = getfolder (1);
 
        /* Check if folder exists */
-       if (check_folder (folder) == NOTOK)
-           return NOTOK;
+       create_folder(m_mailpath(folder), 0, exit);
 
        /* Record the folder name */
        ct->c_folder = add (folder, NULL);
@@ -597,7 +589,9 @@ store_content (CT ct, CT p)
        return show_content_aux (ct, 1, 0, buffer + 1, dir);
 
     /* record the filename */
-    ct->c_storage = add (buffer, NULL);
+    if ((ct->c_storage = clobber_check (add (buffer, NULL))) == NULL) {
+      return NOTOK;
+    }
 
 got_filename:
     /* flush the output stream */
@@ -864,47 +858,6 @@ losing:
 
 
 /*
- * Check if folder exists, and create
- * if necessary.
- */
-
-static int
-check_folder (char *folder)
-{
-    char *folderdir;
-    struct stat st;
-
-    /* expand path to the folder */
-    folderdir = m_mailpath (folder);
-
-    /* Check if folder exists */
-    if (stat (folderdir, &st) == NOTOK) {
-       int answer;
-       char *ep;
-
-       if (errno != ENOENT) {
-           advise (folderdir, "error on folder");
-           return NOTOK;
-       }
-
-       ep = concat ("Create folder \"", folderdir, "\"? ", NULL);
-       answer = getanswer (ep);
-       free (ep);
-
-       if (!answer)
-           return NOTOK;
-
-       if (!makedir (folderdir)) {
-           advise (NULL, "unable to create folder %s", folderdir);
-           return NOTOK;
-       }
-    }
-
-    return OK;
-}
-
-
-/*
  * Add a file to a folder.
  *
  * Return the new message number of the file
@@ -921,7 +874,7 @@ output_content_folder (char *folder, char *filename)
     /* Read the folder. */
     if ((mp = folder_read (folder))) {
        /* Link file into folder */
-       msgnum = folder_addmsg (&mp, filename, 0, 0, 0, 0);
+       msgnum = folder_addmsg (&mp, filename, 0, 0, 0, 0, (char *)0);
     } else {
        advise (NULL, "unable to read folder %s", folder);
        return NOTOK;
@@ -1084,7 +1037,7 @@ get_storeproc (CT ct)
      * the storeproc.
      */
     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
-       if (!strcasecmp (*ap, "name")
+       if (!mh_strcasecmp (*ap, "name")
            && *(cp = *ep) != '/'
            && *cp != '.'
            && *cp != '|'
@@ -1115,13 +1068,232 @@ copy_some_headers (FILE *out, CT ct)
         * messages are not copied.
         */
        if (!uprf (hp->name, XXX_FIELD_PRF)
-               && strcasecmp (hp->name, VRSN_FIELD)
-               && strcasecmp (hp->name, "Subject")
-               && strcasecmp (hp->name, "Encrypted")
-               && strcasecmp (hp->name, "Message-ID"))
+               && mh_strcasecmp (hp->name, VRSN_FIELD)
+               && mh_strcasecmp (hp->name, "Subject")
+               && mh_strcasecmp (hp->name, "Encrypted")
+               && mh_strcasecmp (hp->name, "Message-ID"))
            fprintf (out, "%s:%s", hp->name, hp->value);
        hp = hp->next;  /* next header field */
     }
 
     return OK;
 }
+
+/******************************************************************************/
+/* -clobber support */
+
+enum clobber_policy_t {
+  NMH_CLOBBER_ALWAYS,
+  NMH_CLOBBER_AUTO,
+  NMH_CLOBBER_SUFFIX,
+  NMH_CLOBBER_ASK,
+  NMH_CLOBBER_NEVER
+};
+
+static enum clobber_policy_t clobber_policy = NMH_CLOBBER_ALWAYS;
+
+int files_not_clobbered = 0;
+
+int
+save_clobber_policy (const char *value) {
+  if (! mh_strcasecmp (value, "always")) {
+    clobber_policy = NMH_CLOBBER_ALWAYS;
+  } else if (! mh_strcasecmp (value, "auto")) {
+    clobber_policy = NMH_CLOBBER_AUTO;
+  } else if (! mh_strcasecmp (value, "suffix")) {
+    clobber_policy = NMH_CLOBBER_SUFFIX;
+  } else if (! mh_strcasecmp (value, "ask")) {
+    clobber_policy = NMH_CLOBBER_ASK;
+  } else if (! mh_strcasecmp (value, "never")) {
+    clobber_policy = NMH_CLOBBER_NEVER;
+  } else {
+    return 1;
+  }
+
+  return 0;
+}
+
+
+static char *
+next_version (char *file, enum clobber_policy_t clobber_policy) {
+  const size_t max_versions = 1000000;
+  /* 8 = log max_versions  +  one for - or .  +  one for null terminator */
+  const size_t buflen = strlen (file) + 8;
+  char *buffer = mh_xmalloc (buflen);
+  size_t version;
+
+  char *extension = NULL;
+  if (clobber_policy == NMH_CLOBBER_AUTO  &&
+      ((extension = strrchr (file, '.')) != NULL)) {
+    *extension++ = '\0';
+  }
+
+  for (version = 1; version < max_versions; ++version) {
+    struct stat st;
+
+    switch (clobber_policy) {
+      case NMH_CLOBBER_AUTO: {
+        snprintf (buffer, buflen, "%s-%ld%s%s", file, (long) version,
+                  extension == NULL  ?  ""  :  ".",
+                  extension == NULL  ?  ""  :  extension);
+        break;
+      }
+
+      case NMH_CLOBBER_SUFFIX:
+        snprintf (buffer, buflen, "%s.%ld", file, (long) version);
+        break;
+
+      default:
+        /* Should never get here. */
+        advise (NULL, "will not overwrite %s, invalid clobber policy", buffer);
+        free (buffer);
+        ++files_not_clobbered;
+        return NULL;
+    }
+
+    if (stat (buffer, &st) == NOTOK) {
+      break;
+    }
+  }
+
+  free (file);
+
+  if (version >= max_versions) {
+    advise (NULL, "will not overwrite %s, too many versions", buffer);
+    free (buffer);
+    buffer = NULL;
+    ++files_not_clobbered;
+  }
+
+  return buffer;
+}
+
+
+static char *
+clobber_check (char *original_file) {
+  /* clobber policy        return value
+   * --------------        ------------
+   *   -always                 file
+   *   -auto           file-<digits>.extension
+   *   -suffix             file.<digits>
+   *   -ask          file, 0, or another filename/path
+   *   -never                   0
+   */
+
+  char *file;
+  char *cwd = NULL;
+  int check_again;
+
+  if (clobber_policy == NMH_CLOBBER_ASK) {
+    /* Save cwd for possible use in loop below. */
+    char *slash;
+
+    cwd = add (original_file, NULL);
+    slash = strrchr (cwd, '/');
+
+    if (slash) {
+      *slash = '\0';
+    } else {
+      /* original_file wasn't a full path, which shouldn't happen. */
+      cwd = NULL;
+    }
+  }
+
+  do {
+    struct stat st;
+
+    file = original_file;
+    check_again = 0;
+
+    switch (clobber_policy) {
+      case NMH_CLOBBER_ALWAYS:
+        break;
+
+      case NMH_CLOBBER_SUFFIX:
+      case NMH_CLOBBER_AUTO:
+        if (stat (file, &st) == OK) {
+          file = next_version (original_file, clobber_policy);
+        }
+        break;
+
+      case NMH_CLOBBER_ASK:
+        if (stat (file, &st) == OK) {
+          enum answers { NMH_YES, NMH_NO, NMH_RENAME };
+          static struct swit answer[4] = {
+            { "yes", 0 }, { "no", 0 }, { "rename", 0 }, { NULL, 0 } };
+          char **ans;
+
+          if (isatty (fileno (stdin))) {
+            char *prompt =
+              concat ("Overwrite \"", file, "\" [y/n/rename]? ", NULL);
+            ans = getans (prompt, answer);
+            free (prompt);
+          } else {
+            /* Overwrite, that's what nmh used to do.  And warn. */
+            advise (NULL, "-clobber ask but no tty, so overwrite %s", file);
+            break;
+          }
+
+          switch ((enum answers) smatch (*ans, answer)) {
+            case NMH_YES:
+              break;
+            case NMH_NO:
+              free (file);
+              file = NULL;
+              ++files_not_clobbered;
+              break;
+            case NMH_RENAME: {
+              char buf[PATH_MAX];
+              printf ("Enter filename or full path of the new file: ");
+              if (fgets (buf, sizeof buf, stdin) == NULL  ||
+                  buf[0] == '\0') {
+                file = NULL;
+                ++files_not_clobbered;
+              } else {
+                char *newline = strchr (buf, '\n');
+                if (newline) {
+                  *newline = '\0';
+                }
+              }
+
+              free (file);
+
+              if (buf[0] == '/') {
+                /* Full path, use it. */
+                file = add (buf, NULL);
+              } else {
+                /* Relative path. */
+                file = cwd  ?  concat (cwd, "/", buf, NULL)  :  add (buf, NULL);
+              }
+
+              check_again = 1;
+              break;
+            }
+          }
+        }
+        break;
+
+      case NMH_CLOBBER_NEVER:
+        if (stat (file, &st) == OK) {
+          /* Keep count of files that would have been clobbered,
+             and return that as process exit status. */
+          advise (NULL, "will not overwrite %s with -clobber never", file);
+          free (file);
+          file = NULL;
+          ++files_not_clobbered;
+        }
+        break;
+    }
+
+    original_file = file;
+  } while (check_again);
+
+  if (cwd) {
+    free (cwd);
+  }
+
+  return file;
+}
+
+/* -clobber support */
+/******************************************************************************/