.IR content ]
\&...
.RB [ \-auto " | " \-noauto ]
+.RB [ \-clobber
+.IR always " | " auto " | " suffix " | " ask " | " never ]
.RB [ \-rcache
.IR policy ]
.RB [ \-wcache
.fi
.RE
.PP
+.SS "Overwriting Existing Files"
+The
+.B \-clobber
+switch controls whether
+.B mhstore
+should overwrite existing files. The allowed values for this switch
+and corresponding behavior when
+.B mhstore
+encounters an existing file are:
+.PP
+.RS 5
+.nf
+.ta \w'suffix 'u
+always Overwrite existing file (default)
+auto Create new file of form name-n.extension
+suffix Create new file of form name.extension.n
+ask Prompt the user to specify whether or not to overwrite
+ the existing file
+never Do not overwrite existing file
+.fi
+.RE
+.PP
+With
+.I auto
+and
+.IR suffix ,
+.I n
+is one beyond the existing file with the highest number in the same
+form.
+If a filename does not have an extension (following a '.'), then
+.I auto
+and
+.I suffix
+create a new file of the form
+.I name-n
+and
+.IR name.n ,
+respectively. With
+.I never
+and
+.IR ask ,
+the exit status of
+.B mhstore
+will be the number of files that were requested but not stored.
+.PP
+With
+.IR ask ,
+if standard input is connected to a terminal,
+the user is prompted to respond
+.IR yes ,
+.IR no ,
+or
+.I rename
+to whether the file should be overwritten. The responses
+can be abbreviated. If the user responds with
+.IR rename ,
+then
+.B mhstore
+prompts the user for the name of the new file to be created. If it is
+a relative path name (does not begin with '/'), then it is relative to
+the current directory. If it is an absolute or relative path to a
+directory that does not exist, the user will be prompted whether to
+create the directory. If standard input is not connected to a
+terminal,
+.I ask
+behaves the same as
+.IR always .
.SS "Reassembling Messages of Type message/partial"
.B mhstore
is also able to reassemble messages that have been
.RB ` +folder "' defaults to the current folder"
.RB ` msgs "' defaults to cur"
.RB ` \-noauto '
+.RB ` \-clobber\ always '
.RB ` \-nocheck '
.RB ` \-rcache\ ask '
.RB ` \-wcache\ ask '
run_test 'mhstore last' 'storing message 15 as file 15.txt'
check $expected 15.txt
+# check -clobber always
+folder +inbox 7 > /dev/null
+touch 7.txt
+cat > $expected <<EOF
+This is message number 7
+EOF
+run_test 'mhstore' 'storing message 7 as file 7.txt'
+check $expected 7.txt 'keep first'
+run_test 'mhstore -clobber always' 'storing message 7 as file 7.txt'
+check $expected 7.txt 'keep first'
+
+# check -clobber auto
+touch 7.txt
+run_test 'mhstore -clobber auto' 'storing message 7 as file 7-1.txt'
+check $expected 7-1.txt 'keep first'
+touch 7-1.txt
+run_test 'mhstore -clobber auto' 'storing message 7 as file 7-2.txt'
+check $expected 7-2.txt 'keep first'
+
+# check -clobber suffix
+run_test 'mhstore -clobber suffix' 'storing message 7 as file 7.txt.1'
+check $expected 7.txt.1 'keep first'
+touch 7.txt.1
+run_test 'mhstore -clobber suffix' 'storing message 7 as file 7.txt.2'
+check $expected 7.txt.2 'keep first'
+
+# Don't check -clobber ask because it requires connection to a
+# terminal, and this test won't always be run with one.
+
+# check -clobber never. Its exit status is the number of files not overwritten.
+run_test 'mhstore -clobber never' \
+ "mhstore: will not overwrite `pwd`/7.txt with -clobber never"
+set +e
+mhstore -clobber never >/dev/null 2>&1
+run_test "echo $?" 1
+set -e
+
+/bin/rm -f 7.txt 7-1.txt 7.txt.1
+
# check with relative nmh-storage profile component
storagedir=storagedir
dir="$MH_TEST_DIR/Mail/inbox/$storagedir"
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
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 */
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 */
+/******************************************************************************/