diff options
author | Amir Goldstein <amir73il@gmail.com> | 2024-10-14 17:25:26 +0200 |
---|---|---|
committer | Amir Goldstein <amir73il@gmail.com> | 2024-11-15 08:56:48 +0100 |
commit | 18e48d0e2c7b137be532fedbb5fba31b43e043b2 (patch) | |
tree | af835bb98e9193dd3f5939972c3b591117373fa9 /fs/overlayfs | |
parent | 87a8a76c34a2ae6f667cc33249dc99705e363d1f (diff) |
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 <amir73il@gmail.com>
Diffstat (limited to 'fs/overlayfs')
-rw-r--r-- | fs/overlayfs/file.c | 49 |
1 files changed, 41 insertions, 8 deletions
diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index 0fa0795d335f..63804db4a0f4 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; } |