/* 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 . * * 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 "mtools.h" #include "vfat.h" #include "nameclash.h" #include "fs.h" #include "stream.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'; if (dn->base[0] == '\xE5') dn->base[0] = '\x05'; return mangled; } static inline void chomp(char *line) { size_t 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 * to get it. */ #if 0 fprintf(stderr,"Entering ask_rename, isprimary=%d.\n", isprimary); #endif if(!opentty(0)) return 0; 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; } /** * 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, sizeof(*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] = (char) 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; case NAMEMATCH_NONE: case NAMEMATCH_ERROR: case NAMEMATCH_SUCCESS: case NAMEMATCH_GREW: return NAMEMATCH_NONE; } return action; } 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(!ch->is_label && 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 */ } if (ssp->longmatch >= 0) { /* 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; /* match_pos may become negative here (-2) in case * of pessimisticShortRename, i.e. creating a * long entry whose shortname matches another * entry's shortname: * mformat -C b: -s 18 -h 2 -t 80 * mcopy /etc/issue b:12345678a * mcopy /etc/issue b:12345678b */ 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; assert(ssp->got_slots); setEntryToPos(&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); } } 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); case NAMEMATCH_NONE: case NAMEMATCH_AUTORENAME: case NAMEMATCH_QUIT: 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; ch->is_label = 0; } int handle_clash_options(ClashHandling_t *ch, int 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); }