cachefiles: Implement a function to get/create a directory in the cache
authorDavid Howells <dhowells@redhat.com>
Thu, 21 Oct 2021 07:34:55 +0000 (08:34 +0100)
committerDavid Howells <dhowells@redhat.com>
Fri, 7 Jan 2022 13:41:40 +0000 (13:41 +0000)
Implement a function to get/create structural directories in the cache.
This is used for setting up a cache and creating volume substructures.  The
directory in memory are marked with the S_KERNEL_FILE inode flag whilst
they're in use to tell rmdir to reject attempts to remove them.

Changes
=======
ver #3:
 - Return an indication as to whether the directory was freshly created.

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/163819631182.215744.3322471539523262619.stgit@warthog.procyon.org.uk/
Link: https://lore.kernel.org/r/163906933130.143852.962088616746509062.stgit@warthog.procyon.org.uk/
Link: https://lore.kernel.org/r/163967141952.1823006.7832985646370603833.stgit@warthog.procyon.org.uk/
Link: https://lore.kernel.org/r/164021542169.640689.18266858945694357839.stgit@warthog.procyon.org.uk/
fs/cachefiles/internal.h
fs/cachefiles/namei.c

index 3783a3e..48768a3 100644 (file)
@@ -126,6 +126,15 @@ static inline int cachefiles_inject_remove_error(void)
 }
 
 /*
+ * namei.c
+ */
+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);
+
+/*
  * security.c
  */
 extern int cachefiles_get_security_ID(struct cachefiles_cache *cache);
index 913f83f..11a3320 100644 (file)
@@ -6,6 +6,7 @@
  */
 
 #include <linux/fs.h>
+#include <linux/namei.h>
 #include "internal.h"
 
 /*
@@ -41,3 +42,143 @@ static void __cachefiles_unmark_inode_in_use(struct cachefiles_object *object,
        inode->i_flags &= ~S_KERNEL_FILE;
        trace_cachefiles_mark_inactive(object, inode);
 }
+
+/*
+ * 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);
+       }
+}