From: Amir Goldstein Date: Mon, 14 Oct 2024 15:25:26 +0000 (+0200) Subject: ovl: store upper real file in ovl_file struct X-Git-Tag: v6.13-rc1~101^2~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=18e48d0e2c7b137be532fedbb5fba31b43e043b2;p=thirdparty%2Fkernel%2Flinux.git ovl: store upper real file in ovl_file struct 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 --- diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index 0fa0795d335f7..63804db4a0f4f 100644 --- a/fs/overlayfs/file.c +++ b/fs/overlayfs/file.c @@ -91,6 +91,7 @@ static int ovl_change_flags(struct file *file, unsigned int flags) struct ovl_file { struct file *realfile; + struct file *upperfile; }; struct ovl_file *ovl_file_alloc(struct file *realfile) @@ -107,33 +108,65 @@ struct ovl_file *ovl_file_alloc(struct file *realfile) void ovl_file_free(struct ovl_file *of) { fput(of->realfile); + if (of->upperfile) + fput(of->upperfile); kfree(of); } +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; }