From: David Levine Date: Sat, 15 Sep 2012 18:40:50 +0000 (-0500) Subject: Added -clobber switch to mhstore(1) [Bug #11160]. X-Git-Url: http://git.marmaro.de/?p=mmh;a=commitdiff_plain;h=dd1c5703125bb10f78add2488ce32e83c523cfbb Added -clobber switch to mhstore(1) [Bug #11160]. --- diff --git a/docs/README.developers b/docs/README.developers index bae6d9c..9038cd2 100644 --- a/docs/README.developers +++ b/docs/README.developers @@ -6,7 +6,19 @@ This file is intended to provide a few tips for anyone doing development on nmh. Developers who learn things "the hard way" about the nmh codebase (as opposed to local info best encoded in a comment) are encouraged to share their wisdom here. -The topics are organized alphabetically. +Following a commit checklist, the topics are organized alphabetically. + +---------------- +commit checklist +---------------- + +1. code updated? +2. test added? +3. man page and other documentation updated? +4. docs/pending-release-notes updated? +5. should commit message reference bug report? +6. update/close bug report (with commit id)? +7. notify nmh-users? ------------------------- diff --git a/docs/pending-release-notes b/docs/pending-release-notes index c286c3d..468da54 100644 --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@ -22,6 +22,8 @@ NEW FEATURES pid.time@localname, where time is in seconds, and matches previous behavior. The random style replaces the localname with some (pseudo)random bytes and uses microsecond-resolution time. +- Added -clobber switch to mhstore(1) to control overwriting of + existing files. ---------------------------- OBSOLETE/DEPRECATED FEATURES diff --git a/man/mhstore.man b/man/mhstore.man index 470714c..5efc66f 100644 --- a/man/mhstore.man +++ b/man/mhstore.man @@ -19,6 +19,8 @@ mhstore \- store contents of MIME messages into files .IR content ] \&... .RB [ \-auto " | " \-noauto ] +.RB [ \-clobber +.IR always " | " auto " | " suffix " | " ask " | " never ] .RB [ \-rcache .IR policy ] .RB [ \-wcache @@ -268,6 +270,73 @@ mhstore-store-application/PostScript: %m%P.ps .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 @@ -462,6 +531,7 @@ mhbuild(1), mhlist(1), mhshow(1), sendfiles(1) .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 ' diff --git a/test/mhstore/test-mhstore b/test/mhstore/test-mhstore index 55cea9f..421580b 100755 --- a/test/mhstore/test-mhstore +++ b/test/mhstore/test-mhstore @@ -239,6 +239,45 @@ EOF 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 </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" diff --git a/uip/mhstore.c b/uip/mhstore.c index e491dd9..fa30e03 100644 --- a/uip/mhstore.c +++ b/uip/mhstore.c @@ -47,16 +47,21 @@ static struct swit switches[] = { { "version", 0 }, #define HELPSW 12 { "help", 0 }, +#define CLOBBERSW 13 + { "clobber always|auto|suffix|ask|never", 0 }, /* * switches for debugging */ -#define DEBUGSW 13 +#define DEBUGSW 14 { "debug", -5 }, { NULL, 0 } }; +int save_clobber_policy (const char *); +extern int files_not_clobbered; + /* mhparse.c */ extern char *tmp; /* directory to place temp files */ @@ -213,6 +218,14 @@ do_cache: case NVERBSW: verbosw = 0; continue; + case CLOBBERSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (save_clobber_policy (cp)) { + adios (NULL, "invalid argument, %s, to %s", argp[-1], + argp[-2]); + } + continue; case DEBUGSW: debugsw = 1; continue; @@ -375,7 +388,7 @@ do_cache: context_save (); /* save the context file */ } - done (0); + done (files_not_clobbered); return 1; } diff --git a/uip/mhstoresbr.c b/uip/mhstoresbr.c index ed1b822..f5e3347 100644 --- a/uip/mhstoresbr.c +++ b/uip/mhstoresbr.c @@ -75,7 +75,7 @@ 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 @@ -589,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 */ @@ -1076,3 +1078,222 @@ copy_some_headers (FILE *out, CT ct) 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-.extension + * -suffix file. + * -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 */ +/******************************************************************************/