root/src/add-ons/kernel/file_systems/ntfs/libntfs/cache.c
/**
 * 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 <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->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; 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
}