diff options
Diffstat (limited to 'mk_direntry.c')
-rw-r--r-- | mk_direntry.c | 709 |
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); +} |