mirror of
https://github.com/wiidev/usbloadergx.git
synced 2024-11-18 09:19:17 +01:00
9e79c9d99b
* code cleanup
674 lines
21 KiB
C
674 lines
21 KiB
C
/**
|
|
* 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 <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#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
|
|
}
|