mirror of
https://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux-stable.git
synced 2025-11-06 08:16:48 +10:00
Use an inode flag, S_KERNEL_FILE, to mark that a backing file is in use by the kernel to prevent cachefiles or other kernel services from interfering with that file. Using S_SWAPFILE instead isn't really viable as that has other effects in the I/O paths. Signed-off-by: David Howells <dhowells@redhat.com> Reviewed-by: Jeff Layton <jlayton@kernel.org> cc: linux-cachefs@redhat.com Link: https://lore.kernel.org/r/163819642273.215744.6414248677118690672.stgit@warthog.procyon.org.uk/ # v1 Link: https://lore.kernel.org/r/163906943215.143852.16972351425323967014.stgit@warthog.procyon.org.uk/ # v2 Link: https://lore.kernel.org/r/163967154118.1823006.13227551961786743991.stgit@warthog.procyon.org.uk/ # v3 Link: https://lore.kernel.org/r/164021541207.640689.564689725898537127.stgit@warthog.procyon.org.uk/ # v4 Link: https://lore.kernel.org/r/164021552299.640689.10578652796777392062.stgit@warthog.procyon.org.uk/ # v4
220 lines
5.3 KiB
C
220 lines
5.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* CacheFiles path walking and related routines
|
|
*
|
|
* Copyright (C) 2021 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/namei.h>
|
|
#include "internal.h"
|
|
|
|
/*
|
|
* Mark the backing file as being a cache file if it's not already in use. The
|
|
* mark tells the culling request command that it's not allowed to cull the
|
|
* file or directory. The caller must hold the inode lock.
|
|
*/
|
|
static bool __cachefiles_mark_inode_in_use(struct cachefiles_object *object,
|
|
struct dentry *dentry)
|
|
{
|
|
struct inode *inode = d_backing_inode(dentry);
|
|
bool can_use = false;
|
|
|
|
if (!(inode->i_flags & S_KERNEL_FILE)) {
|
|
inode->i_flags |= S_KERNEL_FILE;
|
|
trace_cachefiles_mark_active(object, inode);
|
|
can_use = true;
|
|
} else {
|
|
pr_notice("cachefiles: Inode already in use: %pd\n", dentry);
|
|
}
|
|
|
|
return can_use;
|
|
}
|
|
|
|
static bool cachefiles_mark_inode_in_use(struct cachefiles_object *object,
|
|
struct dentry *dentry)
|
|
{
|
|
struct inode *inode = d_backing_inode(dentry);
|
|
bool can_use;
|
|
|
|
inode_lock(inode);
|
|
can_use = __cachefiles_mark_inode_in_use(object, dentry);
|
|
inode_unlock(inode);
|
|
return can_use;
|
|
}
|
|
|
|
/*
|
|
* Unmark a backing inode. The caller must hold the inode lock.
|
|
*/
|
|
static void __cachefiles_unmark_inode_in_use(struct cachefiles_object *object,
|
|
struct dentry *dentry)
|
|
{
|
|
struct inode *inode = d_backing_inode(dentry);
|
|
|
|
inode->i_flags &= ~S_KERNEL_FILE;
|
|
trace_cachefiles_mark_inactive(object, inode);
|
|
}
|
|
|
|
/*
|
|
* Unmark a backing inode and tell cachefilesd that there's something that can
|
|
* be culled.
|
|
*/
|
|
void cachefiles_unmark_inode_in_use(struct cachefiles_object *object,
|
|
struct file *file)
|
|
{
|
|
struct cachefiles_cache *cache = object->volume->cache;
|
|
struct inode *inode = file_inode(file);
|
|
|
|
if (inode) {
|
|
inode_lock(inode);
|
|
__cachefiles_unmark_inode_in_use(object, file->f_path.dentry);
|
|
inode_unlock(inode);
|
|
|
|
if (!test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags)) {
|
|
atomic_long_add(inode->i_blocks, &cache->b_released);
|
|
if (atomic_inc_return(&cache->f_released))
|
|
cachefiles_state_changed(cache);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* get a subdirectory
|
|
*/
|
|
struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
|
|
struct dentry *dir,
|
|
const char *dirname,
|
|
bool *_is_new)
|
|
{
|
|
struct dentry *subdir;
|
|
struct path path;
|
|
int ret;
|
|
|
|
_enter(",,%s", dirname);
|
|
|
|
/* search the current directory for the element name */
|
|
inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
|
|
|
|
retry:
|
|
ret = cachefiles_inject_read_error();
|
|
if (ret == 0)
|
|
subdir = lookup_one_len(dirname, dir, strlen(dirname));
|
|
else
|
|
subdir = ERR_PTR(ret);
|
|
if (IS_ERR(subdir)) {
|
|
trace_cachefiles_vfs_error(NULL, d_backing_inode(dir),
|
|
PTR_ERR(subdir),
|
|
cachefiles_trace_lookup_error);
|
|
if (PTR_ERR(subdir) == -ENOMEM)
|
|
goto nomem_d_alloc;
|
|
goto lookup_error;
|
|
}
|
|
|
|
_debug("subdir -> %pd %s",
|
|
subdir, d_backing_inode(subdir) ? "positive" : "negative");
|
|
|
|
/* we need to create the subdir if it doesn't exist yet */
|
|
if (d_is_negative(subdir)) {
|
|
ret = cachefiles_has_space(cache, 1, 0);
|
|
if (ret < 0)
|
|
goto mkdir_error;
|
|
|
|
_debug("attempt mkdir");
|
|
|
|
path.mnt = cache->mnt;
|
|
path.dentry = dir;
|
|
ret = security_path_mkdir(&path, subdir, 0700);
|
|
if (ret < 0)
|
|
goto mkdir_error;
|
|
ret = cachefiles_inject_write_error();
|
|
if (ret == 0)
|
|
ret = vfs_mkdir(&init_user_ns, d_inode(dir), subdir, 0700);
|
|
if (ret < 0) {
|
|
trace_cachefiles_vfs_error(NULL, d_inode(dir), ret,
|
|
cachefiles_trace_mkdir_error);
|
|
goto mkdir_error;
|
|
}
|
|
|
|
if (unlikely(d_unhashed(subdir))) {
|
|
cachefiles_put_directory(subdir);
|
|
goto retry;
|
|
}
|
|
ASSERT(d_backing_inode(subdir));
|
|
|
|
_debug("mkdir -> %pd{ino=%lu}",
|
|
subdir, d_backing_inode(subdir)->i_ino);
|
|
if (_is_new)
|
|
*_is_new = true;
|
|
}
|
|
|
|
/* Tell rmdir() it's not allowed to delete the subdir */
|
|
inode_lock(d_inode(subdir));
|
|
inode_unlock(d_inode(dir));
|
|
|
|
if (!__cachefiles_mark_inode_in_use(NULL, subdir))
|
|
goto mark_error;
|
|
|
|
inode_unlock(d_inode(subdir));
|
|
|
|
/* we need to make sure the subdir is a directory */
|
|
ASSERT(d_backing_inode(subdir));
|
|
|
|
if (!d_can_lookup(subdir)) {
|
|
pr_err("%s is not a directory\n", dirname);
|
|
ret = -EIO;
|
|
goto check_error;
|
|
}
|
|
|
|
ret = -EPERM;
|
|
if (!(d_backing_inode(subdir)->i_opflags & IOP_XATTR) ||
|
|
!d_backing_inode(subdir)->i_op->lookup ||
|
|
!d_backing_inode(subdir)->i_op->mkdir ||
|
|
!d_backing_inode(subdir)->i_op->rename ||
|
|
!d_backing_inode(subdir)->i_op->rmdir ||
|
|
!d_backing_inode(subdir)->i_op->unlink)
|
|
goto check_error;
|
|
|
|
_leave(" = [%lu]", d_backing_inode(subdir)->i_ino);
|
|
return subdir;
|
|
|
|
check_error:
|
|
cachefiles_put_directory(subdir);
|
|
_leave(" = %d [check]", ret);
|
|
return ERR_PTR(ret);
|
|
|
|
mark_error:
|
|
inode_unlock(d_inode(subdir));
|
|
dput(subdir);
|
|
return ERR_PTR(-EBUSY);
|
|
|
|
mkdir_error:
|
|
inode_unlock(d_inode(dir));
|
|
dput(subdir);
|
|
pr_err("mkdir %s failed with error %d\n", dirname, ret);
|
|
return ERR_PTR(ret);
|
|
|
|
lookup_error:
|
|
inode_unlock(d_inode(dir));
|
|
ret = PTR_ERR(subdir);
|
|
pr_err("Lookup %s failed with error %d\n", dirname, ret);
|
|
return ERR_PTR(ret);
|
|
|
|
nomem_d_alloc:
|
|
inode_unlock(d_inode(dir));
|
|
_leave(" = -ENOMEM");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
/*
|
|
* Put a subdirectory.
|
|
*/
|
|
void cachefiles_put_directory(struct dentry *dir)
|
|
{
|
|
if (dir) {
|
|
inode_lock(dir->d_inode);
|
|
__cachefiles_unmark_inode_in_use(NULL, dir);
|
|
inode_unlock(dir->d_inode);
|
|
dput(dir);
|
|
}
|
|
}
|