summaryrefslogtreecommitdiff
path: root/mk_direntry.c
diff options
context:
space:
mode:
Diffstat (limited to 'mk_direntry.c')
-rw-r--r--mk_direntry.c709
1 files changed, 709 insertions, 0 deletions
diff --git a/mk_direntry.c b/mk_direntry.c
new file mode 100644
index 0000000..cba1846
--- /dev/null
+++ b/mk_direntry.c
@@ -0,0 +1,709 @@
+/* Copyright 1995-1998,2000-2003,2005,2007-2009 Alain Knaff.
+ * This file is part of mtools.
+ *
+ * Mtools is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Mtools is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Mtools. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * mk_direntry.c
+ * Make new directory entries, and handles name clashes
+ *
+ */
+
+/*
+ * This file is used by those commands that need to create new directory entries
+ */
+
+#include "sysincludes.h"
+#include "msdos.h"
+#include "mtools.h"
+#include "vfat.h"
+#include "nameclash.h"
+#include "fs.h"
+#include "stream.h"
+#include "mainloop.h"
+#include "file_name.h"
+
+/**
+ * Converts input to shortname
+ * @param un unix name (in Unix charset)
+ *
+ * @return 1 if name had to be mangled
+ */
+static __inline__ int convert_to_shortname(doscp_t *cp, ClashHandling_t *ch,
+ const char *un, dos_name_t *dn)
+{
+ int mangled;
+
+ /* Then do conversion to dn */
+ ch->name_converter(cp, un, 0, &mangled, dn);
+ dn->sentinel = '\0';
+ return mangled;
+}
+
+static __inline__ void chomp(char *line)
+{
+ int l = strlen(line);
+ while(l > 0 && (line[l-1] == '\n' || line[l-1] == '\r')) {
+ line[--l] = '\0';
+ }
+}
+
+/**
+ * Asks for an alternative new name for a file, in case of a clash
+ */
+static __inline__ int ask_rename(doscp_t *cp, ClashHandling_t *ch,
+ dos_name_t *shortname,
+ char *longname,
+ int isprimary)
+{
+ int mangled;
+
+ /* TODO: Would be nice to suggest "autorenamed" version of name, press
+ * <Return> to get it.
+ */
+#if 0
+ fprintf(stderr,"Entering ask_rename, isprimary=%d.\n", isprimary);
+#endif
+
+ if(!opentty(0))
+ return 0;
+
+#define maxsize (isprimary ? MAX_VNAMELEN+1 : 11+1)
+#define name (isprimary ? argname : shortname)
+
+ mangled = 0;
+ do {
+ char tname[4*MAX_VNAMELEN+1];
+ fprintf(stderr, "New %s name for \"%s\": ",
+ isprimary ? "primary" : "secondary", longname);
+ fflush(stderr);
+ if (! fgets(tname, 4*MAX_VNAMELEN+1, opentty(0)))
+ return 0;
+ chomp(tname);
+ if (isprimary)
+ strcpy(longname, tname);
+ else
+ mangled = convert_to_shortname(cp,
+ ch, tname, shortname);
+ } while (mangled & 1);
+ return 1;
+#undef maxsize
+#undef name
+}
+
+/**
+ * This function determines the action to be taken in case there is a problem
+ * with target name (clash, illegal characters, or reserved)
+ * The decision either comes from the default (ch), or the user will be
+ * prompted if there is no default
+ */
+static __inline__ clash_action ask_namematch(doscp_t *cp,
+ dos_name_t *dosname,
+ char *longname,
+ int isprimary,
+ ClashHandling_t *ch,
+ int no_overwrite,
+ int reason)
+{
+ /* User's answer letter (from keyboard). Only first letter is used,
+ * but we allocate space for 10 in order to account for extra garbage
+ * that user may enter
+ */
+ char ans[10];
+
+ /**
+ * Return value: action to be taken
+ */
+ clash_action a;
+
+ /**
+ * Should this decision be made permanent (do no longer ask same
+ * question)
+ */
+ int perm;
+
+ /**
+ * Buffer for shortname
+ */
+ char name_buffer[4*13];
+
+ /**
+ * Name to be printed
+ */
+ char *name;
+
+#define EXISTS 0
+#define RESERVED 1
+#define ILLEGALS 2
+
+ static const char *reasons[]= {
+ "already exists",
+ "is reserved",
+ "contains illegal character(s)"};
+
+ a = ch->action[isprimary];
+
+ if(a == NAMEMATCH_NONE && !opentty(1)) {
+ /* no default, and no tty either . Skip the troublesome file */
+ return NAMEMATCH_SKIP;
+ }
+
+ if (!isprimary)
+ name = unix_normalize(cp, name_buffer, dosname);
+ else
+ name = longname;
+
+ perm = 0;
+ while (a == NAMEMATCH_NONE) {
+ fprintf(stderr, "%s file name \"%s\" %s.\n",
+ isprimary ? "Long" : "Short", name, reasons[reason]);
+ fprintf(stderr,
+ "a)utorename A)utorename-all r)ename R)ename-all ");
+ if(!no_overwrite)
+ fprintf(stderr,"o)verwrite O)verwrite-all");
+ fprintf(stderr,
+ "\ns)kip S)kip-all q)uit (aArR");
+ if(!no_overwrite)
+ fprintf(stderr,"oO");
+ fprintf(stderr,"sSq): ");
+ fflush(stderr);
+ fflush(opentty(1));
+ if (mtools_raw_tty) {
+ int rep;
+ rep = fgetc(opentty(1));
+ fputs("\n", stderr);
+ if(rep == EOF)
+ ans[0] = 'q';
+ else
+ ans[0] = rep;
+ } else {
+ if(fgets(ans, 9, opentty(0)) == NULL)
+ ans[0] = 'q';
+ }
+ perm = isupper((unsigned char)ans[0]);
+ switch(tolower((unsigned char)ans[0])) {
+ case 'a':
+ a = NAMEMATCH_AUTORENAME;
+ break;
+ case 'r':
+ if(isprimary)
+ a = NAMEMATCH_PRENAME;
+ else
+ a = NAMEMATCH_RENAME;
+ break;
+ case 'o':
+ if(no_overwrite)
+ continue;
+ a = NAMEMATCH_OVERWRITE;
+ break;
+ case 's':
+ a = NAMEMATCH_SKIP;
+ break;
+ case 'q':
+ perm = 0;
+ a = NAMEMATCH_QUIT;
+ break;
+ default:
+ perm = 0;
+ }
+ }
+
+ /* Keep track of this action in case this file collides again */
+ ch->action[isprimary] = a;
+ if (perm)
+ ch->namematch_default[isprimary] = a;
+
+ /* if we were asked to overwrite be careful. We can't set the action
+ * to overwrite, else we get won't get a chance to specify another
+ * action, should overwrite fail. Indeed, we'll be caught in an
+ * infinite loop because overwrite will fail the same way for the
+ * second time */
+ if(a == NAMEMATCH_OVERWRITE)
+ ch->action[isprimary] = NAMEMATCH_NONE;
+ return a;
+}
+
+/*
+ * Processes a name match
+ * dosname short dosname (ignored if is_primary)
+ *
+ *
+ * Returns:
+ * 2 if file is to be overwritten
+ * 1 if file was renamed
+ * 0 if it was skipped
+ *
+ * If a short name is involved, handle conversion between the 11-character
+ * fixed-length record DOS name and a literal null-terminated name (e.g.
+ * "COMMAND COM" (no null) <-> "COMMAND.COM" (null terminated)).
+ *
+ * Also, immediately copy the original name so that messages can use it.
+ */
+static __inline__ clash_action process_namematch(doscp_t *cp,
+ dos_name_t *dosname,
+ char *longname,
+ int isprimary,
+ ClashHandling_t *ch,
+ int no_overwrite,
+ int reason)
+{
+ clash_action action;
+
+#if 0
+ fprintf(stderr,
+ "process_namematch: name=%s, default_action=%d, ask=%d.\n",
+ name, default_action, ch->ask);
+#endif
+
+ action = ask_namematch(cp, dosname, longname,
+ isprimary, ch, no_overwrite, reason);
+
+ switch(action){
+ case NAMEMATCH_QUIT:
+ got_signal = 1;
+ return NAMEMATCH_SKIP;
+ case NAMEMATCH_SKIP:
+ return NAMEMATCH_SKIP;
+ case NAMEMATCH_RENAME:
+ case NAMEMATCH_PRENAME:
+ /* We need to rename the file now. This means we must pass
+ * back through the loop, a) ensuring there isn't a potential
+ * new name collision, and b) finding a big enough VSE.
+ * Change the name, so that it won't collide again.
+ */
+ ask_rename(cp, ch, dosname, longname, isprimary);
+ return action;
+ case NAMEMATCH_AUTORENAME:
+ /* Very similar to NAMEMATCH_RENAME, except that we need to
+ * first generate the name.
+ * TODO: Remember previous name so we don't
+ * keep trying the same one.
+ */
+ if (isprimary) {
+ autorename_long(longname, 1);
+ return NAMEMATCH_PRENAME;
+ } else {
+ autorename_short(dosname, 1);
+ return NAMEMATCH_RENAME;
+ }
+ case NAMEMATCH_OVERWRITE:
+ if(no_overwrite)
+ return NAMEMATCH_SKIP;
+ else
+ return NAMEMATCH_OVERWRITE;
+ default:
+ return NAMEMATCH_NONE;
+ }
+}
+
+static int contains_illegals(const char *string, const char *illegals,
+ int len)
+{
+ for(; *string && len--; string++)
+ if((*string < ' ' && *string != '\005' && !(*string & 0x80)) ||
+ strchr(illegals, *string))
+ return 1;
+ return 0;
+}
+
+static int is_reserved(char *ans, int islong)
+{
+ unsigned int i;
+ static const char *dev3[] = {"CON", "AUX", "PRN", "NUL", " "};
+ static const char *dev4[] = {"COM", "LPT" };
+
+ for (i = 0; i < sizeof(dev3)/sizeof(*dev3); i++)
+ if (!strncasecmp(ans, dev3[i], 3) &&
+ ((islong && !ans[3]) ||
+ (!islong && !strncmp(ans+3," ",5))))
+ return 1;
+
+ for (i = 0; i < sizeof(dev4)/sizeof(*dev4); i++)
+ if (!strncasecmp(ans, dev4[i], 3) &&
+ (ans[3] >= '1' && ans[3] <= '4') &&
+ ((islong && !ans[4]) ||
+ (!islong && !strncmp(ans+4," ",4))))
+ return 1;
+
+ return 0;
+}
+
+static __inline__ clash_action get_slots(Stream_t *Dir,
+ dos_name_t *dosname,
+ char *longname,
+ struct scan_state *ssp,
+ ClashHandling_t *ch)
+{
+ int error;
+ clash_action ret;
+ int match_pos=0;
+ direntry_t entry;
+ int isprimary;
+ int no_overwrite;
+ int reason;
+ int pessimisticShortRename;
+ doscp_t *cp = GET_DOSCONVERT(Dir);
+
+ pessimisticShortRename = (ch->action[0] == NAMEMATCH_AUTORENAME);
+
+ entry.Dir = Dir;
+ no_overwrite = 1;
+ if((is_reserved(longname,1)) ||
+ longname[strspn(longname,". ")] == '\0'){
+ reason = RESERVED;
+ isprimary = 1;
+ } else if(contains_illegals(longname,long_illegals,1024)) {
+ reason = ILLEGALS;
+ isprimary = 1;
+ } else if(is_reserved(dosname->base,0)) {
+ reason = RESERVED;
+ ch->use_longname = 1;
+ isprimary = 0;
+ } else if(contains_illegals(dosname->base,short_illegals,11)) {
+ reason = ILLEGALS;
+ ch->use_longname = 1;
+ isprimary = 0;
+ } else {
+ reason = EXISTS;
+ switch (lookupForInsert(Dir,
+ &entry,
+ dosname, longname, ssp,
+ ch->ignore_entry,
+ ch->source_entry,
+ pessimisticShortRename &&
+ ch->use_longname,
+ ch->use_longname)) {
+ case -1:
+ return NAMEMATCH_ERROR;
+
+ case 0:
+ return NAMEMATCH_SKIP;
+ /* Single-file error error or skip request */
+
+ case 5:
+ return NAMEMATCH_GREW;
+ /* Grew directory, try again */
+
+ case 6:
+ return NAMEMATCH_SUCCESS; /* Success */
+ }
+ match_pos = -2;
+ if (ssp->longmatch > -1) {
+ /* Primary Long Name Match */
+#ifdef debug
+ fprintf(stderr,
+ "Got longmatch=%d for name %s.\n",
+ longmatch, longname);
+#endif
+ match_pos = ssp->longmatch;
+ isprimary = 1;
+ } else if ((ch->use_longname & 1) && (ssp->shortmatch != -1)) {
+ /* Secondary Short Name Match */
+#ifdef debug
+ fprintf(stderr,
+ "Got secondary short name match for name %s.\n",
+ longname);
+#endif
+
+ match_pos = ssp->shortmatch;
+ isprimary = 0;
+ } else if (ssp->shortmatch >= 0) {
+ /* Primary Short Name Match */
+#ifdef debug
+ fprintf(stderr,
+ "Got primary short name match for name %s.\n",
+ longname);
+#endif
+ match_pos = ssp->shortmatch;
+ isprimary = 1;
+ } else
+ return NAMEMATCH_RENAME;
+
+ if(match_pos > -1) {
+ entry.entry = match_pos;
+ dir_read(&entry, &error);
+ if (error)
+ return NAMEMATCH_ERROR;
+ /* if we can't overwrite, don't propose it */
+ no_overwrite = (match_pos == ch->source || IS_DIR(&entry));
+ }
+ }
+ ret = process_namematch(cp, dosname, longname,
+ isprimary, ch, no_overwrite, reason);
+
+ if (ret == NAMEMATCH_OVERWRITE && match_pos > -1){
+ if((entry.dir.attr & 0x5) &&
+ (ask_confirmation("file is read only, overwrite anyway (y/n) ? ")))
+ return NAMEMATCH_RENAME;
+ /* Free up the file to be overwritten */
+ if(fatFreeWithDirentry(&entry))
+ return NAMEMATCH_ERROR;
+
+#if 0
+ if(isprimary &&
+ match_pos - ssp->match_free + 1 >= ssp->size_needed){
+ /* reuse old entry and old short name for overwrite */
+ ssp->free_start = match_pos - ssp->size_needed + 1;
+ ssp->free_size = ssp->size_needed;
+ ssp->slot = match_pos;
+ ssp->got_slots = 1;
+ strncpy(dosname, dir.name, 3);
+ strncpy(dosname + 8, dir.ext, 3);
+ return ret;
+ } else
+#endif
+ {
+ wipeEntry(&entry);
+ return NAMEMATCH_RENAME;
+ }
+ }
+
+ return ret;
+}
+
+
+static __inline__ int write_slots(Stream_t *Dir,
+ dos_name_t *dosname,
+ char *longname,
+ struct scan_state *ssp,
+ write_data_callback *cb,
+ void *arg,
+ int Case)
+{
+ direntry_t entry;
+
+ /* write the file */
+ if (fat_error(Dir))
+ return 0;
+
+ entry.Dir = Dir;
+ entry.entry = ssp->slot;
+ native_to_wchar(longname, entry.name, MAX_VNAMELEN, 0, 0);
+ entry.name[MAX_VNAMELEN]='\0';
+ entry.dir.Case = Case & (EXTCASE | BASECASE);
+ if (cb(dosname, longname, arg, &entry) >= 0) {
+ if ((ssp->size_needed > 1) &&
+ (ssp->free_end - ssp->free_start >= ssp->size_needed)) {
+ ssp->slot = write_vfat(Dir, dosname, longname,
+ ssp->free_start, &entry);
+ } else {
+ ssp->size_needed = 1;
+ write_vfat(Dir, dosname, 0,
+ ssp->free_start, &entry);
+ }
+ /* clear_vses(Dir, ssp->free_start + ssp->size_needed,
+ ssp->free_end); */
+ } else
+ return 0;
+
+ return 1; /* Successfully wrote the file */
+}
+
+static void stripspaces(char *name)
+{
+ char *p,*non_space;
+
+ non_space = name;
+ for(p=name; *p; p++)
+ if (*p != ' ')
+ non_space = p;
+ if(name[0])
+ non_space[1] = '\0';
+}
+
+
+static int _mwrite_one(Stream_t *Dir,
+ char *argname,
+ char *shortname,
+ write_data_callback *cb,
+ void *arg,
+ ClashHandling_t *ch)
+{
+ char longname[VBUFSIZE];
+ const char *dstname;
+ dos_name_t dosname;
+ int expanded;
+ struct scan_state scan;
+ clash_action ret;
+ doscp_t *cp = GET_DOSCONVERT(Dir);
+
+ expanded = 0;
+
+ if(isSpecial(argname)) {
+ fprintf(stderr, "Cannot create entry named . or ..\n");
+ return -1;
+ }
+
+ if(ch->name_converter == dos_name) {
+ if(shortname)
+ stripspaces(shortname);
+ if(argname)
+ stripspaces(argname);
+ }
+
+ if(shortname){
+ convert_to_shortname(cp, ch, shortname, &dosname);
+ if(ch->use_longname & 1){
+ /* short name mangled, treat it as a long name */
+ argname = shortname;
+ shortname = 0;
+ }
+ }
+
+ if (argname[0] && (argname[1] == ':')) {
+ /* Skip drive letter */
+ dstname = argname + 2;
+ } else {
+ dstname = argname;
+ }
+
+ /* Copy original argument dstname to working value longname */
+ strncpy(longname, dstname, VBUFSIZE-1);
+
+ if(shortname) {
+ ch->use_longname =
+ convert_to_shortname(cp, ch, shortname, &dosname);
+ if(strcmp(shortname, longname))
+ ch->use_longname |= 1;
+ } else {
+ ch->use_longname =
+ convert_to_shortname(cp, ch, longname, &dosname);
+ }
+
+ ch->action[0] = ch->namematch_default[0];
+ ch->action[1] = ch->namematch_default[1];
+
+ while (1) {
+ switch((ret=get_slots(Dir, &dosname, longname, &scan, ch))){
+ case NAMEMATCH_ERROR:
+ return -1; /* Non-file-specific error,
+ * quit */
+
+ case NAMEMATCH_SKIP:
+ return -1; /* Skip file (user request or
+ * error) */
+
+ case NAMEMATCH_PRENAME:
+ ch->use_longname =
+ convert_to_shortname(cp, ch,
+ longname,
+ &dosname);
+ continue;
+ case NAMEMATCH_RENAME:
+ continue; /* Renamed file, loop again */
+
+ case NAMEMATCH_GREW:
+ /* No collision, and not enough slots.
+ * Try to grow the directory
+ */
+ if (expanded) { /* Already tried this
+ * once, no good */
+ fprintf(stderr,
+ "%s: No directory slots\n",
+ progname);
+ return -1;
+ }
+ expanded = 1;
+
+ if (dir_grow(Dir, scan.max_entry))
+ return -1;
+ continue;
+ case NAMEMATCH_OVERWRITE:
+ case NAMEMATCH_SUCCESS:
+ return write_slots(Dir, &dosname, longname,
+ &scan, cb, arg,
+ ch->use_longname);
+ default:
+ fprintf(stderr,
+ "Internal error: clash_action=%d\n",
+ ret);
+ return -1;
+ }
+
+ }
+}
+
+int mwrite_one(Stream_t *Dir,
+ const char *_argname,
+ const char *_shortname,
+ write_data_callback *cb,
+ void *arg,
+ ClashHandling_t *ch)
+{
+ char *argname;
+ char *shortname;
+ int ret;
+
+ if(_argname)
+ argname = strdup(_argname);
+ else
+ argname = 0;
+ if(_shortname)
+ shortname = strdup(_shortname);
+ else
+ shortname = 0;
+ ret = _mwrite_one(Dir, argname, shortname, cb, arg, ch);
+ if(argname)
+ free(argname);
+ if(shortname)
+ free(shortname);
+ return ret;
+}
+
+void init_clash_handling(ClashHandling_t *ch)
+{
+ ch->ignore_entry = -1;
+ ch->source_entry = -2;
+ ch->nowarn = 0; /*Don't ask, just do default action if name collision */
+ ch->namematch_default[0] = NAMEMATCH_AUTORENAME;
+ ch->namematch_default[1] = NAMEMATCH_NONE;
+ ch->name_converter = dos_name; /* changed by mlabel */
+ ch->source = -2;
+}
+
+int handle_clash_options(ClashHandling_t *ch, char c)
+{
+ int isprimary;
+ if(isupper(c))
+ isprimary = 0;
+ else
+ isprimary = 1;
+ c = tolower(c);
+ switch(c) {
+ case 'o':
+ /* Overwrite if primary name matches */
+ ch->namematch_default[isprimary] = NAMEMATCH_OVERWRITE;
+ return 0;
+ case 'r':
+ /* Rename primary name interactively */
+ ch->namematch_default[isprimary] = NAMEMATCH_RENAME;
+ return 0;
+ case 's':
+ /* Skip file if primary name collides */
+ ch->namematch_default[isprimary] = NAMEMATCH_SKIP;
+ return 0;
+ case 'm':
+ ch->namematch_default[isprimary] = NAMEMATCH_NONE;
+ return 0;
+ case 'a':
+ ch->namematch_default[isprimary] = NAMEMATCH_AUTORENAME;
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+void dosnameToDirentry(const struct dos_name_t *dn, struct directory *dir) {
+ strncpy(dir->name, dn->base, 8);
+ strncpy(dir->ext, dn->ext, 3);
+}