diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2013-01-09 19:04:18 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2013-01-09 19:04:18 +0000 |
commit | af0c3edb9706e470b45a9c8dd6debcc9e2d543c2 (patch) | |
tree | 340ee9c0f1b504061d4206d05d9fcc265c1302eb /file.c | |
download | mtools-master.tar.gz |
mtools-4.0.18HEADmtools-4.0.18master
Diffstat (limited to 'file.c')
-rw-r--r-- | file.c | 723 |
1 files changed, 723 insertions, 0 deletions
@@ -0,0 +1,723 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +#include "sysincludes.h" +#include "msdos.h" +#include "stream.h" +#include "mtools.h" +#include "fsP.h" +#include "file.h" +#include "htable.h" +#include "dirCache.h" + +typedef struct File_t { + Class_t *Class; + int refs; + struct Fs_t *Fs; /* Filesystem that this fat file belongs to */ + Stream_t *Buffer; + + int (*map)(struct File_t *this, off_t where, size_t *len, int mode, + mt_off_t *res); + size_t FileSize; + + size_t preallocatedSize; + int 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; + int hint; + struct dirCache_t *dcp; + + unsigned int loopDetectRel; + unsigned int loopDetectAbs; +} File_t; + +static Class_t FileClass; +T_HashTable *filehash; + +static File_t *getUnbufferedFile(Stream_t *Stream) +{ + while(Stream->Class != &FileClass) + Stream = Stream->Next; + return (File_t *) Stream; +} + +Fs_t *getFs(Stream_t *Stream) +{ + return getUnbufferedFile(Stream)->Fs; +} + +struct dirCache_t **getDirCacheP(Stream_t *Stream) +{ + return &getUnbufferedFile(Stream)->dcp; +} + +direntry_t *getDirentry(Stream_t *Stream) +{ + return &getUnbufferedFile(Stream)->direntry; +} + + +static int recalcPreallocSize(File_t *This) +{ + size_t currentClusters, neededClusters; + int clus_size; + int neededPrealloc; + Fs_t *Fs = This->Fs; + int r; + +#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 = (This->FileSize + clus_size - 1) / clus_size; + neededClusters = (This->preallocatedSize + clus_size - 1) / clus_size; + neededPrealloc = neededClusters - currentClusters; + if(neededPrealloc < 0) + neededPrealloc = 0; + r = fsPreallocateClusters(Fs, neededPrealloc - This->preallocatedClusters); + if(r) + return r; + 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 size_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); + unsigned long n; + 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(This->Fs, n); + rel++; + if(loopDetect(This, rel, n) < 0) + n = 1; + } while (n <= This->Fs->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); + unsigned long n; + int rel; + off_t clusSize; + + n = This->FirstAbsCluNr; + if(!n) { + printf("Root directory or empty file\n"); + return; + } + + clusSize = This->Fs->cluster_size * This->Fs->sector_size; + + rel = 0; + while(offset >= clusSize) { + n = fatDecode(This->Fs, n); + rel++; + if(loopDetect(This, rel, n) < 0) + return; + if(n > This->Fs->last_fat) + return; + offset -= clusSize; + } + + printf("%lu", n); +} + +static int normal_map(File_t *This, off_t where, size_t *len, int mode, + mt_off_t *res) +{ + int offset; + size_t end; + int NrClu; /* number of clusters to read */ + unsigned int RelCluNr; + unsigned int CurCluNr; + unsigned int NewCluNr; + unsigned int AbsCluNr; + int clus_size; + Fs_t *Fs = This->Fs; + + *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(This->Fs, 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(This->Fs, 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(This->Fs, 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(This->Fs, AbsCluNr); + if (NewCluNr == 1 ){ /* no more space */ + errno = ENOSPC; + return -2; + } + fatAppend(This->Fs, 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) { + *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((Stream_t*)Fs, + (This->PreviousAbsCluNr-2) * Fs->cluster_size + + Fs->clus_start) + offset; + return 1; +} + + +static int root_map(File_t *This, off_t where, size_t *len, int mode UNUSEDP, + mt_off_t *res) +{ + Fs_t *Fs = This->Fs; + + if(Fs->dir_len * Fs->sector_size < (size_t) where) { + *len = 0; + errno = ENOSPC; + return -2; + } + + maximize(*len, Fs->dir_len * Fs->sector_size - where); + if (*len == 0) + return 0; + + *res = sectorsToBytes((Stream_t*)Fs, Fs->dir_start) + where; + return 1; +} + + +static int read_file(Stream_t *Stream, char *buf, mt_off_t iwhere, + size_t len) +{ + DeclareThis(File_t); + mt_off_t pos; + int err; + off_t where = truncBytes32(iwhere); + + Stream_t *Disk = This->Fs->Next; + + err = This->map(This, where, &len, MT_READ, &pos); + if(err <= 0) + return err; + return READS(Disk, buf, pos, len); +} + +static int write_file(Stream_t *Stream, char *buf, mt_off_t iwhere, size_t len) +{ + DeclareThis(File_t); + mt_off_t pos; + int ret; + size_t requestedLen; + Stream_t *Disk = This->Fs->Next; + off_t where = truncBytes32(iwhere); + int err; + + requestedLen = len; + err = This->map(This, where, &len, MT_WRITE, &pos); + if( err <= 0) + return err; + if(batchmode) + ret = force_write(Disk, buf, pos, len); + else + ret = WRITES(Disk, buf, pos, len); + if(ret > (signed int) requestedLen) + ret = requestedLen; + if (ret > 0 && + where + ret > (off_t) This->FileSize ) + This->FileSize = where + ret; + recalcPreallocSize(This); + return ret; +} + + +/* + * 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_size_t *size, + int *type, int *address) +{ + DeclareThis(File_t); + + if(date) + *date = conv_stamp(& This->direntry.dir); + if(size) + *size = (mt_size_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 = This->Fs; + fsPreallocateClusters(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_size_t isize) +{ + DeclareThis(File_t); + + size_t size = truncBytes32(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, + flush_file, /* flush */ + free_file, /* free */ + 0, /* get_geom */ + get_file_data, + pre_allocate_file, + get_dosConvert_pass_through +}; + +static unsigned int getAbsCluNr(File_t *This) +{ + if(This->FirstAbsCluNr) + return This->FirstAbsCluNr; + if(isRootDir((Stream_t *) This)) + return 0; + return 1; +} + +static unsigned int func1(void *Stream) +{ + DeclareThis(File_t); + + return getAbsCluNr(This) ^ (long) This->Fs; +} + +static unsigned int 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 This->Fs != This2->Fs || + 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, + size_t size, direntry_t *entry) +{ + Stream_t *Stream = GetFs(Dir); + DeclareThis(Fs_t); + File_t Pattern; + File_t *File; + + init_hash(); + This->refs++; + + if(first != 1){ + /* we use the illegal cluster 1 to mark newly created files. + * do not manage those by hashtable */ + Pattern.Fs = This; + Pattern.Class = &FileClass; + 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->refs++; + This->refs--; + return (Stream_t *) File; + } + } + + File = New(File_t); + if (!File) + return 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->Class = &FileClass; + File->Fs = This; + 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; + File->refs = 1; + File->Buffer = 0; + hash_add(filehash, (void *) File, &File->hint); + return (Stream_t *) File; +} + +Stream_t *OpenRoot(Stream_t *Dir) +{ + unsigned int num; + direntry_t entry; + size_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; + size_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; +} |