/** * cache.c : deal with LRU caches * * Copyright (c) 2008-2009 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->fixed, item->fixed, 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 *previous; 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; previous = ( struct CACHED_GENERIC* )NULL; 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 { previous = current; 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; i < max_hash; i++ ) px[i] = ( struct HASH_ENTRY* )NULL; } else { cache->free_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 }