| /* 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'; |
| 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 |
| * <Return> 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 */ |
| } |
| 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); |
| 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, char c) |
| { |
| int isprimary; |
| if(isupper(c)) |
| isprimary = 0; |
| else |
| isprimary = 1; |
| c = ch_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); |
| } |