| /* Copyright 1998,2001-2003,2007-2009 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 "vfat.h" | |
| #include "dirCache.h" | |
| #include "dirCacheP.h" | |
| #include <assert.h> | |
| #define BITS_PER_INT (sizeof(unsigned int) * 8) | |
| static __inline__ uint32_t rol(uint32_t arg, int shift) | |
| { | |
| arg &= 0xffffffff; /* for 64 bit machines */ | |
| return (arg << shift) | (arg >> (32 - shift)); | |
| } | |
| static uint32_t calcHash(wchar_t *name) | |
| { | |
| uint32_t hash; | |
| unsigned int i; | |
| wint_t c; | |
| hash = 0; | |
| i = 0; | |
| while(*name) { | |
| /* rotate it */ | |
| hash = rol(hash,5); /* a shift of 5 makes sure we spread quickly | |
| * over the whole width, moreover, 5 is | |
| * prime with 32, which makes sure that | |
| * successive letters cannot cover each | |
| * other easily */ | |
| c = towupper((wint_t)*name); | |
| hash ^= (c * (c+2)) ^ (i * (i+2)); | |
| hash &= 0xffffffff; | |
| i++; | |
| name++; | |
| } | |
| hash = hash * (hash + 2); | |
| /* the following two xors make sure all info is spread evenly over all | |
| * bytes. Important if we only keep the low order bits later on */ | |
| hash ^= (hash & 0xfff) << 12; | |
| hash ^= (hash & 0xff000) << 24; | |
| return hash; | |
| } | |
| static unsigned int addBit(unsigned int *bitmap, | |
| unsigned int hash, int checkOnly) | |
| { | |
| unsigned int bit; | |
| int entry; | |
| bit = 1u << (hash % BITS_PER_INT); | |
| entry = (hash / BITS_PER_INT) % DC_BITMAP_SIZE; | |
| if(checkOnly) | |
| return bitmap[entry] & bit; | |
| else { | |
| bitmap[entry] |= bit; | |
| return 1; | |
| } | |
| } | |
| static int _addHash(dirCache_t *cache, unsigned int hash, int checkOnly) | |
| { | |
| return | |
| addBit(cache->bm0, hash, checkOnly) && | |
| addBit(cache->bm1, rol(hash,12), checkOnly) && | |
| addBit(cache->bm2, rol(hash,24), checkOnly); | |
| } | |
| static void addNameToHash(dirCache_t *cache, wchar_t *name) | |
| { | |
| _addHash(cache, calcHash(name), 0); | |
| } | |
| static void hashDce(dirCache_t *cache, dirCacheEntry_t *dce) | |
| { | |
| if(dce->beginSlot != cache->nrHashed) | |
| return; | |
| cache->nrHashed = dce->endSlot; | |
| if(dce->longName) | |
| addNameToHash(cache, dce->longName); | |
| addNameToHash(cache, dce->shortName); | |
| } | |
| int isHashed(dirCache_t *cache, wchar_t *name) | |
| { | |
| int ret; | |
| ret = _addHash(cache, calcHash(name), 1); | |
| return ret; | |
| } | |
| int growDirCache(dirCache_t *cache, unsigned int slot) | |
| { | |
| if((int) slot < 0) { | |
| fprintf(stderr, "Bad slot %d\n", slot); | |
| exit(1); | |
| } | |
| if( cache->nr_entries <= slot) { | |
| unsigned int i; | |
| cache->entries = realloc(cache->entries, | |
| (slot+1) * 2 * | |
| sizeof(dirCacheEntry_t *)); | |
| if(!cache->entries) | |
| return -1; | |
| for(i= cache->nr_entries; i < (slot+1) * 2; i++) { | |
| cache->entries[i] = 0; | |
| } | |
| cache->nr_entries = (slot+1) * 2; | |
| } | |
| return 0; | |
| } | |
| dirCache_t *allocDirCache(Stream_t *Stream, unsigned int slot) | |
| { | |
| dirCache_t **dcp; | |
| if((int)slot < 0) { | |
| fprintf(stderr, "Bad slot %d\n", slot); | |
| exit(1); | |
| } | |
| dcp = getDirCacheP(Stream); | |
| if(!*dcp) { | |
| *dcp = New(dirCache_t); | |
| if(!*dcp) | |
| return 0; | |
| (*dcp)->entries = NewArray((slot+1)*2+5, dirCacheEntry_t *); | |
| if(!(*dcp)->entries) { | |
| free(*dcp); | |
| return 0; | |
| } | |
| (*dcp)->nr_entries = (slot+1) * 2; | |
| memset( (*dcp)->bm0, 0, sizeof((*dcp)->bm0)); | |
| memset( (*dcp)->bm1, 0, sizeof((*dcp)->bm1)); | |
| memset( (*dcp)->bm2, 0, sizeof((*dcp)->bm1)); | |
| (*dcp)->nrHashed = 0; | |
| } else | |
| if(growDirCache(*dcp, slot) < 0) | |
| return 0; | |
| return *dcp; | |
| } | |
| /* Free a range of entries. The range being cleared is always aligned | |
| * on the begin of an entry. Entries which are cleared entirely are | |
| * free'd. Entries which are cleared half-way (can only happen at end) | |
| * are "shortened" by moving its beginSlot | |
| * If this was a range representing space after end-mark, return beginning | |
| * of range if it had been shortened in its lifetime (which means that end | |
| * mark must have moved => may need to be rewritten) | |
| */ | |
| static int freeDirCacheRange(dirCache_t *cache, | |
| unsigned int beginSlot, | |
| unsigned int endSlot) | |
| { | |
| dirCacheEntry_t *entry; | |
| unsigned int clearBegin; | |
| unsigned int clearEnd; | |
| unsigned int i; | |
| if(endSlot < beginSlot) { | |
| fprintf(stderr, "Bad slots %d %d in free range\n", | |
| beginSlot, endSlot); | |
| exit(1); | |
| } | |
| while(beginSlot < endSlot) { | |
| entry = cache->entries[beginSlot]; | |
| if(!entry) { | |
| beginSlot++; | |
| continue; | |
| } | |
| /* Due to the way this is called, we _always_ de-allocate | |
| * starting from beginning... */ | |
| #ifdef HAVE_ASSERT_H | |
| assert(entry->beginSlot == beginSlot); | |
| #endif | |
| clearEnd = entry->endSlot; | |
| if(clearEnd > endSlot) | |
| clearEnd = endSlot; | |
| clearBegin = beginSlot; | |
| for(i = clearBegin; i <clearEnd; i++) | |
| cache->entries[i] = 0; | |
| entry->beginSlot = clearEnd; | |
| if(entry->beginSlot == entry->endSlot) { | |
| int needWriteEnd = 0; | |
| if(entry->endMarkPos != -1 && | |
| entry->endMarkPos < (int) beginSlot) | |
| needWriteEnd = 1; | |
| if(entry->longName) | |
| free(entry->longName); | |
| if(entry->shortName) | |
| free(entry->shortName); | |
| free(entry); | |
| if(needWriteEnd) { | |
| return (int) beginSlot; | |
| } | |
| } | |
| beginSlot = clearEnd; | |
| } | |
| return -1; | |
| } | |
| static dirCacheEntry_t *allocDirCacheEntry(dirCache_t *cache, | |
| unsigned int beginSlot, | |
| unsigned int endSlot, | |
| dirCacheEntryType_t type) | |
| { | |
| dirCacheEntry_t *entry; | |
| unsigned int i; | |
| if(growDirCache(cache, endSlot) < 0) | |
| return 0; | |
| entry = New(dirCacheEntry_t); | |
| if(!entry) | |
| return 0; | |
| entry->type = type; | |
| entry->longName = 0; | |
| entry->shortName = 0; | |
| entry->beginSlot = beginSlot; | |
| entry->endSlot = endSlot; | |
| entry->endMarkPos = -1; | |
| freeDirCacheRange(cache, beginSlot, endSlot); | |
| for(i=beginSlot; i<endSlot; i++) { | |
| cache->entries[i] = entry; | |
| } | |
| return entry; | |
| } | |
| dirCacheEntry_t *addUsedEntry(dirCache_t *cache, | |
| unsigned int beginSlot, | |
| unsigned int endSlot, | |
| wchar_t *longName, wchar_t *shortName, | |
| struct directory *dir) | |
| { | |
| dirCacheEntry_t *entry; | |
| if(endSlot < beginSlot) { | |
| fprintf(stderr, | |
| "Bad slots %d %d in add used entry\n", | |
| beginSlot, endSlot); | |
| exit(1); | |
| } | |
| entry = allocDirCacheEntry(cache, beginSlot, endSlot, DCET_USED); | |
| if(!entry) | |
| return 0; | |
| entry->beginSlot = beginSlot; | |
| entry->endSlot = endSlot; | |
| if(longName) | |
| entry->longName = wcsdup(longName); | |
| entry->shortName = wcsdup(shortName); | |
| entry->dir = *dir; | |
| hashDce(cache, entry); | |
| return entry; | |
| } | |
| /* Try to merge free slot at position "slot" with slot at previous position | |
| * After successful merge, both will point to a same DCE (the one which used | |
| * to be previous) | |
| * Only does something if both of these slots are actually free | |
| */ | |
| static void mergeFreeSlots(dirCache_t *cache, unsigned int slot) | |
| { | |
| dirCacheEntry_t *previous, *next; | |
| unsigned int i; | |
| if(slot == 0) | |
| return; | |
| previous = cache->entries[slot-1]; | |
| next = cache->entries[slot]; | |
| if(next && next->type == DCET_FREE && | |
| previous && previous->type == DCET_FREE) { | |
| for(i=next->beginSlot; i < next->endSlot; i++) | |
| cache->entries[i] = previous; | |
| previous->endSlot = next->endSlot; | |
| previous->endMarkPos = next->endMarkPos; | |
| free(next); | |
| } | |
| } | |
| /* Create a free range extending from beginSlot to endSlot, and try to merge | |
| * it with any neighboring free ranges */ | |
| dirCacheEntry_t *addFreeEndEntry(dirCache_t *cache, | |
| unsigned int beginSlot, | |
| unsigned int endSlot, | |
| int isAtEnd) | |
| { | |
| dirCacheEntry_t *entry; | |
| if(beginSlot < cache->nrHashed) | |
| cache->nrHashed = beginSlot; | |
| if(endSlot < beginSlot) { | |
| fprintf(stderr, "Bad slots %d %d in add free entry\n", | |
| beginSlot, endSlot); | |
| exit(1); | |
| } | |
| if(endSlot == beginSlot) | |
| return 0; | |
| entry = allocDirCacheEntry(cache, beginSlot, endSlot, DCET_FREE); | |
| if(isAtEnd) | |
| entry->endMarkPos = (int) beginSlot; | |
| mergeFreeSlots(cache, beginSlot); | |
| mergeFreeSlots(cache, endSlot); | |
| return cache->entries[beginSlot]; | |
| } | |
| dirCacheEntry_t *addFreeEntry(dirCache_t *cache, | |
| unsigned int beginSlot, | |
| unsigned int endSlot) | |
| { | |
| return addFreeEndEntry(cache, beginSlot, endSlot, 0); | |
| } | |
| dirCacheEntry_t *addEndEntry(dirCache_t *cache, unsigned int pos) | |
| { | |
| return allocDirCacheEntry(cache, pos, pos+1u, DCET_END); | |
| } | |
| dirCacheEntry_t *lookupInDircache(dirCache_t *cache, unsigned int pos) | |
| { | |
| if(growDirCache(cache, pos+1) < 0) | |
| return 0; | |
| return cache->entries[pos]; | |
| } | |
| void freeDirCache(Stream_t *Stream) | |
| { | |
| dirCache_t *cache, **dcp; | |
| dcp = getDirCacheP(Stream); | |
| cache = *dcp; | |
| if(cache) { | |
| int n; | |
| n=freeDirCacheRange(cache, 0, cache->nr_entries); | |
| if(n >= 0) | |
| low_level_dir_write_end(Stream, n); | |
| free(cache); | |
| *dcp = 0; | |
| } | |
| } |