Wrapfs: lookup-related functions
authorErez Zadok <ezk@cs.sunysb.edu>
Tue, 5 Jan 2010 01:45:06 +0000 (20:45 -0500)
committerErez Zadok <ezk@cs.sunysb.edu>
Tue, 27 Dec 2016 19:04:34 +0000 (14:04 -0500)
Main lookup function, nameidata helpers, and stacking-interposition
functions.

Signed-off-by: Erez Zadok <ezk@cs.sunysb.edu>
fs/wrapfs/lookup.c [new file with mode: 0644]

diff --git a/fs/wrapfs/lookup.c b/fs/wrapfs/lookup.c
new file mode 100644 (file)
index 0000000..480d0cd
--- /dev/null
@@ -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;
+}