X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/794dab6aa5027ffaf024506140c258f6a0a9322d..ef1ba39e8dae81091b6c3e73e72825ef6edea3c6:/uip/mhstoresbr.c diff --git a/uip/mhstoresbr.c b/uip/mhstoresbr.c index 2d0fdc81..9c38a588 100644 --- a/uip/mhstoresbr.c +++ b/uip/mhstoresbr.c @@ -2,40 +2,72 @@ /* * 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 #include -#include #include -#include -#include -#include -#include -#include +#include +#include #include #include +#include + +enum clobber_policy_t { + NMH_CLOBBER_ALWAYS = 0, + NMH_CLOBBER_AUTO, + NMH_CLOBBER_SUFFIX, + NMH_CLOBBER_ASK, + NMH_CLOBBER_NEVER +}; + +static enum clobber_policy_t clobber_policy (const char *); + +struct mhstoreinfo { + CT *cts; /* Top-level list of contents to store. */ + char *cwd; /* cached current directory */ + int autosw; /* -auto enabled */ + int verbosw; /* -verbose enabled */ + int files_not_clobbered; /* output flag indicating that store failed + in order to not clobber an existing file */ + + /* The following must never be touched by a caller: they are for + internal use by the mhstoresbr functions. */ + char *dir; /* directory in which to store contents */ + enum clobber_policy_t clobber_policy; /* -clobber selection */ +}; + +mhstoreinfo_t +mhstoreinfo_create (CT *ct, char *pwd, const char *csw, int asw, int vsw) { + mhstoreinfo_t info; + + NEW(info); + info->cts = ct; + info->cwd = pwd; + info->autosw = asw; + info->verbosw = vsw; + info->files_not_clobbered = 0; + info->dir = NULL; + info->clobber_policy = clobber_policy (csw); + + return info; +} -extern int errno; - -/* - * The list of top-level contents to display - */ -extern CT *cts; - -int autosw = 0; +void +mhstoreinfo_free (mhstoreinfo_t info) { + free (info->cwd); + free (info->dir); + free (info); +} -/* - * Cache of current directory. This must be - * set before these routines are called. - */ -char *cwd; +int +mhstoreinfo_files_not_clobbered (const mhstoreinfo_t info) { + return info->files_not_clobbered; +} -/* - * The directory in which to store the contents. - */ -static char *dir; /* * Type for a compare function for qsort. This keeps @@ -45,38 +77,28 @@ typedef int (*qsort_comp) (const void *, const void *); /* mhmisc.c */ -int part_ok (CT, int); +int part_ok (CT); int type_ok (CT, int); -int make_intermediates (char *); void flush_errors (void); -/* mhshowsbr.c */ -int show_content_aux (CT, int, int, char *, char *); - -/* - * prototypes - */ -void store_all_messages (CT *); - /* * static prototypes */ -static void store_single_message (CT); -static int store_switch (CT); -static int store_generic (CT); -static int store_application (CT); -static int store_multi (CT); -static int store_partial (CT); -static int store_external (CT); +static void store_single_message (CT, mhstoreinfo_t); +static int store_switch (CT, mhstoreinfo_t); +static int store_generic (CT, mhstoreinfo_t); +static int store_application (CT, mhstoreinfo_t); +static int store_multi (CT, mhstoreinfo_t); +static int store_partial (CT, mhstoreinfo_t); +static int store_external (CT, mhstoreinfo_t); static int ct_compar (CT *, CT *); -static int store_content (CT, CT); +static int store_content (CT, CT, mhstoreinfo_t); 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 *, mhstoreinfo_t); /* * Main entry point to store content @@ -84,7 +106,7 @@ static int copy_some_headers (FILE *, CT); */ void -store_all_messages (CT *cts) +store_all_messages (mhstoreinfo_t info) { CT ct, *ctp; char *cp; @@ -93,16 +115,14 @@ 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) - dir = getcpy (cp); + if ((cp = context_find (nmhstorage)) && *cp) + info->dir = mh_xstrdup(cp); else - dir = getcpy (cwd); + info->dir = getcpy (info->cwd); - for (ctp = cts; *ctp; ctp++) { + for (ctp = info->cts; *ctp; ctp++) { ct = *ctp; - store_single_message (ct); + store_single_message (ct, info); } flush_errors (); @@ -115,11 +135,11 @@ store_all_messages (CT *cts) */ static void -store_single_message (CT ct) +store_single_message (CT ct, mhstoreinfo_t info) { if (type_ok (ct, 1)) { umask (ct->c_umask); - store_switch (ct); + store_switch (ct, info); if (ct->c_fp) { fclose (ct->c_fp); ct->c_fp = NULL; @@ -135,43 +155,34 @@ store_single_message (CT ct) */ static int -store_switch (CT ct) +store_switch (CT ct, mhstoreinfo_t info) { switch (ct->c_type) { case CT_MULTIPART: - return store_multi (ct); - break; + return store_multi (ct, info); case CT_MESSAGE: switch (ct->c_subtype) { case MESSAGE_PARTIAL: - return store_partial (ct); - break; + return store_partial (ct, info); case MESSAGE_EXTERNAL: - return store_external (ct); + return store_external (ct, info); case MESSAGE_RFC822: default: - return store_generic (ct); - break; + return store_generic (ct, info); } - break; case CT_APPLICATION: - return store_application (ct); - break; + default: + return store_application (ct, info); case CT_TEXT: case CT_AUDIO: case CT_IMAGE: case CT_VIDEO: - return store_generic (ct); - break; - - default: - adios (NULL, "unknown content type %d", ct->c_type); - break; + return store_generic (ct, info); } return OK; /* NOT REACHED */ @@ -180,21 +191,21 @@ store_switch (CT ct) /* * Generic routine to store a MIME content. - * (audio, video, image, text, message/rfc922) + * (audio, video, image, text, message/rfc822) */ static int -store_generic (CT ct) +store_generic (CT ct, mhstoreinfo_t info) { /* * Check if the content specifies a filename. * Don't bother with this for type "message" * (only "message/rfc822" will use store_generic). */ - if (autosw && ct->c_type != CT_MESSAGE) + if (info->autosw && ct->c_type != CT_MESSAGE) get_storeproc (ct); - return store_content (ct, NULL); + return store_content (ct, NULL, info); } @@ -203,13 +214,12 @@ store_generic (CT ct) */ static int -store_application (CT ct) +store_application (CT ct, mhstoreinfo_t info) { - char **ap, **ep; CI ci = &ct->c_ctinfo; /* Check if the content specifies a filename */ - if (autosw) + if (info->autosw) get_storeproc (ct); /* @@ -218,42 +228,47 @@ store_application (CT ct) * attribute/value pairs which specify if this a tar file. */ if (!ct->c_storeproc && ct->c_subtype == APPLICATION_OCTETS) { - int tarP = 0, zP = 0; - - 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")) - break; + int tarP = 0, zP = 0, gzP = 0; + char *cp; + if ((cp = get_param(ci->ci_first_pm, "type", ' ', 1))) { + if (strcasecmp (cp, "tar") == 0) tarP = 1; - continue; - } + } - /* check for "conversions=compress" attribute */ - if ((!strcasecmp (*ap, "conversions") || !strcasecmp (*ap, "x-conversions")) - && (!strcasecmp (*ep, "compress") || !strcasecmp (*ep, "x-compress"))) { + /* check for "conversions=compress" attribute */ + if ((cp = get_param(ci->ci_first_pm, "conversions", ' ', 1)) || + (cp = get_param(ci->ci_first_pm, "x-conversions", ' ', 1))) { + if (strcasecmp (cp, "compress") == 0 || + strcasecmp (cp, "x-compress") == 0) { zP = 1; - continue; + } + if (strcasecmp (cp, "gzip") == 0 || + strcasecmp (cp, "x-gzip") == 0) { + gzP = 1; } } if (tarP) { ct->c_showproc = add (zP ? "%euncompress | tar tvf -" - : "%etar tvf -", NULL); + : (gzP ? "%egzip -dc | tar tvf -" + : "%etar tvf -"), NULL); if (!ct->c_storeproc) { - if (autosw) { + if (info->autosw) { ct->c_storeproc = add (zP ? "| uncompress | tar xvpf -" - : "| tar xvpf -", NULL); + : (gzP ? "| gzip -dc | tar xvpf -" + : "| tar xvpf -"), NULL); ct->c_umask = 0022; } else { - ct->c_storeproc= add (zP ? "%m%P.tar.Z" : "%m%P.tar", NULL); + ct->c_storeproc= add (zP ? "%m%P.tar.Z" + : (gzP ? "%m%P.tar.gz" + : "%m%P.tar"), NULL); } } } } - return store_content (ct, NULL); + return store_content (ct, NULL, info); } @@ -262,7 +277,7 @@ store_application (CT ct) */ static int -store_multi (CT ct) +store_multi (CT ct, mhstoreinfo_t info) { int result; struct multipart *m = (struct multipart *) ct->c_ctparams; @@ -272,8 +287,15 @@ store_multi (CT ct) for (part = m->mp_parts; part; part = part->mp_next) { CT p = part->mp_part; - if (part_ok (p, 1) && type_ok (p, 1)) { - result = store_switch (p); + if (part_ok (p) && type_ok (p, 1)) { + if (ct->c_storage) { + /* Support mhstore -outfile. The MIME parser doesn't + load c_storage, so we know that p->c_storage is + NULL here. */ + p->c_storage = add (ct->c_storage, NULL); + } + result = store_switch (p, info); + if (result == OK && ct->c_subtype == MULTI_ALTERNATE) break; } @@ -289,7 +311,7 @@ store_multi (CT ct) */ static int -store_partial (CT ct) +store_partial (CT ct, mhstoreinfo_t info) { int cur, hi, i; CT p, *ctp, *ctq; @@ -301,7 +323,7 @@ store_partial (CT ct) return OK; hi = i = 0; - for (ctp = cts; *ctp; ctp++) { + for (ctp = info->cts; *ctp; ctp++) { p = *ctp; if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) { pm = (struct partial *) p->c_ctparams; @@ -323,11 +345,9 @@ store_partial (CT ct) return NOTOK; } - if ((base = (CT *) calloc ((size_t) (i + 1), sizeof(*base))) == NULL) - adios (NULL, "out of memory"); - + base = mh_xcalloc(i + 1, sizeof *base); ctq = base; - for (ctp = cts; *ctp; ctp++) { + for (ctp = info->cts; *ctp; ctp++) { p = *ctp; if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) { pm = (struct partial *) p->c_ctparams; @@ -373,19 +393,19 @@ missing_part: ctq = base; ct = *ctq++; - if (store_content (ct, NULL) == NOTOK) { + if (store_content (ct, NULL, info) == NOTOK) { losing: - free ((char *) base); + free(base); return NOTOK; } for (; *ctq; ctq++) { p = *ctq; - if (store_content (p, ct) == NOTOK) + if (store_content (p, ct, info) == NOTOK) goto losing; } - free ((char *) base); + free(base); return OK; } @@ -395,7 +415,7 @@ losing: */ static int -store_external (CT ct) +store_external (CT ct, mhstoreinfo_t info) { int result = NOTOK; struct exbody *e = (struct exbody *) ct->c_ctparams; @@ -408,7 +428,7 @@ store_external (CT ct) * Check if the parameters for the external body * specified a filename. */ - if (autosw) { + if (info->autosw) { char *cp; if ((cp = e->eb_name) @@ -433,7 +453,12 @@ store_external (CT ct) p->c_partno = ct->c_partno; /* we probably need to check if content is really there */ - result = store_switch (p); + if (ct->c_storage) { + /* Support mhstore -outfile. The MIME parser doesn't load + c_storage, so we know that p->c_storage is NULL here. */ + p->c_storage = add (ct->c_storage, NULL); + } + result = store_switch (p, info); p->c_partno = NULL; return result; @@ -467,9 +492,9 @@ ct_compar (CT *a, CT *b) */ static int -store_content (CT ct, CT p) +store_content (CT ct, CT p, mhstoreinfo_t info) { - int appending = 0, msgnum; + int appending = 0, msgnum = 0; int is_partial = 0, first_partial = 0; int last_partial = 0; char *cp, buffer[BUFSIZ]; @@ -507,11 +532,13 @@ store_content (CT ct, CT p) */ if (p) { appending = 1; - ct->c_storage = add (p->c_storage, NULL); + if (! ct->c_storage) { + ct->c_storage = add (p->c_storage, NULL); - /* record the folder name */ - if (p->c_folder) { - ct->c_folder = add (p->c_folder, NULL); + /* record the folder name */ + if (p->c_folder) { + ct->c_folder = add (p->c_folder, NULL); + } } goto got_filename; } @@ -529,61 +556,70 @@ store_content (CT ct, CT p) if ((cp = ct->c_storeproc) == NULL || *cp == '\0') { CI ci = &ct->c_ctinfo; - snprintf (buffer, sizeof(buffer), "%s-store-%s/%s", - invo_name, ci->ci_type, ci->ci_subtype); - if ((cp = context_find (buffer)) == NULL || *cp == '\0') { - snprintf (buffer, sizeof(buffer), "%s-store-%s", invo_name, ci->ci_type); - if ((cp = context_find (buffer)) == NULL || *cp == '\0') { - cp = ct->c_type == CT_MESSAGE ? "+" : "%m%P.%s"; - } + cp = context_find_by_type ("store", ci->ci_type, ci->ci_subtype); + if (cp == NULL) { + cp = ct->c_type == CT_MESSAGE ? "+" : "%m%P.%s"; } } - /* - * Check the beginning of storage formatting string - * to see if we are saving content to a folder. - */ - if (*cp == '+' || *cp == '@') { - char *tmpfilenam, *folder; + if (! ct->c_storage) { + /* + * Check the beginning of storage formatting string + * to see if we are saving content to a folder. + */ + if (*cp == '+' || *cp == '@') { + char *tmpfilenam, *folder; - /* Store content in temporary file for now */ - tmpfilenam = m_scratch ("", invo_name); - ct->c_storage = add (tmpfilenam, NULL); + /* Store content in temporary file for now */ + if ((tmpfilenam = m_mktemp(invo_name, NULL, NULL)) == NULL) { + adios(NULL, "unable to create temporary file in %s", + get_temp_dir()); + } + ct->c_storage = add (tmpfilenam, NULL); - /* Get the folder name */ - if (cp[1]) - folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); - else - folder = getfolder (1); + /* Get the folder name */ + if (cp[1]) + folder = pluspath (cp); + else + folder = getfolder (1); - /* Check if folder exists */ - if (check_folder (folder) == NOTOK) - return NOTOK; + /* Check if folder exists */ + create_folder(m_mailpath(folder), 0, exit); - /* Record the folder name */ - ct->c_folder = add (folder, NULL); + /* Record the folder name */ + ct->c_folder = add (folder, NULL); - if (cp[1]) - free (folder); + if (cp[1]) + free (folder); - goto got_filename; - } + goto got_filename; + } - /* - * Parse and expand the storage formatting string - * in `cp' into `buffer'. - */ - parse_format_string (ct, cp, buffer, sizeof(buffer), dir); + /* + * Parse and expand the storage formatting string + * in `cp' into `buffer'. + */ + parse_format_string (ct, cp, buffer, sizeof(buffer), info->dir); - /* - * If formatting begins with '|' or '!', then pass - * content to standard input of a command and return. - */ - if (buffer[0] == '|' || buffer[0] == '!') - return show_content_aux (ct, 1, 0, buffer + 1, dir); + /* + * If formatting begins with '|' or '!', then pass + * content to standard input of a command and return. + */ + if (buffer[0] == '|' || buffer[0] == '!') + return show_content_aux (ct, 0, buffer + 1, info->dir, NULL); - /* record the filename */ - ct->c_storage = add (buffer, NULL); + /* record the filename */ + if ((ct->c_storage = clobber_check (add (buffer, NULL), info)) == + NULL) { + return NOTOK; + } + } else { + /* The output filename was explicitly specified, so use it. */ + if ((ct->c_storage = clobber_check (ct->c_storage, info)) == + NULL) { + return NOTOK; + } + } got_filename: /* flush the output stream */ @@ -600,47 +636,49 @@ got_filename: */ if (ct->c_folder && (!is_partial || last_partial)) { msgnum = output_content_folder (ct->c_folder, ct->c_storage); - unlink (ct->c_storage); + (void) m_unlink (ct->c_storage); if (msgnum == NOTOK) return NOTOK; } - /* - * Now print out the name/number of the message - * that we are storing. - */ - if (is_partial) { - if (first_partial) - fprintf (stderr, "reassembling partials "); - if (last_partial) - fprintf (stderr, "%s", ct->c_file); - else - fprintf (stderr, "%s,", ct->c_file); - } else { - fprintf (stderr, "storing message %s", ct->c_file); - if (ct->c_partno) - fprintf (stderr, " part %s", ct->c_partno); - } - - /* - * Unless we are in the "middle" of group of message/partials, - * we now print the name of the file, folder, and/or message - * to which we are storing the content. - */ - if (!is_partial || last_partial) { - if (ct->c_folder) { - fprintf (stderr, " to folder %s as message %d\n", ct->c_folder, msgnum); - } else if (!strcmp(ct->c_storage, "-")) { - fprintf (stderr, " to stdout\n"); - } else { - int cwdlen; - - cwdlen = strlen (cwd); - fprintf (stderr, " as file %s\n", - strncmp (ct->c_storage, cwd, cwdlen) - || ct->c_storage[cwdlen] != '/' - ? ct->c_storage : ct->c_storage + cwdlen + 1); - } + if (info->verbosw) { + /* + * Now print out the name/number of the message + * that we are storing. + */ + if (is_partial) { + if (first_partial) + fprintf (stderr, "reassembling partials "); + if (last_partial) + fprintf (stderr, "%s", ct->c_file); + else + fprintf (stderr, "%s,", ct->c_file); + } else { + fprintf (stderr, "storing message %s", ct->c_file); + if (ct->c_partno) + fprintf (stderr, " part %s", ct->c_partno); + } + + /* + * Unless we are in the "middle" of group of message/partials, + * we now print the name of the file, folder, and/or message + * to which we are storing the content. + */ + if (!is_partial || last_partial) { + if (ct->c_folder) { + fprintf (stderr, " to folder %s as message %d\n", ct->c_folder, + msgnum); + } else if (!strcmp(ct->c_storage, "-")) { + fprintf (stderr, " to stdout\n"); + } else { + int cwdlen = strlen (info->cwd); + + fprintf (stderr, " as file %s\n", + strncmp (ct->c_storage, info->cwd, cwdlen) + || ct->c_storage[cwdlen] != '/' + ? ct->c_storage : ct->c_storage + cwdlen + 1); + } + } } return OK; @@ -735,7 +773,9 @@ losing: break; default: - fwrite (buffer, sizeof(*buffer), cc, fp); + if ((int) fwrite (buffer, sizeof(*buffer), cc, fp) < cc) { + advise ("output_content_file", "fwrite"); + } continue; } break; @@ -849,47 +889,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. * @@ -905,9 +904,9 @@ output_content_folder (char *folder, char *filename) struct msgs *mp; /* Read the folder. */ - if ((mp = folder_read (folder))) { + if ((mp = folder_read (folder, 0))) { /* Link file into folder */ - msgnum = folder_addmsg (&mp, filename, 0, 0, 0); + msgnum = folder_addmsg (&mp, filename, 0, 0, 0, 0, NULL); } else { advise (NULL, "unable to read folder %s", folder); return NOTOK; @@ -976,12 +975,12 @@ parse_format_string (CT ct, char *cp, char *buffer, int buflen, char *dir) buflen--; continue; } else { - char **ap, **ep; + PM pm; char *s = ""; - for (ap = ci->ci_attrs, ep = ci->ci_values; - *ap; ap++, ep++) { - snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep); + for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) { + snprintf (bp, buflen, "%s%s=\"%s\"", s, + pm->pm_name, get_param_value(pm, '?')); len = strlen (bp); bp += len; buflen -= len; @@ -1053,8 +1052,8 @@ raw: static void get_storeproc (CT ct) { - char **ap, **ep, *cp; - CI ci = &ct->c_ctinfo; + char *cp; + CI ci; /* * If the storeproc has already been defined, @@ -1064,22 +1063,40 @@ get_storeproc (CT ct) if (ct->c_storeproc) return; + /* + * If there's a Content-Disposition header and it has a filename, + * use that (RFC-2183). + */ + if (ct->c_dispo) { + if ((cp = get_param(ct->c_dispo_first, "filename", '_', 0)) + && *cp != '/' + && *cp != '.' + && *cp != '|' + && *cp != '!' + && !strchr (cp, '%')) { + ct->c_storeproc = add (cp, NULL); + free(cp); + return; + } + mh_xfree(cp); + } + /* * Check the attribute/value pairs, for the attribute "name". * If found, do a few sanity checks and copy the value into * the storeproc. */ - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!strcasecmp (*ap, "name") - && *(cp = *ep) != '/' - && *cp != '.' - && *cp != '|' - && *cp != '!' - && !strchr (cp, '%')) { + ci = &ct->c_ctinfo; + if ((cp = get_param(ci->ci_first_pm, "name", '_', 0)) + && *cp != '/' + && *cp != '.' + && *cp != '|' + && *cp != '!' + && !strchr (cp, '%')) { ct->c_storeproc = add (cp, NULL); - return; - } + } + mh_xfree(cp); } @@ -1111,3 +1128,226 @@ copy_some_headers (FILE *out, CT ct) return OK; } + +/******************************************************************************/ +/* -clobber support */ + +static +enum clobber_policy_t +clobber_policy (const char *value) { + if (value == NULL || ! strcasecmp (value, "always")) { + return NMH_CLOBBER_ALWAYS; + } + if (! strcasecmp (value, "auto")) { + return NMH_CLOBBER_AUTO; + } + if (! strcasecmp (value, "suffix")) { + return NMH_CLOBBER_SUFFIX; + } + if (! strcasecmp (value, "ask")) { + return NMH_CLOBBER_ASK; + } + if (! strcasecmp (value, "never")) { + return NMH_CLOBBER_NEVER; + } + + adios (NULL, "invalid argument, %s, to clobber", value); +} + + +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) { + int fd; + + 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); + return NULL; + } + + /* Actually (try to) create the file here to avoid a race + condition on file naming + creation. This won't solve the + problem with old NFS that doesn't support O_EXCL, though. + Let the umask strip off permissions from 0666 as desired. + That's what fopen () would do if it was creating the file. */ + if ((fd = open (buffer, O_CREAT | O_EXCL, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | + S_IROTH | S_IWOTH)) >= 0) { + close (fd); + break; + } + } + + free (file); + + if (version >= max_versions) { + advise (NULL, "will not overwrite %s, too many versions", buffer); + free (buffer); + buffer = NULL; + } + + return buffer; +} + + +static char * +clobber_check (char *original_file, mhstoreinfo_t info) { + /* clobber policy return value + * -------------- ------------ + * -always original_file + * -auto original_file-.extension + * -suffix original_file. + * -ask original_file, 0, or another filename/path + * -never 0 + */ + + char *file; + char *cwd = NULL; + int check_again; + + if (! strcmp (original_file, "-")) { + return original_file; + } + + if (info->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 isn't a full path, which should only happen if + it is -. */ + free (cwd); + cwd = NULL; + } + } + + do { + struct stat st; + + file = original_file; + check_again = 0; + + switch (info->clobber_policy) { + case NMH_CLOBBER_ALWAYS: + break; + + case NMH_CLOBBER_SUFFIX: + case NMH_CLOBBER_AUTO: + if (stat (file, &st) == OK) { + if ((file = next_version (original_file, info->clobber_policy)) == + NULL) { + ++info->files_not_clobbered; + } + } + 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, NMH_YES }, + { "no", 0, NMH_NO }, + { "rename", 0, NMH_RENAME }, + { NULL, 0, 0 } }; + char **ans; + + if (isatty (fileno (stdin))) { + char *prompt = + concat ("Overwrite \"", file, "\" [y/n/rename]? ", NULL); + ans = read_switch_multiword (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; + ++info->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; + ++info->files_not_clobbered; + } else { + TrimSuffixC(buf, '\n'); + } + + 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; + ++info->files_not_clobbered; + } + break; + } + + original_file = file; + } while (check_again); + + free (cwd); + + return file; +} + +/* -clobber support */ +/******************************************************************************/