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