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