--- /dev/null
+/*
+ * 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;
+}