lkml.org 
[lkml]   [2021]   [Dec]   [9]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Subject[PATCH v2 47/67] cachefiles: Implement culling daemon commands
    From
    Date
    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>
    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
    ---

    fs/cachefiles/daemon.c | 4 -
    fs/cachefiles/internal.h | 11 ++
    fs/cachefiles/namei.c | 307 ++++++++++++++++++++++++++++++++++++++++++++++
    3 files changed, 320 insertions(+), 2 deletions(-)

    diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c
    index 337597a4e30c..985c3f3e6767 100644
    --- a/fs/cachefiles/daemon.c
    +++ b/fs/cachefiles/daemon.c
    @@ -574,7 +574,7 @@ static int cachefiles_daemon_cull(struct cachefiles_cache *cache, char *args)
    goto notdir;

    cachefiles_begin_secure(cache, &saved_cred);
    - ret = -ENOANO; // PLACEHOLDER: Do culling
    + ret = cachefiles_cull(cache, path.dentry, args);
    cachefiles_end_secure(cache, saved_cred);

    path_put(&path);
    @@ -645,7 +645,7 @@ static int cachefiles_daemon_inuse(struct cachefiles_cache *cache, char *args)
    goto notdir;

    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);

    path_put(&path);
    diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
    index ebb39373716b..f2a4ec2f8668 100644
    --- a/fs/cachefiles/internal.h
    +++ b/fs/cachefiles/internal.h
    @@ -189,11 +189,22 @@ extern struct kmem_cache *cachefiles_object_jar;
    */
    extern void cachefiles_unmark_inode_in_use(struct cachefiles_object *object,
    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,
    struct dentry *dir,
    const char *name);
    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
    */
    diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
    index 2e209e403713..d992d7c21c71 100644
    --- a/fs/cachefiles/namei.c
    +++ b/fs/cachefiles/namei.c
    @@ -215,3 +215,310 @@ void cachefiles_put_directory(struct dentry *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;
    +}

    \
     
     \ /
      Last update: 2021-12-09 18:05    [W:4.070 / U:0.124 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site