/** * cache.c : deal with LRU caches * * Copyright (c) 2008-2010 Jean-Pierre Andre * * This program/include file 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 2 of the License, or * (at your option) any later version. * * This program/include file 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 this program (in the main directory of the NTFS-3G * distribution in the file COPYING); if not, write to the Free Software * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #include "types.h" #include "security.h" #include "cache.h" #include "misc.h" #include "logging.h" /* * General functions to deal with LRU caches * * The cached data have to be organized in a structure in which * the first fields must follow a mandatory pattern and further * fields may contain any fixed size data. They are stored in an * LRU list. * * A compare function must be provided for finding a wanted entry * in the cache. Another function may be provided for invalidating * an entry to facilitate multiple invalidation. * * These functions never return error codes. When there is a * shortage of memory, data is simply not cached. * When there is a hashing bug, hashing is dropped, and sequential * searches are used. */ /* * Enter a new hash index, after a new record has been inserted * * Do not call when a record has been modified (with no key change) */ static void inserthashindex(struct CACHE_HEADER *cache, struct CACHED_GENERIC *current) { int h; struct HASH_ENTRY *link; struct HASH_ENTRY *first; if (cache->dohash) { h = cache->dohash(current); if ((h >= 0) && (h < cache->max_hash)) { /* get a free link and insert at top of hash list */ link = cache->free_hash; if (link) { cache->free_hash = link->next; first = cache->first_hash[h]; if (first) link->next = first; else link->next = NULL; link->entry = current; cache->first_hash[h] = link; } else { ntfs_log_error("No more hash entries," " cache %s hashing dropped\n", cache->name); cache->dohash = (cache_hash)NULL; } } else { ntfs_log_error("Illegal hash value," " cache %s hashing dropped\n", cache->name); cache->dohash = (cache_hash)NULL; } } } /* * Drop a hash index when a record is about to be deleted */ static void drophashindex(struct CACHE_HEADER *cache, const struct CACHED_GENERIC *current, int hash) { struct HASH_ENTRY *link; struct HASH_ENTRY *previous; if (cache->dohash) { if ((hash >= 0) && (hash < cache->max_hash)) { /* find the link and unlink */ link = cache->first_hash[hash]; previous = (struct HASH_ENTRY*)NULL; while (link && (link->entry != current)) { previous = link; link = link->next; } if (link) { if (previous) previous->next = link->next; else cache->first_hash[hash] = link->next; link->next = cache->free_hash; cache->free_hash = link; } else { ntfs_log_error("Bad hash list," " cache %s hashing dropped\n", cache->name); cache->dohash = (cache_hash)NULL; } } else { ntfs_log_error("Illegal hash value," " cache %s hashing dropped\n", cache->name); cache->dohash = (cache_hash)NULL; } } } /* * Fetch an entry from cache * * returns the cache entry, or NULL if not available * The returned entry may be modified, but not freed */ struct CACHED_GENERIC *ntfs_fetch_cache(struct CACHE_HEADER *cache, const struct CACHED_GENERIC *wanted, cache_compare compare) { struct CACHED_GENERIC *current; struct CACHED_GENERIC *previous; struct HASH_ENTRY *link; int h; current = (struct CACHED_GENERIC*)NULL; if (cache) { if (cache->dohash) { /* * When possible, use the hash table to * locate the entry if present */ h = cache->dohash(wanted); link = cache->first_hash[h]; while (link && compare(link->entry, wanted)) link = link->next; if (link) current = link->entry; } if (!cache->dohash) { /* * Search sequentially in LRU list if no hash table * or if hashing has just failed */ current = cache->most_recent_entry; while (current && compare(current, wanted)) { current = current->next; } } if (current) { previous = current->previous; cache->hits++; if (previous) { /* * found and not at head of list, unlink from current * position and relink as head of list */ previous->next = current->next; if (current->next) current->next->previous = current->previous; else cache->oldest_entry = current->previous; current->next = cache->most_recent_entry; current->previous = (struct CACHED_GENERIC*)NULL; cache->most_recent_entry->previous = current; cache->most_recent_entry = current; } } cache->reads++; } return (current); } /* * Enter an inode number into cache * returns the cache entry or NULL if not possible */ struct CACHED_GENERIC *ntfs_enter_cache(struct CACHE_HEADER *cache, const struct CACHED_GENERIC *item, cache_compare compare) { struct CACHED_GENERIC *current; struct CACHED_GENERIC *before; struct HASH_ENTRY *link; int h; current = (struct CACHED_GENERIC*)NULL; if (cache) { if (cache->dohash) { /* * When possible, use the hash table to * find out whether the entry if present */ h = cache->dohash(item); link = cache->first_hash[h]; while (link && compare(link->entry, item)) link = link->next; if (link) { current = link->entry; } } if (!cache->dohash) { /* * Search sequentially in LRU list to locate the end, * and find out whether the entry is already in list * As we normally go to the end, no statistics is * kept. */ current = cache->most_recent_entry; while (current && compare(current, item)) { current = current->next; } } if (!current) { /* * Not in list, get a free entry or reuse the * last entry, and relink as head of list * Note : we assume at least three entries, so * before, previous and first are different when * an entry is reused. */ if (cache->free_entry) { current = cache->free_entry; cache->free_entry = cache->free_entry->next; if (item->varsize) { current->variable = ntfs_malloc( item->varsize); } else current->variable = (void*)NULL; current->varsize = item->varsize; if (!cache->oldest_entry) cache->oldest_entry = current; } else { /* reusing the oldest entry */ current = cache->oldest_entry; before = current->previous; before->next = (struct CACHED_GENERIC*)NULL; if (cache->dohash) drophashindex(cache,current, cache->dohash(current)); if (cache->dofree) cache->dofree(current); cache->oldest_entry = current->previous; if (item->varsize) { if (current->varsize) current->variable = realloc( current->variable, item->varsize); else current->variable = ntfs_malloc( item->varsize); } else { if (current->varsize) free(current->variable); current->variable = (void*)NULL; } current->varsize = item->varsize; } current->next = cache->most_recent_entry; current->previous = (struct CACHED_GENERIC*)NULL; if (cache->most_recent_entry) cache->most_recent_entry->previous = current; cache->most_recent_entry = current; memcpy(current->payload, item->payload, cache->fixed_size); if (item->varsize) { if (current->variable) { memcpy(current->variable, item->variable, item->varsize); } else { /* * no more memory for variable part * recycle entry in free list * not an error, just uncacheable */ cache->most_recent_entry = current->next; current->next = cache->free_entry; cache->free_entry = current; current = (struct CACHED_GENERIC*)NULL; } } else { current->variable = (void*)NULL; current->varsize = 0; } if (cache->dohash && current) inserthashindex(cache,current); } cache->writes++; } return (current); } /* * Invalidate a cache entry * The entry is moved to the free entry list * A specific function may be called for entry deletion */ static void do_invalidate(struct CACHE_HEADER *cache, struct CACHED_GENERIC *current, int flags) { struct CACHED_GENERIC *previous; previous = current->previous; if ((flags & CACHE_FREE) && cache->dofree) cache->dofree(current); /* * Relink into free list */ if (current->next) current->next->previous = current->previous; else cache->oldest_entry = current->previous; if (previous) previous->next = current->next; else cache->most_recent_entry = current->next; current->next = cache->free_entry; cache->free_entry = current; if (current->variable) free(current->variable); current->varsize = 0; } /* * Invalidate entries in cache * * Several entries may have to be invalidated (at least for inodes * associated to directories which have been renamed), a different * compare function may be provided to select entries to invalidate * * Returns the number of deleted entries, this can be used by * the caller to signal a cache corruption if the entry was * supposed to be found. */ int ntfs_invalidate_cache(struct CACHE_HEADER *cache, const struct CACHED_GENERIC *item, cache_compare compare, int flags) { struct CACHED_GENERIC *current; struct CACHED_GENERIC *next; struct HASH_ENTRY *link; int count; int h; current = (struct CACHED_GENERIC*)NULL; count = 0; if (cache) { if (!(flags & CACHE_NOHASH) && cache->dohash) { /* * When possible, use the hash table to * find out whether the entry if present */ h = cache->dohash(item); link = cache->first_hash[h]; while (link) { if (compare(link->entry, item)) link = link->next; else { current = link->entry; link = link->next; if (current) { drophashindex(cache,current,h); do_invalidate(cache, current,flags); count++; } } } } if ((flags & CACHE_NOHASH) || !cache->dohash) { /* * Search sequentially in LRU list */ current = cache->most_recent_entry; while (current) { if (!compare(current, item)) { next = current->next; if (cache->dohash) drophashindex(cache,current, cache->dohash(current)); do_invalidate(cache,current,flags); current = next; count++; } else { current = current->next; } } } } return (count); } int ntfs_remove_cache(struct CACHE_HEADER *cache, struct CACHED_GENERIC *item, int flags) { int count; count = 0; if (cache) { if (cache->dohash) drophashindex(cache,item,cache->dohash(item)); do_invalidate(cache,item,flags); count++; } return (count); } /* * Free memory allocated to a cache */ static void ntfs_free_cache(struct CACHE_HEADER *cache) { struct CACHED_GENERIC *entry; if (cache) { for (entry=cache->most_recent_entry; entry; entry=entry->next) { if (cache->dofree) cache->dofree(entry); if (entry->variable) free(entry->variable); } free(cache); } } /* * Create a cache * * Returns the cache header, or NULL if the cache could not be created */ static struct CACHE_HEADER *ntfs_create_cache(const char *name, cache_free dofree, cache_hash dohash, int full_item_size, int item_count, int max_hash) { struct CACHE_HEADER *cache; struct CACHED_GENERIC *pc; struct CACHED_GENERIC *qc; struct HASH_ENTRY *ph; struct HASH_ENTRY *qh; struct HASH_ENTRY **px; size_t size; int i; size = sizeof(struct CACHE_HEADER) + item_count*full_item_size; if (max_hash) size += item_count*sizeof(struct HASH_ENTRY) + max_hash*sizeof(struct HASH_ENTRY*); cache = (struct CACHE_HEADER*)ntfs_malloc(size); if (cache) { /* header */ cache->name = name; cache->dofree = dofree; if (dohash && max_hash) { cache->dohash = dohash; cache->max_hash = max_hash; } else { cache->dohash = (cache_hash)NULL; cache->max_hash = 0; } cache->fixed_size = full_item_size - sizeof(struct CACHED_GENERIC); cache->reads = 0; cache->writes = 0; cache->hits = 0; /* chain the data entries, and mark an invalid entry */ cache->most_recent_entry = (struct CACHED_GENERIC*)NULL; cache->oldest_entry = (struct CACHED_GENERIC*)NULL; cache->free_entry = &cache->entry[0]; pc = &cache->entry[0]; for (i=0; i<(item_count - 1); i++) { qc = (struct CACHED_GENERIC*)((char*)pc + full_item_size); pc->next = qc; pc->variable = (void*)NULL; pc->varsize = 0; pc = qc; } /* special for the last entry */ pc->next = (struct CACHED_GENERIC*)NULL; pc->variable = (void*)NULL; pc->varsize = 0; if (max_hash) { /* chain the hash entries */ ph = (struct HASH_ENTRY*)(((char*)pc) + full_item_size); cache->free_hash = ph; for (i=0; i<(item_count - 1); i++) { qh = &ph[1]; ph->next = qh; ph = qh; } /* special for the last entry */ if (item_count) { ph->next = (struct HASH_ENTRY*)NULL; } /* create and initialize the hash indexes */ px = (struct HASH_ENTRY**)&ph[1]; cache->first_hash = px; for (i=0; ifree_hash = (struct HASH_ENTRY*)NULL; cache->first_hash = (struct HASH_ENTRY**)NULL; } } return (cache); } /* * Create all LRU caches * * No error return, if creation is not possible, cacheing will * just be not available */ void ntfs_create_lru_caches(ntfs_volume *vol) { #if CACHE_INODE_SIZE /* inode cache */ vol->xinode_cache = ntfs_create_cache("inode",(cache_free)NULL, ntfs_dir_inode_hash, sizeof(struct CACHED_INODE), CACHE_INODE_SIZE, 2*CACHE_INODE_SIZE); #endif #if CACHE_NIDATA_SIZE /* idata cache */ vol->nidata_cache = ntfs_create_cache("nidata", ntfs_inode_nidata_free, ntfs_inode_nidata_hash, sizeof(struct CACHED_NIDATA), CACHE_NIDATA_SIZE, 2*CACHE_NIDATA_SIZE); #endif #if CACHE_LOOKUP_SIZE /* lookup cache */ vol->lookup_cache = ntfs_create_cache("lookup", (cache_free)NULL, ntfs_dir_lookup_hash, sizeof(struct CACHED_LOOKUP), CACHE_LOOKUP_SIZE, 2*CACHE_LOOKUP_SIZE); #endif vol->securid_cache = ntfs_create_cache("securid",(cache_free)NULL, (cache_hash)NULL,sizeof(struct CACHED_SECURID), CACHE_SECURID_SIZE, 0); #if CACHE_LEGACY_SIZE vol->legacy_cache = ntfs_create_cache("legacy",(cache_free)NULL, (cache_hash)NULL, sizeof(struct CACHED_PERMISSIONS_LEGACY), CACHE_LEGACY_SIZE, 0); #endif } /* * Free all LRU caches */ void ntfs_free_lru_caches(ntfs_volume *vol) { #if CACHE_INODE_SIZE ntfs_free_cache(vol->xinode_cache); #endif #if CACHE_NIDATA_SIZE ntfs_free_cache(vol->nidata_cache); #endif #if CACHE_LOOKUP_SIZE ntfs_free_cache(vol->lookup_cache); #endif ntfs_free_cache(vol->securid_cache); #if CACHE_LEGACY_SIZE ntfs_free_cache(vol->legacy_cache); #endif }