From b82f9ba7469be58b0a0bfe5945dbeecc808491e4 Mon Sep 17 00:00:00 2001 From: Erez Zadok Date: Mon, 4 Jan 2010 20:45:06 -0500 Subject: [PATCH] Wrapfs: lookup-related functions Main lookup function, nameidata helpers, and stacking-interposition functions. Signed-off-by: Erez Zadok --- fs/wrapfs/lookup.c | 369 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 fs/wrapfs/lookup.c diff --git a/fs/wrapfs/lookup.c b/fs/wrapfs/lookup.c new file mode 100644 index 000000000000..480d0cda556a --- /dev/null +++ b/fs/wrapfs/lookup.c @@ -0,0 +1,369 @@ +/* + * Copyright (c) 1998-2010 Erez Zadok + * Copyright (c) 2009 Shrikar Archak + * Copyright (c) 2003-2010 Stony Brook University + * Copyright (c) 2003-2010 The Research Foundation of SUNY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "wrapfs.h" + +/* The dentry cache is just so we have properly sized dentries */ +static struct kmem_cache *wrapfs_dentry_cachep; + +int wrapfs_init_dentry_cache(void) +{ + wrapfs_dentry_cachep = + kmem_cache_create("wrapfs_dentry", + sizeof(struct wrapfs_dentry_info), + 0, SLAB_RECLAIM_ACCOUNT, NULL); + + return wrapfs_dentry_cachep ? 0 : -ENOMEM; +} + +void wrapfs_destroy_dentry_cache(void) +{ + if (wrapfs_dentry_cachep) + kmem_cache_destroy(wrapfs_dentry_cachep); +} + +void free_dentry_private_data(struct dentry *dentry) +{ + if (!dentry || !dentry->d_fsdata) + return; + kmem_cache_free(wrapfs_dentry_cachep, dentry->d_fsdata); + dentry->d_fsdata = NULL; +} + +/* allocate new dentry private data */ +int new_dentry_private_data(struct dentry *dentry) +{ + struct wrapfs_dentry_info *info = WRAPFS_D(dentry); + + info = kmem_cache_alloc(wrapfs_dentry_cachep, GFP_ATOMIC); + if (!info) + return -ENOMEM; + + spin_lock_init(&info->lock); + dentry->d_fsdata = info; + + return 0; +} + +/* + * Initialize a nameidata structure (the intent part) we can pass to a lower + * file system. Returns 0 on success or -error (only -ENOMEM possible). + * Inside that nd structure, this function may also return an allocated + * struct file (for open intents). The caller, when done with this nd, must + * kfree the intent file (using release_lower_nd). + * + * XXX: this code, and the callers of this code, should be redone using + * vfs_path_lookup() when (1) the nameidata structure is refactored into a + * separate intent-structure, and (2) open_namei() is broken into a VFS-only + * function and a method that other file systems can call. + */ +int init_lower_nd(struct nameidata *nd, unsigned int flags) +{ + int err = 0; +#ifdef ALLOC_LOWER_ND_FILE + /* + * XXX: one day we may need to have the lower file system return an + * open file for us (esp. for nfs4). + */ + struct file *file; +#endif /* ALLOC_LOWER_ND_FILE */ + + memset(nd, 0, sizeof(struct nameidata)); + if (!flags) + return err; + + switch (flags) { + case LOOKUP_CREATE: + nd->intent.open.flags |= O_CREAT; + /* fall through: shared code for create/open cases */ + case LOOKUP_OPEN: + nd->flags = flags; + nd->intent.open.flags |= (FMODE_READ | FMODE_WRITE); +#ifdef ALLOC_LOWER_ND_FILE + file = kzalloc(sizeof(struct file), GFP_KERNEL); + if (!file) { + err = -ENOMEM; + break; /* exit switch statement and thus return */ + } + nd->intent.open.file = file; +#endif /* ALLOC_LOWER_ND_FILE */ + break; + default: + /* + * We should never get here, for now. + * We can add new cases here later on. + */ + pr_debug("wrapfs: unknown nameidata flag 0x%x\n", flags); + BUG(); + break; + } + + return err; +} + +void release_lower_nd(struct nameidata *nd, int err) +{ + if (!nd->intent.open.file) + return; + else if (!err) + release_open_intent(nd); +#ifdef ALLOC_LOWER_ND_FILE + kfree(nd->intent.open.file); +#endif /* ALLOC_LOWER_ND_FILE */ +} + +static int wrapfs_inode_test(struct inode *inode, void *candidate_lower_inode) +{ + struct inode *current_lower_inode = wrapfs_lower_inode(inode); + if (current_lower_inode == (struct inode *)candidate_lower_inode) + return 1; /* found a match */ + else + return 0; /* no match */ +} + +static int wrapfs_inode_set(struct inode *inode, void *lower_inode) +{ + /* we do actual inode initialization in wrapfs_iget */ + return 0; +} + +static struct inode *wrapfs_iget(struct super_block *sb, + struct inode *lower_inode) +{ + struct wrapfs_inode_info *info; + struct inode *inode; /* the new inode to return */ + int err; + + inode = iget5_locked(sb, /* our superblock */ + /* + * hashval: we use inode number, but we can also use + * "(unsigned long)lower_inode" instead. + */ + lower_inode->i_ino, /* hashval */ + wrapfs_inode_test, /* inode comparison function */ + wrapfs_inode_set, /* inode init function */ + lower_inode); /* data passed to test+set fxns */ + if (!inode) { + err = -EACCES; + iput(lower_inode); + return ERR_PTR(err); + } + /* if found a cached inode, then just return it */ + if (!(inode->i_state & I_NEW)) + return inode; + + /* initialize new inode */ + info = WRAPFS_I(inode); + + inode->i_ino = lower_inode->i_ino; + if (!igrab(lower_inode)) { + err = -ESTALE; + return ERR_PTR(err); + } + wrapfs_set_lower_inode(inode, lower_inode); + + inode->i_version++; + + /* use different set of inode ops for symlinks & directories */ + if (S_ISDIR(lower_inode->i_mode)) + inode->i_op = &wrapfs_dir_iops; + else if (S_ISLNK(lower_inode->i_mode)) + inode->i_op = &wrapfs_symlink_iops; + else + inode->i_op = &wrapfs_main_iops; + + /* use different set of file ops for directories */ + if (S_ISDIR(lower_inode->i_mode)) + inode->i_fop = &wrapfs_dir_fops; + else + inode->i_fop = &wrapfs_main_fops; + + inode->i_mapping->a_ops = &wrapfs_aops; + + inode->i_atime.tv_sec = 0; + inode->i_atime.tv_nsec = 0; + inode->i_mtime.tv_sec = 0; + inode->i_mtime.tv_nsec = 0; + inode->i_ctime.tv_sec = 0; + inode->i_ctime.tv_nsec = 0; + + /* properly initialize special inodes */ + if (S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) || + S_ISFIFO(lower_inode->i_mode) || S_ISSOCK(lower_inode->i_mode)) + init_special_inode(inode, lower_inode->i_mode, + lower_inode->i_rdev); + + /* all well, copy inode attributes */ + fsstack_copy_attr_all(inode, lower_inode); + fsstack_copy_inode_size(inode, lower_inode); + + unlock_new_inode(inode); + return inode; +} + +/* + * Connect a wrapfs inode dentry/inode with several lower ones. This is + * the classic stackable file system "vnode interposition" action. + * + * @dentry: wrapfs's dentry which interposes on lower one + * @sb: wrapfs's super_block + * @lower_path: the lower path (caller does path_get/put) + */ +int wrapfs_interpose(struct dentry *dentry, struct super_block *sb, + struct path *lower_path) +{ + int err = 0; + struct inode *inode; + struct inode *lower_inode; + struct super_block *lower_sb; + + lower_inode = lower_path->dentry->d_inode; + lower_sb = wrapfs_lower_super(sb); + + /* check that the lower file system didn't cross a mount point */ + if (lower_inode->i_sb != lower_sb) { + err = -EXDEV; + goto out; + } + + /* + * We allocate our new inode below by calling wrapfs_iget, + * which will initialize some of the new inode's fields + */ + + /* inherit lower inode number for wrapfs's inode */ + inode = wrapfs_iget(sb, lower_inode); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto out; + } + + d_add(dentry, inode); + +out: + return err; +} + +/* + * Main driver function for wrapfs's lookup. + * + * Returns: NULL (ok), ERR_PTR if an error occurred. + */ +static struct dentry *__wrapfs_lookup(struct dentry *dentry, int flag, + struct path *lower_parent_path) +{ + int err = 0; + struct vfsmount *lower_dir_mnt; + struct dentry *lower_dir_dentry = NULL; + const char *name; + int namelen; + struct nameidata lower_nd; + struct path lower_path; + + /* must initialize dentry operations */ + dentry->d_op = &wrapfs_dops; + + if (IS_ROOT(dentry)) + goto out; + + name = dentry->d_name.name; + namelen = dentry->d_name.len; + + /* Now start the actual lookup procedure. */ + + lower_dir_dentry = lower_parent_path->dentry; + BUG_ON(!lower_dir_dentry || !lower_dir_dentry->d_inode); + BUG_ON(!S_ISDIR(lower_dir_dentry->d_inode->i_mode)); + + /* Now do regular lookup; lookup @name */ + lower_dir_mnt = lower_parent_path->mnt; + + /* Use vfs_path_lookup to check if the dentry exists or no */ + err = vfs_path_lookup(lower_dir_dentry, lower_dir_mnt, name, 0, + &lower_nd); + + /* no error: handle positive dentries */ + if (!err) { + wrapfs_set_lower_path(dentry, &lower_nd.path); + err = wrapfs_interpose(dentry, dentry->d_sb, &lower_nd.path); + if (err) /* path_put underlying path on error */ + wrapfs_put_reset_lower_path(dentry); + goto out; + } + + /* + * We don't consider ENOENT an error, and we want to return a + * negative dentry (ala lookup_one_len). As we know there was no + * inode for this name before (-ENOENT), then it's safe to call + * lookup_one_len (which doesn't take a vfsmount). + */ + if (err == -ENOENT) { + mutex_lock(&lower_dir_dentry->d_inode->i_mutex); + lower_path.dentry = lookup_one_len(name, lower_dir_dentry, + strlen(name)); + mutex_unlock(&lower_dir_dentry->d_inode->i_mutex); + lower_path.mnt = mntget(lower_dir_mnt); + wrapfs_set_lower_path(dentry, &lower_path); + + /* + * If the intent is to create a file, then create a dummy + * inode and associate with the dentry. + */ + if (flag & (LOOKUP_CREATE|LOOKUP_RENAME_TARGET)) + err = 0; + + goto out; + } + + /* if got here: real errors */ + + /* no need to cleanup, just reset */ + wrapfs_reset_lower_path(dentry); /* XXX: still needed? */ + +out: + return ERR_PTR(err); +} + +struct dentry *wrapfs_lookup(struct inode *dir, struct dentry *dentry, + struct nameidata *nd) +{ + struct dentry *ret, *parent; + struct path lower_parent_path; + int err = 0; + + BUG_ON(!nd); + parent = dget_parent(dentry); + + wrapfs_get_lower_path(parent, &lower_parent_path); + + /* allocate dentry private data. We free it in ->d_release */ + err = new_dentry_private_data(dentry); + if (err) { + ret = ERR_PTR(err); + goto out; + } + ret = __wrapfs_lookup(dentry, nd->flags, &lower_parent_path); + if (IS_ERR(ret)) + goto out; + if (ret) + dentry = ret; + if (dentry->d_inode) + fsstack_copy_attr_times(dentry->d_inode, + wrapfs_lower_inode(dentry->d_inode)); + /* update parent directory's atime */ + fsstack_copy_attr_atime(parent->d_inode, + wrapfs_lower_inode(parent->d_inode)); + +out: + wrapfs_put_lower_path(parent, &lower_parent_path); + dput(parent); + return ret; +} -- 2.43.0