X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=fs%2Fcachefiles%2Fcf-namei.c;fp=fs%2Fcachefiles%2Fcf-namei.c;h=20db88ada0c557793e51c249c9183946d8f40e1d;hb=f05f9504c50ed069377d37f02f22e7a16b5921de;hp=0000000000000000000000000000000000000000;hpb=16c70f8c1b54b61c3b951b6fb220df250fe09b32;p=linux-2.6.git diff --git a/fs/cachefiles/cf-namei.c b/fs/cachefiles/cf-namei.c new file mode 100644 index 000000000..20db88ada --- /dev/null +++ b/fs/cachefiles/cf-namei.c @@ -0,0 +1,825 @@ +/* cf-namei.c: CacheFiles path walking and related routines + * + * Copyright (C) 2006 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +/*****************************************************************************/ +/* + * record the fact that an object is now active + */ +static void cachefiles_mark_object_active(struct cachefiles_cache *cache, + struct cachefiles_object *object) +{ + struct cachefiles_object *xobject; + struct rb_node **_p, *_parent = NULL; + struct dentry *dentry; + + write_lock(&cache->active_lock); + + dentry = object->dentry; + _p = &cache->active_nodes.rb_node; + while (*_p) { + _parent = *_p; + xobject = rb_entry(_parent, + struct cachefiles_object, active_node); + + if (xobject->dentry > dentry) + _p = &(*_p)->rb_left; + else if (xobject->dentry < dentry) + _p = &(*_p)->rb_right; + else + BUG(); /* uh oh... this dentry shouldn't be here */ + } + + rb_link_node(&object->active_node, _parent, _p); + rb_insert_color(&object->active_node, &cache->active_nodes); + + write_unlock(&cache->active_lock); +} + +/*****************************************************************************/ +/* + * delete an object representation from the cache + * - file backed objects are unlinked + * - directory backed objects are stuffed into the graveyard for userspace to + * delete + * - unlocks the directory mutex + */ +static int cachefiles_bury_object(struct cachefiles_cache *cache, + struct dentry *dir, + struct dentry *rep) +{ + struct dentry *grave, *alt, *trap; + struct qstr name; + const char *old_name; + char nbuffer[8 + 8 + 1]; + int ret; + + _enter(",'%*.*s','%*.*s'", + dir->d_name.len, dir->d_name.len, dir->d_name.name, + rep->d_name.len, rep->d_name.len, rep->d_name.name); + + /* non-directories can just be unlinked */ + if (!S_ISDIR(rep->d_inode->i_mode)) { + _debug("unlink stale object"); + ret = dir->d_inode->i_op->unlink(dir->d_inode, rep); + + mutex_unlock(&dir->d_inode->i_mutex); + + if (ret == 0) { + _debug("d_delete"); + d_delete(rep); + } else if (ret == -EIO) { + cachefiles_io_error(cache, "Unlink failed"); + } + + _leave(" = %d", ret); + return ret; + } + + /* directories have to be moved to the graveyard */ + _debug("move stale object to graveyard"); + mutex_unlock(&dir->d_inode->i_mutex); + +try_again: + /* first step is to make up a grave dentry in the graveyard */ + sprintf(nbuffer, "%08x%08x", + (uint32_t) xtime.tv_sec, + (uint32_t) atomic_inc_return(&cache->gravecounter)); + + name.name = nbuffer; + name.len = strlen(name.name); + + /* hash the name */ + name.hash = full_name_hash(name.name, name.len); + + if (dir->d_op && dir->d_op->d_hash) { + ret = dir->d_op->d_hash(dir, &name); + if (ret < 0) { + if (ret == -EIO) + cachefiles_io_error(cache, "Hash failed"); + + _leave(" = %d", ret); + return ret; + } + } + + /* do the multiway lock magic */ + trap = lock_rename(cache->graveyard, dir); + + /* do some checks before getting the grave dentry */ + if (rep->d_parent != dir) { + /* the entry was probably culled when we dropped the parent dir + * lock */ + unlock_rename(cache->graveyard, dir); + _leave(" = 0 [culled?]"); + return 0; + } + + if (!S_ISDIR(cache->graveyard->d_inode->i_mode)) { + unlock_rename(cache->graveyard, dir); + cachefiles_io_error(cache, "Graveyard no longer a directory"); + return -EIO; + } + + if (trap == rep) { + unlock_rename(cache->graveyard, dir); + cachefiles_io_error(cache, "May not make directory loop"); + return -EIO; + } + + if (d_mountpoint(rep)) { + unlock_rename(cache->graveyard, dir); + cachefiles_io_error(cache, "Mountpoint in cache"); + return -EIO; + } + + /* see if there's a dentry already there for this name */ + grave = d_lookup(cache->graveyard, &name); + if (!grave) { + _debug("not found"); + + grave = d_alloc(cache->graveyard, &name); + if (!grave) { + unlock_rename(cache->graveyard, dir); + _leave(" = -ENOMEM"); + return -ENOMEM; + } + + alt = cache->graveyard->d_inode->i_op->lookup( + cache->graveyard->d_inode, grave, NULL); + if (IS_ERR(alt)) { + unlock_rename(cache->graveyard, dir); + dput(grave); + + if (PTR_ERR(alt) == -ENOMEM) { + _leave(" = -ENOMEM"); + return -ENOMEM; + } + + cachefiles_io_error(cache, "Lookup error %ld", + PTR_ERR(alt)); + return -EIO; + } + + if (alt) { + dput(grave); + grave = alt; + } + } + + if (grave->d_inode) { + unlock_rename(cache->graveyard, dir); + dput(grave); + grave = NULL; + cond_resched(); + goto try_again; + } + + if (d_mountpoint(grave)) { + unlock_rename(cache->graveyard, dir); + dput(grave); + cachefiles_io_error(cache, "Mountpoint in graveyard"); + return -EIO; + } + + /* target should not be an ancestor of source */ + if (trap == grave) { + unlock_rename(cache->graveyard, dir); + dput(grave); + cachefiles_io_error(cache, "May not make directory loop"); + return -EIO; + } + + /* attempt the rename */ + DQUOT_INIT(dir->d_inode); + DQUOT_INIT(cache->graveyard->d_inode); + + old_name = fsnotify_oldname_init(rep->d_name.name); + + ret = dir->d_inode->i_op->rename(dir->d_inode, rep, + cache->graveyard->d_inode, grave); + + if (ret == 0) { + d_move(rep, grave); + fsnotify_move(dir->d_inode, cache->graveyard->d_inode, + old_name, rep->d_name.name, 1, + grave->d_inode, rep->d_inode); + } else if (ret != -ENOMEM) { + cachefiles_io_error(cache, "Rename failed with error %d", ret); + } + + fsnotify_oldname_free(old_name); + + unlock_rename(cache->graveyard, dir); + dput(grave); + _leave(" = 0"); + return 0; +} + +/*****************************************************************************/ +/* + * delete an object representation from the cache + */ +int cachefiles_delete_object(struct cachefiles_cache *cache, + struct cachefiles_object *object) +{ + struct dentry *dir; + int ret; + + _enter(",{%p}", object->dentry); + + ASSERT(object->dentry); + ASSERT(object->dentry->d_inode); + ASSERT(object->dentry->d_parent); + + dir = dget_parent(object->dentry); + + mutex_lock(&dir->d_inode->i_mutex); + ret = cachefiles_bury_object(cache, dir, object->dentry); + + dput(dir); + _leave(" = %d", ret); + return ret; +} + +/*****************************************************************************/ +/* + * walk from the parent object to the child object through the backing + * filesystem, creating directories as we go + */ +int cachefiles_walk_to_object(struct cachefiles_object *parent, + struct cachefiles_object *object, + char *key, + struct cachefiles_xattr *auxdata) +{ + struct cachefiles_cache *cache; + struct dentry *dir, *next = NULL, *new; + struct qstr name; + uid_t fsuid; + gid_t fsgid; + int ret; + + _enter("{%p}", parent->dentry); + + cache = container_of(parent->fscache.cache, + struct cachefiles_cache, cache); + + ASSERT(parent->dentry); + ASSERT(parent->dentry->d_inode); + + if (!(S_ISDIR(parent->dentry->d_inode->i_mode))) { + // TODO: convert file to dir + _leave("looking up in none directory"); + return -ENOBUFS; + } + + fsuid = current->fsuid; + fsgid = current->fsgid; + current->fsuid = 0; + current->fsgid = 0; + + dir = dget(parent->dentry); + +advance: + /* attempt to transit the first directory component */ + name.name = key; + key = strchr(key, '/'); + if (key) { + name.len = key - (char *) name.name; + *key++ = 0; + } else { + name.len = strlen(name.name); + } + + /* hash the name */ + name.hash = full_name_hash(name.name, name.len); + + if (dir->d_op && dir->d_op->d_hash) { + ret = dir->d_op->d_hash(dir, &name); + if (ret < 0) { + cachefiles_io_error(cache, "Hash failed"); + goto error_out2; + } + } + +lookup_again: + /* search the current directory for the element name */ + _debug("lookup '%s' %x", name.name, name.hash); + + mutex_lock(&dir->d_inode->i_mutex); + + next = d_lookup(dir, &name); + if (!next) { + _debug("not found"); + + new = d_alloc(dir, &name); + if (!new) + goto nomem_d_alloc; + + ASSERT(dir->d_inode->i_op); + ASSERT(dir->d_inode->i_op->lookup); + + next = dir->d_inode->i_op->lookup(dir->d_inode, new, NULL); + if (IS_ERR(next)) + goto lookup_error; + + if (!next) + next = new; + else + dput(new); + + if (next->d_inode) { + ret = -EPERM; + if (!next->d_inode->i_op || + !next->d_inode->i_op->setxattr || + !next->d_inode->i_op->getxattr || + !next->d_inode->i_op->removexattr) + goto error; + + if (key && (!next->d_inode->i_op->lookup || + !next->d_inode->i_op->mkdir || + !next->d_inode->i_op->create || + !next->d_inode->i_op->rename || + !next->d_inode->i_op->rmdir || + !next->d_inode->i_op->unlink)) + goto error; + } + } + + _debug("next -> %p %s", next, next->d_inode ? "positive" : "negative"); + + if (!key) + object->new = !next->d_inode; + + /* we need to create the object if it's negative */ + if (key || object->type == FSCACHE_COOKIE_TYPE_INDEX) { + /* index objects and intervening tree levels must be subdirs */ + if (!next->d_inode) { + DQUOT_INIT(dir->d_inode); + ret = dir->d_inode->i_op->mkdir(dir->d_inode, next, 0); + if (ret < 0) + goto create_error; + + ASSERT(next->d_inode); + + fsnotify_mkdir(dir->d_inode, next); + + _debug("mkdir -> %p{%p{ino=%lu}}", + next, next->d_inode, next->d_inode->i_ino); + + } else if (!S_ISDIR(next->d_inode->i_mode)) { + kerror("inode %lu is not a directory", + next->d_inode->i_ino); + ret = -ENOBUFS; + goto error; + } + + } else { + /* non-index objects start out life as files */ + if (!next->d_inode) { + DQUOT_INIT(dir->d_inode); + ret = dir->d_inode->i_op->create(dir->d_inode, next, + S_IFREG, NULL); + if (ret < 0) + goto create_error; + + ASSERT(next->d_inode); + + fsnotify_create(dir->d_inode, next); + + _debug("create -> %p{%p{ino=%lu}}", + next, next->d_inode, next->d_inode->i_ino); + + } else if (!S_ISDIR(next->d_inode->i_mode) && + !S_ISREG(next->d_inode->i_mode) + ) { + kerror("inode %lu is not a file or directory", + next->d_inode->i_ino); + ret = -ENOBUFS; + goto error; + } + } + + /* process the next component */ + if (key) { + _debug("advance"); + mutex_unlock(&dir->d_inode->i_mutex); + dput(dir); + dir = next; + next = NULL; + goto advance; + } + + /* we've found the object we were looking for */ + object->dentry = next; + + /* if we've found that the terminal object exists, then we need to + * check its attributes and delete it if it's out of date */ + if (!object->new) { + _debug("validate '%*.*s'", + next->d_name.len, next->d_name.len, next->d_name.name); + + ret = cachefiles_check_object_xattr(object, auxdata); + if (ret == -ESTALE) { + /* delete the object (the deleter drops the directory + * mutex) */ + object->dentry = NULL; + + ret = cachefiles_bury_object(cache, dir, next); + dput(next); + next = NULL; + + if (ret < 0) + goto delete_error; + + _debug("redo lookup"); + goto lookup_again; + } + } + + /* note that we're now using this object */ + cachefiles_mark_object_active(cache, object); + + mutex_unlock(&dir->d_inode->i_mutex); + dput(dir); + dir = NULL; + + if (object->new) { + /* attach data to a newly constructed terminal object */ + ret = cachefiles_set_object_xattr(object, auxdata); + if (ret < 0) + goto check_error; + } else { + /* always update the atime on an object we've just looked up + * (this is used to keep track of culling, and atimes are only + * updated by read, write and readdir but not lookup or + * open) */ + touch_atime(cache->mnt, next); + } + + /* open a file interface onto a data file */ + if (object->type != FSCACHE_COOKIE_TYPE_INDEX) { + if (S_ISREG(object->dentry->d_inode->i_mode)) { + const struct address_space_operations *aops; + + ret = -EPERM; + aops = object->dentry->d_inode->i_mapping->a_ops; + if (!aops->bmap || + !aops->prepare_write || + !aops->commit_write) + goto check_error; + + object->backer = object->dentry; + } else { + BUG(); // TODO: open file in data-class subdir + } + } + + current->fsuid = fsuid; + current->fsgid = fsgid; + object->new = 0; + + _leave(" = 0 [%lu]", object->dentry->d_inode->i_ino); + return 0; + +create_error: + if (ret == -EIO) + cachefiles_io_error(cache, "create/mkdir failed"); + goto error; + +check_error: + write_lock(&cache->active_lock); + rb_erase(&object->active_node, &cache->active_nodes); + write_unlock(&cache->active_lock); + + dput(object->dentry); + object->dentry = NULL; + goto error_out; + +delete_error: + _debug("delete error %d", ret); + goto error_out2; + +lookup_error: + _debug("lookup error %ld", PTR_ERR(next)); + dput(new); + ret = PTR_ERR(next); + if (ret == -EIO) + cachefiles_io_error(cache, "Lookup failed"); + next = NULL; + goto error; + +nomem_d_alloc: + ret = -ENOMEM; +error: + mutex_unlock(&dir->d_inode->i_mutex); + dput(next); +error_out2: + dput(dir); +error_out: + current->fsuid = fsuid; + current->fsgid = fsgid; + + _leave(" = ret"); + return ret; +} + +/*****************************************************************************/ +/* + * get a subdirectory + */ +struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache, + struct dentry *dir, + const char *dirname) +{ + struct dentry *subdir, *new; + struct qstr name; + uid_t fsuid; + gid_t fsgid; + int ret; + + _enter(""); + + /* set up the name */ + name.name = dirname; + name.len = strlen(dirname); + name.hash = full_name_hash(name.name, name.len); + + if (dir->d_op && dir->d_op->d_hash) { + ret = dir->d_op->d_hash(dir, &name); + if (ret < 0) { + if (ret == -EIO) + kerror("Hash failed"); + _leave(" = %d", ret); + return ERR_PTR(ret); + } + } + + /* search the current directory for the element name */ + _debug("lookup '%s' %x", name.name, name.hash); + + fsuid = current->fsuid; + fsgid = current->fsgid; + current->fsuid = 0; + current->fsgid = 0; + + mutex_lock(&dir->d_inode->i_mutex); + + subdir = d_lookup(dir, &name); + if (!subdir) { + _debug("not found"); + + new = d_alloc(dir, &name); + if (!new) + goto nomem_d_alloc; + + subdir = dir->d_inode->i_op->lookup(dir->d_inode, new, NULL); + if (IS_ERR(subdir)) + goto lookup_error; + + if (!subdir) + subdir = new; + else + dput(new); + } + + _debug("subdir -> %p %s", + subdir, subdir->d_inode ? "positive" : "negative"); + + /* we need to create the subdir if it doesn't exist yet */ + if (!subdir->d_inode) { + DQUOT_INIT(dir->d_inode); + ret = dir->d_inode->i_op->mkdir(dir->d_inode, subdir, 0700); + if (ret < 0) + goto mkdir_error; + + ASSERT(subdir->d_inode); + + fsnotify_mkdir(dir->d_inode, subdir); + + _debug("mkdir -> %p{%p{ino=%lu}}", + subdir, + subdir->d_inode, + subdir->d_inode->i_ino); + } + + mutex_unlock(&dir->d_inode->i_mutex); + + current->fsuid = fsuid; + current->fsgid = fsgid; + + /* we need to make sure the subdir is a directory */ + ASSERT(subdir->d_inode); + + if (!S_ISDIR(subdir->d_inode->i_mode)) { + kerror("%s is not a directory", dirname); + ret = -EIO; + goto check_error; + } + + ret = -EPERM; + if (!subdir->d_inode->i_op || + !subdir->d_inode->i_op->setxattr || + !subdir->d_inode->i_op->getxattr || + !subdir->d_inode->i_op->lookup || + !subdir->d_inode->i_op->mkdir || + !subdir->d_inode->i_op->create || + !subdir->d_inode->i_op->rename || + !subdir->d_inode->i_op->rmdir || + !subdir->d_inode->i_op->unlink) + goto check_error; + + _leave(" = [%lu]", subdir->d_inode->i_ino); + return subdir; + +check_error: + dput(subdir); + _leave(" = %d [check]", ret); + return ERR_PTR(ret); + +mkdir_error: + mutex_unlock(&dir->d_inode->i_mutex); + kerror("mkdir %s failed with error %d", dirname, ret); + goto error_out; + +lookup_error: + mutex_unlock(&dir->d_inode->i_mutex); + dput(new); + ret = PTR_ERR(subdir); + kerror("Lookup %s failed with error %d", dirname, ret); + goto error_out; + +nomem_d_alloc: + mutex_unlock(&dir->d_inode->i_mutex); + ret = -ENOMEM; + goto error_out; + +error_out: + current->fsuid = fsuid; + current->fsgid = fsgid; + _leave(" = %d", ret); + return ERR_PTR(ret); +} + +/*****************************************************************************/ +/* + * cull an object if it's not in use + * - called only by cache manager daemon + */ +int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir, + char *filename) +{ + struct cachefiles_object *object; + struct rb_node *_n; + struct dentry *victim, *new; + struct qstr name; + int ret; + + _enter(",%*.*s/,%s", + dir->d_name.len, dir->d_name.len, dir->d_name.name, filename); + + /* set up the name */ + name.name = filename; + name.len = strlen(filename); + name.hash = full_name_hash(name.name, name.len); + + if (dir->d_op && dir->d_op->d_hash) { + ret = dir->d_op->d_hash(dir, &name); + if (ret < 0) { + if (ret == -EIO) + cachefiles_io_error(cache, "Hash failed"); + _leave(" = %d", ret); + return ret; + } + } + + /* look up the victim */ + mutex_lock(&dir->d_inode->i_mutex); + + victim = d_lookup(dir, &name); + if (!victim) { + _debug("not found"); + + new = d_alloc(dir, &name); + if (!new) + goto nomem_d_alloc; + + victim = dir->d_inode->i_op->lookup(dir->d_inode, new, NULL); + if (IS_ERR(victim)) + goto lookup_error; + + if (!victim) + victim = new; + else + dput(new); + } + + _debug("victim -> %p %s", + victim, victim->d_inode ? "positive" : "negative"); + + /* if the object is no longer there then we probably retired the object + * at the netfs's request whilst the cull was in progress + */ + if (!victim->d_inode) { + mutex_unlock(&dir->d_inode->i_mutex); + dput(victim); + _leave(" = -ENOENT [absent]"); + return -ENOENT; + } + + /* check to see if we're using this object */ + read_lock(&cache->active_lock); + + _n = cache->active_nodes.rb_node; + + while (_n) { + object = rb_entry(_n, struct cachefiles_object, active_node); + + if (object->dentry > victim) + _n = _n->rb_left; + else if (object->dentry < victim) + _n = _n->rb_right; + else + goto object_in_use; + } + + read_unlock(&cache->active_lock); + + /* okay... the victim is not being used so we can cull it + * - start by marking it as stale + */ + _debug("victim is cullable"); + + ret = cachefiles_remove_object_xattr(cache, victim); + if (ret < 0) + goto error_unlock; + + /* actually remove the victim (drops the dir mutex) */ + _debug("bury"); + + ret = cachefiles_bury_object(cache, dir, victim); + if (ret < 0) + goto error; + + dput(victim); + _leave(" = 0"); + return 0; + + +object_in_use: + read_unlock(&cache->active_lock); + mutex_unlock(&dir->d_inode->i_mutex); + dput(victim); + _leave(" = -EBUSY [in use]"); + return -EBUSY; + +nomem_d_alloc: + mutex_unlock(&dir->d_inode->i_mutex); + _leave(" = -ENOMEM"); + return -ENOMEM; + +lookup_error: + mutex_unlock(&dir->d_inode->i_mutex); + dput(new); + ret = PTR_ERR(victim); + if (ret == -EIO) + cachefiles_io_error(cache, "Lookup failed"); + goto choose_error; + +error_unlock: + mutex_unlock(&dir->d_inode->i_mutex); +error: + dput(victim); +choose_error: + if (ret == -ENOENT) { + /* file or dir now absent - probably retired by netfs */ + _leave(" = -ESTALE [absent]"); + return -ESTALE; + } + + if (ret != -ENOMEM) { + kerror("Internal error: %d", ret); + ret = -EIO; + } + + _leave(" = %d", ret); + return ret; +}