/* Copyright 1996-1999,2001-2003,2007-2009,2011 Alain Knaff. * This file is part of mtools. * * Mtools is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Mtools is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Mtools. If not, see . */ #include "sysincludes.h" #include "msdos.h" #include "stream.h" #include "mtools.h" #include "fsP.h" #include "file.h" #include "htable.h" #include "dirCache.h" #include "buffer.h" typedef struct File_t { struct Stream_t head; struct Stream_t *Buffer; int (*map)(struct File_t *this, uint32_t where, uint32_t *len, int mode, mt_off_t *res); uint32_t FileSize; /* How many bytes do we project to need for this file (includes those already in FileSize) */ uint32_t preallocatedSize; /* How many clusters we have asked the lower layer to reserve for us (only what we will need in the future, excluding already allocated clusters in FileSize) */ uint32_t preallocatedClusters; /* Absolute position of first cluster of file */ unsigned int FirstAbsCluNr; /* Absolute position of previous cluster */ unsigned int PreviousAbsCluNr; /* Relative position of previous cluster */ unsigned int PreviousRelCluNr; direntry_t direntry; size_t hint; struct dirCache_t *dcp; unsigned int loopDetectRel; unsigned int loopDetectAbs; uint32_t where; } File_t; static Class_t FileClass; static T_HashTable *filehash; static File_t *getUnbufferedFile(Stream_t *Stream) { while(Stream->Class != &FileClass) Stream = Stream->Next; return (File_t *) Stream; } static inline Fs_t *_getFs(File_t *File) { return (Fs_t *) File->head.Next; } Fs_t *getFs(Stream_t *Stream) { return (Fs_t *)getUnbufferedFile(Stream)->head.Next; } struct dirCache_t **getDirCacheP(Stream_t *Stream) { return &getUnbufferedFile(Stream)->dcp; } direntry_t *getDirentry(Stream_t *Stream) { return &getUnbufferedFile(Stream)->direntry; } /** * Overflow-safe conversion of bytes to cluster */ static uint32_t filebytesToClusters(uint32_t bytes, uint32_t clus_size) { uint32_t ret = bytes / clus_size; if(bytes % clus_size) ret++; return ret; } static int recalcPreallocSize(File_t *This) { uint32_t currentClusters, neededClusters; unsigned int clus_size; uint32_t neededPrealloc; Fs_t *Fs = _getFs(This); #if 0 if(This->FileSize & 0xc0000000) { fprintf(stderr, "Bad filesize\n"); } if(This->preallocatedSize & 0xc0000000) { fprintf(stderr, "Bad preallocated size %x\n", (int) This->preallocatedSize); } #endif clus_size = Fs->cluster_size * Fs->sector_size; currentClusters = filebytesToClusters(This->FileSize, clus_size); neededClusters = filebytesToClusters(This->preallocatedSize, clus_size); if(neededClusters < currentClusters) neededPrealloc = 0; else neededPrealloc = neededClusters - currentClusters; if(neededPrealloc > This->preallocatedClusters) { int r = fsPreallocateClusters(Fs, neededPrealloc- This->preallocatedClusters); if(r) return r; } else { fsReleasePreallocateClusters(Fs, This->preallocatedClusters - neededPrealloc); } This->preallocatedClusters = neededPrealloc; return 0; } static int _loopDetect(unsigned int *oldrel, unsigned int rel, unsigned int *oldabs, unsigned int absol) { if(*oldrel && rel > *oldrel && absol == *oldabs) { fprintf(stderr, "loop detected! oldrel=%d newrel=%d abs=%d\n", *oldrel, rel, absol); return -1; } if(rel >= 2 * *oldrel + 1) { *oldrel = rel; *oldabs = absol; } return 0; } static int loopDetect(File_t *This, unsigned int rel, unsigned int absol) { return _loopDetect(&This->loopDetectRel, rel, &This->loopDetectAbs, absol); } static unsigned int _countBlocks(Fs_t *This, unsigned int block) { unsigned int blocks; unsigned int rel, oldabs, oldrel; blocks = 0; oldabs = oldrel = rel = 0; while (block <= This->last_fat && block != 1 && block) { blocks++; block = fatDecode(This, block); rel++; if(_loopDetect(&oldrel, rel, &oldabs, block) < 0) block = 1; } return blocks; } unsigned int countBlocks(Stream_t *Dir, unsigned int block) { Stream_t *Stream = GetFs(Dir); DeclareThis(Fs_t); return _countBlocks(This, block); } /* returns number of bytes in a directory. Represents a file size, and * can hence be not bigger than 2^32 */ static uint32_t countBytes(Stream_t *Dir, unsigned int block) { Stream_t *Stream = GetFs(Dir); DeclareThis(Fs_t); return _countBlocks(This, block) * This->sector_size * This->cluster_size; } void printFat(Stream_t *Stream) { File_t *This = getUnbufferedFile(Stream); uint32_t n; unsigned int rel; unsigned long begin, end; int first; n = This->FirstAbsCluNr; if(!n) { printf("Root directory or empty file\n"); return; } rel = 0; first = 1; begin = end = 0; do { if (first || n != end+1) { if (!first) { if (begin != end) printf("-%lu", end); printf("> "); } begin = end = n; printf("<%lu", begin); } else { end++; } first = 0; n = fatDecode(_getFs(This), n); rel++; if(loopDetect(This, rel, n) < 0) n = 1; } while (n <= _getFs(This)->last_fat && n != 1); if(!first) { if (begin != end) printf("-%lu", end); printf(">"); } } void printFatWithOffset(Stream_t *Stream, off_t offset) { File_t *This = getUnbufferedFile(Stream); uint32_t n; unsigned int rel; off_t clusSize; n = This->FirstAbsCluNr; if(!n) { printf("Root directory or empty file\n"); return; } clusSize = _getFs(This)->cluster_size * _getFs(This)->sector_size; rel = 0; while(offset >= clusSize) { n = fatDecode(_getFs(This), n); rel++; if(loopDetect(This, rel, n) < 0) return; if(n > _getFs(This)->last_fat) return; offset -= clusSize; } printf("%lu", (unsigned long) n); } static int normal_map(File_t *This, uint32_t where, uint32_t *len, int mode, mt_off_t *res) { unsigned int offset; size_t end; uint32_t NrClu; /* number of clusters to read */ uint32_t RelCluNr; uint32_t CurCluNr; uint32_t NewCluNr; uint32_t AbsCluNr; uint32_t clus_size; Fs_t *Fs = _getFs(This); *res = 0; clus_size = Fs->cluster_size * Fs->sector_size; offset = where % clus_size; if (mode == MT_READ) maximize(*len, This->FileSize - where); if (*len == 0 ) return 0; if (This->FirstAbsCluNr < 2){ if( mode == MT_READ || *len == 0){ *len = 0; return 0; } NewCluNr = get_next_free_cluster(_getFs(This), 1); if (NewCluNr == 1 ){ errno = ENOSPC; return -2; } hash_remove(filehash, (void *) This, This->hint); This->FirstAbsCluNr = NewCluNr; hash_add(filehash, (void *) This, &This->hint); fatAllocate(_getFs(This), NewCluNr, Fs->end_fat); } RelCluNr = where / clus_size; if (RelCluNr >= This->PreviousRelCluNr){ CurCluNr = This->PreviousRelCluNr; AbsCluNr = This->PreviousAbsCluNr; } else { CurCluNr = 0; AbsCluNr = This->FirstAbsCluNr; } NrClu = (offset + *len - 1) / clus_size; while (CurCluNr <= RelCluNr + NrClu){ if (CurCluNr == RelCluNr){ /* we have reached the beginning of our zone. Save * coordinates */ This->PreviousRelCluNr = RelCluNr; This->PreviousAbsCluNr = AbsCluNr; } NewCluNr = fatDecode(_getFs(This), AbsCluNr); if (NewCluNr == 1 || NewCluNr == 0){ fprintf(stderr,"Fat problem while decoding %d %x\n", AbsCluNr, NewCluNr); exit(1); } if(CurCluNr == RelCluNr + NrClu) break; if (NewCluNr > Fs->last_fat && mode == MT_WRITE){ /* if at end, and writing, extend it */ NewCluNr = get_next_free_cluster(_getFs(This), AbsCluNr); if (NewCluNr == 1 ){ /* no more space */ errno = ENOSPC; return -2; } fatAppend(_getFs(This), AbsCluNr, NewCluNr); } if (CurCluNr < RelCluNr && NewCluNr > Fs->last_fat){ *len = 0; return 0; } if (CurCluNr >= RelCluNr && NewCluNr != AbsCluNr + 1) break; CurCluNr++; AbsCluNr = NewCluNr; if(loopDetect(This, CurCluNr, AbsCluNr)) { errno = EIO; return -2; } } maximize(*len, (1 + CurCluNr - RelCluNr) * clus_size - offset); end = where + *len; if(batchmode && mode == MT_WRITE && end >= This->FileSize) { /* In batch mode, when writing at end of file, "pad" * to nearest cluster boundary so that we don't have * to read that data back from disk. */ *len += ROUND_UP(end, clus_size) - end; } if((*len + offset) / clus_size + This->PreviousAbsCluNr-2 > Fs->num_clus) { fprintf(stderr, "cluster too big\n"); exit(1); } *res = sectorsToBytes(Fs, (This->PreviousAbsCluNr-2) * Fs->cluster_size + Fs->clus_start) + to_mt_off_t(offset); return 1; } static int root_map(File_t *This, uint32_t where, uint32_t *len, int mode UNUSEDP, mt_off_t *res) { Fs_t *Fs = _getFs(This); if(Fs->dir_len * Fs->sector_size < where) { *len = 0; errno = ENOSPC; return -2; } maximize(*len, Fs->dir_len * Fs->sector_size - where); if (*len == 0) return 0; *res = sectorsToBytes(Fs, Fs->dir_start) + to_mt_off_t(where); return 1; } static ssize_t read_file(Stream_t *Stream, char *buf, size_t ilen) { DeclareThis(File_t); mt_off_t pos; int err; uint32_t len = truncSizeTo32u(ilen); ssize_t ret; Stream_t *Disk = _getFs(This)->head.Next; err = This->map(This, This->where, &len, MT_READ, &pos); if(err <= 0) return err; ret = PREADS(Disk, buf, pos, len); if(ret < 0) return ret; This->where += (size_t) ret; return ret; } static ssize_t write_file(Stream_t *Stream, char *buf, size_t ilen) { DeclareThis(File_t); mt_off_t pos; ssize_t ret; uint32_t requestedLen; uint32_t bytesWritten; Stream_t *Disk = _getFs(This)->head.Next; uint32_t maxLen = UINT32_MAX-This->where; uint32_t len; int err; if(ilen > maxLen) { len = maxLen; } else len = (uint32_t) ilen; requestedLen = len; err = This->map(This, This->where, &len, MT_WRITE, &pos); if( err <= 0) return err; if(batchmode) ret = force_pwrite(Disk, buf, pos, len); else ret = PWRITES(Disk, buf, pos, len); if(ret < 0) /* Error occured */ return ret; if((uint32_t)ret > requestedLen) /* More data than requested may be written to lower * levels if batch mode is active, in order to "pad" * the last cluster of a file, so that we don't have * to read that back from disk */ bytesWritten = requestedLen; else bytesWritten = (uint32_t)ret; This->where += bytesWritten; if (This->where > This->FileSize ) This->FileSize = This->where; recalcPreallocSize(This); return (ssize_t) bytesWritten; } static ssize_t pread_file(Stream_t *Stream, char *buf, mt_off_t where, size_t ilen) { DeclareThis(File_t); This->where = truncMtOffTo32u(where); return read_file(Stream, buf, ilen); } static ssize_t pwrite_file(Stream_t *Stream, char *buf, mt_off_t where, size_t ilen) { DeclareThis(File_t); This->where = truncMtOffTo32u(where); return write_file(Stream, buf, ilen); } /* * Convert an MSDOS time & date stamp to the Unix time() format */ static int month[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0 }; static __inline__ time_t conv_stamp(struct directory *dir) { struct tm *tmbuf; long tzone, dst; time_t accum, tmp; accum = DOS_YEAR(dir) - 1970; /* years past */ /* days passed */ accum = accum * 365L + month[DOS_MONTH(dir)-1] + DOS_DAY(dir); /* leap years */ accum += (DOS_YEAR(dir) - 1972) / 4L; /* back off 1 day if before 29 Feb */ if (!(DOS_YEAR(dir) % 4) && DOS_MONTH(dir) < 3) accum--; accum = accum * 24L + DOS_HOUR(dir); /* hours passed */ accum = accum * 60L + DOS_MINUTE(dir); /* minutes passed */ accum = accum * 60L + DOS_SEC(dir); /* seconds passed */ /* correct for Time Zone */ #ifdef HAVE_GETTIMEOFDAY { struct timeval tv; struct timezone tz; gettimeofday(&tv, &tz); tzone = tz.tz_minuteswest * 60L; } #else #if defined HAVE_TZSET && !defined OS_mingw32msvc { #if !defined OS_ultrix && !defined OS_cygwin /* Ultrix defines this to be a different type */ extern long timezone; #endif tzset(); tzone = (long) timezone; } #else tzone = 0; #endif /* HAVE_TZSET */ #endif /* HAVE_GETTIMEOFDAY */ accum += tzone; /* correct for Daylight Saving Time */ tmp = accum; tmbuf = localtime(&tmp); if(tmbuf) { dst = (tmbuf->tm_isdst) ? (-60L * 60L) : 0L; accum += dst; } return accum; } static int get_file_data(Stream_t *Stream, time_t *date, mt_off_t *size, int *type, uint32_t *address) { DeclareThis(File_t); if(date) *date = conv_stamp(& This->direntry.dir); if(size) *size = to_mt_off_t(This->FileSize); if(type) *type = This->direntry.dir.attr & ATTR_DIR; if(address) *address = This->FirstAbsCluNr; return 0; } static int free_file(Stream_t *Stream) { DeclareThis(File_t); Fs_t *Fs = _getFs(This); fsReleasePreallocateClusters(Fs, This->preallocatedClusters); FREE(&This->direntry.Dir); freeDirCache(Stream); return hash_remove(filehash, (void *) Stream, This->hint); } static int flush_file(Stream_t *Stream) { DeclareThis(File_t); direntry_t *entry = &This->direntry; if(isRootDir(Stream)) { return 0; } if(This->FirstAbsCluNr != getStart(entry->Dir, &entry->dir)) { set_word(entry->dir.start, This->FirstAbsCluNr & 0xffff); set_word(entry->dir.startHi, This->FirstAbsCluNr >> 16); dir_write(entry); } return 0; } static int pre_allocate_file(Stream_t *Stream, mt_off_t isize) { DeclareThis(File_t); uint32_t size = truncMtOffTo32u(isize); if(size > This->FileSize && size > This->preallocatedSize) { This->preallocatedSize = size; return recalcPreallocSize(This); } else return 0; } static Class_t FileClass = { read_file, write_file, pread_file, pwrite_file, flush_file, /* flush */ free_file, /* free */ 0, /* get_geom */ get_file_data, pre_allocate_file, get_dosConvert_pass_through, 0 /* discard */ }; static unsigned int getAbsCluNr(File_t *This) { if(This->FirstAbsCluNr) return This->FirstAbsCluNr; if(isRootDir((Stream_t *) This)) return 0; return 1; } static uint32_t func1(void *Stream) { DeclareThis(File_t); return getAbsCluNr(This) ^ (uint32_t) (unsigned long) This->head.Next; } static uint32_t func2(void *Stream) { DeclareThis(File_t); return getAbsCluNr(This); } static int comp(void *Stream, void *Stream2) { DeclareThis(File_t); File_t *This2 = (File_t *) Stream2; return _getFs(This) != _getFs(This2) || getAbsCluNr(This) != getAbsCluNr(This2); } static void init_hash(void) { static int is_initialised=0; if(!is_initialised){ make_ht(func1, func2, comp, 20, &filehash); is_initialised = 1; } } static Stream_t *_internalFileOpen(Stream_t *Dir, unsigned int first, uint32_t size, direntry_t *entry) { Stream_t *Stream = GetFs(Dir); DeclareThis(Fs_t); File_t Pattern; File_t *File; init_hash(); This->head.refs++; if(first != 1){ /* we use the illegal cluster 1 to mark newly created files. * do not manage those by hashtable */ init_head(&Pattern.head, &FileClass, &This->head); if(first || (entry && !IS_DIR(entry))) Pattern.map = normal_map; else Pattern.map = root_map; Pattern.FirstAbsCluNr = first; Pattern.loopDetectRel = 0; Pattern.loopDetectAbs = first; if(!hash_lookup(filehash, (T_HashTableEl) &Pattern, (T_HashTableEl **)&File, 0)){ File->head.refs++; This->head.refs--; return (Stream_t *) File; } } File = New(File_t); if (!File) return NULL; init_head(&File->head, &FileClass, &This->head); File->Buffer = NULL; File->dcp = 0; File->preallocatedClusters = 0; File->preallocatedSize = 0; /* memorize dir for date and attrib */ File->direntry = *entry; if(entry->entry == -3) File->direntry.Dir = (Stream_t *) File; /* root directory */ else COPY(File->direntry.Dir); File->where = 0; if(first || (entry && !IS_DIR(entry))) File->map = normal_map; else File->map = root_map; /* FAT 12/16 root directory */ if(first == 1) File->FirstAbsCluNr = 0; else File->FirstAbsCluNr = first; File->loopDetectRel = 0; File->loopDetectAbs = 0; File->PreviousRelCluNr = 0xffff; File->FileSize = size; hash_add(filehash, (void *) File, &File->hint); return (Stream_t *) File; } static void bufferize(Stream_t **Dir) { Stream_t *BDir; File_t *file = (File_t *) *Dir; if(!*Dir) return; if(file->Buffer){ (*Dir)->refs--; file->Buffer->refs++; *Dir = file->Buffer; return; } BDir = buf_init(*Dir, 64*16384, 512, MDIR_SIZE); if(!BDir){ FREE(Dir); *Dir = NULL; } else { file->Buffer = BDir; *Dir = BDir; } } Stream_t *OpenRoot(Stream_t *Dir) { unsigned int num; direntry_t entry; uint32_t size; Stream_t *file; memset(&entry, 0, sizeof(direntry_t)); num = fat32RootCluster(Dir); /* make the directory entry */ entry.entry = -3; entry.name[0] = '\0'; mk_entry_from_base("/", ATTR_DIR, num, 0, 0, &entry.dir); if(num) size = countBytes(Dir, num); else { Fs_t *Fs = (Fs_t *) GetFs(Dir); size = Fs->dir_len * Fs->sector_size; } file = _internalFileOpen(Dir, num, size, &entry); bufferize(&file); return file; } Stream_t *OpenFileByDirentry(direntry_t *entry) { Stream_t *file; unsigned int first; uint32_t size; first = getStart(entry->Dir, &entry->dir); if(!first && IS_DIR(entry)) return OpenRoot(entry->Dir); if (IS_DIR(entry)) size = countBytes(entry->Dir, first); else size = FILE_SIZE(&entry->dir); file = _internalFileOpen(entry->Dir, first, size, entry); if(IS_DIR(entry)) { bufferize(&file); if(first == 1) dir_grow(file, 0); } return file; } int isRootDir(Stream_t *Stream) { File_t *This = getUnbufferedFile(Stream); return This->map == root_map; }