aboutsummaryrefslogtreecommitdiffstats
path: root/lufis/dircache.c
diff options
context:
space:
mode:
Diffstat (limited to 'lufis/dircache.c')
-rw-r--r--lufis/dircache.c474
1 files changed, 474 insertions, 0 deletions
diff --git a/lufis/dircache.c b/lufis/dircache.c
new file mode 100644
index 0000000..0498355
--- /dev/null
+++ b/lufis/dircache.c
@@ -0,0 +1,474 @@
+/*
+ * dircache.c
+ * Copyright (C) 2002 Florin Malita <mali@go.ro>
+ *
+ * This file is part of LUFS, a free userspace filesystem implementation.
+ * See http://lufs.sourceforge.net/ for updates.
+ *
+ * LUFS 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.
+ *
+ * LUFS 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-1307 USA
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+#include <sys/stat.h>
+
+#include <lufs/proto.h>
+#include <lufs/fs.h>
+
+#include "list.h"
+#include "dircache.h"
+
+static char root_dir[]="/";
+static char current_dir[]=".";
+
+static unsigned long
+hash(char *name){
+ unsigned long res = 0;
+ unsigned int i;
+
+ for(i = 0; i < strlen(name); i++)
+ if(name[i] != '/')
+ res = 0x21413 * (res + name[i]);
+
+ return res % NBUCKETS;
+}
+
+static void
+delete_dir(struct directory *d){
+ struct list_head *p, *tmp;
+ struct direntry *de;
+
+ TRACE("in");
+ list_for_each_safe(p, tmp, &d->d_entries){
+ de = list_entry(p, struct direntry, e_list);
+ list_del(&de->e_list);
+ free(de->e_name);
+ if(de->e_link)
+ free(de->e_link);
+ free(de);
+ }
+
+ list_del(&d->d_list);
+ free(d->d_name);
+ free(d);
+
+ TRACE("out");
+}
+
+struct dir_cache*
+lu_cache_create(struct list_head *cfg){
+ struct dir_cache *cache;
+ int i;
+ const char *c;
+
+ TRACE("creating dir cache...");
+
+ if(!(cache = malloc(sizeof(struct dir_cache))))
+ return NULL;
+
+ memset(cache, 0, sizeof(struct dir_cache));
+
+ for(i = 0; i < NBUCKETS; i++)
+ INIT_LIST_HEAD(&cache->buckets[i]);
+
+ pthread_mutex_init(&cache->lock, NULL);
+
+ cache->ttl = DEF_TTL;
+ if((c = lu_opt_getchar(cfg, "LUFSD", "DirCacheTTL")) && atoi(c))
+ cache->ttl = atoi(c);
+ if((c = lu_opt_getchar(cfg, "MOUNT", "dir_cache_ttl")) && atoi(c))
+ cache->ttl = atoi(c);
+
+ cache->entries = DEF_NENTRIES;
+ if((c = lu_opt_getchar(cfg, "LUFSD", "DirCacheEntries")) && atoi(c))
+ cache->entries = atoi(c);
+ if((c = lu_opt_getchar(cfg, "MOUNT", "dir_cache_entries")) && atoi(c))
+ cache->entries = atoi(c);
+
+ TRACE("entries: %d, ttl: %d", cache->entries, cache->ttl);
+
+ return cache;
+}
+
+void
+lu_cache_destroy(struct dir_cache *cache){
+ struct list_head *p, *tmp;
+ int i;
+
+ for(i = 0; i < NBUCKETS; i++){
+ list_for_each_safe(p, tmp, &cache->buckets[i]){
+ delete_dir(list_entry(p, struct directory, d_list));
+ }
+ }
+
+ free(cache);
+}
+
+static struct directory*
+search(struct dir_cache *cache, char *dir){
+ struct list_head *p, *tmp;
+ struct directory *d;
+ int hsh;
+
+ hsh = hash(dir);
+
+ TRACE("search %s in bucket %u, size=%u", dir, hsh, cache->lengths[hsh]);
+
+ list_for_each_safe(p, tmp, &cache->buckets[hsh]){
+ d = list_entry(p, struct directory, d_list);
+
+ if(time(NULL) - d->d_stamp >= (unsigned long) cache->ttl){
+ TRACE("%s expired...", d->d_name);
+ delete_dir(d);
+ cache->lengths[hsh]--;
+ TRACE("directory deleted");
+ }else if(!strcmp(dir, d->d_name)){
+ TRACE("%s found", dir);
+ d->d_stamp = time(NULL);
+ return d;
+ }
+ }
+
+ TRACE("dir not found");
+ return NULL;
+}
+
+int
+lu_cache_lookup(struct dir_cache *cache, char *dir, char *file, struct lufs_fattr *fattr, char *link, int buflen){
+ struct directory *d;
+ struct direntry *de;
+ struct list_head *p;
+ int res = -1;
+
+ TRACE("looking up %s in dir %s", file, dir);
+
+ pthread_mutex_lock(&cache->lock);
+
+ if(!(d = search(cache, dir)))
+ goto out;
+
+ list_for_each(p, &d->d_entries){
+ de = list_entry(p, struct direntry, e_list);
+ if(!strcmp(file, de->e_name)){
+ TRACE("file found");
+
+ memcpy(fattr, &de->e_attr, sizeof(struct lufs_fattr));
+ if(link){
+ if(de->e_link){
+ if(snprintf(link, buflen, "%s", de->e_link) >= buflen){
+ WARN("link too long!");
+ link[buflen - 1] =0;
+ }
+ }else{
+ link[0] = 0;
+ }
+ }
+
+ res = 0;
+ goto out;
+ }
+ }
+
+ TRACE("file not found!");
+
+ out:
+ pthread_mutex_unlock(&cache->lock);
+ return res;
+}
+
+static void
+shrink(struct dir_cache *cache, int hsh){
+ struct directory *dir;
+
+ TRACE("shrinking bucket %u, len=%u", hsh, cache->lengths[hsh]);
+
+ if(list_empty(&cache->buckets[hsh]))
+ return;
+
+ dir = list_entry(cache->buckets[hsh].prev, struct directory, d_list);
+
+ TRACE("deleting dir %s", dir->d_name);
+
+ delete_dir(dir);
+ cache->lengths[hsh]--;
+}
+
+static void
+check_dir(struct directory *d){
+ struct list_head *p, *tmp;
+ struct direntry *e;
+ struct lufs_fattr dummy;
+ int dot = 0, dotdot = 0;
+
+ memset(&dummy, 0, sizeof(struct lufs_fattr));
+ dummy.f_nlink = 1;
+ dummy.f_uid = dummy.f_gid = 1;
+ dummy.f_mode = S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP;
+ dummy.f_mtime = dummy.f_atime = dummy.f_ctime = time(NULL);
+ dummy.f_size = 512;
+
+ do{
+ list_for_each_safe(p, tmp, &d->d_entries){
+ e = list_entry(p, struct direntry, e_list);
+
+ if(!strcmp(e->e_name, ".")){
+ TRACE("'.' entry found");
+ list_del(&e->e_list);
+ list_add(&e->e_list, &d->d_entries);
+ dot = 1;
+ continue;
+ }
+
+ if(!strcmp(e->e_name, "..")){
+ TRACE("'..' entry found");
+ list_del(&e->e_list);
+ if(!dot)
+ list_add(&e->e_list, &d->d_entries);
+ else
+ list_add(&e->e_list, d->d_entries.next);
+
+ dotdot = 1;
+ }
+ }
+
+ if(!dot)
+ lu_cache_add2dir(d, ".", NULL, &dummy);
+
+ if(!dotdot)
+ lu_cache_add2dir(d, "..", NULL, &dummy);
+
+ }while((!dot) || (!dotdot));
+
+}
+
+void
+lu_cache_add_dir(struct dir_cache *cache, struct directory *d){
+ struct directory *dir;
+ int hsh;
+
+ hsh = hash(d->d_name);
+
+ TRACE("adding dir %s to bucket %i", d->d_name, hsh);
+
+ check_dir(d);
+
+ pthread_mutex_lock(&cache->lock);
+
+ if((dir = search(cache, d->d_name))){
+ TRACE("directory already in cache, deleting...");
+ delete_dir(dir);
+ cache->lengths[hsh]--;
+ }
+
+ d->d_stamp = time(NULL);
+
+ list_add(&d->d_list, &cache->buckets[hsh]);
+ cache->lengths[hsh]++;
+
+ while(cache->lengths[hsh] > cache->entries)
+ shrink(cache, hsh);
+
+ pthread_mutex_unlock(&cache->lock);
+
+ TRACE("out");
+}
+
+int lu_cache_readdir(struct dir_cache *cache, char *dir,
+ fuse_dirh_t h, fuse_dirfil_t filler)
+{
+ struct directory *d;
+ struct direntry *de;
+ struct list_head *p;
+ int res = -1;
+
+ TRACE("reading directory %s", dir);
+
+ pthread_mutex_lock(&cache->lock);
+
+ if(!(d = search(cache, dir)))
+ goto out;
+
+ list_for_each(p, &d->d_entries){
+ de = list_entry(p, struct direntry, e_list);
+ filler(h, de->e_name, 0);
+ }
+
+ d->d_stamp = time(NULL);
+
+ res = 0;
+
+ out:
+ pthread_mutex_unlock(&cache->lock);
+ TRACE("out");
+ return res;
+}
+
+int
+lu_cache_lookup_file(struct dir_cache *cache, char *file, struct lufs_fattr *fattr, char *link, int buflen){
+ int res;
+
+ char *sep, *dir;
+
+ if(!(sep = strrchr(file, '/'))){
+ WARN("separator not present!");
+ return -1;
+ }
+
+ *sep = 0;
+
+ if(sep == file)
+ dir = root_dir;
+ else
+ dir = file;
+
+ if(*(sep+1))
+ file = sep + 1;
+ else
+ file = current_dir;
+
+ TRACE("dir: %s, file: %s", dir, file);
+
+ res = lu_cache_lookup(cache, dir, file, fattr, link, buflen);
+ *sep = '/';
+
+ return res;
+}
+
+int
+lu_cache_invalidate(struct dir_cache *cache, char *file){
+ struct directory *d;
+ char *sep, *dir;
+
+ if(!(sep = strrchr(file, '/'))){
+ WARN("separator not present!");
+ return -1;
+ }
+
+ *sep = 0;
+
+ if(sep == file)
+ dir = root_dir;
+ else
+ dir = file;
+
+ TRACE("invalidating dir %s", dir);
+
+ pthread_mutex_lock(&cache->lock);
+
+ if(!(d = search(cache, dir))){
+ *sep = '/';
+ pthread_mutex_unlock(&cache->lock);
+ return -1;
+ }
+
+ d->d_stamp = 0;
+
+ pthread_mutex_unlock(&cache->lock);
+ *sep = '/';
+
+ return 0;
+}
+
+struct directory*
+lu_cache_mkdir(char *dir){
+ struct directory *res;
+
+ TRACE("create dir %s", dir);
+
+ if(!(res = malloc(sizeof(struct directory)))){
+ WARN("out of mem!");
+ return NULL;
+ }
+
+ memset(res, 0, sizeof(struct directory));
+
+ if(!(res->d_name = malloc(strlen(dir) + 1))){
+ WARN("out of mem!");
+ free(res);
+ return NULL;
+ }
+
+ INIT_LIST_HEAD(&res->d_entries);
+ res->d_stamp = time(NULL);
+ strcpy(res->d_name, dir);
+
+ return res;
+}
+
+int
+lu_cache_add2dir(struct directory *d, char *fname, char *link, struct lufs_fattr *fattr){
+ struct direntry *de;
+
+ TRACE("adding %s->%s to %s", fname, link, d->d_name);
+
+ if(!(de = malloc(sizeof(struct direntry))))
+ goto fail;
+
+
+ if(!(de->e_name = malloc(strlen(fname) + 1)))
+ goto fail_de;
+
+
+ if(link)
+ de->e_link = malloc(strlen(link) + 1);
+ else
+ de->e_link = malloc(2);
+
+ if(!de->e_link)
+ goto fail_ename;
+
+ memcpy(&de->e_attr, fattr, sizeof(struct lufs_fattr));
+ strcpy(de->e_name, fname);
+ if(link)
+ strcpy(de->e_link, link);
+ else
+ strcpy(de->e_link, "");
+
+ list_add_tail(&de->e_list, &d->d_entries);
+
+ return 0;
+
+ fail_ename:
+ free(de->e_name);
+ fail_de:
+ free(de);
+ fail:
+ WARN("out of mem!");
+ return -1;
+}
+
+void
+lu_cache_killdir(struct directory *d){
+ struct list_head *p, *tmp;
+ struct direntry *de;
+
+ TRACE("in");
+
+ list_for_each_safe(p, tmp, &d->d_entries){
+ de = list_entry(p, struct direntry, e_list);
+ list_del(&de->e_list);
+ free(de->e_name);
+ if(de->e_link)
+ free(de->e_link);
+ free(de);
+ }
+
+ free(d->d_name);
+ free(d);
+
+}