diff options
Diffstat (limited to 'libdiskfs/node-cache.c')
-rw-r--r-- | libdiskfs/node-cache.c | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/libdiskfs/node-cache.c b/libdiskfs/node-cache.c new file mode 100644 index 00000000..6b70da83 --- /dev/null +++ b/libdiskfs/node-cache.c @@ -0,0 +1,260 @@ +/* Inode cache. + + Copyright (C) 1994-2015 Free Software Foundation, Inc. + + 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 the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ + +#include "priv.h" + +#define INOHSZ 8192 +#if ((INOHSZ&(INOHSZ-1)) == 0) +#define INOHASH(ino) ((ino)&(INOHSZ-1)) +#else +#define INOHASH(ino) (((unsigned)(ino))%INOHSZ) +#endif + +/* The nodehash is a cache of nodes. + + Access to nodehash and nodehash_nr_items is protected by + nodecache_lock. + + Every node in the nodehash carries a light reference. When we are + asked to give up that light reference, we reacquire our lock + momentarily to check whether someone else reacquired a reference + through the nodehash. */ +static struct node *nodehash[INOHSZ]; +static size_t nodehash_nr_items; +static pthread_rwlock_t nodecache_lock = PTHREAD_RWLOCK_INITIALIZER; + +/* Initialize the inode hash table. */ +static void __attribute__ ((constructor)) +nodecache_init () +{ +} + +/* Lookup node with inode number INUM. Returns NULL if the node is + not found in the node cache. */ +static struct node * +lookup (ino_t inum) +{ + struct node *np; + for (np = nodehash[INOHASH(inum)]; np; np = np->hnext) + if (np->cache_id == inum) + return np; + return NULL; +} + +/* Fetch inode INUM, set *NPP to the node structure; + gain one user reference and lock the node. */ +error_t __attribute__ ((weak)) +diskfs_cached_lookup (ino_t inum, struct node **npp) +{ + return diskfs_cached_lookup_context (inum, npp, NULL); +} + +/* Fetch inode INUM, set *NPP to the node structure; + gain one user reference and lock the node. */ +error_t +diskfs_cached_lookup_context (ino_t inum, struct node **npp, + struct lookup_context *ctx) +{ + error_t err; + struct node *np, *tmp; + + pthread_rwlock_rdlock (&nodecache_lock); + np = lookup (inum); + if (np) + goto gotit; + pthread_rwlock_unlock (&nodecache_lock); + + err = diskfs_user_make_node (&np, ctx); + if (err) + return err; + + np->cache_id = inum; + pthread_mutex_lock (&np->lock); + + /* Put NP in NODEHASH. */ + pthread_rwlock_wrlock (&nodecache_lock); + tmp = lookup (inum); + if (tmp) + { + /* We lost a race. */ + diskfs_nput (np); + np = tmp; + goto gotit; + } + + np->hnext = nodehash[INOHASH(inum)]; + if (np->hnext) + np->hnext->hprevp = &np->hnext; + np->hprevp = &nodehash[INOHASH(inum)]; + nodehash[INOHASH(inum)] = np; + diskfs_nref_light (np); + nodehash_nr_items += 1; + pthread_rwlock_unlock (&nodecache_lock); + + /* Get the contents of NP off disk. */ + err = diskfs_user_read_node (np, ctx); + if (err) + return err; + else + { + *npp = np; + return 0; + } + + gotit: + diskfs_nref (np); + pthread_rwlock_unlock (&nodecache_lock); + pthread_mutex_lock (&np->lock); + *npp = np; + return 0; +} + +/* Lookup node INUM (which must have a reference already) and return it + without allocating any new references. */ +struct node * +diskfs_cached_ifind (ino_t inum) +{ + struct node *np; + + pthread_rwlock_rdlock (&nodecache_lock); + np = lookup (inum); + pthread_rwlock_unlock (&nodecache_lock); + + assert (np); + return np; +} + +void __attribute__ ((weak)) +diskfs_try_dropping_softrefs (struct node *np) +{ + pthread_rwlock_wrlock (&nodecache_lock); + if (np->hprevp != NULL) + { + /* Check if someone reacquired a reference through the + nodehash. */ + struct references result; + refcounts_references (&np->refcounts, &result); + + if (result.hard > 0) + { + /* A reference was reacquired through a hash table lookup. + It's fine, we didn't touch anything yet. */ + pthread_rwlock_unlock (&nodecache_lock); + return; + } + + *np->hprevp = np->hnext; + if (np->hnext) + np->hnext->hprevp = np->hprevp; + np->hnext = NULL; + np->hprevp = NULL; + nodehash_nr_items -= 1; + diskfs_nrele_light (np); + } + pthread_rwlock_unlock (&nodecache_lock); + + diskfs_user_try_dropping_softrefs (np); +} + +/* For each active node, call FUN. The node is to be locked around the call + to FUN. If FUN returns non-zero for any node, then immediately stop, and + return that value. */ +error_t __attribute__ ((weak)) +diskfs_node_iterate (error_t (*fun)(struct node *)) +{ + error_t err = 0; + int n; + size_t num_nodes; + struct node *node, **node_list, **p; + + pthread_rwlock_rdlock (&nodecache_lock); + + /* We must copy everything from the hash table into another data structure + to avoid running into any problems with the hash-table being modified + during processing (normally we delegate access to hash-table with + nodecache_lock, but we can't hold this while locking the + individual node locks). */ + /* XXX: Can we? */ + num_nodes = nodehash_nr_items; + + /* TODO This method doesn't scale beyond a few dozen nodes and should be + replaced. */ + node_list = malloc (num_nodes * sizeof (struct node *)); + if (node_list == NULL) + { + pthread_rwlock_unlock (&nodecache_lock); + error (0, 0, "unable to allocate temporary node table"); + return ENOMEM; + } + + p = node_list; + for (n = 0; n < INOHSZ; n++) + for (node = nodehash[n]; node; node = node->hnext) + { + *p++ = node; + + /* We acquire a hard reference for node, but without using + diskfs_nref. We do this so that diskfs_new_hardrefs will not + get called. */ + refcounts_ref (&node->refcounts, NULL); + } + + pthread_rwlock_unlock (&nodecache_lock); + + p = node_list; + while (num_nodes-- > 0) + { + node = *p++; + if (!err) + { + pthread_mutex_lock (&node->lock); + err = (*fun)(node); + pthread_mutex_unlock (&node->lock); + } + diskfs_nrele (node); + } + + free (node_list); + return err; +} + +/* The user must define this function if she wants to use the node + cache. Create and initialize a node. */ +error_t __attribute__ ((weak)) +diskfs_user_make_node (struct node **npp, struct lookup_context *ctx) +{ + assert (! "diskfs_user_make_node not implemented"); +} + +/* The user must define this function if she wants to use the node + cache. Read stat information out of the on-disk node. */ +error_t __attribute__ ((weak)) +diskfs_user_read_node (struct node *np, struct lookup_context *ctx) +{ + assert (! "diskfs_user_read_node not implemented"); +} + +/* The user must define this function if she wants to use the node + cache. The last hard reference to a node has gone away; arrange to + have all the weak references dropped that can be. */ +void __attribute__ ((weak)) +diskfs_user_try_dropping_softrefs (struct node *np) +{ + assert (! "diskfs_user_try_dropping_softrefs not implemented"); +} |