/* 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);
}