618 lines
14 KiB
C
Executable file
618 lines
14 KiB
C
Executable file
/*
|
|
* 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"
|
|
|
|
static inline int ask_rename(ClashHandling_t *ch,
|
|
char *longname, int isprimary, char *argname)
|
|
{
|
|
char shortname[13];
|
|
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 {
|
|
fprintf(stderr, "New %s name for \"%s\": ",
|
|
isprimary ? "primary" : "secondary", longname);
|
|
fflush(stderr);
|
|
if (! fgets(name, maxsize, opentty(0)))
|
|
return 0;
|
|
|
|
/* Eliminate newline(s) in the file name */
|
|
name[strlen(name)-1]='\0';
|
|
if (!isprimary)
|
|
ch->name_converter(shortname,0, &mangled, argname);
|
|
} while (mangled & 1);
|
|
return 1;
|
|
#undef maxsize
|
|
#undef name
|
|
}
|
|
|
|
static inline clash_action ask_namematch(char *name, int isprimary,
|
|
ClashHandling_t *ch, int no_overwrite,
|
|
int reason)
|
|
{
|
|
char ans[10];
|
|
clash_action a;
|
|
int perm;
|
|
char unix_shortname[13];
|
|
|
|
|
|
#define EXISTS 0
|
|
#define RESERVED 1
|
|
#define ILLEGALS 2
|
|
|
|
static const char *reasons[]= {
|
|
"already exists",
|
|
"is reserved",
|
|
"contains illegal character(s)"};
|
|
|
|
|
|
if (!isprimary)
|
|
name = unix_normalize(unix_shortname, name, name+8);
|
|
|
|
a = ch->action[isprimary];
|
|
|
|
if(a == NAMEMATCH_NONE && !opentty(1)) {
|
|
/* no default, and no tty either . Skip the troublesome file */
|
|
return NAMEMATCH_SKIP;
|
|
}
|
|
|
|
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 {
|
|
fgets(ans, 9, opentty(0));
|
|
}
|
|
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;
|
|
}
|
|
|
|
/* 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(char *name,
|
|
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(name, 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(ch, longname, isprimary, name);
|
|
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(name, 1);
|
|
return NAMEMATCH_PRENAME;
|
|
} else {
|
|
autorename_short(name, 1);
|
|
return NAMEMATCH_RENAME;
|
|
}
|
|
case NAMEMATCH_OVERWRITE:
|
|
if(no_overwrite)
|
|
return NAMEMATCH_SKIP;
|
|
else
|
|
return NAMEMATCH_OVERWRITE;
|
|
default:
|
|
return NAMEMATCH_NONE;
|
|
}
|
|
}
|
|
|
|
|
|
static void clear_scan(char *longname, int use_longname, struct scan_state *s)
|
|
{
|
|
s->shortmatch = s->longmatch = s->slot = -1;
|
|
s->free_end = s->got_slots = s->free_start = 0;
|
|
|
|
if (use_longname & 1)
|
|
s->size_needed = 2 + (strlen(longname)/VSE_NAMELEN);
|
|
else
|
|
s->size_needed = 1;
|
|
}
|
|
|
|
|
|
static int contains_illegals(const char *string, const char *illegals)
|
|
{
|
|
for(; *string ; string++)
|
|
if((*string < ' ' && *string != '\005' && !(*string & 0x80)) ||
|
|
strchr(illegals, *string))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int is_reserved(char *ans, int islong)
|
|
{
|
|
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,
|
|
char *dosname, char *longname,
|
|
struct scan_state *ssp,
|
|
ClashHandling_t *ch)
|
|
{
|
|
int error;
|
|
clash_action ret;
|
|
int match=0;
|
|
direntry_t entry;
|
|
int isprimary;
|
|
int no_overwrite;
|
|
int reason;
|
|
int pessimisticShortRename;
|
|
|
|
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)) {
|
|
reason = ILLEGALS;
|
|
isprimary = 1;
|
|
} else if(is_reserved(dosname,0)) {
|
|
reason = RESERVED;
|
|
ch->use_longname = 1;
|
|
isprimary = 0;
|
|
} else if(contains_illegals(dosname,short_illegals)) {
|
|
reason = ILLEGALS;
|
|
ch->use_longname = 1;
|
|
isprimary = 0;
|
|
} else {
|
|
reason = EXISTS;
|
|
clear_scan(longname, ch->use_longname, ssp);
|
|
switch (lookupForInsert(Dir, dosname, longname, ssp,
|
|
ch->ignore_entry,
|
|
ch->source_entry,
|
|
pessimisticShortRename &&
|
|
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 = -2;
|
|
if (ssp->longmatch > -1) {
|
|
/* Primary Long Name Match */
|
|
#ifdef debug
|
|
fprintf(stderr,
|
|
"Got longmatch=%d for name %s.\n",
|
|
longmatch, longname);
|
|
#endif
|
|
match = 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 = 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 = ssp->shortmatch;
|
|
isprimary = 1;
|
|
} else
|
|
return NAMEMATCH_RENAME;
|
|
|
|
if(match > -1) {
|
|
entry.entry = match;
|
|
dir_read(&entry, &error);
|
|
if (error)
|
|
return NAMEMATCH_ERROR;
|
|
/* if we can't overwrite, don't propose it */
|
|
no_overwrite = (match == ch->source || IS_DIR(&entry));
|
|
}
|
|
}
|
|
ret = process_namematch(isprimary ? longname : dosname, longname,
|
|
isprimary, ch, no_overwrite, reason);
|
|
|
|
if (ret == NAMEMATCH_OVERWRITE && match > -1){
|
|
if((entry.dir.attr & 0x5) &&
|
|
(ask_confirmation("file is read only, overwrite anyway (y/n) ? ",0,0)))
|
|
return NAMEMATCH_RENAME;
|
|
|
|
/* Free up the file to be overwritten */
|
|
if(fatFreeWithDirentry(&entry))
|
|
return NAMEMATCH_ERROR;
|
|
|
|
#if 0
|
|
if(isprimary &&
|
|
match - ssp->match_free + 1 >= ssp->size_needed){
|
|
/* reuse old entry and old short name for overwrite */
|
|
ssp->free_start = match - ssp->size_needed + 1;
|
|
ssp->free_size = ssp->size_needed;
|
|
ssp->slot = match;
|
|
ssp->got_slots = 1;
|
|
strncpy(dosname, dir.name, 3);
|
|
strncpy(dosname + 8, dir.ext, 3);
|
|
return ret;
|
|
} else
|
|
#endif
|
|
{
|
|
entry.dir.name[0] = DELMARK;
|
|
dir_write(&entry);
|
|
return NAMEMATCH_RENAME;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static inline int write_slots(Stream_t *Dir,
|
|
char *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;
|
|
strncpy(entry.name, longname, sizeof(entry.name)-1);
|
|
entry.name[sizeof(entry.name)-1]='\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';
|
|
}
|
|
|
|
|
|
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;
|
|
char dosname[13];
|
|
int expanded;
|
|
struct scan_state scan;
|
|
clash_action ret;
|
|
|
|
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){
|
|
ch->name_converter(shortname,0, &ch->use_longname, dosname);
|
|
if(ch->use_longname & 1){
|
|
/* short name mangled, treat it as a long name */
|
|
argname = shortname;
|
|
shortname = 0;
|
|
}
|
|
}
|
|
|
|
/* Skip drive letter */
|
|
dstname = skip_drive(argname);
|
|
|
|
/* Copy original argument dstname to working value longname */
|
|
strncpy(longname, dstname, VBUFSIZE-1);
|
|
|
|
if(shortname) {
|
|
ch->name_converter(shortname,0, &ch->use_longname, dosname);
|
|
if(strcmp(shortname, longname))
|
|
ch->use_longname |= 1;
|
|
} else
|
|
ch->name_converter(longname,0, &ch->use_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->name_converter(longname,0,
|
|
&ch->use_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;
|
|
}
|
|
}
|