1 /* Copyright 1995-1998,2000-2003,2005,2007-2009 Alain Knaff.
2 * This file is part of mtools.
3 *
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.
8 *
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.
13 *
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/>.
16 *
17 * mk_direntry.c
18 * Make new directory entries, and handles name clashes
19 *
20 */
21
22 /*
23 * This file is used by those commands that need to create new directory entries
24 */
25
26 #include "sysincludes.h"
27 #include "msdos.h"
28 #include "mtools.h"
29 #include "vfat.h"
30 #include "nameclash.h"
31 #include "fs.h"
32 #include "stream.h"
33 #include "mainloop.h"
34 #include "file_name.h"
35
36 /**
37 * Converts input to shortname
38 * @param un unix name (in Unix charset)
39 *
40 * @return 1 if name had to be mangled
41 */
convert_to_shortname(doscp_t * cp,ClashHandling_t * ch,const char * un,dos_name_t * dn)42 static __inline__ int convert_to_shortname(doscp_t *cp, ClashHandling_t *ch,
43 const char *un, dos_name_t *dn)
44 {
45 int mangled;
46
47 /* Then do conversion to dn */
48 ch->name_converter(cp, un, 0, &mangled, dn);
49 dn->sentinel = '\0';
50 if (dn->base[0] == '\xE5')
51 dn->base[0] = '\x05';
52 return mangled;
53 }
54
chomp(char * line)55 static __inline__ void chomp(char *line)
56 {
57 int l = strlen(line);
58 while(l > 0 && (line[l-1] == '\n' || line[l-1] == '\r')) {
59 line[--l] = '\0';
60 }
61 }
62
63 /**
64 * Asks for an alternative new name for a file, in case of a clash
65 */
ask_rename(doscp_t * cp,ClashHandling_t * ch,dos_name_t * shortname,char * longname,int isprimary)66 static __inline__ int ask_rename(doscp_t *cp, ClashHandling_t *ch,
67 dos_name_t *shortname,
68 char *longname,
69 int isprimary)
70 {
71 int mangled;
72
73 /* TODO: Would be nice to suggest "autorenamed" version of name, press
74 * <Return> to get it.
75 */
76 #if 0
77 fprintf(stderr,"Entering ask_rename, isprimary=%d.\n", isprimary);
78 #endif
79
80 if(!opentty(0))
81 return 0;
82
83 mangled = 0;
84 do {
85 char tname[4*MAX_VNAMELEN+1];
86 fprintf(stderr, "New %s name for \"%s\": ",
87 isprimary ? "primary" : "secondary", longname);
88 fflush(stderr);
89 if (! fgets(tname, 4*MAX_VNAMELEN+1, opentty(0)))
90 return 0;
91 chomp(tname);
92 if (isprimary)
93 strcpy(longname, tname);
94 else
95 mangled = convert_to_shortname(cp,
96 ch, tname, shortname);
97 } while (mangled & 1);
98 return 1;
99 }
100
101 /**
102 * This function determines the action to be taken in case there is a problem
103 * with target name (clash, illegal characters, or reserved)
104 * The decision either comes from the default (ch), or the user will be
105 * prompted if there is no default
106 */
ask_namematch(doscp_t * cp,dos_name_t * dosname,char * longname,int isprimary,ClashHandling_t * ch,int no_overwrite,int reason)107 static __inline__ clash_action ask_namematch(doscp_t *cp,
108 dos_name_t *dosname,
109 char *longname,
110 int isprimary,
111 ClashHandling_t *ch,
112 int no_overwrite,
113 int reason)
114 {
115 /* User's answer letter (from keyboard). Only first letter is used,
116 * but we allocate space for 10 in order to account for extra garbage
117 * that user may enter
118 */
119 char ans[10];
120
121 /**
122 * Return value: action to be taken
123 */
124 clash_action a;
125
126 /**
127 * Should this decision be made permanent (do no longer ask same
128 * question)
129 */
130 int perm;
131
132 /**
133 * Buffer for shortname
134 */
135 char name_buffer[4*13];
136
137 /**
138 * Name to be printed
139 */
140 char *name;
141
142 #define EXISTS 0
143 #define RESERVED 1
144 #define ILLEGALS 2
145
146 static const char *reasons[]= {
147 "already exists",
148 "is reserved",
149 "contains illegal character(s)"};
150
151 a = ch->action[isprimary];
152
153 if(a == NAMEMATCH_NONE && !opentty(1)) {
154 /* no default, and no tty either . Skip the troublesome file */
155 return NAMEMATCH_SKIP;
156 }
157
158 if (!isprimary)
159 name = unix_normalize(cp, name_buffer,
160 dosname, sizeof(*dosname));
161 else
162 name = longname;
163
164 perm = 0;
165 while (a == NAMEMATCH_NONE) {
166 fprintf(stderr, "%s file name \"%s\" %s.\n",
167 isprimary ? "Long" : "Short", name, reasons[reason]);
168 fprintf(stderr,
169 "a)utorename A)utorename-all r)ename R)ename-all ");
170 if(!no_overwrite)
171 fprintf(stderr,"o)verwrite O)verwrite-all");
172 fprintf(stderr,
173 "\ns)kip S)kip-all q)uit (aArR");
174 if(!no_overwrite)
175 fprintf(stderr,"oO");
176 fprintf(stderr,"sSq): ");
177 fflush(stderr);
178 fflush(opentty(1));
179 if (mtools_raw_tty) {
180 int rep;
181 rep = fgetc(opentty(1));
182 fputs("\n", stderr);
183 if(rep == EOF)
184 ans[0] = 'q';
185 else
186 ans[0] = rep;
187 } else {
188 if(fgets(ans, 9, opentty(0)) == NULL)
189 ans[0] = 'q';
190 }
191 perm = isupper((unsigned char)ans[0]);
192 switch(tolower((unsigned char)ans[0])) {
193 case 'a':
194 a = NAMEMATCH_AUTORENAME;
195 break;
196 case 'r':
197 if(isprimary)
198 a = NAMEMATCH_PRENAME;
199 else
200 a = NAMEMATCH_RENAME;
201 break;
202 case 'o':
203 if(no_overwrite)
204 continue;
205 a = NAMEMATCH_OVERWRITE;
206 break;
207 case 's':
208 a = NAMEMATCH_SKIP;
209 break;
210 case 'q':
211 perm = 0;
212 a = NAMEMATCH_QUIT;
213 break;
214 default:
215 perm = 0;
216 }
217 }
218
219 /* Keep track of this action in case this file collides again */
220 ch->action[isprimary] = a;
221 if (perm)
222 ch->namematch_default[isprimary] = a;
223
224 /* if we were asked to overwrite be careful. We can't set the action
225 * to overwrite, else we get won't get a chance to specify another
226 * action, should overwrite fail. Indeed, we'll be caught in an
227 * infinite loop because overwrite will fail the same way for the
228 * second time */
229 if(a == NAMEMATCH_OVERWRITE)
230 ch->action[isprimary] = NAMEMATCH_NONE;
231 return a;
232 }
233
234 /*
235 * Processes a name match
236 * dosname short dosname (ignored if is_primary)
237 *
238 *
239 * Returns:
240 * 2 if file is to be overwritten
241 * 1 if file was renamed
242 * 0 if it was skipped
243 *
244 * If a short name is involved, handle conversion between the 11-character
245 * fixed-length record DOS name and a literal null-terminated name (e.g.
246 * "COMMAND COM" (no null) <-> "COMMAND.COM" (null terminated)).
247 *
248 * Also, immediately copy the original name so that messages can use it.
249 */
process_namematch(doscp_t * cp,dos_name_t * dosname,char * longname,int isprimary,ClashHandling_t * ch,int no_overwrite,int reason)250 static __inline__ clash_action process_namematch(doscp_t *cp,
251 dos_name_t *dosname,
252 char *longname,
253 int isprimary,
254 ClashHandling_t *ch,
255 int no_overwrite,
256 int reason)
257 {
258 clash_action action;
259
260 #if 0
261 fprintf(stderr,
262 "process_namematch: name=%s, default_action=%d, ask=%d.\n",
263 name, default_action, ch->ask);
264 #endif
265
266 action = ask_namematch(cp, dosname, longname,
267 isprimary, ch, no_overwrite, reason);
268
269 switch(action){
270 case NAMEMATCH_QUIT:
271 got_signal = 1;
272 return NAMEMATCH_SKIP;
273 case NAMEMATCH_SKIP:
274 return NAMEMATCH_SKIP;
275 case NAMEMATCH_RENAME:
276 case NAMEMATCH_PRENAME:
277 /* We need to rename the file now. This means we must pass
278 * back through the loop, a) ensuring there isn't a potential
279 * new name collision, and b) finding a big enough VSE.
280 * Change the name, so that it won't collide again.
281 */
282 ask_rename(cp, ch, dosname, longname, isprimary);
283 return action;
284 case NAMEMATCH_AUTORENAME:
285 /* Very similar to NAMEMATCH_RENAME, except that we need to
286 * first generate the name.
287 * TODO: Remember previous name so we don't
288 * keep trying the same one.
289 */
290 if (isprimary) {
291 autorename_long(longname, 1);
292 return NAMEMATCH_PRENAME;
293 } else {
294 autorename_short(dosname, 1);
295 return NAMEMATCH_RENAME;
296 }
297 case NAMEMATCH_OVERWRITE:
298 if(no_overwrite)
299 return NAMEMATCH_SKIP;
300 else
301 return NAMEMATCH_OVERWRITE;
302 default:
303 return NAMEMATCH_NONE;
304 }
305 }
306
contains_illegals(const char * string,const char * illegals,int len)307 static int contains_illegals(const char *string, const char *illegals,
308 int len)
309 {
310 for(; *string && len--; string++)
311 if((*string < ' ' && *string != '\005' && !(*string & 0x80)) ||
312 strchr(illegals, *string))
313 return 1;
314 return 0;
315 }
316
is_reserved(char * ans,int islong)317 static int is_reserved(char *ans, int islong)
318 {
319 unsigned int i;
320 static const char *dev3[] = {"CON", "AUX", "PRN", "NUL", " "};
321 static const char *dev4[] = {"COM", "LPT" };
322
323 for (i = 0; i < sizeof(dev3)/sizeof(*dev3); i++)
324 if (!strncasecmp(ans, dev3[i], 3) &&
325 ((islong && !ans[3]) ||
326 (!islong && !strncmp(ans+3," ",5))))
327 return 1;
328
329 for (i = 0; i < sizeof(dev4)/sizeof(*dev4); i++)
330 if (!strncasecmp(ans, dev4[i], 3) &&
331 (ans[3] >= '1' && ans[3] <= '4') &&
332 ((islong && !ans[4]) ||
333 (!islong && !strncmp(ans+4," ",4))))
334 return 1;
335
336 return 0;
337 }
338
get_slots(Stream_t * Dir,dos_name_t * dosname,char * longname,struct scan_state * ssp,ClashHandling_t * ch)339 static __inline__ clash_action get_slots(Stream_t *Dir,
340 dos_name_t *dosname,
341 char *longname,
342 struct scan_state *ssp,
343 ClashHandling_t *ch)
344 {
345 int error;
346 clash_action ret;
347 int match_pos=0;
348 direntry_t entry;
349 int isprimary;
350 int no_overwrite;
351 int reason;
352 int pessimisticShortRename;
353 doscp_t *cp = GET_DOSCONVERT(Dir);
354
355 pessimisticShortRename = (ch->action[0] == NAMEMATCH_AUTORENAME);
356
357 entry.Dir = Dir;
358 no_overwrite = 1;
359 if((is_reserved(longname,1)) ||
360 longname[strspn(longname,". ")] == '\0'){
361 reason = RESERVED;
362 isprimary = 1;
363 } else if(contains_illegals(longname,long_illegals,1024)) {
364 reason = ILLEGALS;
365 isprimary = 1;
366 } else if(is_reserved(dosname->base,0)) {
367 reason = RESERVED;
368 ch->use_longname = 1;
369 isprimary = 0;
370 } else if(!ch->is_label &&
371 contains_illegals(dosname->base,short_illegals,11)) {
372 reason = ILLEGALS;
373 ch->use_longname = 1;
374 isprimary = 0;
375 } else {
376 reason = EXISTS;
377 switch (lookupForInsert(Dir,
378 &entry,
379 dosname, longname, ssp,
380 ch->ignore_entry,
381 ch->source_entry,
382 pessimisticShortRename &&
383 ch->use_longname,
384 ch->use_longname)) {
385 case -1:
386 return NAMEMATCH_ERROR;
387
388 case 0:
389 return NAMEMATCH_SKIP;
390 /* Single-file error error or skip request */
391
392 case 5:
393 return NAMEMATCH_GREW;
394 /* Grew directory, try again */
395
396 case 6:
397 return NAMEMATCH_SUCCESS; /* Success */
398 }
399 match_pos = -2;
400 if (ssp->longmatch > -1) {
401 /* Primary Long Name Match */
402 #ifdef debug
403 fprintf(stderr,
404 "Got longmatch=%d for name %s.\n",
405 longmatch, longname);
406 #endif
407 match_pos = ssp->longmatch;
408 isprimary = 1;
409 } else if ((ch->use_longname & 1) && (ssp->shortmatch != -1)) {
410 /* Secondary Short Name Match */
411 #ifdef debug
412 fprintf(stderr,
413 "Got secondary short name match for name %s.\n",
414 longname);
415 #endif
416
417 match_pos = ssp->shortmatch;
418 isprimary = 0;
419 } else if (ssp->shortmatch >= 0) {
420 /* Primary Short Name Match */
421 #ifdef debug
422 fprintf(stderr,
423 "Got primary short name match for name %s.\n",
424 longname);
425 #endif
426 match_pos = ssp->shortmatch;
427 isprimary = 1;
428 } else
429 return NAMEMATCH_RENAME;
430
431 if(match_pos > -1) {
432 entry.entry = match_pos;
433 dir_read(&entry, &error);
434 if (error)
435 return NAMEMATCH_ERROR;
436 /* if we can't overwrite, don't propose it */
437 no_overwrite = (match_pos == ch->source || IS_DIR(&entry));
438 }
439 }
440 ret = process_namematch(cp, dosname, longname,
441 isprimary, ch, no_overwrite, reason);
442
443 if (ret == NAMEMATCH_OVERWRITE && match_pos > -1){
444 if((entry.dir.attr & 0x5) &&
445 (ask_confirmation("file is read only, overwrite anyway (y/n) ? ")))
446 return NAMEMATCH_RENAME;
447 /* Free up the file to be overwritten */
448 if(fatFreeWithDirentry(&entry))
449 return NAMEMATCH_ERROR;
450
451 #if 0
452 if(isprimary &&
453 match_pos - ssp->match_free + 1 >= ssp->size_needed){
454 /* reuse old entry and old short name for overwrite */
455 ssp->free_start = match_pos - ssp->size_needed + 1;
456 ssp->free_size = ssp->size_needed;
457 ssp->slot = match_pos;
458 ssp->got_slots = 1;
459 strncpy(dosname, dir.name, 3);
460 strncpy(dosname + 8, dir.ext, 3);
461 return ret;
462 } else
463 #endif
464 {
465 wipeEntry(&entry);
466 return NAMEMATCH_RENAME;
467 }
468 }
469
470 return ret;
471 }
472
473
write_slots(Stream_t * Dir,dos_name_t * dosname,char * longname,struct scan_state * ssp,write_data_callback * cb,void * arg,int Case)474 static __inline__ int write_slots(Stream_t *Dir,
475 dos_name_t *dosname,
476 char *longname,
477 struct scan_state *ssp,
478 write_data_callback *cb,
479 void *arg,
480 int Case)
481 {
482 direntry_t entry;
483
484 /* write the file */
485 if (fat_error(Dir))
486 return 0;
487
488 entry.Dir = Dir;
489 entry.entry = ssp->slot;
490 native_to_wchar(longname, entry.name, MAX_VNAMELEN, 0, 0);
491 entry.name[MAX_VNAMELEN]='\0';
492 entry.dir.Case = Case & (EXTCASE | BASECASE);
493 if (cb(dosname, longname, arg, &entry) >= 0) {
494 if ((ssp->size_needed > 1) &&
495 (ssp->free_end - ssp->free_start >= ssp->size_needed)) {
496 ssp->slot = write_vfat(Dir, dosname, longname,
497 ssp->free_start, &entry);
498 } else {
499 ssp->size_needed = 1;
500 write_vfat(Dir, dosname, 0,
501 ssp->free_start, &entry);
502 }
503 /* clear_vses(Dir, ssp->free_start + ssp->size_needed,
504 ssp->free_end); */
505 } else
506 return 0;
507
508 return 1; /* Successfully wrote the file */
509 }
510
stripspaces(char * name)511 static void stripspaces(char *name)
512 {
513 char *p,*non_space;
514
515 non_space = name;
516 for(p=name; *p; p++)
517 if (*p != ' ')
518 non_space = p;
519 if(name[0])
520 non_space[1] = '\0';
521 }
522
523
_mwrite_one(Stream_t * Dir,char * argname,char * shortname,write_data_callback * cb,void * arg,ClashHandling_t * ch)524 static int _mwrite_one(Stream_t *Dir,
525 char *argname,
526 char *shortname,
527 write_data_callback *cb,
528 void *arg,
529 ClashHandling_t *ch)
530 {
531 char longname[VBUFSIZE];
532 const char *dstname;
533 dos_name_t dosname;
534 int expanded;
535 struct scan_state scan;
536 clash_action ret;
537 doscp_t *cp = GET_DOSCONVERT(Dir);
538
539 expanded = 0;
540
541 if(isSpecial(argname)) {
542 fprintf(stderr, "Cannot create entry named . or ..\n");
543 return -1;
544 }
545
546 if(ch->name_converter == dos_name) {
547 if(shortname)
548 stripspaces(shortname);
549 if(argname)
550 stripspaces(argname);
551 }
552
553 if(shortname){
554 convert_to_shortname(cp, ch, shortname, &dosname);
555 if(ch->use_longname & 1){
556 /* short name mangled, treat it as a long name */
557 argname = shortname;
558 shortname = 0;
559 }
560 }
561
562 if (argname[0] && (argname[1] == ':')) {
563 /* Skip drive letter */
564 dstname = argname + 2;
565 } else {
566 dstname = argname;
567 }
568
569 /* Copy original argument dstname to working value longname */
570 strncpy(longname, dstname, VBUFSIZE-1);
571
572 if(shortname) {
573 ch->use_longname =
574 convert_to_shortname(cp, ch, shortname, &dosname);
575 if(strcmp(shortname, longname))
576 ch->use_longname |= 1;
577 } else {
578 ch->use_longname =
579 convert_to_shortname(cp, ch, longname, &dosname);
580 }
581
582 ch->action[0] = ch->namematch_default[0];
583 ch->action[1] = ch->namematch_default[1];
584
585 while (1) {
586 switch((ret=get_slots(Dir, &dosname, longname, &scan, ch))){
587 case NAMEMATCH_ERROR:
588 return -1; /* Non-file-specific error,
589 * quit */
590
591 case NAMEMATCH_SKIP:
592 return -1; /* Skip file (user request or
593 * error) */
594
595 case NAMEMATCH_PRENAME:
596 ch->use_longname =
597 convert_to_shortname(cp, ch,
598 longname,
599 &dosname);
600 continue;
601 case NAMEMATCH_RENAME:
602 continue; /* Renamed file, loop again */
603
604 case NAMEMATCH_GREW:
605 /* No collision, and not enough slots.
606 * Try to grow the directory
607 */
608 if (expanded) { /* Already tried this
609 * once, no good */
610 fprintf(stderr,
611 "%s: No directory slots\n",
612 progname);
613 return -1;
614 }
615 expanded = 1;
616
617 if (dir_grow(Dir, scan.max_entry))
618 return -1;
619 continue;
620 case NAMEMATCH_OVERWRITE:
621 case NAMEMATCH_SUCCESS:
622 return write_slots(Dir, &dosname, longname,
623 &scan, cb, arg,
624 ch->use_longname);
625 default:
626 fprintf(stderr,
627 "Internal error: clash_action=%d\n",
628 ret);
629 return -1;
630 }
631
632 }
633 }
634
mwrite_one(Stream_t * Dir,const char * _argname,const char * _shortname,write_data_callback * cb,void * arg,ClashHandling_t * ch)635 int mwrite_one(Stream_t *Dir,
636 const char *_argname,
637 const char *_shortname,
638 write_data_callback *cb,
639 void *arg,
640 ClashHandling_t *ch)
641 {
642 char *argname;
643 char *shortname;
644 int ret;
645
646 if(_argname)
647 argname = strdup(_argname);
648 else
649 argname = 0;
650 if(_shortname)
651 shortname = strdup(_shortname);
652 else
653 shortname = 0;
654 ret = _mwrite_one(Dir, argname, shortname, cb, arg, ch);
655 if(argname)
656 free(argname);
657 if(shortname)
658 free(shortname);
659 return ret;
660 }
661
init_clash_handling(ClashHandling_t * ch)662 void init_clash_handling(ClashHandling_t *ch)
663 {
664 ch->ignore_entry = -1;
665 ch->source_entry = -2;
666 ch->nowarn = 0; /*Don't ask, just do default action if name collision */
667 ch->namematch_default[0] = NAMEMATCH_AUTORENAME;
668 ch->namematch_default[1] = NAMEMATCH_NONE;
669 ch->name_converter = dos_name; /* changed by mlabel */
670 ch->source = -2;
671 ch->is_label = 0;
672 }
673
handle_clash_options(ClashHandling_t * ch,char c)674 int handle_clash_options(ClashHandling_t *ch, char c)
675 {
676 int isprimary;
677 if(isupper(c))
678 isprimary = 0;
679 else
680 isprimary = 1;
681 c = ch_tolower(c);
682 switch(c) {
683 case 'o':
684 /* Overwrite if primary name matches */
685 ch->namematch_default[isprimary] = NAMEMATCH_OVERWRITE;
686 return 0;
687 case 'r':
688 /* Rename primary name interactively */
689 ch->namematch_default[isprimary] = NAMEMATCH_RENAME;
690 return 0;
691 case 's':
692 /* Skip file if primary name collides */
693 ch->namematch_default[isprimary] = NAMEMATCH_SKIP;
694 return 0;
695 case 'm':
696 ch->namematch_default[isprimary] = NAMEMATCH_NONE;
697 return 0;
698 case 'a':
699 ch->namematch_default[isprimary] = NAMEMATCH_AUTORENAME;
700 return 0;
701 default:
702 return -1;
703 }
704 }
705
dosnameToDirentry(const struct dos_name_t * dn,struct directory * dir)706 void dosnameToDirentry(const struct dos_name_t *dn, struct directory *dir) {
707 strncpy(dir->name, dn->base, 8);
708 strncpy(dir->ext, dn->ext, 3);
709 }
710