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. Store the upper real file along side the original (lower) real file in ovl_file instead of opening a temporary upper file on every operation. Signed-off-by: Amir Goldstein <amir73il@xxxxxxxxx> --- fs/overlayfs/file.c | 49 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index 03bf6037b129..525bcddb49e5 100644 --- a/fs/overlayfs/file.c +++ b/fs/overlayfs/file.c @@ -91,32 +91,63 @@ static int ovl_change_flags(struct file *file, unsigned int flags) struct ovl_file { struct file *realfile; + struct file *upperfile; }; +static bool ovl_is_real_file(const struct file *realfile, + const struct path *realpath) +{ + return file_inode(realfile) == d_inode(realpath->dentry); +} + static int ovl_real_fdget_path(const struct file *file, struct fd *real, struct path *realpath) { struct ovl_file *of = file->private_data; struct file *realfile = of->realfile; - real->word = (unsigned long)realfile; + real->word = 0; if (WARN_ON_ONCE(!realpath->dentry)) return -EIO; - /* Has it been copied up since we'd opened it? */ - 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; + /* + * If the realfile that we want is not where the data used to be at + * open time, either we'd been copied up, or it's an fsync of a + * metacopied file. We need the upperfile either way, so see if it + * is already opened and if it is not then open and store it. + */ + if (unlikely(!ovl_is_real_file(realfile, realpath))) { + struct file *upperfile = READ_ONCE(of->upperfile); + struct file *old; + + if (!upperfile) { /* Nobody opened upperfile yet */ + upperfile = ovl_open_realfile(file, realpath); + if (IS_ERR(upperfile)) + return PTR_ERR(upperfile); + + /* Store the upperfile for later */ + old = cmpxchg_release(&of->upperfile, NULL, upperfile); + if (old) { /* Someone opened upperfile before us */ + fput(upperfile); + upperfile = old; + } + } + /* + * Stored file must be from the right inode, unless someone's + * been corrupting the upper layer. + */ + if (WARN_ON_ONCE(!ovl_is_real_file(upperfile, realpath))) + return -EIO; + + realfile = upperfile; } /* 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); + real->word = (unsigned long)realfile; return 0; } @@ -208,6 +239,8 @@ static int ovl_release(struct inode *inode, struct file *file) struct ovl_file *of = file->private_data; fput(of->realfile); + if (of->upperfile) + fput(of->upperfile); kfree(of); return 0; -- 2.34.1