1 /* Copyright 1996-1999,2001-2003,2007-2009,2011 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, uint32_t where, uint32_t *len, int mode,
37 /* How many bytes do we project to need for this file
38 (includes those already in FileSize) */
39 uint32_t preallocatedSize;
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;
46 /* Absolute position of first cluster of file */
47 unsigned int FirstAbsCluNr;
49 /* Absolute position of previous cluster */
50 unsigned int PreviousAbsCluNr;
52 /* Relative position of previous cluster */
53 unsigned int PreviousRelCluNr;
56 struct dirCache_t *dcp;
58 unsigned int loopDetectRel;
59 unsigned int loopDetectAbs;
62 static Class_t FileClass;
63 static T_HashTable *filehash;
65 static File_t *getUnbufferedFile(Stream_t *Stream)
67 while(Stream->Class != &FileClass)
68 Stream = Stream->Next;
69 return (File_t *) Stream;
72 Fs_t *getFs(Stream_t *Stream)
74 return getUnbufferedFile(Stream)->Fs;
77 struct dirCache_t **getDirCacheP(Stream_t *Stream)
79 return &getUnbufferedFile(Stream)->dcp;
82 direntry_t *getDirentry(Stream_t *Stream)
84 return &getUnbufferedFile(Stream)->direntry;
88 * Overflow-safe conversion of bytes to cluster
90 static uint32_t filebytesToClusters(uint32_t bytes, uint32_t clus_size) {
91 uint32_t ret = bytes / clus_size;
97 static int recalcPreallocSize(File_t *This)
99 uint32_t currentClusters, neededClusters;
100 unsigned int clus_size;
101 uint32_t neededPrealloc;
105 if(This->FileSize & 0xc0000000) {
106 fprintf(stderr, "Bad filesize\n");
108 if(This->preallocatedSize & 0xc0000000) {
109 fprintf(stderr, "Bad preallocated size %x\n",
110 (int) This->preallocatedSize);
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)
119 neededPrealloc = neededClusters - currentClusters;
120 if(neededPrealloc > This->preallocatedClusters) {
121 int r = fsPreallocateClusters(Fs, neededPrealloc-
122 This->preallocatedClusters);
126 fsReleasePreallocateClusters(Fs, This->preallocatedClusters -
129 This->preallocatedClusters = neededPrealloc;
133 static int _loopDetect(unsigned int *oldrel, unsigned int rel,
134 unsigned int *oldabs, unsigned int absol)
136 if(*oldrel && rel > *oldrel && absol == *oldabs) {
137 fprintf(stderr, "loop detected! oldrel=%d newrel=%d abs=%d\n",
138 *oldrel, rel, absol);
142 if(rel >= 2 * *oldrel + 1) {
150 static int loopDetect(File_t *This, unsigned int rel, unsigned int absol)
152 return _loopDetect(&This->loopDetectRel, rel, &This->loopDetectAbs, absol);
155 static unsigned int _countBlocks(Fs_t *This, unsigned int block)
158 unsigned int rel, oldabs, oldrel;
162 oldabs = oldrel = rel = 0;
164 while (block <= This->last_fat && block != 1 && block) {
166 block = fatDecode(This, block);
168 if(_loopDetect(&oldrel, rel, &oldabs, block) < 0)
174 unsigned int countBlocks(Stream_t *Dir, unsigned int block)
176 Stream_t *Stream = GetFs(Dir);
179 return _countBlocks(This, block);
182 /* returns number of bytes in a directory. Represents a file size, and
183 * can hence be not bigger than 2^32
185 static uint32_t countBytes(Stream_t *Dir, unsigned int block)
187 Stream_t *Stream = GetFs(Dir);
190 return _countBlocks(This, block) *
191 This->sector_size * This->cluster_size;
194 void printFat(Stream_t *Stream)
196 File_t *This = getUnbufferedFile(Stream);
199 unsigned long begin, end;
202 n = This->FirstAbsCluNr;
204 printf("Root directory or empty file\n");
212 if (first || n != end+1) {
219 printf("<%lu", begin);
224 n = fatDecode(This->Fs, n);
226 if(loopDetect(This, rel, n) < 0)
228 } while (n <= This->Fs->last_fat && n != 1);
236 void printFatWithOffset(Stream_t *Stream, off_t offset) {
237 File_t *This = getUnbufferedFile(Stream);
242 n = This->FirstAbsCluNr;
244 printf("Root directory or empty file\n");
248 clusSize = This->Fs->cluster_size * This->Fs->sector_size;
251 while(offset >= clusSize) {
252 n = fatDecode(This->Fs, n);
254 if(loopDetect(This, rel, n) < 0)
256 if(n > This->Fs->last_fat)
261 printf("%lu", (unsigned long) n);
264 static int normal_map(File_t *This, uint32_t where, uint32_t *len, int mode,
269 uint32_t NrClu; /* number of clusters to read */
278 clus_size = Fs->cluster_size * Fs->sector_size;
279 offset = where % clus_size;
282 maximize(*len, This->FileSize - where);
286 if (This->FirstAbsCluNr < 2){
287 if( mode == MT_READ || *len == 0){
291 NewCluNr = get_next_free_cluster(This->Fs, 1);
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);
302 RelCluNr = where / clus_size;
304 if (RelCluNr >= This->PreviousRelCluNr){
305 CurCluNr = This->PreviousRelCluNr;
306 AbsCluNr = This->PreviousAbsCluNr;
309 AbsCluNr = This->FirstAbsCluNr;
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
318 This->PreviousRelCluNr = RelCluNr;
319 This->PreviousAbsCluNr = AbsCluNr;
321 NewCluNr = fatDecode(This->Fs, AbsCluNr);
322 if (NewCluNr == 1 || NewCluNr == 0){
323 fprintf(stderr,"Fat problem while decoding %d %x\n",
327 if(CurCluNr == RelCluNr + NrClu)
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 */
336 fatAppend(This->Fs, AbsCluNr, NewCluNr);
339 if (CurCluNr < RelCluNr && NewCluNr > Fs->last_fat){
344 if (CurCluNr >= RelCluNr && NewCluNr != AbsCluNr + 1)
348 if(loopDetect(This, CurCluNr, AbsCluNr)) {
354 maximize(*len, (1 + CurCluNr - RelCluNr) * clus_size - offset);
359 end >= This->FileSize) {
360 /* In batch mode, when writing at end of file, "pad"
361 * to nearest cluster boundary so that we don't have
362 * to read that data back from disk. */
363 *len += ROUND_UP(end, clus_size) - end;
366 if((*len + offset) / clus_size + This->PreviousAbsCluNr-2 >
368 fprintf(stderr, "cluster too big\n");
372 *res = sectorsToBytes(Fs,
373 (This->PreviousAbsCluNr-2) * Fs->cluster_size +
374 Fs->clus_start) + to_mt_off_t(offset);
379 static int root_map(File_t *This, uint32_t where, uint32_t *len,
380 int mode UNUSEDP, mt_off_t *res)
384 if(Fs->dir_len * Fs->sector_size < where) {
390 maximize(*len, Fs->dir_len * Fs->sector_size - where);
394 *res = sectorsToBytes(Fs, Fs->dir_start) +
400 static ssize_t read_file(Stream_t *Stream, char *buf, mt_off_t iwhere,
406 uint32_t where = truncMtOffTo32u(iwhere);
407 uint32_t len = truncSizeTo32u(ilen);
409 Stream_t *Disk = This->Fs->Next;
411 err = This->map(This, where, &len, MT_READ, &pos);
414 return READS(Disk, buf, pos, len);
417 static ssize_t write_file(Stream_t *Stream, char *buf,
418 mt_off_t iwhere, size_t ilen)
423 uint32_t requestedLen;
424 uint32_t bytesWritten;
425 Stream_t *Disk = This->Fs->Next;
426 uint32_t where = truncMtOffTo32u(iwhere);
427 uint32_t maxLen = UINT32_MAX-where;
434 len = (uint32_t) ilen;
436 err = This->map(This, where, &len, MT_WRITE, &pos);
440 ret = force_write(Disk, buf, pos, len);
442 ret = WRITES(Disk, buf, pos, len);
446 if((uint32_t)ret > requestedLen)
447 /* More data than requested may be written to lower
448 * levels if batch mode is active, in order to "pad"
449 * the last cluster of a file, so that we don't have
450 * to read that back from disk */
451 bytesWritten = requestedLen;
453 bytesWritten = (uint32_t)ret;
454 if (where + bytesWritten > This->FileSize )
455 This->FileSize = where + bytesWritten;
456 recalcPreallocSize(This);
457 return (ssize_t)bytesWritten;
462 * Convert an MSDOS time & date stamp to the Unix time() format
465 static int month[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
467 static __inline__ time_t conv_stamp(struct directory *dir)
473 accum = DOS_YEAR(dir) - 1970; /* years past */
476 accum = accum * 365L + month[DOS_MONTH(dir)-1] + DOS_DAY(dir);
479 accum += (DOS_YEAR(dir) - 1972) / 4L;
481 /* back off 1 day if before 29 Feb */
482 if (!(DOS_YEAR(dir) % 4) && DOS_MONTH(dir) < 3)
484 accum = accum * 24L + DOS_HOUR(dir); /* hours passed */
485 accum = accum * 60L + DOS_MINUTE(dir); /* minutes passed */
486 accum = accum * 60L + DOS_SEC(dir); /* seconds passed */
488 /* correct for Time Zone */
489 #ifdef HAVE_GETTIMEOFDAY
494 gettimeofday(&tv, &tz);
495 tzone = tz.tz_minuteswest * 60L;
498 #if defined HAVE_TZSET && !defined OS_mingw32msvc
500 #if !defined OS_ultrix && !defined OS_cygwin
501 /* Ultrix defines this to be a different type */
502 extern long timezone;
505 tzone = (long) timezone;
509 #endif /* HAVE_TZSET */
510 #endif /* HAVE_GETTIMEOFDAY */
514 /* correct for Daylight Saving Time */
516 tmbuf = localtime(&tmp);
518 dst = (tmbuf->tm_isdst) ? (-60L * 60L) : 0L;
525 static int get_file_data(Stream_t *Stream, time_t *date, mt_off_t *size,
526 int *type, uint32_t *address)
531 *date = conv_stamp(& This->direntry.dir);
533 *size = to_mt_off_t(This->FileSize);
535 *type = This->direntry.dir.attr & ATTR_DIR;
537 *address = This->FirstAbsCluNr;
542 static int free_file(Stream_t *Stream)
546 fsReleasePreallocateClusters(Fs, This->preallocatedClusters);
547 FREE(&This->direntry.Dir);
548 freeDirCache(Stream);
549 return hash_remove(filehash, (void *) Stream, This->hint);
553 static int flush_file(Stream_t *Stream)
556 direntry_t *entry = &This->direntry;
558 if(isRootDir(Stream)) {
562 if(This->FirstAbsCluNr != getStart(entry->Dir, &entry->dir)) {
563 set_word(entry->dir.start, This->FirstAbsCluNr & 0xffff);
564 set_word(entry->dir.startHi, This->FirstAbsCluNr >> 16);
571 static int pre_allocate_file(Stream_t *Stream, mt_off_t isize)
575 uint32_t size = truncMtOffTo32u(isize);
577 if(size > This->FileSize &&
578 size > This->preallocatedSize) {
579 This->preallocatedSize = size;
580 return recalcPreallocSize(This);
585 static Class_t FileClass = {
588 flush_file, /* flush */
589 free_file, /* free */
593 get_dosConvert_pass_through,
597 static unsigned int getAbsCluNr(File_t *This)
599 if(This->FirstAbsCluNr)
600 return This->FirstAbsCluNr;
601 if(isRootDir((Stream_t *) This))
606 static uint32_t func1(void *Stream)
610 return getAbsCluNr(This) ^ (uint32_t) (unsigned long) This->Fs;
613 static uint32_t func2(void *Stream)
617 return getAbsCluNr(This);
620 static int comp(void *Stream, void *Stream2)
624 File_t *This2 = (File_t *) Stream2;
626 return This->Fs != This2->Fs ||
627 getAbsCluNr(This) != getAbsCluNr(This2);
630 static void init_hash(void)
632 static int is_initialised=0;
635 make_ht(func1, func2, comp, 20, &filehash);
641 static Stream_t *_internalFileOpen(Stream_t *Dir, unsigned int first,
642 uint32_t size, direntry_t *entry)
644 Stream_t *Stream = GetFs(Dir);
653 /* we use the illegal cluster 1 to mark newly created files.
654 * do not manage those by hashtable */
656 Pattern.Class = &FileClass;
657 if(first || (entry && !IS_DIR(entry)))
658 Pattern.map = normal_map;
660 Pattern.map = root_map;
661 Pattern.FirstAbsCluNr = first;
662 Pattern.loopDetectRel = 0;
663 Pattern.loopDetectAbs = first;
664 if(!hash_lookup(filehash, (T_HashTableEl) &Pattern,
665 (T_HashTableEl **)&File, 0)){
668 return (Stream_t *) File;
676 File->preallocatedClusters = 0;
677 File->preallocatedSize = 0;
678 /* memorize dir for date and attrib */
679 File->direntry = *entry;
680 if(entry->entry == -3)
681 File->direntry.Dir = (Stream_t *) File; /* root directory */
683 COPY(File->direntry.Dir);
685 File->Class = &FileClass;
687 if(first || (entry && !IS_DIR(entry)))
688 File->map = normal_map;
690 File->map = root_map; /* FAT 12/16 root directory */
692 File->FirstAbsCluNr = 0;
694 File->FirstAbsCluNr = first;
696 File->loopDetectRel = 0;
697 File->loopDetectAbs = 0;
699 File->PreviousRelCluNr = 0xffff;
700 File->FileSize = size;
703 hash_add(filehash, (void *) File, &File->hint);
704 return (Stream_t *) File;
707 Stream_t *OpenRoot(Stream_t *Dir)
714 memset(&entry, 0, sizeof(direntry_t));
716 num = fat32RootCluster(Dir);
718 /* make the directory entry */
720 entry.name[0] = '\0';
721 mk_entry_from_base("/", ATTR_DIR, num, 0, 0, &entry.dir);
724 size = countBytes(Dir, num);
726 Fs_t *Fs = (Fs_t *) GetFs(Dir);
727 size = Fs->dir_len * Fs->sector_size;
729 file = _internalFileOpen(Dir, num, size, &entry);
735 Stream_t *OpenFileByDirentry(direntry_t *entry)
741 first = getStart(entry->Dir, &entry->dir);
743 if(!first && IS_DIR(entry))
744 return OpenRoot(entry->Dir);
746 size = countBytes(entry->Dir, first);
748 size = FILE_SIZE(&entry->dir);
749 file = _internalFileOpen(entry->Dir, first, size, entry);
760 int isRootDir(Stream_t *Stream)
762 File_t *This = getUnbufferedFile(Stream);
764 return This->map == root_map;