Initial import package mtools: Programs for accessing MS-DOS disks without mounting...
[profile/ivi/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 "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  */
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         return mangled;
51 }
52
53 static __inline__ void chomp(char *line)
54 {
55         int 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 #define maxsize (isprimary ?  MAX_VNAMELEN+1 : 11+1)
82 #define name (isprimary ? argname : shortname)
83
84         mangled = 0;
85         do {
86                 char tname[4*MAX_VNAMELEN+1];
87                 fprintf(stderr, "New %s name for \"%s\": ",
88                         isprimary ? "primary" : "secondary", longname);
89                 fflush(stderr);
90                 if (! fgets(tname, 4*MAX_VNAMELEN+1, opentty(0)))
91                         return 0;
92                 chomp(tname);
93                 if (isprimary)
94                         strcpy(longname, tname);
95                 else
96                         mangled = convert_to_shortname(cp, 
97                                                        ch, tname, shortname);
98         } while (mangled & 1);
99         return 1;
100 #undef maxsize
101 #undef name
102 }
103
104 /**
105  * This function determines the action to be taken in case there is a problem
106  * with target name (clash, illegal characters, or reserved)
107  * The decision either comes from the default (ch), or the user will be
108  * prompted if there is no default
109  */
110 static __inline__ clash_action ask_namematch(doscp_t *cp,
111                                              dos_name_t *dosname,
112                                              char *longname,
113                                              int isprimary, 
114                                              ClashHandling_t *ch,
115                                              int no_overwrite,
116                                              int reason)
117 {
118         /* User's answer letter (from keyboard). Only first letter is used,
119          * but we allocate space for 10 in order to account for extra garbage
120          * that user may enter
121          */
122         char ans[10];
123
124         /**
125          * Return value: action to be taken
126          */
127         clash_action a;
128
129         /**
130          * Should this decision be made permanent (do no longer ask same
131          * question)
132          */
133         int perm;
134
135         /**
136          * Buffer for shortname
137          */
138         char name_buffer[4*13];
139
140         /**
141          * Name to be printed
142          */
143         char *name;
144
145 #define EXISTS 0
146 #define RESERVED 1
147 #define ILLEGALS 2
148
149         static const char *reasons[]= {
150                 "already exists",
151                 "is reserved",
152                 "contains illegal character(s)"};
153
154         a = ch->action[isprimary];
155
156         if(a == NAMEMATCH_NONE && !opentty(1)) {
157                 /* no default, and no tty either . Skip the troublesome file */
158                 return NAMEMATCH_SKIP;
159         }
160
161         if (!isprimary)
162                 name = unix_normalize(cp, name_buffer, dosname);
163         else
164                 name = longname;
165
166         perm = 0;
167         while (a == NAMEMATCH_NONE) {
168                 fprintf(stderr, "%s file name \"%s\" %s.\n",
169                         isprimary ? "Long" : "Short", name, reasons[reason]);
170                 fprintf(stderr,
171                         "a)utorename A)utorename-all r)ename R)ename-all ");
172                 if(!no_overwrite)
173                         fprintf(stderr,"o)verwrite O)verwrite-all");
174                 fprintf(stderr,
175                         "\ns)kip S)kip-all q)uit (aArR");
176                 if(!no_overwrite)
177                         fprintf(stderr,"oO");
178                 fprintf(stderr,"sSq): ");
179                 fflush(stderr);
180                 fflush(opentty(1));
181                 if (mtools_raw_tty) {
182                         int rep;
183                         rep = fgetc(opentty(1));                        
184                         fputs("\n", stderr);
185                         if(rep == EOF)
186                                 ans[0] = 'q';
187                         else
188                                 ans[0] = rep;
189                 } else {
190                         if(fgets(ans, 9, opentty(0)) == NULL)
191                                 ans[0] = 'q';
192                 }
193                 perm = isupper((unsigned char)ans[0]);
194                 switch(tolower((unsigned char)ans[0])) {
195                         case 'a':
196                                 a = NAMEMATCH_AUTORENAME;
197                                 break;
198                         case 'r':
199                                 if(isprimary)
200                                         a = NAMEMATCH_PRENAME;
201                                 else
202                                         a = NAMEMATCH_RENAME;
203                                 break;
204                         case 'o':
205                                 if(no_overwrite)
206                                         continue;
207                                 a = NAMEMATCH_OVERWRITE;
208                                 break;
209                         case 's':
210                                 a = NAMEMATCH_SKIP;
211                                 break;
212                         case 'q':
213                                 perm = 0;
214                                 a = NAMEMATCH_QUIT;
215                                 break;
216                         default:
217                                 perm = 0;
218                 }
219         }
220
221         /* Keep track of this action in case this file collides again */
222         ch->action[isprimary]  = a;
223         if (perm)
224                 ch->namematch_default[isprimary] = a;
225
226         /* if we were asked to overwrite be careful. We can't set the action
227          * to overwrite, else we get won't get a chance to specify another
228          * action, should overwrite fail. Indeed, we'll be caught in an
229          * infinite loop because overwrite will fail the same way for the
230          * second time */
231         if(a == NAMEMATCH_OVERWRITE)
232                 ch->action[isprimary] = NAMEMATCH_NONE;
233         return a;
234 }
235
236 /*
237  * Processes a name match
238  *  dosname short dosname (ignored if is_primary)
239  *
240  *
241  * Returns:
242  * 2 if file is to be overwritten
243  * 1 if file was renamed
244  * 0 if it was skipped
245  *
246  * If a short name is involved, handle conversion between the 11-character
247  * fixed-length record DOS name and a literal null-terminated name (e.g.
248  * "COMMAND  COM" (no null) <-> "COMMAND.COM" (null terminated)).
249  *
250  * Also, immediately copy the original name so that messages can use it.
251  */
252 static __inline__ clash_action process_namematch(doscp_t *cp,
253                                                  dos_name_t *dosname,
254                                                  char *longname,
255                                                  int isprimary,
256                                                  ClashHandling_t *ch,
257                                                  int no_overwrite,
258                                                  int reason)
259 {
260         clash_action action;
261
262 #if 0
263         fprintf(stderr,
264                 "process_namematch: name=%s, default_action=%d, ask=%d.\n",
265                 name, default_action, ch->ask);
266 #endif
267
268         action = ask_namematch(cp, dosname, longname,
269                                isprimary, ch, no_overwrite, reason);
270
271         switch(action){
272         case NAMEMATCH_QUIT:
273                 got_signal = 1;
274                 return NAMEMATCH_SKIP;
275         case NAMEMATCH_SKIP:
276                 return NAMEMATCH_SKIP;
277         case NAMEMATCH_RENAME:
278         case NAMEMATCH_PRENAME:
279                 /* We need to rename the file now.  This means we must pass
280                  * back through the loop, a) ensuring there isn't a potential
281                  * new name collision, and b) finding a big enough VSE.
282                  * Change the name, so that it won't collide again.
283                  */
284                 ask_rename(cp, ch, dosname, longname, isprimary);
285                 return action;
286         case NAMEMATCH_AUTORENAME:
287                 /* Very similar to NAMEMATCH_RENAME, except that we need to
288                  * first generate the name.
289                  * TODO: Remember previous name so we don't
290                  * keep trying the same one.
291                  */
292                 if (isprimary) {
293                         autorename_long(longname, 1);
294                         return NAMEMATCH_PRENAME;
295                 } else {
296                         autorename_short(dosname, 1);
297                         return NAMEMATCH_RENAME;
298                 }
299         case NAMEMATCH_OVERWRITE:
300                 if(no_overwrite)
301                         return NAMEMATCH_SKIP;
302                 else
303                         return NAMEMATCH_OVERWRITE;
304         default:
305                 return NAMEMATCH_NONE;
306         }
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(contains_illegals(dosname->base,short_illegals,11)) {
373                 reason = ILLEGALS;
374                 ch->use_longname = 1;
375                 isprimary = 0;
376         } else {
377                 reason = EXISTS;
378                 switch (lookupForInsert(Dir,
379                                         &entry,
380                                         dosname, longname, ssp,
381                                         ch->ignore_entry,
382                                         ch->source_entry,
383                                         pessimisticShortRename &&
384                                         ch->use_longname,
385                                         ch->use_longname)) {
386                         case -1:
387                                 return NAMEMATCH_ERROR;
388                                 
389                         case 0:
390                                 return NAMEMATCH_SKIP;
391                                 /* Single-file error error or skip request */
392                                 
393                         case 5:
394                                 return NAMEMATCH_GREW;
395                                 /* Grew directory, try again */
396                                 
397                         case 6:
398                                 return NAMEMATCH_SUCCESS; /* Success */
399                 }       
400                 match_pos = -2;
401                 if (ssp->longmatch > -1) {
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                         isprimary = 0;
420                 } else if (ssp->shortmatch >= 0) {
421                         /* Primary Short Name Match */
422 #ifdef debug
423                         fprintf(stderr,
424                                 "Got primary short name match for name %s.\n",
425                                 longname);
426 #endif
427                         match_pos = ssp->shortmatch;
428                         isprimary = 1;
429                 } else
430                         return NAMEMATCH_RENAME;
431
432                 if(match_pos > -1) {
433                         entry.entry = match_pos;
434                         dir_read(&entry, &error);
435                         if (error)
436                             return NAMEMATCH_ERROR;
437                         /* if we can't overwrite, don't propose it */
438                         no_overwrite = (match_pos == ch->source || IS_DIR(&entry));
439                 }
440         }
441         ret = process_namematch(cp, dosname, longname,
442                                 isprimary, ch, no_overwrite, reason);
443         
444         if (ret == NAMEMATCH_OVERWRITE && match_pos > -1){
445                 if((entry.dir.attr & 0x5) &&
446                    (ask_confirmation("file is read only, overwrite anyway (y/n) ? ")))
447                         return NAMEMATCH_RENAME;
448                 /* Free up the file to be overwritten */
449                 if(fatFreeWithDirentry(&entry))
450                         return NAMEMATCH_ERROR;
451                 
452 #if 0
453                 if(isprimary &&
454                    match_pos - ssp->match_free + 1 >= ssp->size_needed){
455                         /* reuse old entry and old short name for overwrite */
456                         ssp->free_start = match_pos - ssp->size_needed + 1;
457                         ssp->free_size = ssp->size_needed;
458                         ssp->slot = match_pos;
459                         ssp->got_slots = 1;
460                         strncpy(dosname, dir.name, 3);
461                         strncpy(dosname + 8, dir.ext, 3);
462                         return ret;
463                 } else
464 #endif
465                         {
466                         wipeEntry(&entry);
467                         return NAMEMATCH_RENAME;
468                 }
469         }
470
471         return ret;
472 }
473
474
475 static __inline__ int write_slots(Stream_t *Dir,
476                                   dos_name_t *dosname,
477                                   char *longname,
478                                   struct scan_state *ssp,
479                                   write_data_callback *cb,
480                                   void *arg,
481                                   int Case)
482 {
483         direntry_t entry;
484
485         /* write the file */
486         if (fat_error(Dir))
487                 return 0;
488
489         entry.Dir = Dir;
490         entry.entry = ssp->slot;
491         native_to_wchar(longname, entry.name, MAX_VNAMELEN, 0, 0);
492         entry.name[MAX_VNAMELEN]='\0';
493         entry.dir.Case = Case & (EXTCASE | BASECASE);
494         if (cb(dosname, longname, arg, &entry) >= 0) {
495                 if ((ssp->size_needed > 1) &&
496                     (ssp->free_end - ssp->free_start >= ssp->size_needed)) {
497                         ssp->slot = write_vfat(Dir, dosname, longname,
498                                                ssp->free_start, &entry);
499                 } else {
500                         ssp->size_needed = 1;
501                         write_vfat(Dir, dosname, 0,
502                                    ssp->free_start, &entry);
503                 }
504                 /* clear_vses(Dir, ssp->free_start + ssp->size_needed,
505                    ssp->free_end); */
506         } else
507                 return 0;
508
509         return 1;       /* Successfully wrote the file */
510 }
511
512 static void stripspaces(char *name)
513 {
514         char *p,*non_space;
515
516         non_space = name;
517         for(p=name; *p; p++)
518                 if (*p != ' ')
519                         non_space = p;
520         if(name[0])
521                 non_space[1] = '\0';
522 }
523
524
525 static int _mwrite_one(Stream_t *Dir,
526                        char *argname,
527                        char *shortname,
528                        write_data_callback *cb,
529                        void *arg,
530                        ClashHandling_t *ch)
531 {
532         char longname[VBUFSIZE];
533         const char *dstname;
534         dos_name_t dosname;
535         int expanded;
536         struct scan_state scan;
537         clash_action ret;
538         doscp_t *cp = GET_DOSCONVERT(Dir);
539
540         expanded = 0;
541
542         if(isSpecial(argname)) {
543                 fprintf(stderr, "Cannot create entry named . or ..\n");
544                 return -1;
545         }
546
547         if(ch->name_converter == dos_name) {
548                 if(shortname)
549                         stripspaces(shortname);
550                 if(argname)
551                         stripspaces(argname);
552         }
553
554         if(shortname){
555                 convert_to_shortname(cp, ch, shortname, &dosname);
556                 if(ch->use_longname & 1){
557                         /* short name mangled, treat it as a long name */
558                         argname = shortname;
559                         shortname = 0;
560                 }
561         }
562
563         if (argname[0] && (argname[1] == ':')) {
564                 /* Skip drive letter */
565                 dstname = argname + 2;
566         } else {
567                 dstname = argname;
568         }
569
570         /* Copy original argument dstname to working value longname */
571         strncpy(longname, dstname, VBUFSIZE-1);
572
573         if(shortname) {
574                 ch->use_longname =
575                         convert_to_shortname(cp, ch, shortname, &dosname);
576                 if(strcmp(shortname, longname))
577                         ch->use_longname |= 1;
578         } else {
579                 ch->use_longname =
580                         convert_to_shortname(cp, ch, longname, &dosname);
581         }
582
583         ch->action[0] = ch->namematch_default[0];
584         ch->action[1] = ch->namematch_default[1];
585
586         while (1) {
587                 switch((ret=get_slots(Dir, &dosname, longname, &scan, ch))){
588                         case NAMEMATCH_ERROR:
589                                 return -1;      /* Non-file-specific error,
590                                                  * quit */
591                                 
592                         case NAMEMATCH_SKIP:
593                                 return -1;      /* Skip file (user request or
594                                                  * error) */
595
596                         case NAMEMATCH_PRENAME:
597                                 ch->use_longname =
598                                         convert_to_shortname(cp, ch,
599                                                              longname,
600                                                              &dosname);
601                                 continue;
602                         case NAMEMATCH_RENAME:
603                                 continue;       /* Renamed file, loop again */
604
605                         case NAMEMATCH_GREW:
606                                 /* No collision, and not enough slots.
607                                  * Try to grow the directory
608                                  */
609                                 if (expanded) { /* Already tried this
610                                                  * once, no good */
611                                         fprintf(stderr,
612                                                 "%s: No directory slots\n",
613                                                 progname);
614                                         return -1;
615                                 }
616                                 expanded = 1;
617                                 
618                                 if (dir_grow(Dir, scan.max_entry))
619                                         return -1;
620                                 continue;
621                         case NAMEMATCH_OVERWRITE:
622                         case NAMEMATCH_SUCCESS:
623                                 return write_slots(Dir, &dosname, longname,
624                                                    &scan, cb, arg,
625                                                    ch->use_longname);
626                         default:
627                                 fprintf(stderr,
628                                         "Internal error: clash_action=%d\n",
629                                         ret);
630                                 return -1;
631                 }
632
633         }
634 }
635
636 int mwrite_one(Stream_t *Dir,
637                const char *_argname,
638                const char *_shortname,
639                write_data_callback *cb,
640                void *arg,
641                ClashHandling_t *ch)
642 {
643         char *argname;
644         char *shortname;
645         int ret;
646
647         if(_argname)
648                 argname = strdup(_argname);
649         else
650                 argname = 0;
651         if(_shortname)
652                 shortname = strdup(_shortname);
653         else
654                 shortname = 0;
655         ret = _mwrite_one(Dir, argname, shortname, cb, arg, ch);
656         if(argname)
657                 free(argname);
658         if(shortname)
659                 free(shortname);
660         return ret;
661 }
662
663 void init_clash_handling(ClashHandling_t *ch)
664 {
665         ch->ignore_entry = -1;
666         ch->source_entry = -2;
667         ch->nowarn = 0; /*Don't ask, just do default action if name collision */
668         ch->namematch_default[0] = NAMEMATCH_AUTORENAME;
669         ch->namematch_default[1] = NAMEMATCH_NONE;
670         ch->name_converter = dos_name; /* changed by mlabel */
671         ch->source = -2;
672 }
673
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 = 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
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 }