/* * dircache.c * Copyright (C) 2002 Florin Malita * * 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 #include #include #include #include #include #include #include "list.h" #include "dircache.h" static char root_dir[]="/"; static char current_dir[]="."; static unsigned long hash(char *name){ unsigned long res = 0; 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 >= 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, int offset, char *buf, int buflen){ struct directory *d; struct direntry *de; struct list_head *p; int slen, res = -1; unsigned len = 0, off = 0; TRACE("reading directory %s", dir); pthread_mutex_lock(&cache->lock); if(!(d = search(cache, dir))) goto out; buf[0] = 0; list_for_each(p, &d->d_entries){ if(off >= offset){ de = list_entry(p, struct direntry, e_list); slen = strlen(de->e_name); if(len + slen + 2 >= buflen){ TRACE("buffer filled up"); break; } strcat(buf, de->e_name); strcat(buf, "\n"); len += slen + 1; } off++; } d->d_stamp = time(NULL); res = len; 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); }