1 /* Copyright 1995-1998,2000-2003,2005,2007-2009 Alain Knaff.
2 * This file is part of mtools.
4 * Mtools is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Mtools is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Mtools. If not, see <http://www.gnu.org/licenses/>.
18 * Make new directory entries, and handles name clashes
23 * This file is used by those commands that need to create new directory entries
26 #include "sysincludes.h"
29 #include "nameclash.h"
32 #include "file_name.h"
35 * Converts input to shortname
36 * @param un unix name (in Unix charset)
38 * @return 1 if name had to be mangled
40 static inline int convert_to_shortname(doscp_t *cp, ClashHandling_t *ch,
41 const char *un, dos_name_t *dn)
45 /* Then do conversion to dn */
46 ch->name_converter(cp, un, 0, &mangled, dn);
48 if (dn->base[0] == '\xE5')
53 static inline void chomp(char *line)
55 size_t l = strlen(line);
56 while(l > 0 && (line[l-1] == '\n' || line[l-1] == '\r')) {
62 * Asks for an alternative new name for a file, in case of a clash
64 static inline int ask_rename(doscp_t *cp, ClashHandling_t *ch,
65 dos_name_t *shortname,
71 /* TODO: Would be nice to suggest "autorenamed" version of name, press
75 fprintf(stderr,"Entering ask_rename, isprimary=%d.\n", isprimary);
83 char tname[4*MAX_VNAMELEN+1];
84 fprintf(stderr, "New %s name for \"%s\": ",
85 isprimary ? "primary" : "secondary", longname);
87 if (! fgets(tname, 4*MAX_VNAMELEN+1, opentty(0)))
91 strcpy(longname, tname);
93 mangled = convert_to_shortname(cp,
94 ch, tname, shortname);
95 } while (mangled & 1);
100 * This function determines the action to be taken in case there is a problem
101 * with target name (clash, illegal characters, or reserved)
102 * The decision either comes from the default (ch), or the user will be
103 * prompted if there is no default
105 static inline clash_action ask_namematch(doscp_t *cp,
113 /* User's answer letter (from keyboard). Only first letter is used,
114 * but we allocate space for 10 in order to account for extra garbage
115 * that user may enter
120 * Return value: action to be taken
125 * Should this decision be made permanent (do no longer ask same
131 * Buffer for shortname
133 char name_buffer[4*13];
144 static const char *reasons[]= {
147 "contains illegal character(s)"};
149 a = ch->action[isprimary];
151 if(a == NAMEMATCH_NONE && !opentty(1)) {
152 /* no default, and no tty either . Skip the troublesome file */
153 return NAMEMATCH_SKIP;
157 name = unix_normalize(cp, name_buffer,
158 dosname, sizeof(*dosname));
163 while (a == NAMEMATCH_NONE) {
164 fprintf(stderr, "%s file name \"%s\" %s.\n",
165 isprimary ? "Long" : "Short", name, reasons[reason]);
167 "a)utorename A)utorename-all r)ename R)ename-all ");
169 fprintf(stderr,"o)verwrite O)verwrite-all");
171 "\ns)kip S)kip-all q)uit (aArR");
173 fprintf(stderr,"oO");
174 fprintf(stderr,"sSq): ");
177 if (mtools_raw_tty) {
179 rep = fgetc(opentty(1));
186 if(fgets(ans, 9, opentty(0)) == NULL)
189 perm = isupper((unsigned char)ans[0]);
190 switch(tolower((unsigned char)ans[0])) {
192 a = NAMEMATCH_AUTORENAME;
196 a = NAMEMATCH_PRENAME;
198 a = NAMEMATCH_RENAME;
203 a = NAMEMATCH_OVERWRITE;
217 /* Keep track of this action in case this file collides again */
218 ch->action[isprimary] = a;
220 ch->namematch_default[isprimary] = a;
222 /* if we were asked to overwrite be careful. We can't set the action
223 * to overwrite, else we get won't get a chance to specify another
224 * action, should overwrite fail. Indeed, we'll be caught in an
225 * infinite loop because overwrite will fail the same way for the
227 if(a == NAMEMATCH_OVERWRITE)
228 ch->action[isprimary] = NAMEMATCH_NONE;
233 * Processes a name match
234 * dosname short dosname (ignored if is_primary)
238 * 2 if file is to be overwritten
239 * 1 if file was renamed
240 * 0 if it was skipped
242 * If a short name is involved, handle conversion between the 11-character
243 * fixed-length record DOS name and a literal null-terminated name (e.g.
244 * "COMMAND COM" (no null) <-> "COMMAND.COM" (null terminated)).
246 * Also, immediately copy the original name so that messages can use it.
248 static inline clash_action process_namematch(doscp_t *cp,
260 "process_namematch: name=%s, default_action=%d, ask=%d.\n",
261 name, default_action, ch->ask);
264 action = ask_namematch(cp, dosname, longname,
265 isprimary, ch, no_overwrite, reason);
270 return NAMEMATCH_SKIP;
272 return NAMEMATCH_SKIP;
273 case NAMEMATCH_RENAME:
274 case NAMEMATCH_PRENAME:
275 /* We need to rename the file now. This means we must pass
276 * back through the loop, a) ensuring there isn't a potential
277 * new name collision, and b) finding a big enough VSE.
278 * Change the name, so that it won't collide again.
280 ask_rename(cp, ch, dosname, longname, isprimary);
282 case NAMEMATCH_AUTORENAME:
283 /* Very similar to NAMEMATCH_RENAME, except that we need to
284 * first generate the name.
285 * TODO: Remember previous name so we don't
286 * keep trying the same one.
289 autorename_long(longname, 1);
290 return NAMEMATCH_PRENAME;
292 autorename_short(dosname, 1);
293 return NAMEMATCH_RENAME;
295 case NAMEMATCH_OVERWRITE:
297 return NAMEMATCH_SKIP;
299 return NAMEMATCH_OVERWRITE;
301 case NAMEMATCH_ERROR:
302 case NAMEMATCH_SUCCESS:
304 return NAMEMATCH_NONE;
309 static int contains_illegals(const char *string, const char *illegals,
312 for(; *string && len--; string++)
313 if((*string < ' ' && *string != '\005' && !(*string & 0x80)) ||
314 strchr(illegals, *string))
319 static int is_reserved(char *ans, int islong)
322 static const char *dev3[] = {"CON", "AUX", "PRN", "NUL", " "};
323 static const char *dev4[] = {"COM", "LPT" };
325 for (i = 0; i < sizeof(dev3)/sizeof(*dev3); i++)
326 if (!strncasecmp(ans, dev3[i], 3) &&
327 ((islong && !ans[3]) ||
328 (!islong && !strncmp(ans+3," ",5))))
331 for (i = 0; i < sizeof(dev4)/sizeof(*dev4); i++)
332 if (!strncasecmp(ans, dev4[i], 3) &&
333 (ans[3] >= '1' && ans[3] <= '4') &&
334 ((islong && !ans[4]) ||
335 (!islong && !strncmp(ans+4," ",4))))
341 static inline clash_action get_slots(Stream_t *Dir,
344 struct scan_state *ssp,
354 int pessimisticShortRename;
355 doscp_t *cp = GET_DOSCONVERT(Dir);
357 pessimisticShortRename = (ch->action[0] == NAMEMATCH_AUTORENAME);
361 if((is_reserved(longname,1)) ||
362 longname[strspn(longname,". ")] == '\0'){
365 } else if(contains_illegals(longname,long_illegals,1024)) {
368 } else if(is_reserved(dosname->base,0)) {
370 ch->use_longname = 1;
372 } else if(!ch->is_label &&
373 contains_illegals(dosname->base,short_illegals,11)) {
375 ch->use_longname = 1;
379 switch (lookupForInsert(Dir,
381 dosname, longname, ssp,
384 pessimisticShortRename &&
388 return NAMEMATCH_ERROR;
391 return NAMEMATCH_SKIP;
392 /* Single-file error error or skip request */
395 return NAMEMATCH_GREW;
396 /* Grew directory, try again */
399 return NAMEMATCH_SUCCESS; /* Success */
401 if (ssp->longmatch >= 0) {
402 /* Primary Long Name Match */
405 "Got longmatch=%d for name %s.\n",
406 longmatch, longname);
408 match_pos = ssp->longmatch;
410 } else if ((ch->use_longname & 1) && (ssp->shortmatch != -1)) {
411 /* Secondary Short Name Match */
414 "Got secondary short name match for name %s.\n",
418 match_pos = ssp->shortmatch;
419 /* match_pos may become negative here (-2) in case
420 * of pessimisticShortRename, i.e. creating a
421 * long entry whose shortname matches another
423 * mformat -C b: -s 18 -h 2 -t 80
424 * mcopy /etc/issue b:12345678a
425 * mcopy /etc/issue b:12345678b
429 } else if (ssp->shortmatch >= 0) {
430 /* Primary Short Name Match */
433 "Got primary short name match for name %s.\n",
436 match_pos = ssp->shortmatch;
439 return NAMEMATCH_RENAME;
442 entry.entry = match_pos;
443 dir_read(&entry, &error);
445 return NAMEMATCH_ERROR;
446 /* if we can't overwrite, don't propose it */
447 no_overwrite = (match_pos == ch->source || IS_DIR(&entry));
450 ret = process_namematch(cp, dosname, longname,
451 isprimary, ch, no_overwrite, reason);
453 if (ret == NAMEMATCH_OVERWRITE && match_pos > -1){
454 if((entry.dir.attr & 0x5) &&
455 (ask_confirmation("file is read only, overwrite anyway (y/n) ? ")))
456 return NAMEMATCH_RENAME;
457 /* Free up the file to be overwritten */
458 if(fatFreeWithDirentry(&entry))
459 return NAMEMATCH_ERROR;
463 match_pos - ssp->match_free + 1 >= ssp->size_needed){
464 /* reuse old entry and old short name for overwrite */
465 ssp->free_start = match_pos - ssp->size_needed + 1;
466 ssp->free_size = ssp->size_needed;
467 ssp->slot = match_pos;
469 strncpy(dosname, dir.name, 3);
470 strncpy(dosname + 8, dir.ext, 3);
476 return NAMEMATCH_RENAME;
484 static inline int write_slots(Stream_t *Dir,
487 struct scan_state *ssp,
488 write_data_callback *cb,
499 assert(ssp->got_slots);
500 setEntryToPos(&entry, ssp->slot);
501 native_to_wchar(longname, entry.name, MAX_VNAMELEN, 0, 0);
502 entry.name[MAX_VNAMELEN]='\0';
503 entry.dir.Case = Case & (EXTCASE | BASECASE);
504 if (cb(dosname, longname, arg, &entry) >= 0) {
505 if ((ssp->size_needed > 1) &&
506 (ssp->free_end - ssp->free_start >= ssp->size_needed)) {
507 ssp->slot = write_vfat(Dir, dosname, longname,
508 ssp->free_start, &entry);
510 ssp->size_needed = 1;
511 write_vfat(Dir, dosname, 0,
512 ssp->free_start, &entry);
517 return 1; /* Successfully wrote the file */
520 static void stripspaces(char *name)
533 static int _mwrite_one(Stream_t *Dir,
536 write_data_callback *cb,
540 char longname[VBUFSIZE];
544 struct scan_state scan;
546 doscp_t *cp = GET_DOSCONVERT(Dir);
550 if(isSpecial(argname)) {
551 fprintf(stderr, "Cannot create entry named . or ..\n");
555 if(ch->name_converter == dos_name) {
557 stripspaces(shortname);
559 stripspaces(argname);
563 convert_to_shortname(cp, ch, shortname, &dosname);
564 if(ch->use_longname & 1){
565 /* short name mangled, treat it as a long name */
571 if (argname[0] && (argname[1] == ':')) {
572 /* Skip drive letter */
573 dstname = argname + 2;
578 /* Copy original argument dstname to working value longname */
579 strncpy(longname, dstname, VBUFSIZE-1);
583 convert_to_shortname(cp, ch, shortname, &dosname);
584 if(strcmp(shortname, longname))
585 ch->use_longname |= 1;
588 convert_to_shortname(cp, ch, longname, &dosname);
591 ch->action[0] = ch->namematch_default[0];
592 ch->action[1] = ch->namematch_default[1];
595 switch((ret=get_slots(Dir, &dosname, longname, &scan, ch))){
596 case NAMEMATCH_ERROR:
597 return -1; /* Non-file-specific error,
601 return -1; /* Skip file (user request or
604 case NAMEMATCH_PRENAME:
606 convert_to_shortname(cp, ch,
610 case NAMEMATCH_RENAME:
611 continue; /* Renamed file, loop again */
614 /* No collision, and not enough slots.
615 * Try to grow the directory
617 if (expanded) { /* Already tried this
620 "%s: No directory slots\n",
626 if (dir_grow(Dir, scan.max_entry))
629 case NAMEMATCH_OVERWRITE:
630 case NAMEMATCH_SUCCESS:
631 return write_slots(Dir, &dosname, longname,
635 case NAMEMATCH_AUTORENAME:
638 "Internal error: clash_action=%d\n",
646 int mwrite_one(Stream_t *Dir,
647 const char *_argname,
648 const char *_shortname,
649 write_data_callback *cb,
658 argname = strdup(_argname);
662 shortname = strdup(_shortname);
665 ret = _mwrite_one(Dir, argname, shortname, cb, arg, ch);
673 void init_clash_handling(ClashHandling_t *ch)
675 ch->ignore_entry = -1;
676 ch->source_entry = -2;
677 ch->nowarn = 0; /*Don't ask, just do default action if name collision */
678 ch->namematch_default[0] = NAMEMATCH_AUTORENAME;
679 ch->namematch_default[1] = NAMEMATCH_NONE;
680 ch->name_converter = dos_name; /* changed by mlabel */
685 int handle_clash_options(ClashHandling_t *ch, int c)
695 /* Overwrite if primary name matches */
696 ch->namematch_default[isprimary] = NAMEMATCH_OVERWRITE;
699 /* Rename primary name interactively */
700 ch->namematch_default[isprimary] = NAMEMATCH_RENAME;
703 /* Skip file if primary name collides */
704 ch->namematch_default[isprimary] = NAMEMATCH_SKIP;
707 ch->namematch_default[isprimary] = NAMEMATCH_NONE;
710 ch->namematch_default[isprimary] = NAMEMATCH_AUTORENAME;
717 void dosnameToDirentry(const struct dos_name_t *dn, struct directory *dir) {
718 strncpy(dir->name, dn->base, 8);
719 strncpy(dir->ext, dn->ext, 3);