mirror of https://github.com/torvalds/linux.git
cachefiles: Implement culling daemon commands
Implement the ability for the userspace daemon to try and cull a file or
directory in the cache. Two daemon commands are implemented:
(1) The "inuse" command. This queries if a file is in use or whether it
can be deleted. It checks the S_KERNEL_FILE flag on the inode
referred to by the specified filename.
(2) The "cull" command. This asks for a file or directory to be removed,
where removal means either unlinking it or moving it to the graveyard
directory for userspace to dismantle.
Changes
=======
ver #2:
- Fix logging of wrong error[1].
- Need to unmark an inode we've moved to the graveyard before unlocking.
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/20211203094950.GA2480@kili/ [1]
Link: https://lore.kernel.org/r/163819643179.215744.13641580295708315695.stgit@warthog.procyon.org.uk/ # v1
Link: https://lore.kernel.org/r/163906945705.143852.8177595531814485350.stgit@warthog.procyon.org.uk/ # v2
Link: https://lore.kernel.org/r/163967155792.1823006.1088936326902550910.stgit@warthog.procyon.org.uk/ # v3
Link: https://lore.kernel.org/r/164021555037.640689.9472627499842585255.stgit@warthog.procyon.org.uk/ # v4
This commit is contained in:
parent
169379eaef
commit
07a90e9740
|
|
@ -574,7 +574,7 @@ static int cachefiles_daemon_cull(struct cachefiles_cache *cache, char *args)
|
||||||
goto notdir;
|
goto notdir;
|
||||||
|
|
||||||
cachefiles_begin_secure(cache, &saved_cred);
|
cachefiles_begin_secure(cache, &saved_cred);
|
||||||
ret = -ENOANO; // PLACEHOLDER: Do culling
|
ret = cachefiles_cull(cache, path.dentry, args);
|
||||||
cachefiles_end_secure(cache, saved_cred);
|
cachefiles_end_secure(cache, saved_cred);
|
||||||
|
|
||||||
path_put(&path);
|
path_put(&path);
|
||||||
|
|
@ -645,7 +645,7 @@ static int cachefiles_daemon_inuse(struct cachefiles_cache *cache, char *args)
|
||||||
goto notdir;
|
goto notdir;
|
||||||
|
|
||||||
cachefiles_begin_secure(cache, &saved_cred);
|
cachefiles_begin_secure(cache, &saved_cred);
|
||||||
ret = -ENOANO; // PLACEHOLDER: Check if in use
|
ret = cachefiles_check_in_use(cache, path.dentry, args);
|
||||||
cachefiles_end_secure(cache, saved_cred);
|
cachefiles_end_secure(cache, saved_cred);
|
||||||
|
|
||||||
path_put(&path);
|
path_put(&path);
|
||||||
|
|
|
||||||
|
|
@ -189,12 +189,23 @@ extern struct kmem_cache *cachefiles_object_jar;
|
||||||
*/
|
*/
|
||||||
extern void cachefiles_unmark_inode_in_use(struct cachefiles_object *object,
|
extern void cachefiles_unmark_inode_in_use(struct cachefiles_object *object,
|
||||||
struct file *file);
|
struct file *file);
|
||||||
|
extern int cachefiles_bury_object(struct cachefiles_cache *cache,
|
||||||
|
struct cachefiles_object *object,
|
||||||
|
struct dentry *dir,
|
||||||
|
struct dentry *rep,
|
||||||
|
enum fscache_why_object_killed why);
|
||||||
extern struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
|
extern struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
|
||||||
struct dentry *dir,
|
struct dentry *dir,
|
||||||
const char *name,
|
const char *name,
|
||||||
bool *_is_new);
|
bool *_is_new);
|
||||||
extern void cachefiles_put_directory(struct dentry *dir);
|
extern void cachefiles_put_directory(struct dentry *dir);
|
||||||
|
|
||||||
|
extern int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir,
|
||||||
|
char *filename);
|
||||||
|
|
||||||
|
extern int cachefiles_check_in_use(struct cachefiles_cache *cache,
|
||||||
|
struct dentry *dir, char *filename);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* security.c
|
* security.c
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -217,3 +217,310 @@ void cachefiles_put_directory(struct dentry *dir)
|
||||||
dput(dir);
|
dput(dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove a regular file from the cache.
|
||||||
|
*/
|
||||||
|
static int cachefiles_unlink(struct cachefiles_cache *cache,
|
||||||
|
struct cachefiles_object *object,
|
||||||
|
struct dentry *dir, struct dentry *dentry,
|
||||||
|
enum fscache_why_object_killed why)
|
||||||
|
{
|
||||||
|
struct path path = {
|
||||||
|
.mnt = cache->mnt,
|
||||||
|
.dentry = dir,
|
||||||
|
};
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
trace_cachefiles_unlink(object, dentry, why);
|
||||||
|
ret = security_path_unlink(&path, dentry);
|
||||||
|
if (ret < 0) {
|
||||||
|
cachefiles_io_error(cache, "Unlink security error");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = cachefiles_inject_remove_error();
|
||||||
|
if (ret == 0) {
|
||||||
|
ret = vfs_unlink(&init_user_ns, d_backing_inode(dir), dentry, NULL);
|
||||||
|
if (ret == -EIO)
|
||||||
|
cachefiles_io_error(cache, "Unlink failed");
|
||||||
|
}
|
||||||
|
if (ret != 0)
|
||||||
|
trace_cachefiles_vfs_error(object, d_backing_inode(dir), ret,
|
||||||
|
cachefiles_trace_unlink_error);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete an object representation from the cache
|
||||||
|
* - File backed objects are unlinked
|
||||||
|
* - Directory backed objects are stuffed into the graveyard for userspace to
|
||||||
|
* delete
|
||||||
|
*/
|
||||||
|
int cachefiles_bury_object(struct cachefiles_cache *cache,
|
||||||
|
struct cachefiles_object *object,
|
||||||
|
struct dentry *dir,
|
||||||
|
struct dentry *rep,
|
||||||
|
enum fscache_why_object_killed why)
|
||||||
|
{
|
||||||
|
struct dentry *grave, *trap;
|
||||||
|
struct path path, path_to_graveyard;
|
||||||
|
char nbuffer[8 + 8 + 1];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
_enter(",'%pd','%pd'", dir, rep);
|
||||||
|
|
||||||
|
if (rep->d_parent != dir) {
|
||||||
|
inode_unlock(d_inode(dir));
|
||||||
|
_leave(" = -ESTALE");
|
||||||
|
return -ESTALE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* non-directories can just be unlinked */
|
||||||
|
if (!d_is_dir(rep)) {
|
||||||
|
dget(rep); /* Stop the dentry being negated if it's only pinned
|
||||||
|
* by a file struct.
|
||||||
|
*/
|
||||||
|
ret = cachefiles_unlink(cache, object, dir, rep, why);
|
||||||
|
dput(rep);
|
||||||
|
|
||||||
|
inode_unlock(d_inode(dir));
|
||||||
|
_leave(" = %d", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* directories have to be moved to the graveyard */
|
||||||
|
_debug("move stale object to graveyard");
|
||||||
|
inode_unlock(d_inode(dir));
|
||||||
|
|
||||||
|
try_again:
|
||||||
|
/* first step is to make up a grave dentry in the graveyard */
|
||||||
|
sprintf(nbuffer, "%08x%08x",
|
||||||
|
(uint32_t) ktime_get_real_seconds(),
|
||||||
|
(uint32_t) atomic_inc_return(&cache->gravecounter));
|
||||||
|
|
||||||
|
/* do the multiway lock magic */
|
||||||
|
trap = lock_rename(cache->graveyard, dir);
|
||||||
|
|
||||||
|
/* do some checks before getting the grave dentry */
|
||||||
|
if (rep->d_parent != dir || IS_DEADDIR(d_inode(rep))) {
|
||||||
|
/* the entry was probably culled when we dropped the parent dir
|
||||||
|
* lock */
|
||||||
|
unlock_rename(cache->graveyard, dir);
|
||||||
|
_leave(" = 0 [culled?]");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!d_can_lookup(cache->graveyard)) {
|
||||||
|
unlock_rename(cache->graveyard, dir);
|
||||||
|
cachefiles_io_error(cache, "Graveyard no longer a directory");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trap == rep) {
|
||||||
|
unlock_rename(cache->graveyard, dir);
|
||||||
|
cachefiles_io_error(cache, "May not make directory loop");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d_mountpoint(rep)) {
|
||||||
|
unlock_rename(cache->graveyard, dir);
|
||||||
|
cachefiles_io_error(cache, "Mountpoint in cache");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
grave = lookup_one_len(nbuffer, cache->graveyard, strlen(nbuffer));
|
||||||
|
if (IS_ERR(grave)) {
|
||||||
|
unlock_rename(cache->graveyard, dir);
|
||||||
|
trace_cachefiles_vfs_error(object, d_inode(cache->graveyard),
|
||||||
|
PTR_ERR(grave),
|
||||||
|
cachefiles_trace_lookup_error);
|
||||||
|
|
||||||
|
if (PTR_ERR(grave) == -ENOMEM) {
|
||||||
|
_leave(" = -ENOMEM");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachefiles_io_error(cache, "Lookup error %ld", PTR_ERR(grave));
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d_is_positive(grave)) {
|
||||||
|
unlock_rename(cache->graveyard, dir);
|
||||||
|
dput(grave);
|
||||||
|
grave = NULL;
|
||||||
|
cond_resched();
|
||||||
|
goto try_again;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d_mountpoint(grave)) {
|
||||||
|
unlock_rename(cache->graveyard, dir);
|
||||||
|
dput(grave);
|
||||||
|
cachefiles_io_error(cache, "Mountpoint in graveyard");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* target should not be an ancestor of source */
|
||||||
|
if (trap == grave) {
|
||||||
|
unlock_rename(cache->graveyard, dir);
|
||||||
|
dput(grave);
|
||||||
|
cachefiles_io_error(cache, "May not make directory loop");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* attempt the rename */
|
||||||
|
path.mnt = cache->mnt;
|
||||||
|
path.dentry = dir;
|
||||||
|
path_to_graveyard.mnt = cache->mnt;
|
||||||
|
path_to_graveyard.dentry = cache->graveyard;
|
||||||
|
ret = security_path_rename(&path, rep, &path_to_graveyard, grave, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
cachefiles_io_error(cache, "Rename security error %d", ret);
|
||||||
|
} else {
|
||||||
|
struct renamedata rd = {
|
||||||
|
.old_mnt_userns = &init_user_ns,
|
||||||
|
.old_dir = d_inode(dir),
|
||||||
|
.old_dentry = rep,
|
||||||
|
.new_mnt_userns = &init_user_ns,
|
||||||
|
.new_dir = d_inode(cache->graveyard),
|
||||||
|
.new_dentry = grave,
|
||||||
|
};
|
||||||
|
trace_cachefiles_rename(object, rep, grave, why);
|
||||||
|
ret = cachefiles_inject_read_error();
|
||||||
|
if (ret == 0)
|
||||||
|
ret = vfs_rename(&rd);
|
||||||
|
if (ret != 0)
|
||||||
|
trace_cachefiles_vfs_error(object, d_inode(dir), ret,
|
||||||
|
cachefiles_trace_rename_error);
|
||||||
|
if (ret != 0 && ret != -ENOMEM)
|
||||||
|
cachefiles_io_error(cache,
|
||||||
|
"Rename failed with error %d", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
__cachefiles_unmark_inode_in_use(object, rep);
|
||||||
|
unlock_rename(cache->graveyard, dir);
|
||||||
|
dput(grave);
|
||||||
|
_leave(" = 0");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Look up an inode to be checked or culled. Return -EBUSY if the inode is
|
||||||
|
* marked in use.
|
||||||
|
*/
|
||||||
|
static struct dentry *cachefiles_lookup_for_cull(struct cachefiles_cache *cache,
|
||||||
|
struct dentry *dir,
|
||||||
|
char *filename)
|
||||||
|
{
|
||||||
|
struct dentry *victim;
|
||||||
|
int ret = -ENOENT;
|
||||||
|
|
||||||
|
inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
|
||||||
|
|
||||||
|
victim = lookup_one_len(filename, dir, strlen(filename));
|
||||||
|
if (IS_ERR(victim))
|
||||||
|
goto lookup_error;
|
||||||
|
if (d_is_negative(victim))
|
||||||
|
goto lookup_put;
|
||||||
|
if (d_inode(victim)->i_flags & S_KERNEL_FILE)
|
||||||
|
goto lookup_busy;
|
||||||
|
return victim;
|
||||||
|
|
||||||
|
lookup_busy:
|
||||||
|
ret = -EBUSY;
|
||||||
|
lookup_put:
|
||||||
|
inode_unlock(d_inode(dir));
|
||||||
|
dput(victim);
|
||||||
|
return ERR_PTR(ret);
|
||||||
|
|
||||||
|
lookup_error:
|
||||||
|
inode_unlock(d_inode(dir));
|
||||||
|
ret = PTR_ERR(victim);
|
||||||
|
if (ret == -ENOENT)
|
||||||
|
return ERR_PTR(-ESTALE); /* Probably got retired by the netfs */
|
||||||
|
|
||||||
|
if (ret == -EIO) {
|
||||||
|
cachefiles_io_error(cache, "Lookup failed");
|
||||||
|
} else if (ret != -ENOMEM) {
|
||||||
|
pr_err("Internal error: %d\n", ret);
|
||||||
|
ret = -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERR_PTR(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cull an object if it's not in use
|
||||||
|
* - called only by cache manager daemon
|
||||||
|
*/
|
||||||
|
int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir,
|
||||||
|
char *filename)
|
||||||
|
{
|
||||||
|
struct dentry *victim;
|
||||||
|
struct inode *inode;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
_enter(",%pd/,%s", dir, filename);
|
||||||
|
|
||||||
|
victim = cachefiles_lookup_for_cull(cache, dir, filename);
|
||||||
|
if (IS_ERR(victim))
|
||||||
|
return PTR_ERR(victim);
|
||||||
|
|
||||||
|
/* check to see if someone is using this object */
|
||||||
|
inode = d_inode(victim);
|
||||||
|
inode_lock(inode);
|
||||||
|
if (inode->i_flags & S_KERNEL_FILE) {
|
||||||
|
ret = -EBUSY;
|
||||||
|
} else {
|
||||||
|
/* Stop the cache from picking it back up */
|
||||||
|
inode->i_flags |= S_KERNEL_FILE;
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
inode_unlock(inode);
|
||||||
|
if (ret < 0)
|
||||||
|
goto error_unlock;
|
||||||
|
|
||||||
|
ret = cachefiles_bury_object(cache, NULL, dir, victim,
|
||||||
|
FSCACHE_OBJECT_WAS_CULLED);
|
||||||
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
dput(victim);
|
||||||
|
_leave(" = 0");
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error_unlock:
|
||||||
|
inode_unlock(d_inode(dir));
|
||||||
|
error:
|
||||||
|
dput(victim);
|
||||||
|
if (ret == -ENOENT)
|
||||||
|
return -ESTALE; /* Probably got retired by the netfs */
|
||||||
|
|
||||||
|
if (ret != -ENOMEM) {
|
||||||
|
pr_err("Internal error: %d\n", ret);
|
||||||
|
ret = -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
_leave(" = %d", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find out if an object is in use or not
|
||||||
|
* - called only by cache manager daemon
|
||||||
|
* - returns -EBUSY or 0 to indicate whether an object is in use or not
|
||||||
|
*/
|
||||||
|
int cachefiles_check_in_use(struct cachefiles_cache *cache, struct dentry *dir,
|
||||||
|
char *filename)
|
||||||
|
{
|
||||||
|
struct dentry *victim;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
victim = cachefiles_lookup_for_cull(cache, dir, filename);
|
||||||
|
if (IS_ERR(victim))
|
||||||
|
return PTR_ERR(victim);
|
||||||
|
|
||||||
|
inode_unlock(d_inode(dir));
|
||||||
|
dput(victim);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue