/* * linux/fs/ext2/dcache.c * * Copyright (C) 1992, 1993, 1994 Remy Card (card@masi.ibp.fr) * Laboratoire MASI - Institut Blaise Pascal * Universite Pierre et Marie Curie (Paris VI) * */ /* * dcache.c contains the code that handles the directory cache used by * lookup() and readdir() */ #include #include #include #include #ifndef DONT_USE_DCACHE #define DCACHE_NAME_LEN 32 struct dir_cache_entry { unsigned short dev; unsigned long dir; unsigned long ino; char name[DCACHE_NAME_LEN + 1]; int len; struct dir_cache_entry * queue_prev; struct dir_cache_entry * queue_next; struct dir_cache_entry * prev; struct dir_cache_entry * next; }; static struct dir_cache_entry * first = NULL; static struct dir_cache_entry * last = NULL; static struct dir_cache_entry * first_free = NULL; static int cache_initialized = 0; #ifdef EXT2FS_DEBUG_CACHE static int hits = 0; static int misses = 0; #endif #define CACHE_SIZE 128 static struct dir_cache_entry dcache[CACHE_SIZE]; #define HASH_QUEUES 16 static struct dir_cache_entry * queue_head[HASH_QUEUES]; static struct dir_cache_entry * queue_tail[HASH_QUEUES]; #define hash(dev,dir) ((dev ^ dir) % HASH_QUEUES) /* * Initialize the cache */ static void init_cache (void) { int i; dcache[0].prev = NULL; dcache[0].next = &dcache[1]; dcache[0].queue_next = dcache[0].queue_prev = NULL; for (i = 1; i < CACHE_SIZE - 1; i++) { dcache[i].prev = &dcache[i - 1]; dcache[i].next = &dcache[i + 1]; dcache[i].queue_next = dcache[i].queue_prev = NULL; } dcache[i].prev = &dcache[i - 1]; dcache[i].next = NULL; dcache[i].queue_next = dcache[i].queue_prev = NULL; first_free = &dcache[0]; for (i = 0; i < HASH_QUEUES; i++) queue_tail[i] = queue_head[i] = NULL; cache_initialized = 1; } /* * Find a name in the cache */ static struct dir_cache_entry * find_name (int queue, unsigned short dev, unsigned long dir, const char * name, int len) { struct dir_cache_entry * p; for (p = queue_head[queue]; p != NULL && (p->dev != dev || p->dir != dir || p->len != len || strncmp (name, p->name, p->len) != 0); p = p->queue_next) ; return p; } #ifdef EXT2FS_DEBUG_CACHE /* * List the cache entries for debugging */ static void show_cache (const char * func_name) { struct dir_cache_entry * p; printk ("%s: cache status\n", func_name); for (p = first; p != NULL; p = p->next) printk ("dev:%04x, dir=%4lu, name=%s\n", p->dev, p->dir, p->name); } #endif /* * Add an entry at the beginning of the cache */ static void add_to_cache (struct dir_cache_entry * p) { p->prev = NULL; p->next = first; if (first) first->prev = p; if (!last) last = p; first = p; } /* * Add an entry at the beginning of a queue */ static void add_to_queue (int queue, struct dir_cache_entry * p) { p->queue_prev = NULL; p->queue_next = queue_head[queue]; if (queue_head[queue]) queue_head[queue]->queue_prev = p; if (!queue_tail[queue]) queue_tail[queue] = p; queue_head[queue] = p; } /* * Remove an entry from the cache */ static void remove_from_cache (struct dir_cache_entry * p) { if (p->prev) p->prev->next = p->next; else first = p->next; if (p->next) p->next->prev = p->prev; else last = p->prev; p->prev = NULL; p->next = NULL; } /* * Remove an entry from a queue */ static void remove_from_queue (int queue, struct dir_cache_entry * p) { if (p->queue_prev) p->queue_prev->queue_next = p->queue_next; else queue_head[queue] = p->queue_next; if (p->queue_next) p->queue_next->queue_prev = p->queue_prev; else queue_tail[queue] = p->queue_prev; p->queue_prev = NULL; p->queue_next = NULL; } /* * Invalidate all cache entries on a device (called by put_super() when * a file system is unmounted) */ void ext2_dcache_invalidate (unsigned short dev) { struct dir_cache_entry * p; struct dir_cache_entry * p2; if (!cache_initialized) init_cache (); for (p = first; p != NULL; p = p2) { p2 = p->next; if (p->dev == dev) { remove_from_cache (p); remove_from_queue (hash (p->dev, p->dir), p); p->next = first_free; first_free = p; } } #ifdef EXT2FS_DEBUG_CACHE show_cache ("dcache_invalidate"); #endif } /* * Lookup a directory entry in the cache */ unsigned long ext2_dcache_lookup (unsigned short dev, unsigned long dir, const char * name, int len) { char our_name[EXT2_NAME_LEN]; int queue; struct dir_cache_entry * p; if (!cache_initialized) init_cache (); if (len > DCACHE_NAME_LEN) return 0; memcpy (our_name, (char *) name, len); our_name[len] = '\0'; #ifdef EXT2FS_DEBUG_CACHE printk ("dcache_lookup (%04x, %lu, %s, %d)\n", dev, dir, our_name, len); #endif queue = hash (dev, dir); if ((p = find_name (queue, dev, dir, our_name, len))) { if (p != first) { remove_from_cache (p); add_to_cache (p); } if (p != queue_head[queue]) { remove_from_queue (queue, p); add_to_queue (queue, p); } #ifdef EXT2FS_DEBUG_CACHE hits++; printk ("dcache_lookup: %s,hit,inode=%lu,hits=%d,misses=%d\n", our_name, p->ino, hits, misses); show_cache ("dcache_lookup"); #endif return p->ino; } else { #ifdef EXT2FS_DEBUG_CACHE misses++; printk ("dcache_lookup: %s,miss,hits=%d,misses=%d\n", our_name, hits, misses); show_cache ("dcache_lookup"); #endif return 0; } } /* * Add a directory entry to the cache * * This function is called by ext2_lookup(), ext2_readdir() * and the functions which create directory entries */ void ext2_dcache_add (unsigned short dev, unsigned long dir, const char * name, int len, unsigned long ino) { struct dir_cache_entry * p; int queue; if (!cache_initialized) init_cache (); #ifdef EXT2FS_DEBUG_CACHE printk ("dcache_add (%04x, %lu, %s, %d, %lu)\n", dev, dir, name, len, ino); #endif if (len > DCACHE_NAME_LEN) return; queue = hash (dev, dir); if ((p = find_name (queue, dev, dir, name, len))) { p->dir = dir; p->ino = ino; if (p != first) { remove_from_cache (p); add_to_cache (p); } if (p != queue_head[queue]) { remove_from_queue (queue, p); add_to_queue (queue, p); } } else { if (first_free) { p = first_free; first_free = p->next; } else { if (!last) panic ("dcache_add: last == NULL\n"); else { p = last; last = p->prev; if (last) last->next = NULL; remove_from_queue (hash (p->dev, p->dir), p); } } p->dev = dev; p->dir = dir; p->ino = ino; strncpy (p->name, name, len); p->len = len; p->name[len] = '\0'; add_to_cache (p); add_to_queue (queue, p); } #ifdef EXT2FS_DEBUG_CACHE show_cache ("dcache_add"); #endif } /* * Remove a directory from the cache * * This function is called by the functions which remove directory entries */ void ext2_dcache_remove (unsigned short dev, unsigned long dir, const char * name, int len) { struct dir_cache_entry * p; int queue; if (!cache_initialized) init_cache (); #ifdef EXT2FS_DEBUG_CACHE printk ("dcache_remove (%04x, %lu, %s, %d)\n", dev, dir, name, len); #endif if (len > DCACHE_NAME_LEN) return; queue = hash (dev, dir); if ((p = find_name (queue, dev, dir, name, len))) { remove_from_cache (p); remove_from_queue (queue, p); p->next = first_free; first_free = p; } #ifdef EXT2FS_DEBUG_CACHE show_cache ("dcache_remove"); #endif } #endif