cachefiles: Implement a function to get/create a directory in the cache

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/ # v1
Link: https://lore.kernel.org/r/163906933130.143852.962088616746509062.stgit@warthog.procyon.org.uk/ # v2
Link: https://lore.kernel.org/r/163967141952.1823006.7832985646370603833.stgit@warthog.procyon.org.uk/ # v3
Link: https://lore.kernel.org/r/164021542169.640689.18266858945694357839.stgit@warthog.procyon.org.uk/ # v4
This commit is contained in:
David Howells 2021-10-21 08:34:55 +01:00
parent 1bd9c4e4f0
commit 32759f7d7a
2 changed files with 150 additions and 0 deletions

View file

@ -125,6 +125,15 @@ static inline int cachefiles_inject_remove_error(void)
return cachefiles_error_injection_state & 2 ? -EIO : 0;
}
/*
* 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
*/

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