cachefiles: Implement culling daemon commands
authorDavid Howells <dhowells@redhat.com>
Thu, 21 Oct 2021 07:50:10 +0000 (08:50 +0100)
committerDavid Howells <dhowells@redhat.com>
Fri, 7 Jan 2022 13:42:35 +0000 (13:42 +0000)
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/
Link: https://lore.kernel.org/r/163819643179.215744.13641580295708315695.stgit@warthog.procyon.org.uk/
Link: https://lore.kernel.org/r/163906945705.143852.8177595531814485350.stgit@warthog.procyon.org.uk/
Link: https://lore.kernel.org/r/163967155792.1823006.1088936326902550910.stgit@warthog.procyon.org.uk/
Link: https://lore.kernel.org/r/164021555037.640689.9472627499842585255.stgit@warthog.procyon.org.uk/
fs/cachefiles/daemon.c
fs/cachefiles/internal.h
fs/cachefiles/namei.c

index 337597a..985c3f3 100644 (file)
@@ -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);
index 7c67a70..654dbd5 100644 (file)
@@ -189,12 +189,23 @@ 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,
                                               bool *_is_new);
 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
  */
index db60a67..e87c401 100644 (file)
@@ -217,3 +217,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;
+}