blob: da1c10533eff50782e6285a885f30a0642b1cb34 [file] [log] [blame]
Alistair Delvabeaee832021-02-24 11:27:23 -08001/* 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)
Yi Kong39bbd962022-01-09 19:41:38 +080039 *
Alistair Delvabeaee832021-02-24 11:27:23 -080040 * @return 1 if name had to be mangled
41 */
42static __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
55static __inline__ void chomp(char *line)
56{
Yi Kong39bbd962022-01-09 19:41:38 +080057 size_t l = strlen(line);
Alistair Delvabeaee832021-02-24 11:27:23 -080058 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 */
66static __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
Yi Kong39bbd962022-01-09 19:41:38 +080073 /* TODO: Would be nice to suggest "autorenamed" version of name, press
Alistair Delvabeaee832021-02-24 11:27:23 -080074 * <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
Yi Kong39bbd962022-01-09 19:41:38 +080095 mangled = convert_to_shortname(cp,
Alistair Delvabeaee832021-02-24 11:27:23 -080096 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 */
107static __inline__ clash_action ask_namematch(doscp_t *cp,
108 dos_name_t *dosname,
109 char *longname,
Yi Kong39bbd962022-01-09 19:41:38 +0800110 int isprimary,
Alistair Delvabeaee832021-02-24 11:27:23 -0800111 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;
Yi Kong39bbd962022-01-09 19:41:38 +0800181 rep = fgetc(opentty(1));
Alistair Delvabeaee832021-02-24 11:27:23 -0800182 fputs("\n", stderr);
183 if(rep == EOF)
184 ans[0] = 'q';
185 else
Yi Kong39bbd962022-01-09 19:41:38 +0800186 ans[0] = (char) rep;
Alistair Delvabeaee832021-02-24 11:27:23 -0800187 } 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 */
250static __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;
Yi Kong39bbd962022-01-09 19:41:38 +0800302 case NAMEMATCH_NONE:
303 case NAMEMATCH_ERROR:
304 case NAMEMATCH_SUCCESS:
305 case NAMEMATCH_GREW:
Alistair Delvabeaee832021-02-24 11:27:23 -0800306 return NAMEMATCH_NONE;
307 }
Yi Kong39bbd962022-01-09 19:41:38 +0800308 return action;
Alistair Delvabeaee832021-02-24 11:27:23 -0800309}
310
311static int contains_illegals(const char *string, const char *illegals,
312 int len)
313{
314 for(; *string && len--; string++)
315 if((*string < ' ' && *string != '\005' && !(*string & 0x80)) ||
316 strchr(illegals, *string))
317 return 1;
318 return 0;
319}
320
321static int is_reserved(char *ans, int islong)
322{
323 unsigned int i;
324 static const char *dev3[] = {"CON", "AUX", "PRN", "NUL", " "};
325 static const char *dev4[] = {"COM", "LPT" };
326
327 for (i = 0; i < sizeof(dev3)/sizeof(*dev3); i++)
328 if (!strncasecmp(ans, dev3[i], 3) &&
329 ((islong && !ans[3]) ||
330 (!islong && !strncmp(ans+3," ",5))))
331 return 1;
332
333 for (i = 0; i < sizeof(dev4)/sizeof(*dev4); i++)
334 if (!strncasecmp(ans, dev4[i], 3) &&
335 (ans[3] >= '1' && ans[3] <= '4') &&
336 ((islong && !ans[4]) ||
337 (!islong && !strncmp(ans+4," ",4))))
338 return 1;
Yi Kong39bbd962022-01-09 19:41:38 +0800339
Alistair Delvabeaee832021-02-24 11:27:23 -0800340 return 0;
341}
342
343static __inline__ clash_action get_slots(Stream_t *Dir,
344 dos_name_t *dosname,
345 char *longname,
346 struct scan_state *ssp,
347 ClashHandling_t *ch)
348{
349 int error;
350 clash_action ret;
351 int match_pos=0;
352 direntry_t entry;
353 int isprimary;
354 int no_overwrite;
355 int reason;
356 int pessimisticShortRename;
357 doscp_t *cp = GET_DOSCONVERT(Dir);
358
359 pessimisticShortRename = (ch->action[0] == NAMEMATCH_AUTORENAME);
360
361 entry.Dir = Dir;
362 no_overwrite = 1;
363 if((is_reserved(longname,1)) ||
364 longname[strspn(longname,". ")] == '\0'){
365 reason = RESERVED;
366 isprimary = 1;
367 } else if(contains_illegals(longname,long_illegals,1024)) {
368 reason = ILLEGALS;
369 isprimary = 1;
370 } else if(is_reserved(dosname->base,0)) {
371 reason = RESERVED;
372 ch->use_longname = 1;
373 isprimary = 0;
374 } else if(!ch->is_label &&
375 contains_illegals(dosname->base,short_illegals,11)) {
376 reason = ILLEGALS;
377 ch->use_longname = 1;
378 isprimary = 0;
379 } else {
380 reason = EXISTS;
381 switch (lookupForInsert(Dir,
382 &entry,
383 dosname, longname, ssp,
384 ch->ignore_entry,
385 ch->source_entry,
386 pessimisticShortRename &&
387 ch->use_longname,
388 ch->use_longname)) {
389 case -1:
390 return NAMEMATCH_ERROR;
Yi Kong39bbd962022-01-09 19:41:38 +0800391
Alistair Delvabeaee832021-02-24 11:27:23 -0800392 case 0:
393 return NAMEMATCH_SKIP;
394 /* Single-file error error or skip request */
Yi Kong39bbd962022-01-09 19:41:38 +0800395
Alistair Delvabeaee832021-02-24 11:27:23 -0800396 case 5:
397 return NAMEMATCH_GREW;
398 /* Grew directory, try again */
Yi Kong39bbd962022-01-09 19:41:38 +0800399
Alistair Delvabeaee832021-02-24 11:27:23 -0800400 case 6:
401 return NAMEMATCH_SUCCESS; /* Success */
Yi Kong39bbd962022-01-09 19:41:38 +0800402 }
Alistair Delvabeaee832021-02-24 11:27:23 -0800403 match_pos = -2;
404 if (ssp->longmatch > -1) {
405 /* Primary Long Name Match */
406#ifdef debug
407 fprintf(stderr,
408 "Got longmatch=%d for name %s.\n",
409 longmatch, longname);
Yi Kong39bbd962022-01-09 19:41:38 +0800410#endif
Alistair Delvabeaee832021-02-24 11:27:23 -0800411 match_pos = ssp->longmatch;
412 isprimary = 1;
413 } else if ((ch->use_longname & 1) && (ssp->shortmatch != -1)) {
414 /* Secondary Short Name Match */
415#ifdef debug
416 fprintf(stderr,
417 "Got secondary short name match for name %s.\n",
418 longname);
419#endif
420
421 match_pos = ssp->shortmatch;
422 isprimary = 0;
423 } else if (ssp->shortmatch >= 0) {
424 /* Primary Short Name Match */
425#ifdef debug
426 fprintf(stderr,
427 "Got primary short name match for name %s.\n",
428 longname);
429#endif
430 match_pos = ssp->shortmatch;
431 isprimary = 1;
432 } else
433 return NAMEMATCH_RENAME;
434
435 if(match_pos > -1) {
436 entry.entry = match_pos;
437 dir_read(&entry, &error);
438 if (error)
439 return NAMEMATCH_ERROR;
440 /* if we can't overwrite, don't propose it */
441 no_overwrite = (match_pos == ch->source || IS_DIR(&entry));
442 }
443 }
444 ret = process_namematch(cp, dosname, longname,
445 isprimary, ch, no_overwrite, reason);
Yi Kong39bbd962022-01-09 19:41:38 +0800446
Alistair Delvabeaee832021-02-24 11:27:23 -0800447 if (ret == NAMEMATCH_OVERWRITE && match_pos > -1){
448 if((entry.dir.attr & 0x5) &&
449 (ask_confirmation("file is read only, overwrite anyway (y/n) ? ")))
450 return NAMEMATCH_RENAME;
451 /* Free up the file to be overwritten */
452 if(fatFreeWithDirentry(&entry))
453 return NAMEMATCH_ERROR;
Yi Kong39bbd962022-01-09 19:41:38 +0800454
Alistair Delvabeaee832021-02-24 11:27:23 -0800455#if 0
456 if(isprimary &&
457 match_pos - ssp->match_free + 1 >= ssp->size_needed){
458 /* reuse old entry and old short name for overwrite */
459 ssp->free_start = match_pos - ssp->size_needed + 1;
460 ssp->free_size = ssp->size_needed;
461 ssp->slot = match_pos;
462 ssp->got_slots = 1;
463 strncpy(dosname, dir.name, 3);
464 strncpy(dosname + 8, dir.ext, 3);
465 return ret;
466 } else
467#endif
468 {
469 wipeEntry(&entry);
470 return NAMEMATCH_RENAME;
471 }
472 }
473
474 return ret;
475}
476
477
478static __inline__ int write_slots(Stream_t *Dir,
479 dos_name_t *dosname,
480 char *longname,
481 struct scan_state *ssp,
482 write_data_callback *cb,
483 void *arg,
484 int Case)
485{
486 direntry_t entry;
487
488 /* write the file */
489 if (fat_error(Dir))
490 return 0;
491
492 entry.Dir = Dir;
493 entry.entry = ssp->slot;
494 native_to_wchar(longname, entry.name, MAX_VNAMELEN, 0, 0);
495 entry.name[MAX_VNAMELEN]='\0';
496 entry.dir.Case = Case & (EXTCASE | BASECASE);
497 if (cb(dosname, longname, arg, &entry) >= 0) {
498 if ((ssp->size_needed > 1) &&
499 (ssp->free_end - ssp->free_start >= ssp->size_needed)) {
500 ssp->slot = write_vfat(Dir, dosname, longname,
501 ssp->free_start, &entry);
502 } else {
503 ssp->size_needed = 1;
504 write_vfat(Dir, dosname, 0,
505 ssp->free_start, &entry);
506 }
507 /* clear_vses(Dir, ssp->free_start + ssp->size_needed,
508 ssp->free_end); */
509 } else
510 return 0;
511
512 return 1; /* Successfully wrote the file */
513}
514
515static void stripspaces(char *name)
516{
517 char *p,*non_space;
518
519 non_space = name;
520 for(p=name; *p; p++)
521 if (*p != ' ')
522 non_space = p;
523 if(name[0])
524 non_space[1] = '\0';
525}
526
527
528static int _mwrite_one(Stream_t *Dir,
529 char *argname,
530 char *shortname,
531 write_data_callback *cb,
532 void *arg,
533 ClashHandling_t *ch)
534{
535 char longname[VBUFSIZE];
536 const char *dstname;
537 dos_name_t dosname;
538 int expanded;
539 struct scan_state scan;
540 clash_action ret;
541 doscp_t *cp = GET_DOSCONVERT(Dir);
542
543 expanded = 0;
544
545 if(isSpecial(argname)) {
546 fprintf(stderr, "Cannot create entry named . or ..\n");
547 return -1;
548 }
549
550 if(ch->name_converter == dos_name) {
551 if(shortname)
552 stripspaces(shortname);
553 if(argname)
554 stripspaces(argname);
555 }
556
557 if(shortname){
558 convert_to_shortname(cp, ch, shortname, &dosname);
559 if(ch->use_longname & 1){
560 /* short name mangled, treat it as a long name */
561 argname = shortname;
562 shortname = 0;
563 }
564 }
565
566 if (argname[0] && (argname[1] == ':')) {
567 /* Skip drive letter */
568 dstname = argname + 2;
569 } else {
570 dstname = argname;
571 }
572
573 /* Copy original argument dstname to working value longname */
574 strncpy(longname, dstname, VBUFSIZE-1);
575
576 if(shortname) {
577 ch->use_longname =
578 convert_to_shortname(cp, ch, shortname, &dosname);
579 if(strcmp(shortname, longname))
580 ch->use_longname |= 1;
581 } else {
582 ch->use_longname =
583 convert_to_shortname(cp, ch, longname, &dosname);
584 }
585
586 ch->action[0] = ch->namematch_default[0];
587 ch->action[1] = ch->namematch_default[1];
588
589 while (1) {
590 switch((ret=get_slots(Dir, &dosname, longname, &scan, ch))){
591 case NAMEMATCH_ERROR:
592 return -1; /* Non-file-specific error,
593 * quit */
Yi Kong39bbd962022-01-09 19:41:38 +0800594
Alistair Delvabeaee832021-02-24 11:27:23 -0800595 case NAMEMATCH_SKIP:
596 return -1; /* Skip file (user request or
597 * error) */
598
599 case NAMEMATCH_PRENAME:
600 ch->use_longname =
601 convert_to_shortname(cp, ch,
602 longname,
603 &dosname);
604 continue;
605 case NAMEMATCH_RENAME:
606 continue; /* Renamed file, loop again */
607
608 case NAMEMATCH_GREW:
609 /* No collision, and not enough slots.
610 * Try to grow the directory
611 */
612 if (expanded) { /* Already tried this
613 * once, no good */
614 fprintf(stderr,
615 "%s: No directory slots\n",
616 progname);
617 return -1;
618 }
619 expanded = 1;
Yi Kong39bbd962022-01-09 19:41:38 +0800620
Alistair Delvabeaee832021-02-24 11:27:23 -0800621 if (dir_grow(Dir, scan.max_entry))
622 return -1;
623 continue;
624 case NAMEMATCH_OVERWRITE:
625 case NAMEMATCH_SUCCESS:
626 return write_slots(Dir, &dosname, longname,
627 &scan, cb, arg,
628 ch->use_longname);
Yi Kong39bbd962022-01-09 19:41:38 +0800629 case NAMEMATCH_NONE:
630 case NAMEMATCH_AUTORENAME:
631 case NAMEMATCH_QUIT:
Alistair Delvabeaee832021-02-24 11:27:23 -0800632 fprintf(stderr,
633 "Internal error: clash_action=%d\n",
634 ret);
635 return -1;
636 }
637
638 }
639}
640
641int mwrite_one(Stream_t *Dir,
642 const char *_argname,
643 const char *_shortname,
644 write_data_callback *cb,
645 void *arg,
646 ClashHandling_t *ch)
647{
648 char *argname;
649 char *shortname;
650 int ret;
651
652 if(_argname)
653 argname = strdup(_argname);
654 else
655 argname = 0;
656 if(_shortname)
657 shortname = strdup(_shortname);
658 else
659 shortname = 0;
660 ret = _mwrite_one(Dir, argname, shortname, cb, arg, ch);
661 if(argname)
662 free(argname);
663 if(shortname)
664 free(shortname);
665 return ret;
666}
667
668void init_clash_handling(ClashHandling_t *ch)
669{
670 ch->ignore_entry = -1;
671 ch->source_entry = -2;
672 ch->nowarn = 0; /*Don't ask, just do default action if name collision */
673 ch->namematch_default[0] = NAMEMATCH_AUTORENAME;
674 ch->namematch_default[1] = NAMEMATCH_NONE;
675 ch->name_converter = dos_name; /* changed by mlabel */
676 ch->source = -2;
677 ch->is_label = 0;
678}
679
680int handle_clash_options(ClashHandling_t *ch, char c)
681{
682 int isprimary;
683 if(isupper(c))
684 isprimary = 0;
685 else
686 isprimary = 1;
687 c = ch_tolower(c);
688 switch(c) {
689 case 'o':
690 /* Overwrite if primary name matches */
691 ch->namematch_default[isprimary] = NAMEMATCH_OVERWRITE;
692 return 0;
693 case 'r':
694 /* Rename primary name interactively */
695 ch->namematch_default[isprimary] = NAMEMATCH_RENAME;
696 return 0;
697 case 's':
698 /* Skip file if primary name collides */
699 ch->namematch_default[isprimary] = NAMEMATCH_SKIP;
700 return 0;
701 case 'm':
702 ch->namematch_default[isprimary] = NAMEMATCH_NONE;
703 return 0;
704 case 'a':
705 ch->namematch_default[isprimary] = NAMEMATCH_AUTORENAME;
706 return 0;
707 default:
708 return -1;
709 }
710}
711
712void dosnameToDirentry(const struct dos_name_t *dn, struct directory *dir) {
713 strncpy(dir->name, dn->base, 8);
714 strncpy(dir->ext, dn->ext, 3);
715}