diff options
Diffstat (limited to 'lufis/dircache.c')
-rw-r--r-- | lufis/dircache.c | 474 |
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); + +} |