| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* CacheFiles path walking and related routines |
| * |
| * Copyright (C) 2021 Red Hat, Inc. All Rights Reserved. |
| * Written by David Howells (dhowells@redhat.com) |
| */ |
| |
| #include <linux/fs.h> |
| #include <linux/namei.h> |
| #include "internal.h" |
| |
| /* |
| * Mark the backing file as being a cache file if it's not already in use. The |
| * mark tells the culling request command that it's not allowed to cull the |
| * file or directory. The caller must hold the inode lock. |
| */ |
| static bool __cachefiles_mark_inode_in_use(struct cachefiles_object *object, |
| struct dentry *dentry) |
| { |
| struct inode *inode = d_backing_inode(dentry); |
| bool can_use = false; |
| |
| if (!(inode->i_flags & S_KERNEL_FILE)) { |
| inode->i_flags |= S_KERNEL_FILE; |
| trace_cachefiles_mark_active(object, inode); |
| can_use = true; |
| } else { |
| pr_notice("cachefiles: Inode already in use: %pd\n", dentry); |
| } |
| |
| return can_use; |
| } |
| |
| /* |
| * Unmark a backing inode. The caller must hold the inode lock. |
| */ |
| static void __cachefiles_unmark_inode_in_use(struct cachefiles_object *object, |
| struct dentry *dentry) |
| { |
| struct inode *inode = d_backing_inode(dentry); |
| |
| 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); |
| } |
| } |