1 /* Copyright 1996-1999,2001-2003,2007-2009 Alain Knaff.
2 * This file is part of mtools.
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.
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.
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/>.
18 #include "sysincludes.h"
27 typedef struct File_t {
30 struct Fs_t *Fs; /* Filesystem that this fat file belongs to */
33 int (*map)(struct File_t *this, off_t where, size_t *len, int mode,
37 size_t preallocatedSize;
38 int preallocatedClusters;
40 /* Absolute position of first cluster of file */
41 unsigned int FirstAbsCluNr;
43 /* Absolute position of previous cluster */
44 unsigned int PreviousAbsCluNr;
46 /* Relative position of previous cluster */
47 unsigned int PreviousRelCluNr;
50 struct dirCache_t *dcp;
52 unsigned int loopDetectRel;
53 unsigned int loopDetectAbs;
56 static Class_t FileClass;
57 T_HashTable *filehash;
59 static File_t *getUnbufferedFile(Stream_t *Stream)
61 while(Stream->Class != &FileClass)
62 Stream = Stream->Next;
63 return (File_t *) Stream;
66 Fs_t *getFs(Stream_t *Stream)
68 return getUnbufferedFile(Stream)->Fs;
71 struct dirCache_t **getDirCacheP(Stream_t *Stream)
73 return &getUnbufferedFile(Stream)->dcp;
76 direntry_t *getDirentry(Stream_t *Stream)
78 return &getUnbufferedFile(Stream)->direntry;
82 static int recalcPreallocSize(File_t *This)
84 size_t currentClusters, neededClusters;
91 if(This->FileSize & 0xc0000000) {
92 fprintf(stderr, "Bad filesize\n");
94 if(This->preallocatedSize & 0xc0000000) {
95 fprintf(stderr, "Bad preallocated size %x\n",
96 (int) This->preallocatedSize);
99 clus_size = Fs->cluster_size * Fs->sector_size;
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)
106 r = fsPreallocateClusters(Fs, neededPrealloc - This->preallocatedClusters);
109 This->preallocatedClusters = neededPrealloc;
113 static int _loopDetect(unsigned int *oldrel, unsigned int rel,
114 unsigned int *oldabs, unsigned int absol)
116 if(*oldrel && rel > *oldrel && absol == *oldabs) {
117 fprintf(stderr, "loop detected! oldrel=%d newrel=%d abs=%d\n",
118 *oldrel, rel, absol);
122 if(rel >= 2 * *oldrel + 1) {
130 static int loopDetect(File_t *This, unsigned int rel, unsigned int absol)
132 return _loopDetect(&This->loopDetectRel, rel, &This->loopDetectAbs, absol);
135 static unsigned int _countBlocks(Fs_t *This, unsigned int block)
138 unsigned int rel, oldabs, oldrel;
142 oldabs = oldrel = rel = 0;
144 while (block <= This->last_fat && block != 1 && block) {
146 block = fatDecode(This, block);
148 if(_loopDetect(&oldrel, rel, &oldabs, block) < 0)
154 unsigned int countBlocks(Stream_t *Dir, unsigned int block)
156 Stream_t *Stream = GetFs(Dir);
159 return _countBlocks(This, block);
162 /* returns number of bytes in a directory. Represents a file size, and
163 * can hence be not bigger than 2^32
165 static size_t countBytes(Stream_t *Dir, unsigned int block)
167 Stream_t *Stream = GetFs(Dir);
170 return _countBlocks(This, block) *
171 This->sector_size * This->cluster_size;
174 void printFat(Stream_t *Stream)
176 File_t *This = getUnbufferedFile(Stream);
179 unsigned long begin, end;
182 n = This->FirstAbsCluNr;
184 printf("Root directory or empty file\n");
192 if (first || n != end+1) {
199 printf("<%lu", begin);
204 n = fatDecode(This->Fs, n);
206 if(loopDetect(This, rel, n) < 0)
208 } while (n <= This->Fs->last_fat && n != 1);
216 static int normal_map(File_t *This, off_t where, size_t *len, int mode,
221 int NrClu; /* number of clusters to read */
222 unsigned int RelCluNr;
223 unsigned int CurCluNr;
224 unsigned int NewCluNr;
225 unsigned int AbsCluNr;
230 clus_size = Fs->cluster_size * Fs->sector_size;
231 offset = where % clus_size;
234 maximize(*len, This->FileSize - where);
238 if (This->FirstAbsCluNr < 2){
239 if( mode == MT_READ || *len == 0){
243 NewCluNr = get_next_free_cluster(This->Fs, 1);
248 hash_remove(filehash, (void *) This, This->hint);
249 This->FirstAbsCluNr = NewCluNr;
250 hash_add(filehash, (void *) This, &This->hint);
251 fatAllocate(This->Fs, NewCluNr, Fs->end_fat);
254 RelCluNr = where / clus_size;
256 if (RelCluNr >= This->PreviousRelCluNr){
257 CurCluNr = This->PreviousRelCluNr;
258 AbsCluNr = This->PreviousAbsCluNr;
261 AbsCluNr = This->FirstAbsCluNr;
265 NrClu = (offset + *len - 1) / clus_size;
266 while (CurCluNr <= RelCluNr + NrClu){
267 if (CurCluNr == RelCluNr){
268 /* we have reached the beginning of our zone. Save
270 This->PreviousRelCluNr = RelCluNr;
271 This->PreviousAbsCluNr = AbsCluNr;
273 NewCluNr = fatDecode(This->Fs, AbsCluNr);
274 if (NewCluNr == 1 || NewCluNr == 0){
275 fprintf(stderr,"Fat problem while decoding %d %x\n",
279 if(CurCluNr == RelCluNr + NrClu)
281 if (NewCluNr > Fs->last_fat && mode == MT_WRITE){
282 /* if at end, and writing, extend it */
283 NewCluNr = get_next_free_cluster(This->Fs, AbsCluNr);
284 if (NewCluNr == 1 ){ /* no more space */
288 fatAppend(This->Fs, AbsCluNr, NewCluNr);
291 if (CurCluNr < RelCluNr && NewCluNr > Fs->last_fat){
296 if (CurCluNr >= RelCluNr && NewCluNr != AbsCluNr + 1)
300 if(loopDetect(This, CurCluNr, AbsCluNr)) {
306 maximize(*len, (1 + CurCluNr - RelCluNr) * clus_size - offset);
311 end >= This->FileSize) {
312 *len += ROUND_UP(end, clus_size) - end;
315 if((*len + offset) / clus_size + This->PreviousAbsCluNr-2 >
317 fprintf(stderr, "cluster too big\n");
321 *res = sectorsToBytes((Stream_t*)Fs,
322 (This->PreviousAbsCluNr-2) * Fs->cluster_size +
323 Fs->clus_start) + offset;
328 static int root_map(File_t *This, off_t where, size_t *len, int mode,
333 if(Fs->dir_len * Fs->sector_size < (size_t) where) {
339 maximize(*len, Fs->dir_len * Fs->sector_size - where);
343 *res = sectorsToBytes((Stream_t*)Fs, Fs->dir_start) + where;
348 static int read_file(Stream_t *Stream, char *buf, mt_off_t iwhere,
354 off_t where = truncBytes32(iwhere);
356 Stream_t *Disk = This->Fs->Next;
358 err = This->map(This, where, &len, MT_READ, &pos);
361 return READS(Disk, buf, pos, len);
364 static int write_file(Stream_t *Stream, char *buf, mt_off_t iwhere, size_t len)
370 Stream_t *Disk = This->Fs->Next;
371 off_t where = truncBytes32(iwhere);
375 err = This->map(This, where, &len, MT_WRITE, &pos);
379 ret = force_write(Disk, buf, pos, len);
381 ret = WRITES(Disk, buf, pos, len);
382 if(ret > (signed int) requestedLen)
385 where + ret > (off_t) This->FileSize )
386 This->FileSize = where + ret;
387 recalcPreallocSize(This);
393 * Convert an MSDOS time & date stamp to the Unix time() format
396 static int month[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
398 static __inline__ time_t conv_stamp(struct directory *dir)
404 accum = DOS_YEAR(dir) - 1970; /* years past */
407 accum = accum * 365L + month[DOS_MONTH(dir)-1] + DOS_DAY(dir);
410 accum += (DOS_YEAR(dir) - 1972) / 4L;
412 /* back off 1 day if before 29 Feb */
413 if (!(DOS_YEAR(dir) % 4) && DOS_MONTH(dir) < 3)
415 accum = accum * 24L + DOS_HOUR(dir); /* hours passed */
416 accum = accum * 60L + DOS_MINUTE(dir); /* minutes passed */
417 accum = accum * 60L + DOS_SEC(dir); /* seconds passed */
419 /* correct for Time Zone */
420 #ifdef HAVE_GETTIMEOFDAY
425 gettimeofday(&tv, &tz);
426 tzone = tz.tz_minuteswest * 60L;
429 #if defined HAVE_TZSET && !defined OS_mingw32msvc
431 #if !defined OS_ultrix && !defined OS_cygwin
432 /* Ultrix defines this to be a different type */
433 extern long timezone;
436 tzone = (long) timezone;
440 #endif /* HAVE_TZSET */
441 #endif /* HAVE_GETTIMEOFDAY */
445 /* correct for Daylight Saving Time */
447 tmbuf = localtime(&tmp);
448 dst = (tmbuf->tm_isdst) ? (-60L * 60L) : 0L;
455 static int get_file_data(Stream_t *Stream, time_t *date, mt_size_t *size,
456 int *type, int *address)
461 *date = conv_stamp(& This->direntry.dir);
463 *size = (mt_size_t) This->FileSize;
465 *type = This->direntry.dir.attr & ATTR_DIR;
467 *address = This->FirstAbsCluNr;
472 static int free_file(Stream_t *Stream)
476 fsPreallocateClusters(Fs, -This->preallocatedClusters);
477 FREE(&This->direntry.Dir);
478 freeDirCache(Stream);
479 return hash_remove(filehash, (void *) Stream, This->hint);
483 static int flush_file(Stream_t *Stream)
486 direntry_t *entry = &This->direntry;
488 if(isRootDir(Stream)) {
492 if(This->FirstAbsCluNr != getStart(entry->Dir, &entry->dir)) {
493 set_word(entry->dir.start, This->FirstAbsCluNr & 0xffff);
494 set_word(entry->dir.startHi, This->FirstAbsCluNr >> 16);
501 static int pre_allocate_file(Stream_t *Stream, mt_size_t isize)
505 size_t size = truncBytes32(isize);
507 if(size > This->FileSize &&
508 size > This->preallocatedSize) {
509 This->preallocatedSize = size;
510 return recalcPreallocSize(This);
515 static Class_t FileClass = {
518 flush_file, /* flush */
519 free_file, /* free */
523 get_dosConvert_pass_through
526 static unsigned int getAbsCluNr(File_t *This)
528 if(This->FirstAbsCluNr)
529 return This->FirstAbsCluNr;
530 if(isRootDir((Stream_t *) This))
535 static unsigned int func1(void *Stream)
539 return getAbsCluNr(This) ^ (long) This->Fs;
542 static unsigned int func2(void *Stream)
546 return getAbsCluNr(This);
549 static int comp(void *Stream, void *Stream2)
553 File_t *This2 = (File_t *) Stream2;
555 return This->Fs != This2->Fs ||
556 getAbsCluNr(This) != getAbsCluNr(This2);
559 static void init_hash(void)
561 static int is_initialised=0;
564 make_ht(func1, func2, comp, 20, &filehash);
570 static Stream_t *_internalFileOpen(Stream_t *Dir, unsigned int first,
571 size_t size, direntry_t *entry)
573 Stream_t *Stream = GetFs(Dir);
582 /* we use the illegal cluster 1 to mark newly created files.
583 * do not manage those by hashtable */
585 Pattern.Class = &FileClass;
586 if(first || (entry && !IS_DIR(entry)))
587 Pattern.map = normal_map;
589 Pattern.map = root_map;
590 Pattern.FirstAbsCluNr = first;
591 Pattern.loopDetectRel = 0;
592 Pattern.loopDetectAbs = first;
593 if(!hash_lookup(filehash, (T_HashTableEl) &Pattern,
594 (T_HashTableEl **)&File, 0)){
597 return (Stream_t *) File;
605 File->preallocatedClusters = 0;
606 File->preallocatedSize = 0;
607 /* memorize dir for date and attrib */
608 File->direntry = *entry;
609 if(entry->entry == -3)
610 File->direntry.Dir = (Stream_t *) File; /* root directory */
612 COPY(File->direntry.Dir);
614 File->Class = &FileClass;
616 if(first || (entry && !IS_DIR(entry)))
617 File->map = normal_map;
619 File->map = root_map; /* FAT 12/16 root directory */
621 File->FirstAbsCluNr = 0;
623 File->FirstAbsCluNr = first;
625 File->loopDetectRel = 0;
626 File->loopDetectAbs = 0;
628 File->PreviousRelCluNr = 0xffff;
629 File->FileSize = size;
632 hash_add(filehash, (void *) File, &File->hint);
633 return (Stream_t *) File;
636 Stream_t *OpenRoot(Stream_t *Dir)
643 memset(&entry, 0, sizeof(direntry_t));
645 num = fat32RootCluster(Dir);
647 /* make the directory entry */
649 entry.name[0] = '\0';
650 mk_entry_from_base("/", ATTR_DIR, num, 0, 0, &entry.dir);
653 size = countBytes(Dir, num);
655 Fs_t *Fs = (Fs_t *) GetFs(Dir);
656 size = Fs->dir_len * Fs->sector_size;
658 file = _internalFileOpen(Dir, num, size, &entry);
664 Stream_t *OpenFileByDirentry(direntry_t *entry)
670 first = getStart(entry->Dir, &entry->dir);
672 if(!first && IS_DIR(entry))
673 return OpenRoot(entry->Dir);
675 size = countBytes(entry->Dir, first);
677 size = FILE_SIZE(&entry->dir);
678 file = _internalFileOpen(entry->Dir, first, size, entry);
689 int isRootDir(Stream_t *Stream)
691 File_t *This = getUnbufferedFile(Stream);
693 return This->map == root_map;