/* Directory name lookup caching Copyright (C) 1996 Free Software Foundation, Inc. Written by Michael I. Bushnell, p/BSG. This file is part of the GNU Hurd. The GNU Hurd 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, or (at your option) any later version. The GNU Hurd 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; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ #include "priv.h" #include /* Maximum number of names to cache at once */ #define MAXCACHE 256 /* Current number of names cached */ static int cache_cur; /* Cache entry */ struct lookup_cache { struct lookup_cache *next, *prev; /* These do not hold references; special code exists to call purge routines when the nodes lose refs. */ struct node *dp; /* directory */ struct node *np; /* node */ /* strlen of name member */ size_t namelen; char name[0]; }; /* Cache itself */ static struct lookup_cache *lookup_cache_head, *lookup_cache_tail; /* Buffer to hold statistics */ static struct { long pos_hits; long neg_hits; long miss; } statistics; /* Node NP has just been found in DIR with NAME. If NP is null, that means that this name has been confirmed as absent in the directory. */ void diskfs_enter_cache (struct node *dir, struct node *np, char *name) { struct lookup_cache *lc, *tmp = 0; size_t len = strlen (name) + 1; lc = malloc (sizeof (struct lookup_cache) + len); lc->dp = dir; lc->np = np; lc->namelen = len - 1; bcopy (name, lc->name, len); spin_lock (&diskfs_node_refcnt_lock); if (cache_cur == MAXCACHE) { /* Free the entry at the tail */ tmp = lookup_cache_tail; lookup_cache_tail = lookup_cache_tail->prev; if (lookup_cache_tail) lookup_cache_tail->next = 0; else lookup_cache_head = 0; } else cache_cur++; /* Add the new entry at the front */ lc->next = lookup_cache_head; lc->prev = 0; if (lc->next) lc->next->prev = lc; lookup_cache_head = lc; if (!lc->next) lookup_cache_tail = lc; spin_unlock (&diskfs_node_refcnt_lock); if (tmp) free (tmp); } /* Purge all references in the cache to NP as a node inside directory DP. */ void diskfs_purge_cache (struct node *dp, struct node *np) { struct lookup_cache *lc, *nxt; spin_lock (&diskfs_node_refcnt_lock); for (lc = lookup_cache_head; lc; lc = nxt) if (lc->np == np && lc->dp == dp) { if (lc->prev) lc->prev->next = lc->next; if (lc->next) lc->next->prev = lc->prev; nxt = lc->next; if (lookup_cache_tail == lc) lookup_cache_tail = lc->prev; free (lc); } else nxt = lc->next; spin_unlock (&diskfs_node_refcnt_lock); } /* Purge all references in the cache to NP, either as a node or as a directory. diskfs_node_refcnt_lock must be held around this call. */ void _diskfs_purge_cache_deletion (struct node *np) { struct lookup_cache *lc, *nxt; for (lc = lookup_cache_head; lc; lc = nxt) if (lc->np == np || lc->dp == np) { if (lc->prev) lc->prev->next = lc->next; if (lc->next) lc->next->prev = lc->prev; nxt = lc->next; if (lookup_cache_tail == lc) lookup_cache_tail = lc->prev; free (lc); } else nxt = lc->next; } /* Scan the cache looking for NAME inside DIR. If we don't know anything entry at all, then return 0. If the entry is confirmed to not exist, then return -1. Otherwise, return NP for the entry, with a newly allocated reference. */ struct node * diskfs_check_cache (struct node *dir, char *name) { struct lookup_cache *lc; size_t len = strlen (name); spin_lock (&diskfs_node_refcnt_lock); for (lc = lookup_cache_head; lc; lc = lc->next) if (lc->dp == dir && lc->namelen == len && lc->name[0] == name[0] && !strcmp (lc->name, name)) { if (lc->np) { lc->np->references++; statistics.pos_hits++; spin_unlock (&diskfs_node_refcnt_lock); return lc->np; } else { statistics.neg_hits++; spin_unlock (&diskfs_node_refcnt_lock); return (struct node *) -1; } } statistics.miss++; spin_unlock (&diskfs_node_refcnt_lock); return 0; }