From 173e937552432db9406f04eb7905541b774ac7cd Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Tue, 13 Jan 2026 12:39:50 +0000 Subject: fs: export may_delete() as may_delete_dentry() For many years btrfs as been using a copy of may_delete() in fs/btrfs/ioctl.c:btrfs_may_delete(). Everytime may_delete() is updated we need to update the btrfs copy, and this is a maintenance burden. Currently there are minor differences between both because the btrfs side lacks updates done in may_delete(). Export may_delete() so that btrfs can use it and with the less generic name may_delete_dentry(). While at it change the calls in vfs_rmdir() to pass a boolean literal instead of 1 and 0 as the last argument since the argument has a bool type. Signed-off-by: Filipe Manana Link: https://patch.msgid.link/e09128fd53f01b19d0a58f0e7d24739f79f47f6d.1768307858.git.fdmanana@suse.com Reviewed-by: David Sterba Signed-off-by: Christian Brauner --- fs/namei.c | 17 +++++++++-------- include/linux/fs.h | 3 +++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index bf0f66f0e9b9..28aebc786e8f 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3604,7 +3604,7 @@ EXPORT_SYMBOL(__check_sticky); * 11. We don't allow removal of NFS sillyrenamed files; it's handled by * nfs_async_unlink(). */ -static int may_delete(struct mnt_idmap *idmap, struct inode *dir, +int may_delete_dentry(struct mnt_idmap *idmap, struct inode *dir, struct dentry *victim, bool isdir) { struct inode *inode = d_backing_inode(victim); @@ -3646,6 +3646,7 @@ static int may_delete(struct mnt_idmap *idmap, struct inode *dir, return -EBUSY; return 0; } +EXPORT_SYMBOL(may_delete_dentry); /* Check whether we can create an object with dentry child in directory * dir. @@ -5209,7 +5210,7 @@ SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode) int vfs_rmdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, struct delegated_inode *delegated_inode) { - int error = may_delete(idmap, dir, dentry, 1); + int error = may_delete_dentry(idmap, dir, dentry, true); if (error) return error; @@ -5344,7 +5345,7 @@ int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, struct delegated_inode *delegated_inode) { struct inode *target = dentry->d_inode; - int error = may_delete(idmap, dir, dentry, 0); + int error = may_delete_dentry(idmap, dir, dentry, false); if (error) return error; @@ -5816,7 +5817,7 @@ int vfs_rename(struct renamedata *rd) if (source == target) return 0; - error = may_delete(rd->mnt_idmap, old_dir, old_dentry, is_dir); + error = may_delete_dentry(rd->mnt_idmap, old_dir, old_dentry, is_dir); if (error) return error; @@ -5826,11 +5827,11 @@ int vfs_rename(struct renamedata *rd) new_is_dir = d_is_dir(new_dentry); if (!(flags & RENAME_EXCHANGE)) - error = may_delete(rd->mnt_idmap, new_dir, - new_dentry, is_dir); + error = may_delete_dentry(rd->mnt_idmap, new_dir, + new_dentry, is_dir); else - error = may_delete(rd->mnt_idmap, new_dir, - new_dentry, new_is_dir); + error = may_delete_dentry(rd->mnt_idmap, new_dir, + new_dentry, new_is_dir); } if (error) return error; diff --git a/include/linux/fs.h b/include/linux/fs.h index 04ceeca12a0d..06632783a76c 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2657,6 +2657,9 @@ static inline int path_permission(const struct path *path, int mask) int __check_sticky(struct mnt_idmap *idmap, struct inode *dir, struct inode *inode); +int may_delete_dentry(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *victim, bool isdir); + static inline bool execute_ok(struct inode *inode) { return (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode); -- cgit v1.2.3 From 26aab3a485d500cb89ef7340797982bd066f63a5 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Tue, 13 Jan 2026 12:39:51 +0000 Subject: fs: export may_create() as may_create_dentry() For many years btrfs as been using a copy of may_create() in fs/btrfs/ioctl.c:btrfs_may_create(). Everytime may_create() is updated we need to update the btrfs copy, and this is a maintenance burden. Currently there are minor differences between both because the btrfs side lacks updates done in may_create(). Export may_create() so that btrfs can use it and with the less generic name may_create_dentry(). Signed-off-by: Filipe Manana Link: https://patch.msgid.link/ce5174bca079f4cdcbb8dd145f0924feb1f227cd.1768307858.git.fdmanana@suse.com Reviewed-by: David Sterba Signed-off-by: Christian Brauner --- fs/namei.c | 19 ++++++++++--------- include/linux/fs.h | 2 ++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 28aebc786e8f..676b8c016839 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3657,8 +3657,8 @@ EXPORT_SYMBOL(may_delete_dentry); * 4. We should have write and exec permissions on dir * 5. We can't do it if dir is immutable (done in permission()) */ -static inline int may_create(struct mnt_idmap *idmap, - struct inode *dir, struct dentry *child) +int may_create_dentry(struct mnt_idmap *idmap, + struct inode *dir, struct dentry *child) { audit_inode_child(dir, child, AUDIT_TYPE_CHILD_CREATE); if (child->d_inode) @@ -3670,6 +3670,7 @@ static inline int may_create(struct mnt_idmap *idmap, return inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC); } +EXPORT_SYMBOL(may_create_dentry); // p1 != p2, both are on the same filesystem, ->s_vfs_rename_mutex is held static struct dentry *lock_two_directories(struct dentry *p1, struct dentry *p2) @@ -4116,7 +4117,7 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode, struct inode *dir = d_inode(dentry->d_parent); int error; - error = may_create(idmap, dir, dentry); + error = may_create_dentry(idmap, dir, dentry); if (error) return error; @@ -4142,7 +4143,7 @@ int vfs_mkobj(struct dentry *dentry, umode_t mode, void *arg) { struct inode *dir = dentry->d_parent->d_inode; - int error = may_create(&nop_mnt_idmap, dir, dentry); + int error = may_create_dentry(&nop_mnt_idmap, dir, dentry); if (error) return error; @@ -4961,7 +4962,7 @@ int vfs_mknod(struct mnt_idmap *idmap, struct inode *dir, struct delegated_inode *delegated_inode) { bool is_whiteout = S_ISCHR(mode) && dev == WHITEOUT_DEV; - int error = may_create(idmap, dir, dentry); + int error = may_create_dentry(idmap, dir, dentry); if (error) return error; @@ -5107,7 +5108,7 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, unsigned max_links = dir->i_sb->s_max_links; struct dentry *de; - error = may_create(idmap, dir, dentry); + error = may_create_dentry(idmap, dir, dentry); if (error) goto err; @@ -5497,7 +5498,7 @@ int vfs_symlink(struct mnt_idmap *idmap, struct inode *dir, { int error; - error = may_create(idmap, dir, dentry); + error = may_create_dentry(idmap, dir, dentry); if (error) return error; @@ -5605,7 +5606,7 @@ int vfs_link(struct dentry *old_dentry, struct mnt_idmap *idmap, if (!inode) return -ENOENT; - error = may_create(idmap, dir, new_dentry); + error = may_create_dentry(idmap, dir, new_dentry); if (error) return error; @@ -5822,7 +5823,7 @@ int vfs_rename(struct renamedata *rd) return error; if (!target) { - error = may_create(rd->mnt_idmap, new_dir, new_dentry); + error = may_create_dentry(rd->mnt_idmap, new_dir, new_dentry); } else { new_is_dir = d_is_dir(new_dentry); diff --git a/include/linux/fs.h b/include/linux/fs.h index 06632783a76c..2d28eff6eb6a 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2659,6 +2659,8 @@ int __check_sticky(struct mnt_idmap *idmap, struct inode *dir, int may_delete_dentry(struct mnt_idmap *idmap, struct inode *dir, struct dentry *victim, bool isdir); +int may_create_dentry(struct mnt_idmap *idmap, + struct inode *dir, struct dentry *child); static inline bool execute_ok(struct inode *inode) { -- cgit v1.2.3 From 5f84a1092dee7b2687804d71c0dd50edd6f2d32a Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Tue, 13 Jan 2026 12:39:52 +0000 Subject: btrfs: use may_delete_dentry() in btrfs_ioctl_snap_destroy() There is no longer the need to use btrfs_may_delete(), which was a copy of the VFS private function may_delete(), since now that functionality is exported by the VFS as a function named may_delete_dentry(). In fact our local copy of may_delete() lacks an update that happened to that function which is point number 7 in that function's comment: "7. If the victim has an unknown uid or gid we can't change the inode." which corresponds to this code: /* Inode writeback is not safe when the uid or gid are invalid. */ if (!vfsuid_valid(i_uid_into_vfsuid(idmap, inode)) || !vfsgid_valid(i_gid_into_vfsgid(idmap, inode))) return -EOVERFLOW; As long as we keep a separate copy, duplicating code, we are also prone to updates to the VFS being missed in our local copy. So change btrfs_ioctl_snap_destroy() to use the VFS function and remove btrfs_may_delete(). Signed-off-by: Filipe Manana Link: https://patch.msgid.link/46b13dc5c957deb72a7f085916757a20878a8e73.1768307858.git.fdmanana@suse.com Reviewed-by: David Sterba Signed-off-by: Christian Brauner --- fs/btrfs/ioctl.c | 58 +------------------------------------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index acb484546b1d..2d0b2619309d 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -815,62 +815,6 @@ free_pending: return ret; } -/* copy of may_delete in fs/namei.c() - * Check whether we can remove a link victim from directory dir, check - * whether the type of victim is right. - * 1. We can't do it if dir is read-only (done in permission()) - * 2. We should have write and exec permissions on dir - * 3. We can't remove anything from append-only dir - * 4. We can't do anything with immutable dir (done in permission()) - * 5. If the sticky bit on dir is set we should either - * a. be owner of dir, or - * b. be owner of victim, or - * c. have CAP_FOWNER capability - * 6. If the victim is append-only or immutable we can't do anything with - * links pointing to it. - * 7. If we were asked to remove a directory and victim isn't one - ENOTDIR. - * 8. If we were asked to remove a non-directory and victim isn't one - EISDIR. - * 9. We can't remove a root or mountpoint. - * 10. We don't allow removal of NFS sillyrenamed files; it's handled by - * nfs_async_unlink(). - */ - -static int btrfs_may_delete(struct mnt_idmap *idmap, - struct inode *dir, struct dentry *victim, int isdir) -{ - int ret; - - if (d_really_is_negative(victim)) - return -ENOENT; - - /* The @victim is not inside @dir. */ - if (d_inode(victim->d_parent) != dir) - return -EINVAL; - audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE); - - ret = inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC); - if (ret) - return ret; - if (IS_APPEND(dir)) - return -EPERM; - if (check_sticky(idmap, dir, d_inode(victim)) || - IS_APPEND(d_inode(victim)) || IS_IMMUTABLE(d_inode(victim)) || - IS_SWAPFILE(d_inode(victim))) - return -EPERM; - if (isdir) { - if (!d_is_dir(victim)) - return -ENOTDIR; - if (IS_ROOT(victim)) - return -EBUSY; - } else if (d_is_dir(victim)) - return -EISDIR; - if (IS_DEADDIR(dir)) - return -ENOENT; - if (victim->d_flags & DCACHE_NFSFS_RENAMED) - return -EBUSY; - return 0; -} - /* copy of may_create in fs/namei.c() */ static inline int btrfs_may_create(struct mnt_idmap *idmap, struct inode *dir, const struct dentry *child) @@ -2420,7 +2364,7 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file, } /* check if subvolume may be deleted by a user */ - ret = btrfs_may_delete(idmap, dir, dentry, 1); + ret = may_delete_dentry(idmap, dir, dentry, true); if (ret) goto out_end_removing; -- cgit v1.2.3 From 6c91c776a92315a02e020fd558c5319864f1f104 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Tue, 13 Jan 2026 12:39:53 +0000 Subject: btrfs: use may_create_dentry() in btrfs_mksubvol() There is no longer the need to use btrfs_may_create(), which was a copy of the VFS private function may_create(), since now that functionality is exported by the VFS as a function named may_create_dentry(). So change btrfs_mksubvol() to use the VFS function and remove btrfs_may_create(). Note that the btrfs copy was missing an audit_inode_child() call that we have in the VFS function. This only reinforces the need to use a common function, as it's very easy for the btrfs copy to get out of sync and therefore a maintenance burden. Signed-off-by: Filipe Manana Link: https://patch.msgid.link/adf8c802c77ec1c855ea9fe12491120ccc29a294.1768307858.git.fdmanana@suse.com Reviewed-by: David Sterba Signed-off-by: Christian Brauner --- fs/btrfs/ioctl.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 2d0b2619309d..d1ab03691606 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -815,19 +815,6 @@ free_pending: return ret; } -/* copy of may_create in fs/namei.c() */ -static inline int btrfs_may_create(struct mnt_idmap *idmap, - struct inode *dir, const struct dentry *child) -{ - if (d_really_is_positive(child)) - return -EEXIST; - if (IS_DEADDIR(dir)) - return -ENOENT; - if (!fsuidgid_has_mapping(dir->i_sb, idmap)) - return -EOVERFLOW; - return inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC); -} - /* * Create a new subvolume below @parent. This is largely modeled after * sys_mkdirat and vfs_mkdir, but we only do a single component lookup @@ -849,7 +836,7 @@ static noinline int btrfs_mksubvol(struct dentry *parent, if (IS_ERR(dentry)) return PTR_ERR(dentry); - ret = btrfs_may_create(idmap, dir, dentry); + ret = may_create_dentry(idmap, dir, dentry); if (ret) goto out_dput; -- cgit v1.2.3