@@ -47,6 +47,7 @@ static struct percpu_counter nr_files __cacheline_aligned_in_smp;
struct backing_file {
struct file file;
struct path user_path;
+ void *private_data;
};
static inline struct backing_file *backing_file(struct file *f)
@@ -60,6 +61,12 @@ struct path *backing_file_user_path(struct file *f)
}
EXPORT_SYMBOL_GPL(backing_file_user_path);
+void **backing_file_private_ptr(struct file *f)
+{
+ return &backing_file(f)->private_data;
+}
+EXPORT_SYMBOL_GPL(backing_file_private_ptr);
+
static inline void file_free(struct file *f)
{
security_file_free(f);
@@ -100,6 +100,12 @@ extern void chroot_fs_refs(const struct path *, const struct path *);
struct file *alloc_empty_file(int flags, const struct cred *cred);
struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred);
struct file *alloc_empty_backing_file(int flags, const struct cred *cred);
+void **backing_file_private_ptr(struct file *f);
+
+static inline void *backing_file_private(struct file *f)
+{
+ return READ_ONCE(*backing_file_private_ptr(f));
+}
static inline void file_put_write_access(struct file *file)
{
@@ -14,6 +14,8 @@
#include <linux/backing-file.h>
#include "overlayfs.h"
+#include "../internal.h" /* for backing_file_private{,_ptr}() */
+
static char ovl_whatisit(struct inode *inode, struct inode *realinode)
{
if (realinode != ovl_inode_upper(inode))
@@ -94,6 +96,7 @@ static int ovl_real_fdget_meta(const struct file *file, struct fd *real,
{
struct dentry *dentry = file_dentry(file);
struct file *realfile = file->private_data;
+ struct file *upperfile = backing_file_private(realfile);
struct path realpath;
int err;
@@ -114,15 +117,37 @@ static int ovl_real_fdget_meta(const struct file *file, struct fd *real,
if (!realpath.dentry)
return -EIO;
- /* Has it been copied up since we'd opened it? */
+stashed_upper:
+ if (upperfile && file_inode(upperfile) == d_inode(realpath.dentry))
+ realfile = upperfile;
+
+ /*
+ * If realfile is lower and has been copied up since we'd opened it,
+ * open the real upper file and stash it in backing_file_private().
+ */
if (unlikely(file_inode(realfile) != d_inode(realpath.dentry))) {
- struct file *f = ovl_open_realfile(file, &realpath);
- if (IS_ERR(f))
- return PTR_ERR(f);
- real->word = (unsigned long)f | FDPUT_FPUT;
- return 0;
+ struct file *old;
+
+ /* Stashed upperfile has a mismatched inode */
+ if (unlikely(upperfile))
+ return -EIO;
+
+ upperfile = ovl_open_realfile(file, &realpath);
+ if (IS_ERR(upperfile))
+ return PTR_ERR(upperfile);
+
+ old = cmpxchg_release(backing_file_private_ptr(realfile), NULL,
+ upperfile);
+ if (old) {
+ fput(upperfile);
+ upperfile = old;
+ }
+
+ goto stashed_upper;
}
+ real->word = (unsigned long)realfile;
+
/* Did the flags change since open? */
if (unlikely((file->f_flags ^ realfile->f_flags) & ~OVL_OPEN_FLAGS))
return ovl_change_flags(realfile, file->f_flags);
@@ -177,7 +202,16 @@ static int ovl_open(struct inode *inode, struct file *file)
static int ovl_release(struct inode *inode, struct file *file)
{
- fput(file->private_data);
+ struct file *realfile = file->private_data;
+ struct file *upperfile = backing_file_private(realfile);
+
+ fput(realfile);
+ /*
+ * If realfile is lower and file was copied up and accessed, we need
+ * to put reference also on the stashed real upperfile.
+ */
+ if (upperfile)
+ fput(upperfile);
return 0;
}
When an overlayfs file is opened as lower and then the file is copied up, every operation on the overlayfs open file will open a temporary backing file to the upper dentry and close it at the end of the operation. The original (lower) real file is stored in file->private_data pointer. We could have allocated a struct ovl_real_file to potentially store two backing files, the lower and the upper, but the original backing file struct is not very space optimized (it has no memcache pool), so add a private_data pointer to the backing_file struct and store the optional second backing upper file in there instead of opening a temporary upper file on every operation. Signed-off-by: Amir Goldstein <amir73il@gmail.com> --- fs/file_table.c | 7 +++++++ fs/internal.h | 6 ++++++ fs/overlayfs/file.c | 48 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 54 insertions(+), 7 deletions(-)