Imported Upstream version 4.0.18
[platform/upstream/mtools.git] / file.c
1 /*  Copyright 1996-1999,2001-2003,2007-2009,2011 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
18 #include "sysincludes.h"
19 #include "msdos.h"
20 #include "stream.h"
21 #include "mtools.h"
22 #include "fsP.h"
23 #include "file.h"
24 #include "htable.h"
25 #include "dirCache.h"
26
27 typedef struct File_t {
28         Class_t *Class;
29         int refs;
30         struct Fs_t *Fs;        /* Filesystem that this fat file belongs to */
31         Stream_t *Buffer;
32
33         int (*map)(struct File_t *this, off_t where, size_t *len, int mode,
34                            mt_off_t *res);
35         size_t FileSize;
36
37         size_t preallocatedSize;
38         int preallocatedClusters;
39
40         /* Absolute position of first cluster of file */
41         unsigned int FirstAbsCluNr;
42
43         /* Absolute position of previous cluster */
44         unsigned int PreviousAbsCluNr;
45
46         /* Relative position of previous cluster */
47         unsigned int PreviousRelCluNr;
48         direntry_t direntry;
49         int hint;
50         struct dirCache_t *dcp;
51
52         unsigned int loopDetectRel;
53         unsigned int loopDetectAbs;
54 } File_t;
55
56 static Class_t FileClass;
57 T_HashTable *filehash;
58
59 static File_t *getUnbufferedFile(Stream_t *Stream)
60 {
61         while(Stream->Class != &FileClass)
62                 Stream = Stream->Next;
63         return (File_t *) Stream;
64 }
65
66 Fs_t *getFs(Stream_t *Stream)
67 {
68         return getUnbufferedFile(Stream)->Fs;
69 }
70
71 struct dirCache_t **getDirCacheP(Stream_t *Stream)
72 {
73         return &getUnbufferedFile(Stream)->dcp;
74 }
75
76 direntry_t *getDirentry(Stream_t *Stream)
77 {
78         return &getUnbufferedFile(Stream)->direntry;
79 }
80
81
82 static int recalcPreallocSize(File_t *This)
83 {
84         size_t currentClusters, neededClusters;
85         int clus_size;
86         int neededPrealloc;
87         Fs_t *Fs = This->Fs;
88         int r;
89
90 #if 0
91         if(This->FileSize & 0xc0000000) {
92                 fprintf(stderr, "Bad filesize\n");
93         }
94         if(This->preallocatedSize & 0xc0000000) {
95                 fprintf(stderr, "Bad preallocated size %x\n",
96                                 (int) This->preallocatedSize);
97         }
98 #endif
99         clus_size = Fs->cluster_size * Fs->sector_size;
100
101         currentClusters = (This->FileSize + clus_size - 1) / clus_size;
102         neededClusters = (This->preallocatedSize + clus_size - 1) / clus_size;
103         neededPrealloc = neededClusters - currentClusters;
104         if(neededPrealloc < 0)
105                 neededPrealloc = 0;
106         r = fsPreallocateClusters(Fs, neededPrealloc - This->preallocatedClusters);
107         if(r)
108                 return r;
109         This->preallocatedClusters = neededPrealloc;
110         return 0;
111 }
112
113 static int _loopDetect(unsigned int *oldrel, unsigned int rel,
114                        unsigned int *oldabs, unsigned int absol)
115 {
116         if(*oldrel && rel > *oldrel && absol == *oldabs) {
117                 fprintf(stderr, "loop detected! oldrel=%d newrel=%d abs=%d\n",
118                                 *oldrel, rel, absol);
119                 return -1;
120         }
121
122         if(rel >= 2 * *oldrel + 1) {
123                 *oldrel = rel;
124                 *oldabs = absol;
125         }
126         return 0;
127 }
128
129
130 static int loopDetect(File_t *This, unsigned int rel, unsigned int absol)
131 {
132         return _loopDetect(&This->loopDetectRel, rel, &This->loopDetectAbs, absol);
133 }
134
135 static unsigned int _countBlocks(Fs_t *This, unsigned int block)
136 {
137         unsigned int blocks;
138         unsigned int rel, oldabs, oldrel;
139
140         blocks = 0;
141         
142         oldabs = oldrel = rel = 0;
143
144         while (block <= This->last_fat && block != 1 && block) {
145                 blocks++;
146                 block = fatDecode(This, block);
147                 rel++;
148                 if(_loopDetect(&oldrel, rel, &oldabs, block) < 0)
149                         block = -1;
150         }
151         return blocks;
152 }
153
154 unsigned int countBlocks(Stream_t *Dir, unsigned int block)
155 {
156         Stream_t *Stream = GetFs(Dir);
157         DeclareThis(Fs_t);
158
159         return _countBlocks(This, block);
160 }
161
162 /* returns number of bytes in a directory.  Represents a file size, and
163  * can hence be not bigger than 2^32
164  */
165 static size_t countBytes(Stream_t *Dir, unsigned int block)
166 {
167         Stream_t *Stream = GetFs(Dir);
168         DeclareThis(Fs_t);
169
170         return _countBlocks(This, block) *
171                 This->sector_size * This->cluster_size;
172 }
173
174 void printFat(Stream_t *Stream)
175 {
176         File_t *This = getUnbufferedFile(Stream);
177         unsigned long n;
178         int rel;
179         unsigned long begin, end;
180         int first;
181
182         n = This->FirstAbsCluNr;
183         if(!n) {
184                 printf("Root directory or empty file\n");
185                 return;
186         }
187
188         rel = 0;
189         first = 1;
190         begin = end = 0;
191         do {
192                 if (first || n != end+1) {
193                         if (!first) {
194                                 if (begin != end)
195                                         printf("-%lu", end);
196                                 printf("> ");
197                         }
198                         begin = end = n;
199                         printf("<%lu", begin);
200                 } else {
201                         end++;
202                 }
203                 first = 0;
204                 n = fatDecode(This->Fs, n);
205                 rel++;
206                 if(loopDetect(This, rel, n) < 0)
207                         n = 1;
208         } while (n <= This->Fs->last_fat && n != 1);
209         if(!first) {
210                 if (begin != end)
211                         printf("-%lu", end);
212                 printf(">");
213         }
214 }
215
216 void printFatWithOffset(Stream_t *Stream, off_t offset) {
217         File_t *This = getUnbufferedFile(Stream);
218         unsigned long n;
219         int rel;
220         off_t clusSize;
221
222         n = This->FirstAbsCluNr;
223         if(!n) {
224                 printf("Root directory or empty file\n");
225                 return;
226         }
227
228         clusSize = This->Fs->cluster_size * This->Fs->sector_size;
229
230         rel = 0;
231         while(offset >= clusSize) {
232                 n = fatDecode(This->Fs, n);
233                 rel++;
234                 if(loopDetect(This, rel, n) < 0)
235                         return;
236                 if(n > This->Fs->last_fat)
237                         return;
238                 offset -= clusSize;
239         }
240
241         printf("%lu", n);
242 }
243
244 static int normal_map(File_t *This, off_t where, size_t *len, int mode,
245                                                    mt_off_t *res)
246 {
247         int offset;
248         size_t end;
249         int NrClu; /* number of clusters to read */
250         unsigned int RelCluNr;
251         unsigned int CurCluNr;
252         unsigned int NewCluNr;
253         unsigned int AbsCluNr;
254         int clus_size;
255         Fs_t *Fs = This->Fs;
256
257         *res = 0;
258         clus_size = Fs->cluster_size * Fs->sector_size;
259         offset = where % clus_size;
260
261         if (mode == MT_READ)
262                 maximize(*len, This->FileSize - where);
263         if (*len == 0 )
264                 return 0;
265
266         if (This->FirstAbsCluNr < 2){
267                 if( mode == MT_READ || *len == 0){
268                         *len = 0;
269                         return 0;
270                 }
271                 NewCluNr = get_next_free_cluster(This->Fs, 1);
272                 if (NewCluNr == 1 ){
273                         errno = ENOSPC;
274                         return -2;
275                 }
276                 hash_remove(filehash, (void *) This, This->hint);
277                 This->FirstAbsCluNr = NewCluNr;
278                 hash_add(filehash, (void *) This, &This->hint);
279                 fatAllocate(This->Fs, NewCluNr, Fs->end_fat);
280         }
281
282         RelCluNr = where / clus_size;
283         
284         if (RelCluNr >= This->PreviousRelCluNr){
285                 CurCluNr = This->PreviousRelCluNr;
286                 AbsCluNr = This->PreviousAbsCluNr;
287         } else {
288                 CurCluNr = 0;
289                 AbsCluNr = This->FirstAbsCluNr;
290         }
291
292
293         NrClu = (offset + *len - 1) / clus_size;
294         while (CurCluNr <= RelCluNr + NrClu){
295                 if (CurCluNr == RelCluNr){
296                         /* we have reached the beginning of our zone. Save
297                          * coordinates */
298                         This->PreviousRelCluNr = RelCluNr;
299                         This->PreviousAbsCluNr = AbsCluNr;
300                 }
301                 NewCluNr = fatDecode(This->Fs, AbsCluNr);
302                 if (NewCluNr == 1 || NewCluNr == 0){
303                         fprintf(stderr,"Fat problem while decoding %d %x\n",
304                                 AbsCluNr, NewCluNr);
305                         exit(1);
306                 }
307                 if(CurCluNr == RelCluNr + NrClu)                        
308                         break;
309                 if (NewCluNr > Fs->last_fat && mode == MT_WRITE){
310                         /* if at end, and writing, extend it */
311                         NewCluNr = get_next_free_cluster(This->Fs, AbsCluNr);
312                         if (NewCluNr == 1 ){ /* no more space */
313                                 errno = ENOSPC;
314                                 return -2;
315                         }
316                         fatAppend(This->Fs, AbsCluNr, NewCluNr);
317                 }
318
319                 if (CurCluNr < RelCluNr && NewCluNr > Fs->last_fat){
320                         *len = 0;
321                         return 0;
322                 }
323
324                 if (CurCluNr >= RelCluNr && NewCluNr != AbsCluNr + 1)
325                         break;
326                 CurCluNr++;
327                 AbsCluNr = NewCluNr;
328                 if(loopDetect(This, CurCluNr, AbsCluNr)) {
329                         errno = EIO;
330                         return -2;
331                 }
332         }
333
334         maximize(*len, (1 + CurCluNr - RelCluNr) * clus_size - offset);
335         
336         end = where + *len;
337         if(batchmode &&
338            mode == MT_WRITE &&
339            end >= This->FileSize) {
340                 *len += ROUND_UP(end, clus_size) - end;
341         }
342
343         if((*len + offset) / clus_size + This->PreviousAbsCluNr-2 >
344                 Fs->num_clus) {
345                 fprintf(stderr, "cluster too big\n");
346                 exit(1);
347         }
348
349         *res = sectorsToBytes((Stream_t*)Fs,
350                                                   (This->PreviousAbsCluNr-2) * Fs->cluster_size +
351                                                   Fs->clus_start) + offset;
352         return 1;
353 }
354
355
356 static int root_map(File_t *This, off_t where, size_t *len, int mode UNUSEDP,
357                     mt_off_t *res)
358 {
359         Fs_t *Fs = This->Fs;
360
361         if(Fs->dir_len * Fs->sector_size < (size_t) where) {
362                 *len = 0;
363                 errno = ENOSPC;
364                 return -2;
365         }
366
367         maximize(*len, Fs->dir_len * Fs->sector_size - where);
368         if (*len == 0)
369             return 0;
370         
371         *res = sectorsToBytes((Stream_t*)Fs, Fs->dir_start) + where;
372         return 1;
373 }
374         
375
376 static int read_file(Stream_t *Stream, char *buf, mt_off_t iwhere,
377                                          size_t len)
378 {
379         DeclareThis(File_t);
380         mt_off_t pos;
381         int err;
382         off_t where = truncBytes32(iwhere);
383
384         Stream_t *Disk = This->Fs->Next;
385         
386         err = This->map(This, where, &len, MT_READ, &pos);
387         if(err <= 0)
388                 return err;
389         return READS(Disk, buf, pos, len);
390 }
391
392 static int write_file(Stream_t *Stream, char *buf, mt_off_t iwhere, size_t len)
393 {
394         DeclareThis(File_t);
395         mt_off_t pos;
396         int ret;
397         size_t requestedLen;
398         Stream_t *Disk = This->Fs->Next;
399         off_t where = truncBytes32(iwhere);
400         int err;
401
402         requestedLen = len;
403         err = This->map(This, where, &len, MT_WRITE, &pos);
404         if( err <= 0)
405                 return err;
406         if(batchmode)
407                 ret = force_write(Disk, buf, pos, len);
408         else
409                 ret = WRITES(Disk, buf, pos, len);
410         if(ret > (signed int) requestedLen)
411                 ret = requestedLen;
412         if (ret > 0 &&
413             where + ret > (off_t) This->FileSize )
414                 This->FileSize = where + ret;
415         recalcPreallocSize(This);
416         return ret;
417 }
418
419
420 /*
421  * Convert an MSDOS time & date stamp to the Unix time() format
422  */
423
424 static int month[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
425                                           0, 0, 0 };
426 static __inline__ time_t conv_stamp(struct directory *dir)
427 {
428         struct tm *tmbuf;
429         long tzone, dst;
430         time_t accum, tmp;
431
432         accum = DOS_YEAR(dir) - 1970; /* years past */
433
434         /* days passed */
435         accum = accum * 365L + month[DOS_MONTH(dir)-1] + DOS_DAY(dir);
436
437         /* leap years */
438         accum += (DOS_YEAR(dir) - 1972) / 4L;
439
440         /* back off 1 day if before 29 Feb */
441         if (!(DOS_YEAR(dir) % 4) && DOS_MONTH(dir) < 3)
442                 accum--;
443         accum = accum * 24L + DOS_HOUR(dir); /* hours passed */
444         accum = accum * 60L + DOS_MINUTE(dir); /* minutes passed */
445         accum = accum * 60L + DOS_SEC(dir); /* seconds passed */
446
447         /* correct for Time Zone */
448 #ifdef HAVE_GETTIMEOFDAY
449         {
450                 struct timeval tv;
451                 struct timezone tz;
452                 
453                 gettimeofday(&tv, &tz);
454                 tzone = tz.tz_minuteswest * 60L;
455         }
456 #else
457 #if defined HAVE_TZSET && !defined OS_mingw32msvc
458         {
459 #if !defined OS_ultrix && !defined OS_cygwin
460                 /* Ultrix defines this to be a different type */
461                 extern long timezone;
462 #endif
463                 tzset();
464                 tzone = (long) timezone;
465         }
466 #else
467         tzone = 0;
468 #endif /* HAVE_TZSET */
469 #endif /* HAVE_GETTIMEOFDAY */
470
471         accum += tzone;
472
473         /* correct for Daylight Saving Time */
474         tmp = accum;
475         tmbuf = localtime(&tmp);
476         if(tmbuf) {
477                 dst = (tmbuf->tm_isdst) ? (-60L * 60L) : 0L;
478                 accum += dst;
479         }
480         return accum;
481 }
482
483
484 static int get_file_data(Stream_t *Stream, time_t *date, mt_size_t *size,
485                          int *type, int *address)
486 {
487         DeclareThis(File_t);
488
489         if(date)
490                 *date = conv_stamp(& This->direntry.dir);
491         if(size)
492                 *size = (mt_size_t) This->FileSize;
493         if(type)
494                 *type = This->direntry.dir.attr & ATTR_DIR;
495         if(address)
496                 *address = This->FirstAbsCluNr;
497         return 0;
498 }
499
500
501 static int free_file(Stream_t *Stream)
502 {
503         DeclareThis(File_t);
504         Fs_t *Fs = This->Fs;
505         fsPreallocateClusters(Fs, -This->preallocatedClusters);
506         FREE(&This->direntry.Dir);
507         freeDirCache(Stream);
508         return hash_remove(filehash, (void *) Stream, This->hint);
509 }
510
511
512 static int flush_file(Stream_t *Stream)
513 {
514         DeclareThis(File_t);
515         direntry_t *entry = &This->direntry;
516
517         if(isRootDir(Stream)) {
518                 return 0;
519         }
520
521         if(This->FirstAbsCluNr != getStart(entry->Dir, &entry->dir)) {
522                 set_word(entry->dir.start, This->FirstAbsCluNr & 0xffff);
523                 set_word(entry->dir.startHi, This->FirstAbsCluNr >> 16);
524                 dir_write(entry);
525         }
526         return 0;
527 }
528
529
530 static int pre_allocate_file(Stream_t *Stream, mt_size_t isize)
531 {
532         DeclareThis(File_t);
533
534         size_t size = truncBytes32(isize);
535
536         if(size > This->FileSize &&
537            size > This->preallocatedSize) {
538                 This->preallocatedSize = size;
539                 return recalcPreallocSize(This);
540         } else
541                 return 0;
542 }
543
544 static Class_t FileClass = {
545         read_file,
546         write_file,
547         flush_file, /* flush */
548         free_file, /* free */
549         0, /* get_geom */
550         get_file_data,
551         pre_allocate_file,
552         get_dosConvert_pass_through
553 };
554
555 static unsigned int getAbsCluNr(File_t *This)
556 {
557         if(This->FirstAbsCluNr)
558                 return This->FirstAbsCluNr;
559         if(isRootDir((Stream_t *) This))
560                 return 0;
561         return 1;
562 }
563
564 static unsigned int func1(void *Stream)
565 {
566         DeclareThis(File_t);
567
568         return getAbsCluNr(This) ^ (long) This->Fs;
569 }
570
571 static unsigned int func2(void *Stream)
572 {
573         DeclareThis(File_t);
574
575         return getAbsCluNr(This);
576 }
577
578 static int comp(void *Stream, void *Stream2)
579 {
580         DeclareThis(File_t);
581
582         File_t *This2 = (File_t *) Stream2;
583
584         return This->Fs != This2->Fs ||
585                 getAbsCluNr(This) != getAbsCluNr(This2);
586 }
587
588 static void init_hash(void)
589 {
590         static int is_initialised=0;
591         
592         if(!is_initialised){
593                 make_ht(func1, func2, comp, 20, &filehash);
594                 is_initialised = 1;
595         }
596 }
597
598
599 static Stream_t *_internalFileOpen(Stream_t *Dir, unsigned int first,
600                                    size_t size, direntry_t *entry)
601 {
602         Stream_t *Stream = GetFs(Dir);
603         DeclareThis(Fs_t);
604         File_t Pattern;
605         File_t *File;
606
607         init_hash();
608         This->refs++;
609
610         if(first != 1){
611                 /* we use the illegal cluster 1 to mark newly created files.
612                  * do not manage those by hashtable */
613                 Pattern.Fs = This;
614                 Pattern.Class = &FileClass;
615                 if(first || (entry && !IS_DIR(entry)))
616                         Pattern.map = normal_map;
617                 else
618                         Pattern.map = root_map;
619                 Pattern.FirstAbsCluNr = first;
620                 Pattern.loopDetectRel = 0;
621                 Pattern.loopDetectAbs = first;
622                 if(!hash_lookup(filehash, (T_HashTableEl) &Pattern,
623                                 (T_HashTableEl **)&File, 0)){
624                         File->refs++;
625                         This->refs--;
626                         return (Stream_t *) File;
627                 }
628         }
629
630         File = New(File_t);
631         if (!File)
632                 return NULL;
633         File->dcp = 0;
634         File->preallocatedClusters = 0;
635         File->preallocatedSize = 0;
636         /* memorize dir for date and attrib */
637         File->direntry = *entry;
638         if(entry->entry == -3)
639                 File->direntry.Dir = (Stream_t *) File; /* root directory */
640         else
641                 COPY(File->direntry.Dir);
642
643         File->Class = &FileClass;
644         File->Fs = This;
645         if(first || (entry && !IS_DIR(entry)))
646                 File->map = normal_map;
647         else
648                 File->map = root_map; /* FAT 12/16 root directory */
649         if(first == 1)
650                 File->FirstAbsCluNr = 0;
651         else
652                 File->FirstAbsCluNr = first;
653
654         File->loopDetectRel = 0;
655         File->loopDetectAbs = 0;
656
657         File->PreviousRelCluNr = 0xffff;
658         File->FileSize = size;
659         File->refs = 1;
660         File->Buffer = 0;
661         hash_add(filehash, (void *) File, &File->hint);
662         return (Stream_t *) File;
663 }
664
665 Stream_t *OpenRoot(Stream_t *Dir)
666 {
667         unsigned int num;
668         direntry_t entry;
669         size_t size;
670         Stream_t *file;
671
672         memset(&entry, 0, sizeof(direntry_t));
673
674         num = fat32RootCluster(Dir);
675
676         /* make the directory entry */
677         entry.entry = -3;
678         entry.name[0] = '\0';
679         mk_entry_from_base("/", ATTR_DIR, num, 0, 0, &entry.dir);
680
681         if(num)
682                 size = countBytes(Dir, num);
683         else {
684                 Fs_t *Fs = (Fs_t *) GetFs(Dir);
685                 size = Fs->dir_len * Fs->sector_size;
686         }
687         file = _internalFileOpen(Dir, num, size, &entry);
688         bufferize(&file);
689         return file;
690 }
691
692
693 Stream_t *OpenFileByDirentry(direntry_t *entry)
694 {
695         Stream_t *file;
696         unsigned int first;
697         size_t size;
698
699         first = getStart(entry->Dir, &entry->dir);
700
701         if(!first && IS_DIR(entry))
702                 return OpenRoot(entry->Dir);
703         if (IS_DIR(entry))
704                 size = countBytes(entry->Dir, first);
705         else
706                 size = FILE_SIZE(&entry->dir);
707         file = _internalFileOpen(entry->Dir, first, size, entry);
708         if(IS_DIR(entry)) {
709                 bufferize(&file);
710                 if(first == 1)
711                         dir_grow(file, 0);
712         }
713
714         return file;
715 }
716
717
718 int isRootDir(Stream_t *Stream)
719 {
720         File_t *This = getUnbufferedFile(Stream);
721
722         return This->map == root_map;
723 }