4.2.8-ckt8 -stable review patch.  If anyone has any objections, please let me 
know.

---8<------------------------------------------------------------

From: Miklos Szeredi <[email protected]>

commit d101a125954eae1d397adda94ca6319485a50493 upstream.

This series fixes bugs in nfs and ext4 due to 4bacc9c9234c ("overlayfs:
Make f_path always point to the overlay and f_inode to the underlay").

Regular files opened on overlayfs will result in the file being opened on
the underlying filesystem, while f_path points to the overlayfs
mount/dentry.

This confuses filesystems which get the dentry from struct file and assume
it's theirs.

Add a new helper, file_dentry() [*], to get the filesystem's own dentry
from the file.  This checks file->f_path.dentry->d_flags against
DCACHE_OP_REAL, and returns file->f_path.dentry if DCACHE_OP_REAL is not
set (this is the common, non-overlayfs case).

In the uncommon case it will call into overlayfs's ->d_real() to get the
underlying dentry, matching file_inode(file).

The reason we need to check against the inode is that if the file is copied
up while being open, d_real() would return the upper dentry, while the open
file comes from the lower dentry.

[*] If possible, it's better simply to use file_inode() instead.

Signed-off-by: Miklos Szeredi <[email protected]>
Signed-off-by: Theodore Ts'o <[email protected]>
Tested-by: Goldwyn Rodrigues <[email protected]>
Reviewed-by: Trond Myklebust <[email protected]>
Cc: David Howells <[email protected]>
Cc: Al Viro <[email protected]>
Cc: Daniel Axtens <[email protected]>
[ kamal: backport to 4.2-stable: context ]
Signed-off-by: Kamal Mostafa <[email protected]>
---
 fs/dcache.c            |  5 ++++-
 fs/overlayfs/super.c   | 33 +++++++++++++++++++++++++++++++++
 include/linux/dcache.h | 11 +++++++++++
 include/linux/fs.h     | 10 ++++++++++
 4 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/fs/dcache.c b/fs/dcache.c
index 403d24d..4f3d081 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1666,7 +1666,8 @@ void d_set_d_op(struct dentry *dentry, const struct 
dentry_operations *op)
                                DCACHE_OP_REVALIDATE    |
                                DCACHE_OP_WEAK_REVALIDATE       |
                                DCACHE_OP_DELETE        |
-                               DCACHE_OP_SELECT_INODE));
+                               DCACHE_OP_SELECT_INODE  |
+                               DCACHE_OP_REAL));
        dentry->d_op = op;
        if (!op)
                return;
@@ -1684,6 +1685,8 @@ void d_set_d_op(struct dentry *dentry, const struct 
dentry_operations *op)
                dentry->d_flags |= DCACHE_OP_PRUNE;
        if (op->d_select_inode)
                dentry->d_flags |= DCACHE_OP_SELECT_INODE;
+       if (op->d_real)
+               dentry->d_flags |= DCACHE_OP_REAL;
 
 }
 EXPORT_SYMBOL(d_set_d_op);
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 000b2ed..a1acc60 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -276,6 +276,37 @@ static void ovl_dentry_release(struct dentry *dentry)
        }
 }
 
+static struct dentry *ovl_d_real(struct dentry *dentry, struct inode *inode)
+{
+       struct dentry *real;
+
+       if (d_is_dir(dentry)) {
+               if (!inode || inode == d_inode(dentry))
+                       return dentry;
+               goto bug;
+       }
+
+       real = ovl_dentry_upper(dentry);
+       if (real && (!inode || inode == d_inode(real)))
+               return real;
+
+       real = ovl_dentry_lower(dentry);
+       if (!real)
+               goto bug;
+
+       if (!inode || inode == d_inode(real))
+               return real;
+
+       /* Handle recursion */
+       if (real->d_flags & DCACHE_OP_REAL)
+               return real->d_op->d_real(real, inode);
+
+bug:
+       WARN(1, "ovl_d_real(%pd4, %s:%lu\n): real dentry not found\n", dentry,
+            inode ? inode->i_sb->s_id : "NULL", inode ? inode->i_ino : 0);
+       return dentry;
+}
+
 static int ovl_dentry_revalidate(struct dentry *dentry, unsigned int flags)
 {
        struct ovl_entry *oe = dentry->d_fsdata;
@@ -320,11 +351,13 @@ static int ovl_dentry_weak_revalidate(struct dentry 
*dentry, unsigned int flags)
 static const struct dentry_operations ovl_dentry_operations = {
        .d_release = ovl_dentry_release,
        .d_select_inode = ovl_d_select_inode,
+       .d_real = ovl_d_real,
 };
 
 static const struct dentry_operations ovl_reval_dentry_operations = {
        .d_release = ovl_dentry_release,
        .d_select_inode = ovl_d_select_inode,
+       .d_real = ovl_d_real,
        .d_revalidate = ovl_dentry_revalidate,
        .d_weak_revalidate = ovl_dentry_weak_revalidate,
 };
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 8a2e009..9c6a4fd 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -161,6 +161,7 @@ struct dentry_operations {
        struct vfsmount *(*d_automount)(struct path *);
        int (*d_manage)(struct dentry *, bool);
        struct inode *(*d_select_inode)(struct dentry *, unsigned);
+       struct dentry *(*d_real)(struct dentry *, struct inode *);
 } ____cacheline_aligned;
 
 /*
@@ -228,6 +229,8 @@ struct dentry_operations {
 #define DCACHE_FALLTHRU                        0x01000000 /* Fall through to 
lower layer */
 #define DCACHE_OP_SELECT_INODE         0x02000000 /* Unioned entry: dcache op 
selects inode */
 
+#define DCACHE_OP_REAL                 0x08000000
+
 extern seqlock_t rename_lock;
 
 /*
@@ -582,4 +585,12 @@ static inline struct dentry *d_backing_dentry(struct 
dentry *upper)
        return upper;
 }
 
+static inline struct dentry *d_real(struct dentry *dentry)
+{
+       if (unlikely(dentry->d_flags & DCACHE_OP_REAL))
+               return dentry->d_op->d_real(dentry, NULL);
+       else
+               return dentry;
+}
+
 #endif /* __LINUX_DCACHE_H */
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 5f59f86..b675ea0 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1209,6 +1209,16 @@ static inline struct inode *file_inode(const struct file 
*f)
        return f->f_inode;
 }
 
+static inline struct dentry *file_dentry(const struct file *file)
+{
+       struct dentry *dentry = file->f_path.dentry;
+
+       if (unlikely(dentry->d_flags & DCACHE_OP_REAL))
+               return dentry->d_op->d_real(dentry, file_inode(file));
+       else
+               return dentry;
+}
+
 static inline int posix_lock_file_wait(struct file *filp, struct file_lock *fl)
 {
        return posix_lock_inode_wait(file_inode(filp), fl);
-- 
2.7.4

Reply via email to