diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-06 13:44:44 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-06 13:44:44 +0100 |
| commit | af41be785702163f9e4e15810cfce8b8bb7fdad6 (patch) | |
| tree | e8af33a26a6e00ea1ab7a203abe9bbeb86338894 | |
| parent | 3b99a4c0a0441f358cae7f38c470c5d9991f3cf2 (diff) | |
| parent | cf6f88615485a68df77092de1f90f88708a32fa6 (diff) | |
| download | linux-next-af41be785702163f9e4e15810cfce8b8bb7fdad6.tar.gz linux-next-af41be785702163f9e4e15810cfce8b8bb7fdad6.zip | |
Merge branch 'vfs.all' of https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
147 files changed, 2512 insertions, 1981 deletions
diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 08d01bc62c31..c274c5eef733 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -61,7 +61,7 @@ inode_operations prototypes:: - int (*create) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t, bool); + int (*create) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t); struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); diff --git a/Documentation/filesystems/overlayfs.rst b/Documentation/filesystems/overlayfs.rst index eb846518e6ac..1a29a5afabd7 100644 --- a/Documentation/filesystems/overlayfs.rst +++ b/Documentation/filesystems/overlayfs.rst @@ -347,6 +347,22 @@ The resulting access permissions should be the same. The difference is in the time of copy (on-demand vs. up-front). +Idmapped mounts +--------------- + +The overlay mount itself can be turned into an idmapped mount by applying an +idmapping to it with mount_setattr(2) and MOUNT_ATTR_IDMAP, just like for +other filesystems that support idmapped mounts. + +The mount idmapping only changes how ownership and permissions of the overlay +inodes are presented to and interpreted for the caller. It does not change +how overlayfs accesses the underlying layers: those are still accessed with +the stashed mounter's credentials through their own mounts, which may +themselves be idmapped. The overlay mount idmapping and any layer idmapping +compose, an underlying id is first mapped according to the relevant layer +idmapping and then according to the overlay mount idmapping. + + Multiple lower layers --------------------- diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst index d13f0a23c882..02522fbfd968 100644 --- a/Documentation/filesystems/porting.rst +++ b/Documentation/filesystems/porting.rst @@ -1401,3 +1401,11 @@ as with d_dispose_if_unused() these are not trivial; with this variant of API it's more explicit, since grabbing ->d_lock is caller-side, but d_dispose_if_unused() had all the same issues. It's a low-level primitive; use only if you have no alternative. + +--- + +**mandatory** + +The .create inode_operation no longer receives the 'excl' arg. It must +always assume the file does not already exist. If the filesystem needs +to be involved in non-exclusive create, it should provide atomic_open. diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index 7c753148af88..651b83b00440 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -415,7 +415,7 @@ As of kernel 2.6.22, the following members are defined: .. code-block:: c struct inode_operations { - int (*create) (struct mnt_idmap *, struct inode *,struct dentry *, umode_t, bool); + int (*create) (struct mnt_idmap *, struct inode *,struct dentry *, umode_t); struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); diff --git a/MAINTAINERS b/MAINTAINERS index bfd9b39914ca..ce0d6fde885a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9494,11 +9494,6 @@ L: linux-fbdev@vger.kernel.org S: Maintained F: drivers/video/fbdev/efifb.c -EFS FILESYSTEM -S: Orphan -W: http://aeschi.ch.eu.org/efs/ -F: fs/efs/ - EHEA (IBM pSeries eHEA 10Gb ethernet adapter) DRIVER L: netdev@vger.kernel.org S: Orphan diff --git a/block/bdev.c b/block/bdev.c index 85ce57bd2ae4..28b0d40c362f 100644 --- a/block/bdev.c +++ b/block/bdev.c @@ -304,7 +304,12 @@ int bdev_freeze(struct block_device *bdev) mutex_lock(&bdev->bd_fsfreeze_mutex); - if (atomic_inc_return(&bdev->bd_fsfreeze_count) > 1) { + /* A device being removed from its filesystem refuses freezes. */ + if (!atomic_inc_unless_negative(&bdev->bd_fsfreeze_count)) { + mutex_unlock(&bdev->bd_fsfreeze_mutex); + return -EBUSY; + } + if (atomic_read(&bdev->bd_fsfreeze_count) > 1) { mutex_unlock(&bdev->bd_fsfreeze_mutex); return 0; } @@ -340,18 +345,18 @@ int bdev_thaw(struct block_device *bdev) mutex_lock(&bdev->bd_fsfreeze_mutex); - /* - * If this returns < 0 it means that @bd_fsfreeze_count was - * already 0 and no decrement was performed. - */ - nr_freeze = atomic_dec_if_positive(&bdev->bd_fsfreeze_count); - if (nr_freeze < 0) + /* <= 0: not frozen (0) or a freeze deny is held (< 0); leave it. */ + nr_freeze = atomic_read(&bdev->bd_fsfreeze_count); + if (nr_freeze <= 0) goto out; error = 0; - if (nr_freeze > 0) + if (nr_freeze > 1) { + atomic_dec(&bdev->bd_fsfreeze_count); goto out; + } + /* Keep the count positive across the thaw so a deny is refused. */ mutex_lock(&bdev->bd_holder_lock); if (bdev->bd_holder_ops && bdev->bd_holder_ops->thaw) { error = bdev->bd_holder_ops->thaw(bdev); @@ -360,14 +365,52 @@ int bdev_thaw(struct block_device *bdev) mutex_unlock(&bdev->bd_holder_lock); } - if (error) - atomic_inc(&bdev->bd_fsfreeze_count); + if (!error) + atomic_dec(&bdev->bd_fsfreeze_count); out: mutex_unlock(&bdev->bd_fsfreeze_mutex); return error; } EXPORT_SYMBOL(bdev_thaw); +/** + * bdev_deny_freeze - make a block device unfreezable + * @bdev: block device + * + * Reserve @bdev against bdev_freeze() the way deny_write_access() reserves a + * file against writers. bd_fsfreeze_count is sign-encoded: > 0 counts active + * freezes, < 0 counts deniers, so a deny succeeds only while no freeze is in + * progress. While held, bdev_freeze() returns -EBUSY. Pair with + * bdev_allow_freeze(). + * + * A filesystem removing, adding or replacing a member device denies freezes on + * it for the duration, so a claim a freeze walk might act on is never torn down + * behind the freezer's back. The deny is device-scoped, not (device, + * superblock)-scoped: a device shared by several superblocks is refused for all + * of them. No in-tree filesystem removes a shared claim from a live superblock. + * + * Return: 0, or -EBUSY if the device is currently frozen. + */ +int bdev_deny_freeze(struct block_device *bdev) +{ + return atomic_dec_unless_positive(&bdev->bd_fsfreeze_count) ? 0 : -EBUSY; +} +EXPORT_SYMBOL_GPL(bdev_deny_freeze); + +/** + * bdev_allow_freeze - allow freezing a block device again + * @bdev: block device + * + * Undo one bdev_deny_freeze(). + */ +void bdev_allow_freeze(struct block_device *bdev) +{ + /* A deny must be held, i.e. the count must be negative. */ + WARN_ON_ONCE(atomic_read(&bdev->bd_fsfreeze_count) >= 0); + atomic_inc(&bdev->bd_fsfreeze_count); +} +EXPORT_SYMBOL_GPL(bdev_allow_freeze); + /* * pseudo-fs */ @@ -1153,6 +1196,39 @@ put_no_open: } /** + * bdev_yield_claim - give up the holder claim on an open block device + * @bdev_file: open block device + * + * Yield the holder and any write access for @bdev_file without closing it, so + * the caller can still act on the device - e.g. bdev_allow_freeze() it - before + * the final bdev_fput(). bdev_fput() yields too, so calling it afterwards is + * safe. + */ +void bdev_yield_claim(struct file *bdev_file) +{ + struct block_device *bdev; + struct gendisk *disk; + + if (!bdev_file->private_data) + return; + + bdev = file_bdev(bdev_file); + disk = bdev->bd_disk; + + mutex_lock(&disk->open_mutex); + bdev_yield_write_access(bdev_file); + bd_yield_claim(bdev_file); + /* + * Tell release we already gave up our hold on the + * device and if write restrictions are available that + * we already gave up write access to the device. + */ + bdev_file->private_data = BDEV_I(bdev_file->f_mapping->host); + mutex_unlock(&disk->open_mutex); +} +EXPORT_SYMBOL_GPL(bdev_yield_claim); + +/** * bdev_fput - yield claim to the block device and put the file * @bdev_file: open block device * @@ -1165,22 +1241,7 @@ void bdev_fput(struct file *bdev_file) if (WARN_ON_ONCE(bdev_file->f_op != &def_blk_fops)) return; - if (bdev_file->private_data) { - struct block_device *bdev = file_bdev(bdev_file); - struct gendisk *disk = bdev->bd_disk; - - mutex_lock(&disk->open_mutex); - bdev_yield_write_access(bdev_file); - bd_yield_claim(bdev_file); - /* - * Tell release we already gave up our hold on the - * device and if write restrictions are available that - * we already gave up write access to the device. - */ - bdev_file->private_data = BDEV_I(bdev_file->f_mapping->host); - mutex_unlock(&disk->open_mutex); - } - + bdev_yield_claim(bdev_file); fput(bdev_file); } EXPORT_SYMBOL(bdev_fput); diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index b1c4ceb65026..aef0fcc6aba1 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -413,7 +413,7 @@ static noinline int __init devtmpfs_setup(void *p) { int err; - err = ksys_unshare(CLONE_NEWNS); + err = ksys_unshare(UNSHARE_EMPTY_MNTNS); if (err) goto out; err = init_mount("devtmpfs", "/", "devtmpfs", DEVTMPFS_MFLAGS, NULL); diff --git a/drivers/block/rnbd/rnbd-srv.c b/drivers/block/rnbd/rnbd-srv.c index 10e8c438bb43..79c9a5fb418f 100644 --- a/drivers/block/rnbd/rnbd-srv.c +++ b/drivers/block/rnbd/rnbd-srv.c @@ -11,6 +11,7 @@ #include <linux/module.h> #include <linux/blkdev.h> +#include <linux/fs_struct.h> #include "rnbd-srv.h" #include "rnbd-srv-trace.h" @@ -734,7 +735,8 @@ static int process_msg_open(struct rnbd_srv_session *srv_sess, goto reject; } - bdev_file = bdev_file_open_by_path(full_path, open_flags, NULL, NULL); + scoped_with_init_fs() + bdev_file = bdev_file_open_by_path(full_path, open_flags, NULL, NULL); if (IS_ERR(bdev_file)) { ret = PTR_ERR(bdev_file); pr_err("Opening device '%s' on session %s failed, failed to open the block device, err: %pe\n", diff --git a/drivers/char/misc_minor_kunit.c b/drivers/char/misc_minor_kunit.c index e930c78e1ef9..e85210cbb640 100644 --- a/drivers/char/misc_minor_kunit.c +++ b/drivers/char/misc_minor_kunit.c @@ -5,6 +5,7 @@ #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/file.h> +#include <linux/fs_struct.h> #include <linux/init_syscalls.h> /* static minor (LCD_MINOR) */ @@ -160,18 +161,22 @@ static void __init miscdev_test_can_open(struct kunit *test, struct miscdevice * char *devname; devname = kasprintf(GFP_KERNEL, "/dev/%s", misc->name); - ret = init_mknod(devname, S_IFCHR | 0600, - new_encode_dev(MKDEV(MISC_MAJOR, misc->minor))); - if (ret != 0) - KUNIT_FAIL(test, "failed to create node\n"); - filp = filp_open(devname, O_RDONLY, 0); - if (IS_ERR(filp)) - KUNIT_FAIL(test, "failed to open misc device: %ld\n", PTR_ERR(filp)); - else - fput(filp); + /* Tests run in a nullfs kthread; borrow the init fs to resolve /dev. */ + scoped_with_init_fs() { + ret = init_mknod(devname, S_IFCHR | 0600, + new_encode_dev(MKDEV(MISC_MAJOR, misc->minor))); + if (ret != 0) + KUNIT_FAIL(test, "failed to create node\n"); + + filp = filp_open(devname, O_RDONLY, 0); + if (IS_ERR(filp)) + KUNIT_FAIL(test, "failed to open misc device: %ld\n", PTR_ERR(filp)); + else + fput(filp); - init_unlink(devname); + init_unlink(devname); + } kfree(devname); } diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c index ca473ca198b8..0f72352030ba 100644 --- a/drivers/crypto/ccp/sev-dev.c +++ b/drivers/crypto/ccp/sev-dev.c @@ -260,20 +260,16 @@ static int sev_cmd_buffer_len(int cmd) static struct file *open_file_as_root(const char *filename, int flags, umode_t mode) { - struct path root __free(path_put) = {}; - - task_lock(&init_task); - get_fs_root(init_task.fs, &root); - task_unlock(&init_task); - CLASS(prepare_creds, cred)(); if (!cred) return ERR_PTR(-ENOMEM); cred->fsuid = GLOBAL_ROOT_UID; - scoped_with_creds(cred) - return file_open_root(&root, filename, flags, mode); + scoped_with_init_fs() { + scoped_with_creds(cred) + return filp_open(filename, flags, mode); + } } static int sev_read_init_ex_file(void) diff --git a/drivers/target/target_core_alua.c b/drivers/target/target_core_alua.c index 10250aca5a81..140154d93c43 100644 --- a/drivers/target/target_core_alua.c +++ b/drivers/target/target_core_alua.c @@ -18,6 +18,8 @@ #include <linux/fcntl.h> #include <linux/file.h> #include <linux/fs.h> +#include <linux/fs_struct.h> +#include <linux/kthread.h> #include <scsi/scsi_proto.h> #include <linux/unaligned.h> @@ -856,10 +858,17 @@ static int core_alua_write_tpg_metadata( unsigned char *md_buf, u32 md_buf_len) { - struct file *file = filp_open(path, O_RDWR | O_CREAT | O_TRUNC, 0600); + struct file *file; loff_t pos = 0; int ret; + if (tsk_is_kthread(current)) { + scoped_with_init_fs() + file = filp_open(path, O_RDWR | O_CREAT | O_TRUNC, 0600); + } else { + file = filp_open(path, O_RDWR | O_CREAT | O_TRUNC, 0600); + } + if (IS_ERR(file)) { pr_err("filp_open(%s) for ALUA metadata failed\n", path); return -ENODEV; diff --git a/drivers/target/target_core_pr.c b/drivers/target/target_core_pr.c index 11790f2c5d80..cfd949e7a095 100644 --- a/drivers/target/target_core_pr.c +++ b/drivers/target/target_core_pr.c @@ -18,6 +18,7 @@ #include <linux/file.h> #include <linux/fcntl.h> #include <linux/fs.h> +#include <linux/fs_struct.h> #include <scsi/scsi_proto.h> #include <linux/unaligned.h> @@ -1969,7 +1970,8 @@ static int __core_scsi3_write_aptpl_to_file( if (!path) return -ENOMEM; - file = filp_open(path, flags, 0600); + scoped_with_init_fs() + file = filp_open(path, flags, 0600); if (IS_ERR(file)) { pr_err("filp_open(%s) for APTPL metadata" " failed\n", path); diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 5783d0336f96..3829554ca369 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -645,7 +645,6 @@ error: * @dir: The parent directory * @dentry: The name of file to be created * @mode: The UNIX file mode to set - * @excl: True if the file must not yet exist * * open(.., O_CREAT) is handled in v9fs_vfs_atomic_open(). This is only called * for mknod(2). @@ -654,7 +653,7 @@ error: static int v9fs_vfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dir); u32 perm = unixmode2p9mode(v9ses, mode); @@ -689,7 +688,7 @@ static struct dentry *v9fs_vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, p9_debug(P9_DEBUG_VFS, "name %pd\n", dentry); v9ses = v9fs_inode2v9ses(dir); - perm = unixmode2p9mode(v9ses, mode | S_IFDIR); + perm = unixmode2p9mode(v9ses, mode); fid = v9fs_create(v9ses, dir, dentry, NULL, perm, P9_OREAD); if (IS_ERR(fid)) return ERR_CAST(fid); diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index f7396d20cb6c..116b29e95f21 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -213,12 +213,11 @@ int v9fs_open_to_dotl_flags(int flags) * @dir: directory inode that is being created * @dentry: dentry that is being deleted * @omode: create permissions - * @excl: True if the file must not yet exist * */ static int v9fs_vfs_create_dotl(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t omode, bool excl) + struct dentry *dentry, umode_t omode) { return v9fs_vfs_mknod_dotl(idmap, dir, dentry, omode, 0); } @@ -362,7 +361,6 @@ static struct dentry *v9fs_vfs_mkdir_dotl(struct mnt_idmap *idmap, p9_debug(P9_DEBUG_VFS, "name %pd\n", dentry); v9ses = v9fs_inode2v9ses(dir); - omode |= S_IFDIR; if (dir->i_mode & S_ISGID) omode |= S_ISGID; diff --git a/fs/Kconfig b/fs/Kconfig index cf6ae64776e6..64ff193fb42c 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -315,7 +315,6 @@ source "fs/hfs/Kconfig" source "fs/hfsplus/Kconfig" source "fs/befs/Kconfig" source "fs/bfs/Kconfig" -source "fs/efs/Kconfig" source "fs/jffs2/Kconfig" # UBIFS File system configuration source "fs/ubifs/Kconfig" diff --git a/fs/Makefile b/fs/Makefile index 89a8a9d207d1..aa847be93bc6 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -92,7 +92,6 @@ obj-$(CONFIG_HPFS_FS) += hpfs/ obj-$(CONFIG_NTFS_FS) += ntfs/ obj-$(CONFIG_NTFS3_FS) += ntfs3/ obj-$(CONFIG_UFS_FS) += ufs/ -obj-$(CONFIG_EFS_FS) += efs/ obj-$(CONFIG_JFFS2_FS) += jffs2/ obj-$(CONFIG_UBIFS_FS) += ubifs/ obj-$(CONFIG_AFFS_FS) += affs/ diff --git a/fs/affs/affs.h b/fs/affs/affs.h index 44a3f69d275f..d6b3393633f2 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -169,7 +169,7 @@ extern int affs_hash_name(struct super_block *sb, const u8 *name, unsigned int l extern struct dentry *affs_lookup(struct inode *dir, struct dentry *dentry, unsigned int); extern int affs_unlink(struct inode *dir, struct dentry *dentry); extern int affs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool); + struct dentry *dentry, umode_t mode); extern struct dentry *affs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); extern int affs_rmdir(struct inode *dir, struct dentry *dentry); diff --git a/fs/affs/namei.c b/fs/affs/namei.c index c3c6532da4b0..5311828b19e8 100644 --- a/fs/affs/namei.c +++ b/fs/affs/namei.c @@ -243,7 +243,7 @@ affs_unlink(struct inode *dir, struct dentry *dentry) int affs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode; @@ -287,7 +287,7 @@ affs_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (!inode) return ERR_PTR(-ENOSPC); - inode->i_mode = S_IFDIR | mode; + inode->i_mode = mode; affs_mode_to_prot(inode); inode->i_op = &affs_dir_inode_operations; diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 6df56fe9163f..81565366d937 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -34,7 +34,7 @@ static bool afs_lookup_filldir(struct dir_context *ctx, const char *name, int nl u64 ino, u32 uniquifier); #define AFS_LOOKUP ((filldir_t)0x137UL) static int afs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl); + struct dentry *dentry, umode_t mode); static struct dentry *afs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); static int afs_rmdir(struct inode *dir, struct dentry *dentry); @@ -1333,7 +1333,7 @@ static struct dentry *afs_mkdir(struct mnt_idmap *idmap, struct inode *dir, op->file[0].modification = true; op->file[0].update_ctime = true; op->dentry = dentry; - op->create.mode = S_IFDIR | mode; + op->create.mode = mode; op->create.reason = afs_edit_dir_for_mkdir; op->mtime = current_time(dir); op->ops = &afs_mkdir_operation; @@ -1633,7 +1633,7 @@ static const struct afs_operation_ops afs_create_operation = { * create a regular file on an AFS filesystem */ static int afs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct afs_operation *op; struct afs_vnode *dvnode = AFS_FS_I(dir); diff --git a/fs/autofs/root.c b/fs/autofs/root.c index 186e960f1e23..b36439f4521e 100644 --- a/fs/autofs/root.c +++ b/fs/autofs/root.c @@ -741,7 +741,7 @@ static struct dentry *autofs_dir_mkdir(struct mnt_idmap *idmap, autofs_del_active(dentry); - inode = autofs_get_inode(dir->i_sb, S_IFDIR | mode); + inode = autofs_get_inode(dir->i_sb, mode); if (!inode) return ERR_PTR(-ENOMEM); diff --git a/fs/bad_inode.c b/fs/bad_inode.c index acf8613f5e36..486c40f73e51 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -29,7 +29,7 @@ static const struct file_operations bad_file_ops = static int bad_inode_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, bool excl) + umode_t mode) { return -EIO; } diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c index 5b40ab09a796..4c3b4db08cde 100644 --- a/fs/bfs/dir.c +++ b/fs/bfs/dir.c @@ -83,7 +83,7 @@ const struct file_operations bfs_dir_operations = { }; static int bfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { int err; struct inode *inode; diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index f1863a891db6..6efe3e53c742 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -11,6 +11,7 @@ #include <linux/file.h> #include <linux/kernfs.h> #include <linux/mm.h> +#include <linux/net.h> #include <linux/xattr.h> __bpf_kfunc_start_defs(); @@ -359,6 +360,39 @@ __bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__s } #endif /* CONFIG_CGROUPS */ +#ifdef CONFIG_NET +/** + * bpf_sock_read_xattr - read xattr of a socket's inode in sockfs + * @sock: socket to get xattr from + * @name__str: name of the xattr + * @value_p: output buffer of the xattr value + * + * Get xattr *name__str* of *sock* and store the output in *value_p*. + * + * For security reasons, only *name__str* with prefix "user." is allowed. + * + * Return: length of the xattr value on success, a negative value on error. + */ +__bpf_kfunc int bpf_sock_read_xattr(struct socket *sock, const char *name__str, + struct bpf_dynptr *value_p) +{ + struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p; + u32 value_len; + void *value; + + /* Only allow reading "user.*" xattrs */ + if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) + return -EPERM; + + value_len = __bpf_dynptr_size(value_ptr); + value = __bpf_dynptr_data_rw(value_ptr, value_len); + if (!value) + return -EINVAL; + + return sock_read_xattr(sock, name__str, value, value_len); +} +#endif /* CONFIG_NET */ + /** * bpf_real_data_inode - get the real inode hosting a file's data * @file: file to resolve @@ -390,6 +424,9 @@ BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_real_data_inode, KF_SLEEPABLE | KF_RET_NULL) +#ifdef CONFIG_NET +BTF_ID_FLAGS(func, bpf_sock_read_xattr, KF_RCU) +#endif BTF_KFUNCS_END(bpf_fs_kfunc_set_ids) static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id) diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c index 318ddb790429..dc0834f920c3 100644 --- a/fs/btrfs/dev-replace.c +++ b/fs/btrfs/dev-replace.c @@ -247,8 +247,8 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info, return -EINVAL; } - bdev_file = bdev_file_open_by_path(device_path, BLK_OPEN_WRITE, - fs_info->sb, &fs_holder_ops); + /* Unfreezable for the whole replace; see btrfs_dev_replace_start(). */ + bdev_file = btrfs_open_device_deny_freeze(device_path, fs_info->sb); if (IS_ERR(bdev_file)) { btrfs_err(fs_info, "target device %s is invalid!", device_path); return PTR_ERR(bdev_file); @@ -327,7 +327,8 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info, return 0; error: - bdev_fput(bdev_file); + /* Undo the open-time freeze deny. */ + btrfs_release_device_allow_freeze(bdev_file); return ret; } @@ -624,6 +625,15 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info, if (ret) return ret; + /* Deny the source before mark, so every 'leave' unwinds both denied. */ + if (src_device->bdev) { + ret = bdev_deny_freeze(src_device->bdev); + if (ret) { + btrfs_destroy_dev_replace_tgtdev(tgt_device, true); + return ret; + } + } + ret = mark_block_group_to_copy(fs_info, src_device); if (ret) return ret; @@ -708,7 +718,9 @@ static int btrfs_dev_replace_start(struct btrfs_fs_info *fs_info, return ret; leave: - btrfs_destroy_dev_replace_tgtdev(tgt_device); + if (src_device->bdev) + bdev_allow_freeze(src_device->bdev); + btrfs_destroy_dev_replace_tgtdev(tgt_device, true); return ret; } @@ -889,6 +901,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info, */ ret = btrfs_start_delalloc_roots(fs_info, LONG_MAX, false); if (ret) { + /* Stays started/resumable; keep both denied. */ mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); return ret; } @@ -902,6 +915,7 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info, while (1) { trans = btrfs_start_transaction(root, 0); if (IS_ERR(trans)) { + /* Stays started/resumable; keep both denied. */ mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); return PTR_ERR(trans); } @@ -954,7 +968,10 @@ error: mutex_unlock(&fs_devices->device_list_mutex); btrfs_rm_dev_replace_blocked(fs_info); if (tgt_device) - btrfs_destroy_dev_replace_tgtdev(tgt_device); + btrfs_destroy_dev_replace_tgtdev(tgt_device, true); + /* The source stays a member; re-allow freezing it. */ + if (src_device->bdev) + bdev_allow_freeze(src_device->bdev); btrfs_rm_dev_replace_unblocked(fs_info); mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); @@ -1027,6 +1044,8 @@ error: mutex_unlock(&dev_replace->lock_finishing_cancel_unmount); + /* The target is now a member; the source is freed (allow + release). */ + bdev_allow_freeze(tgt_device->bdev); btrfs_rm_dev_replace_free_srcdev(src_device); return 0; @@ -1155,8 +1174,9 @@ int btrfs_dev_replace_cancel(struct btrfs_fs_info *fs_info) btrfs_dev_name(src_device), src_device->devid, btrfs_dev_name(tgt_device)); + /* A suspended replace never re-denied freezing; do not allow. */ if (tgt_device) - btrfs_destroy_dev_replace_tgtdev(tgt_device); + btrfs_destroy_dev_replace_tgtdev(tgt_device, false); break; default: up_write(&dev_replace->rwsem); @@ -1186,6 +1206,11 @@ void btrfs_dev_replace_suspend_for_unmount(struct btrfs_fs_info *fs_info) dev_replace->time_stopped = ktime_get_real_seconds(); dev_replace->item_needs_writeback = 1; btrfs_info(fs_info, "suspending dev_replace for unmount"); + /* Reopened freezable next mount; resume re-denies. */ + if (dev_replace->srcdev && dev_replace->srcdev->bdev) + bdev_allow_freeze(dev_replace->srcdev->bdev); + if (dev_replace->tgtdev && dev_replace->tgtdev->bdev) + bdev_allow_freeze(dev_replace->tgtdev->bdev); break; } @@ -1198,6 +1223,7 @@ int btrfs_resume_dev_replace_async(struct btrfs_fs_info *fs_info) { struct task_struct *task; struct btrfs_dev_replace *dev_replace = &fs_info->dev_replace; + int ret = 0; down_write(&dev_replace->rwsem); @@ -1241,8 +1267,33 @@ int btrfs_resume_dev_replace_async(struct btrfs_fs_info *fs_info) return 0; } + /* Re-deny for the resumed replace; stay suspended if frozen now. */ + if (dev_replace->srcdev->bdev && + bdev_deny_freeze(dev_replace->srcdev->bdev)) + goto suspend; + if (bdev_deny_freeze(dev_replace->tgtdev->bdev)) { + if (dev_replace->srcdev->bdev) + bdev_allow_freeze(dev_replace->srcdev->bdev); + goto suspend; + } + task = kthread_run(btrfs_dev_replace_kthread, fs_info, "btrfs-devrepl"); - return PTR_ERR_OR_ZERO(task); + if (IS_ERR(task)) { + bdev_allow_freeze(dev_replace->tgtdev->bdev); + if (dev_replace->srcdev->bdev) + bdev_allow_freeze(dev_replace->srcdev->bdev); + /* Undo the deny and suspend, but still fail the mount. */ + ret = PTR_ERR(task); + goto suspend; + } + return 0; + +suspend: + btrfs_exclop_finish(fs_info); + down_write(&dev_replace->rwsem); + dev_replace->replace_state = BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED; + up_write(&dev_replace->rwsem); + return ret; } static int btrfs_dev_replace_kthread(void *data) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 272598f6ae77..b618ab18b733 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -6832,7 +6832,7 @@ static int btrfs_mknod(struct mnt_idmap *idmap, struct inode *dir, } static int btrfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; @@ -6936,7 +6936,7 @@ static struct dentry *btrfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, inode = new_inode(dir->i_sb); if (!inode) return ERR_PTR(-ENOMEM); - inode_init_owner(idmap, inode, dir, S_IFDIR | mode); + inode_init_owner(idmap, inode, dir, mode); inode->i_op = &btrfs_dir_inode_operations; inode->i_fop = &btrfs_dir_file_operations; return ERR_PTR(btrfs_create_common(dir, dentry, inode)); diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 81e87bc39828..6b6520997da5 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2628,7 +2628,7 @@ static long btrfs_ioctl_rm_dev_v2(struct file *file, void __user *arg) err_drop: mnt_drop_write_file(file); if (bdev_file) - bdev_fput(bdev_file); + btrfs_release_device_allow_freeze(bdev_file); out: btrfs_put_dev_args_from_path(&args); return ret; @@ -2678,7 +2678,7 @@ static long btrfs_ioctl_rm_dev(struct file *file, void __user *arg) mnt_drop_write_file(file); if (bdev_file) - bdev_fput(bdev_file); + btrfs_release_device_allow_freeze(bdev_file); out: btrfs_put_dev_args_from_path(&args); return ret; diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 6eab4cc73ce4..e68ce323bb06 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -12,6 +12,7 @@ #include <linux/uuid.h> #include <linux/list_sort.h> #include <linux/namei.h> +#include <linux/fs_struct.h> #include "misc.h" #include "disk-io.h" #include "extent-tree.h" @@ -480,7 +481,12 @@ btrfs_get_bdev_and_sb(const char *device_path, blk_mode_t flags, void *holder, struct block_device *bdev; int ret; - *bdev_file = bdev_file_open_by_path(device_path, flags, holder, &fs_holder_ops); + if (holder) + *bdev_file = fs_bdev_file_open_by_path(device_path, flags, + holder, holder); + else + *bdev_file = bdev_file_open_by_path(device_path, flags, NULL, + NULL); if (IS_ERR(*bdev_file)) { ret = PTR_ERR(*bdev_file); @@ -495,7 +501,7 @@ btrfs_get_bdev_and_sb(const char *device_path, blk_mode_t flags, void *holder, if (holder) { ret = set_blocksize(*bdev_file, BTRFS_BDEV_BLOCKSIZE); if (ret) { - bdev_fput(*bdev_file); + fs_bdev_file_release(*bdev_file, holder); goto error; } } @@ -503,7 +509,10 @@ btrfs_get_bdev_and_sb(const char *device_path, blk_mode_t flags, void *holder, *disk_super = btrfs_read_disk_super(bdev, 0, false); if (IS_ERR(*disk_super)) { ret = PTR_ERR(*disk_super); - bdev_fput(*bdev_file); + if (holder) + fs_bdev_file_release(*bdev_file, holder); + else + bdev_fput(*bdev_file); goto error; } @@ -727,7 +736,7 @@ static int btrfs_open_one_device(struct btrfs_fs_devices *fs_devices, error_free_page: btrfs_release_disk_super(disk_super); - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, holder); return -EINVAL; } @@ -1087,7 +1096,7 @@ static void __btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices, continue; if (device->bdev_file) { - bdev_fput(device->bdev_file); + fs_bdev_file_release(device->bdev_file, device->bdev_file->private_data); device->bdev = NULL; device->bdev_file = NULL; fs_devices->open_devices--; @@ -1124,7 +1133,18 @@ void btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices) mutex_unlock(&uuid_mutex); } -static void btrfs_close_bdev(struct btrfs_device *device) +/* Release a device that was made unfreezable for a membership change. */ +void btrfs_release_device_allow_freeze(struct file *bdev_file) +{ + struct super_block *sb = bdev_file->private_data; + + /* Unregister before re-allowing (strand-safe); file still open (UAF-safe). */ + fs_bdev_unregister(bdev_file, sb); + bdev_allow_freeze(file_bdev(bdev_file)); + bdev_fput(bdev_file); +} + +static void btrfs_close_bdev(struct btrfs_device *device, bool allow_freeze) { if (!device->bdev) return; @@ -1134,7 +1154,12 @@ static void btrfs_close_bdev(struct btrfs_device *device) invalidate_bdev(device->bdev); } - bdev_fput(device->bdev_file); + /* @allow_freeze undoes a replace-time deny; unmount-close was never denied. */ + if (allow_freeze) + btrfs_release_device_allow_freeze(device->bdev_file); + else + fs_bdev_file_release(device->bdev_file, + device->bdev_file->private_data); } static void btrfs_close_one_device(struct btrfs_device *device) @@ -1155,7 +1180,7 @@ static void btrfs_close_one_device(struct btrfs_device *device) fs_devices->missing_devices--; } - btrfs_close_bdev(device); + btrfs_close_bdev(device, false); if (device->bdev) { fs_devices->open_devices--; device->bdev = NULL; @@ -2125,8 +2150,16 @@ static int btrfs_add_dev_item(struct btrfs_trans_handle *trans, static void update_dev_time(const char *device_path) { struct path path; + int err; + + if (tsk_is_kthread(current)) { + scoped_with_init_fs() + err = kern_path(device_path, LOOKUP_FOLLOW, &path); + } else { + err = kern_path(device_path, LOOKUP_FOLLOW, &path); + } - if (!kern_path(device_path, LOOKUP_FOLLOW, &path)) { + if (!err) { vfs_utimes(&path, NULL); path_put(&path); } @@ -2373,6 +2406,13 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info, fs_info->fs_devices->rw_devices == 1) return BTRFS_ERROR_DEV_ONLY_WRITABLE; + /* Removal and freezing are mutually exclusive; refuse if frozen now. */ + if (device->bdev) { + ret = bdev_deny_freeze(device->bdev); + if (ret) + return ret; + } + if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) { mutex_lock(&fs_info->chunk_mutex); list_del_init(&device->dev_alloc_list); @@ -2399,6 +2439,8 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info, device->devid, ret); btrfs_abort_transaction(trans, ret); btrfs_end_transaction(trans); + if (device->bdev) + bdev_allow_freeze(device->bdev); return ret; } @@ -2490,6 +2532,8 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info, return btrfs_commit_transaction(trans); error_undo: + if (device->bdev) + bdev_allow_freeze(device->bdev); if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) { mutex_lock(&fs_info->chunk_mutex); list_add(&device->dev_alloc_list, @@ -2534,7 +2578,8 @@ void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev) mutex_lock(&uuid_mutex); - btrfs_close_bdev(srcdev); + /* The source was made unfreezable for the replace; undo it. */ + btrfs_close_bdev(srcdev, true); synchronize_rcu(); btrfs_free_device(srcdev); @@ -2555,7 +2600,8 @@ void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev) mutex_unlock(&uuid_mutex); } -void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev) +void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev, + bool allow_freeze) { struct btrfs_fs_devices *fs_devices = tgtdev->fs_info->fs_devices; @@ -2576,7 +2622,7 @@ void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev) btrfs_scratch_superblocks(tgtdev->fs_info, tgtdev); - btrfs_close_bdev(tgtdev); + btrfs_close_bdev(tgtdev, allow_freeze); synchronize_rcu(); btrfs_free_device(tgtdev); } @@ -2845,6 +2891,37 @@ next_slot: return 0; } +/* + * Open @path for @sb with freezing denied before the holder claim is published, + * so a racing bdev_freeze() can never reach a claim a device add or replace may + * still abort. The deny is taken on a throwaway non-holder probe open, then the + * holder is opened by the probe's dev_t. Balanced by the caller. + */ +struct file *btrfs_open_device_deny_freeze(const char *path, + struct super_block *sb) +{ + struct file *probe_file, *bdev_file; + int ret; + + /* WRITE so bdev_file_open_by_path() rejects a read-only device. */ + probe_file = bdev_file_open_by_path(path, BLK_OPEN_WRITE, NULL, NULL); + if (IS_ERR(probe_file)) + return probe_file; + + ret = bdev_deny_freeze(file_bdev(probe_file)); + if (ret) { + bdev_fput(probe_file); + return ERR_PTR(ret); + } + + bdev_file = fs_bdev_file_open_by_dev(file_bdev(probe_file)->bd_dev, + BLK_OPEN_WRITE, sb, sb); + if (IS_ERR(bdev_file)) + bdev_allow_freeze(file_bdev(probe_file)); + bdev_fput(probe_file); + return bdev_file; +} + int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path) { struct btrfs_root *root = fs_info->dev_root; @@ -2863,8 +2940,8 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path if (sb_rdonly(sb) && !fs_devices->seeding) return -EROFS; - bdev_file = bdev_file_open_by_path(device_path, BLK_OPEN_WRITE, - fs_info->sb, &fs_holder_ops); + /* Forbid freezing until the device is a committed member (or unwound). */ + bdev_file = btrfs_open_device_deny_freeze(device_path, fs_info->sb); if (IS_ERR(bdev_file)) return PTR_ERR(bdev_file); @@ -3035,8 +3112,10 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path up_write(&sb->s_umount); locked = false; - if (ret) /* transaction commit */ + if (ret) { /* transaction commit */ + bdev_allow_freeze(file_bdev(bdev_file)); return ret; + } ret = btrfs_relocate_sys_chunks(fs_info); if (ret < 0) @@ -3044,8 +3123,10 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path "Failed to relocate sys chunks after device initialization. This can be fixed using the \"btrfs balance\" command."); trans = btrfs_attach_transaction(root); if (IS_ERR(trans)) { - if (PTR_ERR(trans) == -ENOENT) + if (PTR_ERR(trans) == -ENOENT) { + bdev_allow_freeze(file_bdev(bdev_file)); return 0; + } ret = PTR_ERR(trans); trans = NULL; goto error_sysfs; @@ -3065,6 +3146,7 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path /* Update ctime/mtime for blkid or udev */ update_dev_time(device_path); + bdev_allow_freeze(file_bdev(bdev_file)); return ret; error_sysfs: @@ -3094,7 +3176,7 @@ error_free_zone: error_free_device: btrfs_free_device(device); error: - bdev_fput(bdev_file); + btrfs_release_device_allow_freeze(bdev_file); if (locked) { mutex_unlock(&uuid_mutex); up_write(&sb->s_umount); diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h index 63be45c3298c..df2c671ab6fa 100644 --- a/fs/btrfs/volumes.h +++ b/fs/btrfs/volumes.h @@ -744,6 +744,7 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices, struct btrfs_device *btrfs_scan_one_device(const char *path, bool mount_arg_dev); int btrfs_forget_devices(dev_t devt); void btrfs_close_devices(struct btrfs_fs_devices *fs_devices); +void btrfs_release_device_allow_freeze(struct file *bdev_file); void btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices); void btrfs_assign_next_active_device(struct btrfs_device *device, struct btrfs_device *this_dev); @@ -768,6 +769,8 @@ struct btrfs_device *btrfs_find_device(const struct btrfs_fs_devices *fs_devices const struct btrfs_dev_lookup_args *args); int btrfs_shrink_device(struct btrfs_device *device, u64 new_size); int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *path); +struct file *btrfs_open_device_deny_freeze(const char *path, + struct super_block *sb); int btrfs_balance(struct btrfs_fs_info *fs_info, struct btrfs_balance_control *bctl, struct btrfs_ioctl_balance_args *bargs); @@ -788,7 +791,8 @@ int btrfs_init_writeback_bio_size(struct btrfs_fs_info *fs_info); int btrfs_run_dev_stats(struct btrfs_trans_handle *trans); void btrfs_rm_dev_replace_remove_srcdev(struct btrfs_device *srcdev); void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev); -void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev); +void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev, + bool allow_freeze); unsigned long btrfs_full_stripe_len(struct btrfs_fs_info *fs_info, u64 logical); u64 btrfs_calc_stripe_length(const struct btrfs_chunk_map *map); diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index 27ce9e55e947..b957149abe5a 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -978,7 +978,7 @@ out: } static int ceph_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return ceph_mknod(idmap, dir, dentry, mode, 0); } @@ -1142,7 +1142,6 @@ static struct dentry *ceph_mkdir(struct mnt_idmap *idmap, struct inode *dir, goto out; } - mode |= S_IFDIR; req->r_new_inode = ceph_new_inode(dir, dentry, &mode, &as_ctx); if (IS_ERR(req->r_new_inode)) { ret = ERR_CAST(req->r_new_inode); diff --git a/fs/coda/dir.c b/fs/coda/dir.c index 835eb7fdfdad..67148edfadee 100644 --- a/fs/coda/dir.c +++ b/fs/coda/dir.c @@ -134,7 +134,7 @@ static inline void coda_dir_drop_nlink(struct inode *dir) /* creation routines: create, mknod, mkdir, link, symlink */ static int coda_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *de, umode_t mode, bool excl) + struct dentry *de, umode_t mode) { int error; const char *name=de->d_name.name; @@ -179,7 +179,12 @@ static struct dentry *coda_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (is_root_inode(dir) && coda_iscontrol(name, len)) return ERR_PTR(-EPERM); - attrs.va_mode = mode; + /* + * vfs_mkdir() now passes S_IFDIR in @mode, but @mode is forwarded + * verbatim to userspace, which has only ever been given the permission + * bits. Strip the type bit until venus is known to cope with it. + */ + attrs.va_mode = mode & ~S_IFDIR; error = venus_mkdir(dir->i_sb, coda_i2f(dir), name, len, &newfid, &attrs); if (error) diff --git a/fs/coredump.c b/fs/coredump.c index e68a76ff92a3..ac3cd74808c6 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -921,15 +921,10 @@ static bool coredump_file(struct core_name *cn, struct coredump_params *cprm, * with a fully qualified path" rule is to control where * coredumps may be placed using root privileges, * current->fs->root must not be used. Instead, use the - * root directory of init_task. + * root directory of PID 1. */ - struct path root; - - task_lock(&init_task); - get_fs_root(init_task.fs, &root); - task_unlock(&init_task); - file = file_open_root(&root, cn->corename, open_flags, 0600); - path_put(&root); + scoped_with_init_fs() + file = filp_open(cn->corename, open_flags, 0600); } else { file = filp_open(cn->corename, open_flags, 0600); } diff --git a/fs/cramfs/inode.c b/fs/cramfs/inode.c index 4edbfccd0bbe..d4cd03f4f60d 100644 --- a/fs/cramfs/inode.c +++ b/fs/cramfs/inode.c @@ -504,7 +504,7 @@ static void cramfs_kill_sb(struct super_block *sb) sb->s_mtd = NULL; } else if (IS_ENABLED(CONFIG_CRAMFS_BLOCKDEV) && sb->s_bdev) { sync_blockdev(sb->s_bdev); - bdev_fput(sb->s_bdev_file); + fs_bdev_file_release(sb->s_bdev_file, sb); } kfree(sbi); } diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 7aaf1913f9c6..525297c7ebd8 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -268,7 +268,7 @@ out: static int ecryptfs_create(struct mnt_idmap *idmap, struct inode *directory_inode, struct dentry *ecryptfs_dentry, - umode_t mode, bool excl) + umode_t mode) { struct inode *ecryptfs_inode; int rc; diff --git a/fs/efivarfs/inode.c b/fs/efivarfs/inode.c index 95dcad83da11..f0d009555fc6 100644 --- a/fs/efivarfs/inode.c +++ b/fs/efivarfs/inode.c @@ -75,7 +75,7 @@ static bool efivarfs_valid_name(const char *str, int len) } static int efivarfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode = NULL; struct efivar_entry *var; diff --git a/fs/efs/Kconfig b/fs/efs/Kconfig deleted file mode 100644 index 0833e533df9d..000000000000 --- a/fs/efs/Kconfig +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -config EFS_FS - tristate "EFS file system support (read only)" - depends on BLOCK - select BUFFER_HEAD - help - EFS is an older file system used for non-ISO9660 CD-ROMs and hard - disk partitions by SGI's IRIX operating system (IRIX 6.0 and newer - uses the XFS file system for hard disk partitions however). - - This implementation only offers read-only access. If you don't know - what all this is about, it's safe to say N. For more information - about EFS see its home page at <http://aeschi.ch.eu.org/efs/>. - - To compile the EFS file system support as a module, choose M here: the - module will be called efs. diff --git a/fs/efs/Makefile b/fs/efs/Makefile deleted file mode 100644 index 85e5b88f9471..000000000000 --- a/fs/efs/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Makefile for the linux efs-filesystem routines. -# - -obj-$(CONFIG_EFS_FS) += efs.o - -efs-objs := super.o inode.o namei.o dir.o file.o symlink.o diff --git a/fs/efs/dir.c b/fs/efs/dir.c deleted file mode 100644 index 35ad0092c115..000000000000 --- a/fs/efs/dir.c +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * dir.c - * - * Copyright (c) 1999 Al Smith - */ - -#include <linux/buffer_head.h> -#include <linux/filelock.h> -#include "efs.h" - -static int efs_readdir(struct file *, struct dir_context *); - -const struct file_operations efs_dir_operations = { - .llseek = generic_file_llseek, - .read = generic_read_dir, - .iterate_shared = efs_readdir, - .setlease = generic_setlease, -}; - -const struct inode_operations efs_dir_inode_operations = { - .lookup = efs_lookup, -}; - -static int efs_readdir(struct file *file, struct dir_context *ctx) -{ - struct inode *inode = file_inode(file); - efs_block_t block; - int slot; - - if (inode->i_size & (EFS_DIRBSIZE-1)) - pr_warn("%s(): directory size not a multiple of EFS_DIRBSIZE\n", - __func__); - - /* work out where this entry can be found */ - block = ctx->pos >> EFS_DIRBSIZE_BITS; - - /* each block contains at most 256 slots */ - slot = ctx->pos & 0xff; - - /* look at all blocks */ - while (block < inode->i_blocks) { - struct efs_dir *dirblock; - struct buffer_head *bh; - - /* read the dir block */ - bh = sb_bread(inode->i_sb, efs_bmap(inode, block)); - - if (!bh) { - pr_err("%s(): failed to read dir block %d\n", - __func__, block); - break; - } - - dirblock = (struct efs_dir *) bh->b_data; - - if (be16_to_cpu(dirblock->magic) != EFS_DIRBLK_MAGIC) { - pr_err("%s(): invalid directory block\n", __func__); - brelse(bh); - break; - } - - for (; slot < dirblock->slots; slot++) { - struct efs_dentry *dirslot; - efs_ino_t inodenum; - const char *nameptr; - int namelen; - - if (dirblock->space[slot] == 0) - continue; - - dirslot = (struct efs_dentry *) (((char *) bh->b_data) + EFS_SLOTAT(dirblock, slot)); - - inodenum = be32_to_cpu(dirslot->inode); - namelen = dirslot->namelen; - nameptr = dirslot->name; - pr_debug("%s(): block %d slot %d/%d: inode %u, name \"%s\", namelen %u\n", - __func__, block, slot, dirblock->slots-1, - inodenum, nameptr, namelen); - if (!namelen) - continue; - /* found the next entry */ - ctx->pos = (block << EFS_DIRBSIZE_BITS) | slot; - - /* sanity check */ - if (nameptr - (char *) dirblock + namelen > EFS_DIRBSIZE) { - pr_warn("directory entry %d exceeds directory block\n", - slot); - continue; - } - - /* copy filename and data in dirslot */ - if (!dir_emit(ctx, nameptr, namelen, inodenum, DT_UNKNOWN)) { - brelse(bh); - return 0; - } - } - brelse(bh); - - slot = 0; - block++; - } - ctx->pos = (block << EFS_DIRBSIZE_BITS) | slot; - return 0; -} diff --git a/fs/efs/efs.h b/fs/efs/efs.h deleted file mode 100644 index 918d2b9abb76..000000000000 --- a/fs/efs/efs.h +++ /dev/null @@ -1,144 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (c) 1999 Al Smith, <Al.Smith@aeschi.ch.eu.org> - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - * Portions derived from IRIX header files (c) 1988 Silicon Graphics - */ -#ifndef _EFS_EFS_H_ -#define _EFS_EFS_H_ - -#ifdef pr_fmt -#undef pr_fmt -#endif - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/fs.h> -#include <linux/uaccess.h> - -#define EFS_VERSION "1.0a" - -/* 1 block is 512 bytes */ -#define EFS_BLOCKSIZE_BITS 9 -#define EFS_BLOCKSIZE (1 << EFS_BLOCKSIZE_BITS) - -typedef int32_t efs_block_t; -typedef uint32_t efs_ino_t; - -#define EFS_DIRECTEXTENTS 12 - -/* - * layout of an extent, in memory and on disk. 8 bytes exactly. - */ -typedef union extent_u { - unsigned char raw[8]; - struct extent_s { - unsigned int ex_magic:8; /* magic # (zero) */ - unsigned int ex_bn:24; /* basic block */ - unsigned int ex_length:8; /* numblocks in this extent */ - unsigned int ex_offset:24; /* logical offset into file */ - } cooked; -} efs_extent; - -typedef struct edevs { - __be16 odev; - __be32 ndev; -} efs_devs; - -/* - * extent based filesystem inode as it appears on disk. The efs inode - * is exactly 128 bytes long. - */ -struct efs_dinode { - __be16 di_mode; /* mode and type of file */ - __be16 di_nlink; /* number of links to file */ - __be16 di_uid; /* owner's user id */ - __be16 di_gid; /* owner's group id */ - __be32 di_size; /* number of bytes in file */ - __be32 di_atime; /* time last accessed */ - __be32 di_mtime; /* time last modified */ - __be32 di_ctime; /* time created */ - __be32 di_gen; /* generation number */ - __be16 di_numextents; /* # of extents */ - u_char di_version; /* version of inode */ - u_char di_spare; /* spare - used by AFS */ - union di_addr { - efs_extent di_extents[EFS_DIRECTEXTENTS]; - efs_devs di_dev; /* device for IFCHR/IFBLK */ - } di_u; -}; - -/* efs inode storage in memory */ -struct efs_inode_info { - int numextents; - int lastextent; - - efs_extent extents[EFS_DIRECTEXTENTS]; - struct inode vfs_inode; -}; - -#include <linux/efs_fs_sb.h> - -#define EFS_DIRBSIZE_BITS EFS_BLOCKSIZE_BITS -#define EFS_DIRBSIZE (1 << EFS_DIRBSIZE_BITS) - -struct efs_dentry { - __be32 inode; - unsigned char namelen; - char name[3]; -}; - -#define EFS_DENTSIZE (sizeof(struct efs_dentry) - 3 + 1) -#define EFS_MAXNAMELEN ((1 << (sizeof(char) * 8)) - 1) - -#define EFS_DIRBLK_HEADERSIZE 4 -#define EFS_DIRBLK_MAGIC 0xbeef /* moo */ - -struct efs_dir { - __be16 magic; - unsigned char firstused; - unsigned char slots; - - unsigned char space[EFS_DIRBSIZE - EFS_DIRBLK_HEADERSIZE]; -}; - -#define EFS_MAXENTS \ - ((EFS_DIRBSIZE - EFS_DIRBLK_HEADERSIZE) / \ - (EFS_DENTSIZE + sizeof(char))) - -#define EFS_SLOTAT(dir, slot) EFS_REALOFF((dir)->space[slot]) - -#define EFS_REALOFF(offset) ((offset << 1)) - - -static inline struct efs_inode_info *INODE_INFO(struct inode *inode) -{ - return container_of(inode, struct efs_inode_info, vfs_inode); -} - -static inline struct efs_sb_info *SUPER_INFO(struct super_block *sb) -{ - return sb->s_fs_info; -} - -struct statfs; -struct fid; - -extern const struct inode_operations efs_dir_inode_operations; -extern const struct file_operations efs_dir_operations; -extern const struct address_space_operations efs_symlink_aops; - -extern struct inode *efs_iget(struct super_block *, unsigned long); -extern efs_block_t efs_map_block(struct inode *, efs_block_t); -extern int efs_get_block(struct inode *, sector_t, struct buffer_head *, int); - -extern struct dentry *efs_lookup(struct inode *, struct dentry *, unsigned int); -extern struct dentry *efs_fh_to_dentry(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type); -extern struct dentry *efs_fh_to_parent(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type); -extern struct dentry *efs_get_parent(struct dentry *); -extern int efs_bmap(struct inode *, int); - -#endif /* _EFS_EFS_H_ */ diff --git a/fs/efs/file.c b/fs/efs/file.c deleted file mode 100644 index 9153dfe79bbc..000000000000 --- a/fs/efs/file.c +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * file.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include <linux/buffer_head.h> -#include "efs.h" - -int efs_get_block(struct inode *inode, sector_t iblock, - struct buffer_head *bh_result, int create) -{ - int error = -EROFS; - long phys; - - if (create) - return error; - if (iblock >= inode->i_blocks) - return 0; - - phys = efs_map_block(inode, iblock); - if (phys) - map_bh(bh_result, inode->i_sb, phys); - return 0; -} - -int efs_bmap(struct inode *inode, efs_block_t block) { - - if (block < 0) { - pr_warn("%s(): block < 0\n", __func__); - return 0; - } - - /* are we about to read past the end of a file ? */ - if (!(block < inode->i_blocks)) - return 0; - - return efs_map_block(inode, block); -} diff --git a/fs/efs/inode.c b/fs/efs/inode.c deleted file mode 100644 index 4b132729e638..000000000000 --- a/fs/efs/inode.c +++ /dev/null @@ -1,315 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * inode.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang, - * and from work (c) 1998 Mike Shaver. - */ - -#include <linux/buffer_head.h> -#include <linux/module.h> -#include <linux/fs.h> -#include "efs.h" -#include <linux/efs_fs_sb.h> - -static int efs_read_folio(struct file *file, struct folio *folio) -{ - return block_read_full_folio(folio, efs_get_block); -} - -static sector_t _efs_bmap(struct address_space *mapping, sector_t block) -{ - return generic_block_bmap(mapping,block,efs_get_block); -} - -static const struct address_space_operations efs_aops = { - .read_folio = efs_read_folio, - .bmap = _efs_bmap -}; - -static inline void extent_copy(efs_extent *src, efs_extent *dst) { - /* - * this is slightly evil. it doesn't just copy - * efs_extent from src to dst, it also mangles - * the bits so that dst ends up in cpu byte-order. - */ - - dst->cooked.ex_magic = (unsigned int) src->raw[0]; - dst->cooked.ex_bn = ((unsigned int) src->raw[1] << 16) | - ((unsigned int) src->raw[2] << 8) | - ((unsigned int) src->raw[3] << 0); - dst->cooked.ex_length = (unsigned int) src->raw[4]; - dst->cooked.ex_offset = ((unsigned int) src->raw[5] << 16) | - ((unsigned int) src->raw[6] << 8) | - ((unsigned int) src->raw[7] << 0); - return; -} - -struct inode *efs_iget(struct super_block *super, unsigned long ino) -{ - int i, inode_index; - dev_t device; - u32 rdev; - struct buffer_head *bh; - struct efs_sb_info *sb = SUPER_INFO(super); - struct efs_inode_info *in; - efs_block_t block, offset; - struct efs_dinode *efs_inode; - struct inode *inode; - - inode = iget_locked(super, ino); - if (!inode) - return ERR_PTR(-ENOMEM); - if (!(inode_state_read_once(inode) & I_NEW)) - return inode; - - in = INODE_INFO(inode); - - /* - ** EFS layout: - ** - ** | cylinder group | cylinder group | cylinder group ..etc - ** |inodes|data |inodes|data |inodes|data ..etc - ** - ** work out the inode block index, (considering initially that the - ** inodes are stored as consecutive blocks). then work out the block - ** number of that inode given the above layout, and finally the - ** offset of the inode within that block. - */ - - inode_index = inode->i_ino / - (EFS_BLOCKSIZE / sizeof(struct efs_dinode)); - - block = sb->fs_start + sb->first_block + - (sb->group_size * (inode_index / sb->inode_blocks)) + - (inode_index % sb->inode_blocks); - - offset = (inode->i_ino % - (EFS_BLOCKSIZE / sizeof(struct efs_dinode))) * - sizeof(struct efs_dinode); - - bh = sb_bread(inode->i_sb, block); - if (!bh) { - pr_warn("%s() failed at block %d\n", __func__, block); - goto read_inode_error; - } - - efs_inode = (struct efs_dinode *) (bh->b_data + offset); - - inode->i_mode = be16_to_cpu(efs_inode->di_mode); - set_nlink(inode, be16_to_cpu(efs_inode->di_nlink)); - i_uid_write(inode, (uid_t)be16_to_cpu(efs_inode->di_uid)); - i_gid_write(inode, (gid_t)be16_to_cpu(efs_inode->di_gid)); - inode->i_size = be32_to_cpu(efs_inode->di_size); - inode_set_atime(inode, be32_to_cpu(efs_inode->di_atime), 0); - inode_set_mtime(inode, be32_to_cpu(efs_inode->di_mtime), 0); - inode_set_ctime(inode, be32_to_cpu(efs_inode->di_ctime), 0); - - /* this is the number of blocks in the file */ - if (inode->i_size == 0) { - inode->i_blocks = 0; - } else { - inode->i_blocks = ((inode->i_size - 1) >> EFS_BLOCKSIZE_BITS) + 1; - } - - rdev = be16_to_cpu(efs_inode->di_u.di_dev.odev); - if (rdev == 0xffff) { - rdev = be32_to_cpu(efs_inode->di_u.di_dev.ndev); - if (sysv_major(rdev) > 0xfff) - device = 0; - else - device = MKDEV(sysv_major(rdev), sysv_minor(rdev)); - } else - device = old_decode_dev(rdev); - - /* get the number of extents for this object */ - in->numextents = be16_to_cpu(efs_inode->di_numextents); - in->lastextent = 0; - - /* copy the extents contained within the inode to memory */ - for(i = 0; i < EFS_DIRECTEXTENTS; i++) { - extent_copy(&(efs_inode->di_u.di_extents[i]), &(in->extents[i])); - if (i < in->numextents && in->extents[i].cooked.ex_magic != 0) { - pr_warn("extent %d has bad magic number in inode %llu\n", - i, inode->i_ino); - brelse(bh); - goto read_inode_error; - } - } - - brelse(bh); - pr_debug("efs_iget(): inode %llu, extents %d, mode %o\n", - inode->i_ino, in->numextents, inode->i_mode); - switch (inode->i_mode & S_IFMT) { - case S_IFDIR: - inode->i_op = &efs_dir_inode_operations; - inode->i_fop = &efs_dir_operations; - break; - case S_IFREG: - inode->i_fop = &generic_ro_fops; - inode->i_data.a_ops = &efs_aops; - break; - case S_IFLNK: - inode->i_op = &page_symlink_inode_operations; - inode_nohighmem(inode); - inode->i_data.a_ops = &efs_symlink_aops; - break; - case S_IFCHR: - case S_IFBLK: - case S_IFIFO: - init_special_inode(inode, inode->i_mode, device); - break; - default: - pr_warn("unsupported inode mode %o\n", inode->i_mode); - goto read_inode_error; - break; - } - - unlock_new_inode(inode); - return inode; - -read_inode_error: - pr_warn("failed to read inode %llu\n", inode->i_ino); - iget_failed(inode); - return ERR_PTR(-EIO); -} - -static inline efs_block_t -efs_extent_check(efs_extent *ptr, efs_block_t block, struct efs_sb_info *sb) { - efs_block_t start; - efs_block_t length; - efs_block_t offset; - - /* - * given an extent and a logical block within a file, - * can this block be found within this extent ? - */ - start = ptr->cooked.ex_bn; - length = ptr->cooked.ex_length; - offset = ptr->cooked.ex_offset; - - if ((block >= offset) && (block < offset+length)) { - return(sb->fs_start + start + block - offset); - } else { - return 0; - } -} - -efs_block_t efs_map_block(struct inode *inode, efs_block_t block) { - struct efs_sb_info *sb = SUPER_INFO(inode->i_sb); - struct efs_inode_info *in = INODE_INFO(inode); - struct buffer_head *bh = NULL; - - int cur, last, first = 1; - int ibase, ioffset, dirext, direxts, indext, indexts; - efs_block_t iblock, result = 0, lastblock = 0; - efs_extent ext, *exts; - - last = in->lastextent; - - if (in->numextents <= EFS_DIRECTEXTENTS) { - /* first check the last extent we returned */ - if ((result = efs_extent_check(&in->extents[last], block, sb))) - return result; - - /* if we only have one extent then nothing can be found */ - if (in->numextents == 1) { - pr_err("%s() failed to map (1 extent)\n", __func__); - return 0; - } - - direxts = in->numextents; - - /* - * check the stored extents in the inode - * start with next extent and check forwards - */ - for(dirext = 1; dirext < direxts; dirext++) { - cur = (last + dirext) % in->numextents; - if ((result = efs_extent_check(&in->extents[cur], block, sb))) { - in->lastextent = cur; - return result; - } - } - - pr_err("%s() failed to map block %u (dir)\n", __func__, block); - return 0; - } - - pr_debug("%s(): indirect search for logical block %u\n", - __func__, block); - direxts = in->extents[0].cooked.ex_offset; - indexts = in->numextents; - - for(indext = 0; indext < indexts; indext++) { - cur = (last + indext) % indexts; - - /* - * work out which direct extent contains `cur'. - * - * also compute ibase: i.e. the number of the first - * indirect extent contained within direct extent `cur'. - * - */ - ibase = 0; - for(dirext = 0; cur < ibase && dirext < direxts; dirext++) { - ibase += in->extents[dirext].cooked.ex_length * - (EFS_BLOCKSIZE / sizeof(efs_extent)); - } - - if (dirext == direxts) { - /* should never happen */ - pr_err("couldn't find direct extent for indirect extent %d (block %u)\n", - cur, block); - if (bh) brelse(bh); - return 0; - } - - /* work out block number and offset of this indirect extent */ - iblock = sb->fs_start + in->extents[dirext].cooked.ex_bn + - (cur - ibase) / - (EFS_BLOCKSIZE / sizeof(efs_extent)); - ioffset = (cur - ibase) % - (EFS_BLOCKSIZE / sizeof(efs_extent)); - - if (first || lastblock != iblock) { - if (bh) brelse(bh); - - bh = sb_bread(inode->i_sb, iblock); - if (!bh) { - pr_err("%s() failed at block %d\n", - __func__, iblock); - return 0; - } - pr_debug("%s(): read indirect extent block %d\n", - __func__, iblock); - first = 0; - lastblock = iblock; - } - - exts = (efs_extent *) bh->b_data; - - extent_copy(&(exts[ioffset]), &ext); - - if (ext.cooked.ex_magic != 0) { - pr_err("extent %d has bad magic number in block %d\n", - cur, iblock); - if (bh) brelse(bh); - return 0; - } - - if ((result = efs_extent_check(&ext, block, sb))) { - if (bh) brelse(bh); - in->lastextent = cur; - return result; - } - } - if (bh) brelse(bh); - pr_err("%s() failed to map block %u (indir)\n", __func__, block); - return 0; -} - -MODULE_DESCRIPTION("Extent File System (efs)"); -MODULE_LICENSE("GPL"); diff --git a/fs/efs/namei.c b/fs/efs/namei.c deleted file mode 100644 index 38961ee1d1af..000000000000 --- a/fs/efs/namei.c +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * namei.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include <linux/buffer_head.h> -#include <linux/string.h> -#include <linux/exportfs.h> -#include "efs.h" - - -static efs_ino_t efs_find_entry(struct inode *inode, const char *name, int len) -{ - struct buffer_head *bh; - - int slot, namelen; - char *nameptr; - struct efs_dir *dirblock; - struct efs_dentry *dirslot; - efs_ino_t inodenum; - efs_block_t block; - - if (inode->i_size & (EFS_DIRBSIZE-1)) - pr_warn("%s(): directory size not a multiple of EFS_DIRBSIZE\n", - __func__); - - for(block = 0; block < inode->i_blocks; block++) { - - bh = sb_bread(inode->i_sb, efs_bmap(inode, block)); - if (!bh) { - pr_err("%s(): failed to read dir block %d\n", - __func__, block); - return 0; - } - - dirblock = (struct efs_dir *) bh->b_data; - - if (be16_to_cpu(dirblock->magic) != EFS_DIRBLK_MAGIC) { - pr_err("%s(): invalid directory block\n", __func__); - brelse(bh); - return 0; - } - - for (slot = 0; slot < dirblock->slots; slot++) { - dirslot = (struct efs_dentry *) (((char *) bh->b_data) + EFS_SLOTAT(dirblock, slot)); - - namelen = dirslot->namelen; - nameptr = dirslot->name; - - if ((namelen == len) && (!memcmp(name, nameptr, len))) { - inodenum = be32_to_cpu(dirslot->inode); - brelse(bh); - return inodenum; - } - } - brelse(bh); - } - return 0; -} - -struct dentry *efs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) -{ - efs_ino_t inodenum; - struct inode *inode = NULL; - - inodenum = efs_find_entry(dir, dentry->d_name.name, dentry->d_name.len); - if (inodenum) - inode = efs_iget(dir->i_sb, inodenum); - - return d_splice_alias(inode, dentry); -} - -static struct inode *efs_nfs_get_inode(struct super_block *sb, u64 ino, - u32 generation) -{ - struct inode *inode; - - if (ino == 0) - return ERR_PTR(-ESTALE); - inode = efs_iget(sb, ino); - if (IS_ERR(inode)) - return ERR_CAST(inode); - - if (generation && inode->i_generation != generation) { - iput(inode); - return ERR_PTR(-ESTALE); - } - - return inode; -} - -struct dentry *efs_fh_to_dentry(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type) -{ - return generic_fh_to_dentry(sb, fid, fh_len, fh_type, - efs_nfs_get_inode); -} - -struct dentry *efs_fh_to_parent(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type) -{ - return generic_fh_to_parent(sb, fid, fh_len, fh_type, - efs_nfs_get_inode); -} - -struct dentry *efs_get_parent(struct dentry *child) -{ - struct dentry *parent = ERR_PTR(-ENOENT); - efs_ino_t ino; - - ino = efs_find_entry(d_inode(child), "..", 2); - if (ino) - parent = d_obtain_alias(efs_iget(child->d_sb, ino)); - - return parent; -} diff --git a/fs/efs/super.c b/fs/efs/super.c deleted file mode 100644 index 11fea3bbce7c..000000000000 --- a/fs/efs/super.c +++ /dev/null @@ -1,368 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * super.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include <linux/init.h> -#include <linux/module.h> -#include <linux/exportfs.h> -#include <linux/slab.h> -#include <linux/buffer_head.h> -#include <linux/vfs.h> -#include <linux/blkdev.h> -#include <linux/fs_context.h> -#include "efs.h" -#include <linux/efs_vh.h> -#include <linux/efs_fs_sb.h> - -static int efs_statfs(struct dentry *dentry, struct kstatfs *buf); -static int efs_init_fs_context(struct fs_context *fc); - -static void efs_kill_sb(struct super_block *s) -{ - struct efs_sb_info *sbi = SUPER_INFO(s); - kill_block_super(s); - kfree(sbi); -} - -static struct pt_types sgi_pt_types[] = { - {0x00, "SGI vh"}, - {0x01, "SGI trkrepl"}, - {0x02, "SGI secrepl"}, - {0x03, "SGI raw"}, - {0x04, "SGI bsd"}, - {SGI_SYSV, "SGI sysv"}, - {0x06, "SGI vol"}, - {SGI_EFS, "SGI efs"}, - {0x08, "SGI lv"}, - {0x09, "SGI rlv"}, - {0x0A, "SGI xfs"}, - {0x0B, "SGI xfslog"}, - {0x0C, "SGI xlv"}, - {0x82, "Linux swap"}, - {0x83, "Linux native"}, - {0, NULL} -}; - -/* - * File system definition and registration. - */ -static struct file_system_type efs_fs_type = { - .owner = THIS_MODULE, - .name = "efs", - .kill_sb = efs_kill_sb, - .fs_flags = FS_REQUIRES_DEV, - .init_fs_context = efs_init_fs_context, -}; -MODULE_ALIAS_FS("efs"); - -static struct kmem_cache * efs_inode_cachep; - -static struct inode *efs_alloc_inode(struct super_block *sb) -{ - struct efs_inode_info *ei; - ei = alloc_inode_sb(sb, efs_inode_cachep, GFP_KERNEL); - if (!ei) - return NULL; - return &ei->vfs_inode; -} - -static void efs_free_inode(struct inode *inode) -{ - kmem_cache_free(efs_inode_cachep, INODE_INFO(inode)); -} - -static void init_once(void *foo) -{ - struct efs_inode_info *ei = (struct efs_inode_info *) foo; - - inode_init_once(&ei->vfs_inode); -} - -static int __init init_inodecache(void) -{ - efs_inode_cachep = kmem_cache_create("efs_inode_cache", - sizeof(struct efs_inode_info), 0, - SLAB_RECLAIM_ACCOUNT|SLAB_ACCOUNT, - init_once); - if (efs_inode_cachep == NULL) - return -ENOMEM; - return 0; -} - -static void destroy_inodecache(void) -{ - /* - * Make sure all delayed rcu free inodes are flushed before we - * destroy cache. - */ - rcu_barrier(); - kmem_cache_destroy(efs_inode_cachep); -} - -static const struct super_operations efs_superblock_operations = { - .alloc_inode = efs_alloc_inode, - .free_inode = efs_free_inode, - .statfs = efs_statfs, -}; - -static const struct export_operations efs_export_ops = { - .encode_fh = generic_encode_ino32_fh, - .fh_to_dentry = efs_fh_to_dentry, - .fh_to_parent = efs_fh_to_parent, - .get_parent = efs_get_parent, -}; - -static int __init init_efs_fs(void) { - int err; - pr_info(EFS_VERSION" - http://aeschi.ch.eu.org/efs/\n"); - err = init_inodecache(); - if (err) - goto out1; - err = register_filesystem(&efs_fs_type); - if (err) - goto out; - return 0; -out: - destroy_inodecache(); -out1: - return err; -} - -static void __exit exit_efs_fs(void) { - unregister_filesystem(&efs_fs_type); - destroy_inodecache(); -} - -module_init(init_efs_fs) -module_exit(exit_efs_fs) - -static efs_block_t efs_validate_vh(struct volume_header *vh) { - int i; - __be32 cs, *ui; - int csum; - efs_block_t sblock = 0; /* shuts up gcc */ - struct pt_types *pt_entry; - int pt_type, slice = -1; - - if (be32_to_cpu(vh->vh_magic) != VHMAGIC) { - /* - * assume that we're dealing with a partition and allow - * read_super() to try and detect a valid superblock - * on the next block. - */ - return 0; - } - - ui = ((__be32 *) (vh + 1)) - 1; - for(csum = 0; ui >= ((__be32 *) vh);) { - cs = *ui--; - csum += be32_to_cpu(cs); - } - if (csum) { - pr_warn("SGI disklabel: checksum bad, label corrupted\n"); - return 0; - } - -#ifdef DEBUG - pr_debug("bf: \"%16s\"\n", vh->vh_bootfile); - - for(i = 0; i < NVDIR; i++) { - int j; - char name[VDNAMESIZE+1]; - - for(j = 0; j < VDNAMESIZE; j++) { - name[j] = vh->vh_vd[i].vd_name[j]; - } - name[j] = (char) 0; - - if (name[0]) { - pr_debug("vh: %8s block: 0x%08x size: 0x%08x\n", - name, (int) be32_to_cpu(vh->vh_vd[i].vd_lbn), - (int) be32_to_cpu(vh->vh_vd[i].vd_nbytes)); - } - } -#endif - - for(i = 0; i < NPARTAB; i++) { - pt_type = (int) be32_to_cpu(vh->vh_pt[i].pt_type); - for(pt_entry = sgi_pt_types; pt_entry->pt_name; pt_entry++) { - if (pt_type == pt_entry->pt_type) break; - } -#ifdef DEBUG - if (be32_to_cpu(vh->vh_pt[i].pt_nblks)) { - pr_debug("pt %2d: start: %08d size: %08d type: 0x%02x (%s)\n", - i, (int)be32_to_cpu(vh->vh_pt[i].pt_firstlbn), - (int)be32_to_cpu(vh->vh_pt[i].pt_nblks), - pt_type, (pt_entry->pt_name) ? - pt_entry->pt_name : "unknown"); - } -#endif - if (IS_EFS(pt_type)) { - sblock = be32_to_cpu(vh->vh_pt[i].pt_firstlbn); - slice = i; - } - } - - if (slice == -1) { - pr_notice("partition table contained no EFS partitions\n"); -#ifdef DEBUG - } else { - pr_info("using slice %d (type %s, offset 0x%x)\n", slice, - (pt_entry->pt_name) ? pt_entry->pt_name : "unknown", - sblock); -#endif - } - return sblock; -} - -static int efs_validate_super(struct efs_sb_info *sb, struct efs_super *super) { - - if (!IS_EFS_MAGIC(be32_to_cpu(super->fs_magic))) - return -1; - - sb->fs_magic = be32_to_cpu(super->fs_magic); - sb->total_blocks = be32_to_cpu(super->fs_size); - sb->first_block = be32_to_cpu(super->fs_firstcg); - sb->group_size = be32_to_cpu(super->fs_cgfsize); - sb->data_free = be32_to_cpu(super->fs_tfree); - sb->inode_free = be32_to_cpu(super->fs_tinode); - sb->inode_blocks = be16_to_cpu(super->fs_cgisize); - sb->total_groups = be16_to_cpu(super->fs_ncg); - - return 0; -} - -static int efs_fill_super(struct super_block *s, struct fs_context *fc) -{ - struct efs_sb_info *sb; - struct buffer_head *bh; - struct inode *root; - - sb = kzalloc_obj(struct efs_sb_info); - if (!sb) - return -ENOMEM; - s->s_fs_info = sb; - s->s_time_min = 0; - s->s_time_max = U32_MAX; - - s->s_magic = EFS_SUPER_MAGIC; - if (!sb_set_blocksize(s, EFS_BLOCKSIZE)) { - pr_err("device does not support %d byte blocks\n", - EFS_BLOCKSIZE); - return invalf(fc, "device does not support %d byte blocks\n", - EFS_BLOCKSIZE); - } - - /* read the vh (volume header) block */ - bh = sb_bread(s, 0); - - if (!bh) { - pr_err("cannot read volume header\n"); - return -EIO; - } - - /* - * if this returns zero then we didn't find any partition table. - * this isn't (yet) an error - just assume for the moment that - * the device is valid and go on to search for a superblock. - */ - sb->fs_start = efs_validate_vh((struct volume_header *) bh->b_data); - brelse(bh); - - if (sb->fs_start == -1) { - return -EINVAL; - } - - bh = sb_bread(s, sb->fs_start + EFS_SUPER); - if (!bh) { - pr_err("cannot read superblock\n"); - return -EIO; - } - - if (efs_validate_super(sb, (struct efs_super *) bh->b_data)) { -#ifdef DEBUG - pr_warn("invalid superblock at block %u\n", - sb->fs_start + EFS_SUPER); -#endif - brelse(bh); - return -EINVAL; - } - brelse(bh); - - if (!sb_rdonly(s)) { -#ifdef DEBUG - pr_info("forcing read-only mode\n"); -#endif - s->s_flags |= SB_RDONLY; - } - s->s_op = &efs_superblock_operations; - s->s_export_op = &efs_export_ops; - root = efs_iget(s, EFS_ROOTINODE); - if (IS_ERR(root)) { - pr_err("get root inode failed\n"); - return PTR_ERR(root); - } - - s->s_root = d_make_root(root); - if (!(s->s_root)) { - pr_err("get root dentry failed\n"); - return -ENOMEM; - } - - return 0; -} - -static int efs_get_tree(struct fs_context *fc) -{ - return get_tree_bdev(fc, efs_fill_super); -} - -static int efs_reconfigure(struct fs_context *fc) -{ - sync_filesystem(fc->root->d_sb); - fc->sb_flags |= SB_RDONLY; - - return 0; -} - -static const struct fs_context_operations efs_context_opts = { - .get_tree = efs_get_tree, - .reconfigure = efs_reconfigure, -}; - -/* - * Set up the filesystem mount context. - */ -static int efs_init_fs_context(struct fs_context *fc) -{ - fc->ops = &efs_context_opts; - - return 0; -} - -static int efs_statfs(struct dentry *dentry, struct kstatfs *buf) { - struct super_block *sb = dentry->d_sb; - struct efs_sb_info *sbi = SUPER_INFO(sb); - u64 id = huge_encode_dev(sb->s_bdev->bd_dev); - - buf->f_type = EFS_SUPER_MAGIC; /* efs magic number */ - buf->f_bsize = EFS_BLOCKSIZE; /* blocksize */ - buf->f_blocks = sbi->total_groups * /* total data blocks */ - (sbi->group_size - sbi->inode_blocks); - buf->f_bfree = sbi->data_free; /* free data blocks */ - buf->f_bavail = sbi->data_free; /* free blocks for non-root */ - buf->f_files = sbi->total_groups * /* total inodes */ - sbi->inode_blocks * - (EFS_BLOCKSIZE / sizeof(struct efs_dinode)); - buf->f_ffree = sbi->inode_free; /* free inodes */ - buf->f_fsid = u64_to_fsid(id); - buf->f_namelen = EFS_MAXNAMELEN; /* max filename length */ - - return 0; -} - diff --git a/fs/efs/symlink.c b/fs/efs/symlink.c deleted file mode 100644 index 7749feded722..000000000000 --- a/fs/efs/symlink.c +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * symlink.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include <linux/string.h> -#include <linux/pagemap.h> -#include <linux/buffer_head.h> -#include "efs.h" - -static int efs_symlink_read_folio(struct file *file, struct folio *folio) -{ - char *link = folio_address(folio); - struct buffer_head *bh; - struct inode *inode = folio->mapping->host; - efs_block_t size = inode->i_size; - int err; - - err = -ENAMETOOLONG; - if (size > 2 * EFS_BLOCKSIZE) - goto fail; - - /* read first 512 bytes of link target */ - err = -EIO; - bh = sb_bread(inode->i_sb, efs_bmap(inode, 0)); - if (!bh) - goto fail; - memcpy(link, bh->b_data, (size > EFS_BLOCKSIZE) ? EFS_BLOCKSIZE : size); - brelse(bh); - if (size > EFS_BLOCKSIZE) { - bh = sb_bread(inode->i_sb, efs_bmap(inode, 1)); - if (!bh) - goto fail; - memcpy(link + EFS_BLOCKSIZE, bh->b_data, size - EFS_BLOCKSIZE); - brelse(bh); - } - link[size] = '\0'; - err = 0; -fail: - folio_end_read(folio, err == 0); - return err; -} - -const struct address_space_operations efs_symlink_aops = { - .read_folio = efs_symlink_read_folio -}; diff --git a/fs/erofs/super.c b/fs/erofs/super.c index c5881bb8d52b..c6c5f8510857 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -147,8 +147,8 @@ static int erofs_init_device(struct erofs_buf *buf, struct super_block *sb, if (!sbi->devs->flatdev) { file = erofs_is_fileio_mode(sbi) ? filp_open(dif->path, O_RDONLY | O_LARGEFILE, 0) : - bdev_file_open_by_path(dif->path, - BLK_OPEN_READ, sb->s_type, NULL); + fs_bdev_file_open_by_path(dif->path, + BLK_OPEN_READ, sb->s_type, sb); if (IS_ERR(file)) { if (file == ERR_PTR(-ENOTBLK)) return -EINVAL; @@ -790,28 +790,34 @@ static int erofs_fc_reconfigure(struct fs_context *fc) static int erofs_release_device_info(int id, void *ptr, void *data) { + struct super_block *sb = data; struct erofs_device_info *dif = ptr; fs_put_dax(dif->dax_dev, NULL); - if (dif->file) - fput(dif->file); + if (dif->file) { + if (S_ISBLK(file_inode(dif->file)->i_mode)) + fs_bdev_file_release(dif->file, sb); + else + fput(dif->file); + } kfree(dif->path); kfree(dif); return 0; } -static void erofs_free_dev_context(struct erofs_dev_context *devs) +static void erofs_free_dev_context(struct erofs_dev_context *devs, + struct super_block *sb) { if (!devs) return; - idr_for_each(&devs->tree, &erofs_release_device_info, NULL); + idr_for_each(&devs->tree, &erofs_release_device_info, sb); idr_destroy(&devs->tree); kfree(devs); } -static void erofs_sb_free(struct erofs_sb_info *sbi) +static void erofs_sb_free(struct erofs_sb_info *sbi, struct super_block *sb) { - erofs_free_dev_context(sbi->devs); + erofs_free_dev_context(sbi->devs, sb); kfree_sensitive(sbi->domain_id); if (sbi->dif0.file) fput(sbi->dif0.file); @@ -823,8 +829,13 @@ static void erofs_fc_free(struct fs_context *fc) { struct erofs_sb_info *sbi = fc->s_fs_info; - if (sbi) /* free here if an error occurs before transferring to sb */ - erofs_sb_free(sbi); + /* + * Freed here only if an error occurs before the sb is set up; at that + * point no block-backed device has been claimed (that happens in + * fill_super), so the NULL sb never reaches fs_bdev_file_release(). + */ + if (sbi) + erofs_sb_free(sbi, NULL); } static const struct fs_context_operations erofs_context_ops = { @@ -878,7 +889,7 @@ static void erofs_kill_sb(struct super_block *sb) kill_block_super(sb); erofs_drop_internal_inodes(sbi); fs_put_dax(sbi->dif0.dax_dev, NULL); - erofs_sb_free(sbi); + erofs_sb_free(sbi, sb); sb->s_fs_info = NULL; } @@ -890,7 +901,7 @@ static void erofs_put_super(struct super_block *sb) erofs_shrinker_unregister(sb); erofs_xattr_prefixes_cleanup(sb); erofs_drop_internal_inodes(sbi); - erofs_free_dev_context(sbi->devs); + erofs_free_dev_context(sbi->devs, sb); sbi->devs = NULL; } diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index b7d5e44ad38e..cd9c9eca58f8 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -538,7 +538,7 @@ out: } static int exfat_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode; diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index 0d09d22fe708..8666233ec63b 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -99,7 +99,7 @@ struct dentry *ext2_get_parent(struct dentry *child) */ static int ext2_create (struct mnt_idmap * idmap, struct inode * dir, struct dentry * dentry, - umode_t mode, bool excl) + umode_t mode) { struct inode *inode; int err; @@ -236,7 +236,7 @@ static struct dentry *ext2_mkdir(struct mnt_idmap * idmap, inode_inc_link_count(dir); - inode = ext2_new_inode(dir, S_IFDIR | mode, &dentry->d_name); + inode = ext2_new_inode(dir, mode, &dentry->d_name); err = PTR_ERR(inode); if (IS_ERR(inode)) goto out_dir; diff --git a/fs/ext4/extents-test.c b/fs/ext4/extents-test.c index bd7795a82607..c3836ecb89f9 100644 --- a/fs/ext4/extents-test.c +++ b/fs/ext4/extents-test.c @@ -126,11 +126,6 @@ struct kunit_ext_test_param { struct kunit_ext_data_state exp_data_state[3]; }; -static void ext_kill_sb(struct super_block *sb) -{ - generic_shutdown_super(sb); -} - static int ext_init_fs_context(struct fs_context *fc) { return 0; @@ -138,13 +133,13 @@ static int ext_init_fs_context(struct fs_context *fc) static int ext_set(struct super_block *sb, struct fs_context *fc) { - return 0; + return set_anon_super_fc(sb, fc); } static struct file_system_type ext_fs_type = { .name = "extents test", .init_fs_context = ext_init_fs_context, - .kill_sb = ext_kill_sb, + .kill_sb = kill_anon_super, }; static void extents_kunit_exit(struct kunit *test) diff --git a/fs/ext4/mballoc-test.c b/fs/ext4/mballoc-test.c index 0424b8b0b4c3..d31780075c21 100644 --- a/fs/ext4/mballoc-test.c +++ b/fs/ext4/mballoc-test.c @@ -59,11 +59,6 @@ static const struct super_operations mbt_sops = { .free_inode = mbt_free_inode, }; -static void mbt_kill_sb(struct super_block *sb) -{ - generic_shutdown_super(sb); -} - static int mbt_init_fs_context(struct fs_context *fc) { return 0; @@ -72,7 +67,7 @@ static int mbt_init_fs_context(struct fs_context *fc) static struct file_system_type mbt_fs_type = { .name = "mballoc test", .init_fs_context = mbt_init_fs_context, - .kill_sb = mbt_kill_sb, + .kill_sb = kill_anon_super, }; static int mbt_mb_init(struct super_block *sb) @@ -136,7 +131,7 @@ static void mbt_mb_release(struct super_block *sb) static int mbt_set(struct super_block *sb, struct fs_context *fc) { - return 0; + return set_anon_super_fc(sb, fc); } static struct super_block *mbt_ext4_alloc_super_block(void) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index cc49ae04a6f6..640a03ee02c7 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -2811,7 +2811,7 @@ static int ext4_add_nondir(handle_t *handle, * with d_instantiate(). */ static int ext4_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { handle_t *handle; struct inode *inode; @@ -3009,7 +3009,7 @@ static struct dentry *ext4_mkdir(struct mnt_idmap *idmap, struct inode *dir, credits = (EXT4_DATA_TRANS_BLOCKS(dir->i_sb) + EXT4_INDEX_EXTRA_TRANS_BLOCKS + 3); retry: - inode = ext4_new_inode_start_handle(idmap, dir, S_IFDIR | mode, + inode = ext4_new_inode_start_handle(idmap, dir, mode, &dentry->d_name, 0, NULL, EXT4_HT_DIR, credits); handle = ext4_journal_current_handle(); diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 245f67d10ded..467aa44109bc 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -5797,7 +5797,7 @@ failed_mount: brelse(sbi->s_sbh); if (sbi->s_journal_bdev_file) { invalidate_bdev(file_bdev(sbi->s_journal_bdev_file)); - bdev_fput(sbi->s_journal_bdev_file); + fs_bdev_file_release(sbi->s_journal_bdev_file, sb); } out_fail: invalidate_bdev(sb->s_bdev); @@ -5981,9 +5981,9 @@ static struct file *ext4_get_journal_blkdev(struct super_block *sb, struct ext4_super_block *es; int errno; - bdev_file = bdev_file_open_by_dev(j_dev, + bdev_file = fs_bdev_file_open_by_dev(j_dev, BLK_OPEN_READ | BLK_OPEN_WRITE | BLK_OPEN_RESTRICT_WRITES, - sb, &fs_holder_ops); + sb, sb); if (IS_ERR(bdev_file)) { ext4_msg(sb, KERN_ERR, "failed to open journal device unknown-block(%u,%u) %pe", @@ -6043,7 +6043,7 @@ static struct file *ext4_get_journal_blkdev(struct super_block *sb, out_bh: brelse(bh); out_bdev: - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, sb); return ERR_PTR(errno); } @@ -6082,7 +6082,7 @@ static journal_t *ext4_open_dev_journal(struct super_block *sb, out_journal: ext4_journal_destroy(EXT4_SB(sb), journal); out_bdev: - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, sb); return ERR_PTR(errno); } @@ -7499,7 +7499,7 @@ static void ext4_kill_sb(struct super_block *sb) kill_block_super(sb); if (bdev_file) - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, sb); } static struct file_system_type ext4_fs_type = { diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index cac03b8e91a1..5ae647a352aa 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -366,7 +366,7 @@ fail_drop: } static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct f2fs_sb_info *sbi = F2FS_I_SB(dir); struct f2fs_lock_context lc; @@ -742,7 +742,7 @@ static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (err) return ERR_PTR(err); - inode = f2fs_new_inode(idmap, dir, S_IFDIR | mode, NULL); + inode = f2fs_new_inode(idmap, dir, mode, NULL); if (IS_ERR(inode)) return ERR_CAST(inode); diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 2b8d96411156..11c81e956c29 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -1971,7 +1971,7 @@ static void destroy_device_list(struct f2fs_sb_info *sbi) for (i = 0; i < sbi->s_ndevs; i++) { if (i > 0) - bdev_fput(FDEV(i).bdev_file); + fs_bdev_file_release(FDEV(i).bdev_file, sbi->sb); #ifdef CONFIG_BLK_DEV_ZONED kvfree(FDEV(i).blkz_seq); #endif @@ -4898,8 +4898,8 @@ static int f2fs_scan_devices(struct f2fs_sb_info *sbi) FDEV(i).end_blk = FDEV(i).start_blk + SEGS_TO_BLKS(sbi, FDEV(i).total_segments) - 1; - FDEV(i).bdev_file = bdev_file_open_by_path( - FDEV(i).path, mode, sbi->sb, NULL); + FDEV(i).bdev_file = fs_bdev_file_open_by_path( + FDEV(i).path, mode, sbi->sb, sbi->sb); } } if (IS_ERR(FDEV(i).bdev_file)) diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 0fd2971ad4b1..ee6824c5d136 100644 --- a/fs/fat/namei_msdos.c +++ b/fs/fat/namei_msdos.c @@ -29,6 +29,9 @@ static int msdos_format_name(const unsigned char *name, int len, unsigned char c; int space; + if (len > NAME_MAX) + return -ENAMETOOLONG; + if (name[0] == '.') { /* dotfile because . and .. already done */ if (opts->dotsOK) { /* Get rid of dot - test for it elsewhere */ @@ -262,7 +265,7 @@ static int msdos_add_entry(struct inode *dir, const unsigned char *name, /***** Create a file */ static int msdos_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode = NULL; diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index e909447873e3..139d3ef4bfae 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -755,7 +755,7 @@ error: } static int vfat_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; struct inode *inode; diff --git a/fs/fs_struct.c b/fs/fs_struct.c index 394875d06fd6..34699f3b6f88 100644 --- a/fs/fs_struct.c +++ b/fs/fs_struct.c @@ -8,6 +8,7 @@ #include <linux/fs_struct.h> #include <linux/init_task.h> #include "internal.h" +#include "mount.h" /* * Replace the fs->{rootmnt,root} with {mnt,dentry}. Put the old values. @@ -60,8 +61,11 @@ void chroot_fs_refs(const struct path *old_root, const struct path *new_root) read_lock(&tasklist_lock); for_each_process_thread(g, p) { + if (p->flags & (PF_KTHREAD | PF_EXITING | PF_DUMPCORE)) + continue; + task_lock(p); - fs = p->fs; + fs = p->real_fs; if (fs) { int hits = 0; write_seqlock(&fs->seq); @@ -89,12 +93,13 @@ void free_fs_struct(struct fs_struct *fs) void exit_fs(struct task_struct *tsk) { - struct fs_struct *fs = tsk->fs; + struct fs_struct *fs = tsk->real_fs; if (fs) { int kill; task_lock(tsk); read_seqlock_excl(&fs->seq); + tsk->real_fs = NULL; tsk->fs = NULL; kill = !--fs->users; read_sequnlock_excl(&fs->seq); @@ -126,7 +131,7 @@ struct fs_struct *copy_fs_struct(struct fs_struct *old) int unshare_fs_struct(void) { - struct fs_struct *fs = current->fs; + struct fs_struct *fs = current->real_fs; struct fs_struct *new_fs = copy_fs_struct(fs); int kill; @@ -135,8 +140,10 @@ int unshare_fs_struct(void) task_lock(current); read_seqlock_excl(&fs->seq); + VFS_WARN_ON_ONCE(fs != current->fs); kill = !--fs->users; current->fs = new_fs; + current->real_fs = new_fs; read_sequnlock_excl(&fs->seq); task_unlock(current); @@ -147,9 +154,99 @@ int unshare_fs_struct(void) } EXPORT_SYMBOL_GPL(unshare_fs_struct); +/* + * PID 1 may choose to stop sharing fs_struct state with us. + * Either via unshare(CLONE_FS) or unshare(CLONE_NEWNS). Of + * course, PID 1 could have chosen to create arbitrary process + * trees that all share fs_struct state via CLONE_FS. This is a + * strong statement: We only care about PID 1 aka the thread-group + * leader so subthread's fs_struct state doesn't matter. + * + * PID 1 unsharing fs_struct state is a bug. PID 1 relies on + * various kthreads to be able to perform work based on its + * fs_struct state. Breaking that contract sucks for both sides. + * So just don't bother with extra work for this. No sane init + * system should ever do this. + * + * On older kernels if PID 1 unshared its filesystem state with us the + * kernel simply used the stale fs_struct state implicitly pinning + * anything that PID 1 had last used. Even if PID 1 might've moved on to + * some completely different fs_struct state and might've even unmounted + * the old root. + * + * This has hilarious consequences: Think continuing to dump coredump + * state into an implicitly pinned directory somewhere. Calling random + * binaries in the old rootfs via usermodehelpers. + * + * Be aggressive about this: We simply reject operating on stale + * fs_struct state by reverting to nullfs. Every kworker that does + * lookups after this point will fail. Every usermodehelper call will + * fail. Tough luck but let's be kind and emit a warning to userspace. + */ +static inline void validate_fs_switch(struct fs_struct *old_fs) +{ + might_sleep(); + + if (likely(current->pid != 1)) + return; + /* @old_fs may be dangling but for comparison it's fine */ + if (old_fs != userspace_init_fs) + return; + pr_warn("VFS: Pid 1 stopped sharing filesystem state\n"); + set_fs_root(userspace_init_fs, &init_fs.root); + set_fs_pwd(userspace_init_fs, &init_fs.root); +} + +struct fs_struct *switch_fs_struct(struct fs_struct *new_fs) +{ + struct fs_struct *fs; + + scoped_guard(task_lock, current) { + fs = current->fs; + VFS_WARN_ON_ONCE(fs != current->real_fs); + read_seqlock_excl(&fs->seq); + current->fs = new_fs; + current->real_fs = new_fs; + if (--fs->users) + new_fs = NULL; + else + new_fs = fs; + read_sequnlock_excl(&fs->seq); + } + + validate_fs_switch(fs); + return new_fs; +} + /* to be mentioned only in INIT_TASK */ struct fs_struct init_fs = { .users = 1, .seq = __SEQLOCK_UNLOCKED(init_fs.seq), .umask = 0022, }; + +struct fs_struct *userspace_init_fs __ro_after_init; +EXPORT_SYMBOL_GPL(userspace_init_fs); + +void __init init_userspace_fs(void) +{ + struct mount *m; + struct path root; + + /* Move PID 1 from nullfs into the initramfs. */ + m = topmost_overmount(current->nsproxy->mnt_ns->root); + root.mnt = &m->mnt; + root.dentry = root.mnt->mnt_root; + + VFS_WARN_ON_ONCE(current->pid != 1); + + set_fs_root(current->fs, &root); + set_fs_pwd(current->fs, &root); + + /* Hold a reference for the global pointer. */ + read_seqlock_excl(¤t->fs->seq); + current->fs->users++; + read_sequnlock_excl(¤t->fs->seq); + + userspace_init_fs = current->fs; +} diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 0e2a1039fa43..d4e0029810c0 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1084,7 +1084,7 @@ static int fuse_mknod(struct mnt_idmap *idmap, struct inode *dir, } static int fuse_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *entry, umode_t mode, bool excl) + struct dentry *entry, umode_t mode) { return fuse_mknod(idmap, dir, entry, mode, 0); } @@ -1117,6 +1117,14 @@ static struct dentry *fuse_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (!fm->fc->dont_mask) mode &= ~current_umask(); + /* + * vfs_mkdir() now passes S_IFDIR in @mode, but @mode is forwarded + * verbatim to the userspace server which has only ever been given the + * permission bits. Strip the type bit until the protocol is known to + * cope with it. + */ + mode &= ~S_IFDIR; + memset(&inarg, 0, sizeof(inarg)); inarg.mode = mode; inarg.umask = current_umask(); diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 8a77794bbd4a..f361876c5583 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -963,15 +963,14 @@ fail: * @dir: The directory in which to create the file * @dentry: The dentry of the new file * @mode: The mode of the new file - * @excl: Force fail if inode exists * * Returns: errno */ static int gfs2_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { - return gfs2_create_inode(dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0, excl); + return gfs2_create_inode(dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0, 1); } /** @@ -1351,7 +1350,7 @@ static struct dentry *gfs2_mkdir(struct mnt_idmap *idmap, struct inode *dir, { unsigned dsize = gfs2_max_stuffed_size(GFS2_I(dir)); - return ERR_PTR(gfs2_create_inode(dir, dentry, NULL, S_IFDIR | mode, 0, NULL, dsize, 0)); + return ERR_PTR(gfs2_create_inode(dir, dentry, NULL, mode, 0, NULL, dsize, 0)); } /** diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c index e13450bb933e..e1f1fb351464 100644 --- a/fs/hfs/dir.c +++ b/fs/hfs/dir.c @@ -184,7 +184,7 @@ static int hfs_dir_release(struct inode *inode, struct file *file) * the directory and the name (and its length) of the new file. */ static int hfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; int res; @@ -219,7 +219,7 @@ static struct dentry *hfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct inode *inode; int res; - inode = hfs_new_inode(dir, &dentry->d_name, S_IFDIR | mode); + inode = hfs_new_inode(dir, &dentry->d_name, mode); if (IS_ERR(inode)) return ERR_CAST(inode); diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 8bf6c7cdd9a8..51fcba2e6d40 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -562,7 +562,7 @@ out: } static int hfsplus_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); } @@ -570,7 +570,7 @@ static int hfsplus_create(struct mnt_idmap *idmap, struct inode *dir, static struct dentry *hfsplus_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - return ERR_PTR(hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFDIR, 0)); + return ERR_PTR(hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode, 0)); } static int hfsplus_rename(struct mnt_idmap *idmap, diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index abe86d72d9ef..7add056d47d8 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -593,7 +593,7 @@ static struct inode *hostfs_iget(struct super_block *sb, char *name) } static int hostfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; char *name; diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c index 353e13a615f5..9446f4038874 100644 --- a/fs/hpfs/namei.c +++ b/fs/hpfs/namei.c @@ -105,10 +105,10 @@ static struct dentry *hpfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (!uid_eq(result->i_uid, current_fsuid()) || !gid_eq(result->i_gid, current_fsgid()) || - result->i_mode != (mode | S_IFDIR)) { + result->i_mode != mode) { result->i_uid = current_fsuid(); result->i_gid = current_fsgid(); - result->i_mode = mode | S_IFDIR; + result->i_mode = mode; hpfs_write_inode_nolock(result); } hpfs_update_directory_times(dir); @@ -129,7 +129,7 @@ bail: } static int hpfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { const unsigned char *name = dentry->d_name.name; unsigned len = dentry->d_name.len; diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 216e1a0dd0b2..38e9e59f64d8 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -971,7 +971,7 @@ static struct dentry *hugetlbfs_mkdir(struct mnt_idmap *idmap, struct inode *dir struct dentry *dentry, umode_t mode) { int retval = hugetlbfs_mknod(idmap, dir, dentry, - mode | S_IFDIR, 0); + mode, 0); if (!retval) inc_nlink(dir); return ERR_PTR(retval); @@ -979,7 +979,7 @@ static struct dentry *hugetlbfs_mkdir(struct mnt_idmap *idmap, struct inode *dir static int hugetlbfs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, bool excl) + umode_t mode) { return hugetlbfs_mknod(idmap, dir, dentry, mode | S_IFREG, 0); } diff --git a/fs/inode.c b/fs/inode.c index 31c5b9ee3a81..a31aa7cb47f6 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -763,21 +763,18 @@ void clear_inode(struct inode *inode) fsverity_cleanup_inode(inode); /* - * We have to cycle the i_pages lock here because reclaim can be in the - * process of removing the last page (in __filemap_remove_folio()) - * and we must not free the mapping under it. + * We have to cycle the i_pages lock here because reclaim + * can be in the process of removing the last page (in + * __filemap_remove_folio()) and we must not free the mapping + * under it. We also remove nodes which are empty; these + * can occur in two different ways. The first is that radix + * tree expansion can fail partway and the second is that THP + * collapse_file() can allocate some temporary nodes and not + * clean them up. */ - xa_lock_irq(&inode->i_data.i_pages); + xa_destroy(&inode->i_data.i_pages); + BUG_ON(inode->i_data.nrpages); - /* - * Almost always, mapping_empty(&inode->i_data) here; but there are - * two known and long-standing ways in which nodes may get left behind - * (when deep radix-tree node allocation failed partway; or when THP - * collapse_file() failed). Until those two known cases are cleaned up, - * or a cleanup function is called here, do not BUG_ON(!mapping_empty), - * nor even WARN_ON(!mapping_empty). - */ - xa_unlock_irq(&inode->i_data.i_pages); BUG_ON(!(inode_state_read_once(inode) & I_FREEING)); BUG_ON(inode_state_read_once(inode) & I_CLEAR); BUG_ON(!list_empty(&inode->i_wb_list)); diff --git a/fs/internal.h b/fs/internal.h index 355d93f92208..174f06357555 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -137,6 +137,7 @@ extern int reconfigure_super(struct fs_context *); extern bool super_trylock_shared(struct super_block *sb); struct super_block *user_get_super(dev_t, bool excl); void put_super(struct super_block *sb); +void __init super_dev_init(void); extern bool mount_capable(struct fs_context *); /* diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 276720bc18dc..6f07dbe6e8f2 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -768,7 +768,7 @@ EXPORT_SYMBOL_GPL(iomap_is_partially_uptodate); */ struct folio *iomap_get_folio(struct iomap_iter *iter, loff_t pos, size_t len) { - fgf_t fgp = FGP_WRITEBEGIN | FGP_NOFS; + fgf_t fgp = FGP_WRITEBEGIN; if (iter->flags & IOMAP_NOWAIT) fgp |= FGP_NOWAIT; @@ -1156,7 +1156,6 @@ static bool iomap_write_end(struct iomap_iter *iter, size_t len, size_t copied, static int iomap_write_iter(struct iomap_iter *iter, struct iov_iter *i, const struct iomap_write_ops *write_ops) { - ssize_t total_written = 0; int status = 0; struct address_space *mapping = iter->inode->i_mapping; size_t chunk = mapping_max_folio_size(mapping); @@ -1252,12 +1251,11 @@ retry: goto retry; } } else { - total_written += written; iomap_iter_advance(iter, written); } } while (iov_iter_count(i) && iomap_length(iter)); - return total_written ? 0 : status; + return status; } ssize_t diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index e2cd5f92babe..64c6b7286c20 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -10,6 +10,7 @@ #include <linux/iomap.h> #include <linux/task_io_accounting_ops.h> #include <linux/fserror.h> +#include <linux/init.h> #include "internal.h" #include "trace.h" @@ -88,9 +89,9 @@ static inline enum fserror_type iomap_dio_err_type(const struct iomap_dio *dio) return FSERR_DIRECTIO_READ; } -static inline bool should_report_dio_fserror(const struct iomap_dio *dio) +static inline bool should_report_dio_fserror(int error) { - switch (dio->error) { + switch (error) { case 0: case -EAGAIN: case -ENOTBLK: @@ -110,7 +111,7 @@ ssize_t iomap_dio_complete(struct iomap_dio *dio) if (dops && dops->end_io) ret = dops->end_io(iocb, dio->size, ret, dio->flags); - if (should_report_dio_fserror(dio)) + if (should_report_dio_fserror(dio->error)) fserror_report_io(file_inode(iocb->ki_filp), iomap_dio_err_type(dio), offset, dio->size, dio->error, GFP_NOFS); @@ -403,6 +404,14 @@ out_put_bio: return ret; } +static inline unsigned int iomap_dio_alignment(struct inode *inode, + struct block_device *bdev, unsigned int dio_flags) +{ + if (dio_flags & IOMAP_DIO_FSBLOCK_ALIGNED) + return i_blocksize(inode); + return bdev_logical_block_size(bdev); +} + static int iomap_dio_bio_iter(struct iomap_iter *iter, struct iomap_dio *dio) { const struct iomap *iomap = &iter->iomap; @@ -421,10 +430,7 @@ static int iomap_dio_bio_iter(struct iomap_iter *iter, struct iomap_dio *dio) * File systems that write out of place and always allocate new blocks * need each bio to be block aligned as that's the unit of allocation. */ - if (dio->flags & IOMAP_DIO_FSBLOCK_ALIGNED) - alignment = fs_block_size; - else - alignment = bdev_logical_block_size(iomap->bdev); + alignment = iomap_dio_alignment(inode, iomap->bdev, dio->flags); if ((pos | length) & (alignment - 1)) return -EINVAL; @@ -893,12 +899,277 @@ out_free_dio: } EXPORT_SYMBOL_GPL(__iomap_dio_rw); +struct iomap_dio_simple { + struct kiocb *iocb; + size_t size; + unsigned int dio_flags; + struct work_struct work; + /* + * Align @bio to a cacheline boundary so that, combined with the + * front_pad passed to bioset_init(), the bio sits at the start of + * a cacheline in memory returned by the (HWCACHE-aligned) bio + * slab. This keeps the hot fields block layer touches on submit + * and completion (bi_iter, bi_status, ...) within a single line. + */ + struct bio bio ____cacheline_aligned_in_smp; +}; + +static struct bio_set iomap_dio_simple_pool; + +static ssize_t iomap_dio_simple_complete(struct iomap_dio_simple *sr) +{ + struct bio *bio = &sr->bio; + struct kiocb *iocb = sr->iocb; + struct inode *inode = file_inode(iocb->ki_filp); + ssize_t ret; + + if (unlikely(bio->bi_status)) { + ret = blk_status_to_errno(bio->bi_status); + if (should_report_dio_fserror(ret)) + fserror_report_io(inode, FSERR_DIRECTIO_READ, + iocb->ki_pos, sr->size, ret, + GFP_NOFS); + } else { + ret = sr->size; + iocb->ki_pos += ret; + } + + if (sr->dio_flags & IOMAP_DIO_USER_BACKED) { + bio_check_pages_dirty(bio); + } else { + bio_release_pages(bio, false); + bio_put(bio); + } + inode_dio_end(inode); + trace_iomap_dio_complete(iocb, ret < 0 ? ret : 0, ret); + return ret; +} + +static void iomap_dio_simple_complete_work(struct work_struct *work) +{ + struct iomap_dio_simple *sr = + container_of(work, struct iomap_dio_simple, work); + struct kiocb *iocb = sr->iocb; + + WRITE_ONCE(iocb->private, NULL); + iocb->ki_complete(iocb, iomap_dio_simple_complete(sr)); +} + +static void iomap_dio_simple_end_io(struct bio *bio) +{ + struct iomap_dio_simple *sr = + container_of(bio, struct iomap_dio_simple, bio); + struct kiocb *iocb = sr->iocb; + + if (unlikely(sr->bio.bi_status)) { + struct inode *inode = file_inode(iocb->ki_filp); + + INIT_WORK(&sr->work, iomap_dio_simple_complete_work); + queue_work(inode->i_sb->s_dio_done_wq, &sr->work); + return; + } + + WRITE_ONCE(iocb->private, NULL); + iocb->ki_complete(iocb, iomap_dio_simple_complete(sr)); +} + +static inline bool +iomap_dio_simple_supported(struct kiocb *iocb, struct iov_iter *iter, + const struct iomap_dio_ops *dops, + unsigned int dio_flags, size_t done_before) +{ + struct inode *inode = file_inode(iocb->ki_filp); + size_t count = iov_iter_count(iter); + + if (dops || done_before) + return false; + if (iov_iter_rw(iter) != READ) + return false; + if (!count) + return false; + /* + * Simple dio is an optimization for small IO. Filter out large IO + * early as it's the most common case to fail for typical direct IO + * workloads. + */ + if (count > inode->i_sb->s_blocksize) + return false; + if (dio_flags & (IOMAP_DIO_FORCE_WAIT | IOMAP_DIO_PARTIAL | + IOMAP_DIO_BOUNCE)) + return false; + if (iocb->ki_pos + count > i_size_read(inode)) + return false; + if (IS_ENCRYPTED(inode)) + return false; + + return true; +} + +/* + * Fast path for small, block-aligned direct I/Os that map to a single + * contiguous on-disk extent. + * + * iomap_dio_simple_supported() enforces the cheap up-front constraints before + * entering this path. + * + * @dops must be NULL: a non-NULL @dops means the caller wants its + * ->end_io / ->submit_io hooks invoked, and in particular wants its bios to be + * allocated from the filesystem-private @dops->bio_set (whose front_pad sizes a + * filesystem-private wrapper around the bio). The fast path instead allocates + * from the shared iomap_dio_simple_pool, whose front_pad matches struct + * iomap_dio_simple; the two wrappers are not interchangeable, so we must fall + * back to __iomap_dio_rw() in that case. + * + * @done_before must be zero: a non-zero caller-accumulated residual cannot be + * carried through a single-bio inline completion. + * + * @iter must describe a non-empty READ no larger than the inode block size: + * writes, zero-length I/O, and larger requests need the generic iomap direct + * I/O path. + * + * @dio_flags must not request IOMAP_DIO_FORCE_WAIT, IOMAP_DIO_PARTIAL, or + * IOMAP_DIO_BOUNCE: this path does not support forced waiting, partial direct + * I/O, or bouncing. The range must also stay within i_size and encrypted + * inodes must use the generic iomap direct I/O path. + * + * -ENOTBLK is the private sentinel returned by iomap_dio_simple() when it + * decides the request does not fit the fast path. In that case we proceed to + * the generic __iomap_dio_rw() slow path. Any other errno is a real result and + * is propagated as-is, in particular -EAGAIN for IOCB_NOWAIT must reach the + * caller. + */ +static ssize_t +iomap_dio_simple(struct kiocb *iocb, struct iov_iter *iter, + const struct iomap_ops *ops, void *private, + unsigned int dio_flags) +{ + struct inode *inode = file_inode(iocb->ki_filp); + size_t count = iov_iter_count(iter); + bool wait_for_completion = is_sync_kiocb(iocb); + struct iomap_iter iomi = { + .inode = inode, + .pos = iocb->ki_pos, + .len = count, + .flags = IOMAP_DIRECT, + .private = private, + }; + struct iomap_dio_simple *sr; + unsigned int alignment; + struct bio *bio; + ssize_t ret; + + if (iocb->ki_flags & IOCB_NOWAIT) + iomi.flags |= IOMAP_NOWAIT; + + ret = kiocb_write_and_wait(iocb, count); + if (ret) + return ret; + + inode_dio_begin(inode); + + ret = ops->iomap_begin(inode, iomi.pos, count, iomi.flags, + &iomi.iomap, &iomi.srcmap); + if (ret) { + inode_dio_end(inode); + return ret; + } + + if (iomi.iomap.type != IOMAP_MAPPED || + iomi.iomap.offset + iomi.iomap.length < iomi.pos + count || + (iomi.iomap.flags & IOMAP_F_INTEGRITY)) { + ret = -ENOTBLK; + goto out_iomap_end; + } + + alignment = iomap_dio_alignment(inode, iomi.iomap.bdev, dio_flags); + if ((iomi.pos | count) & (alignment - 1)) { + ret = -EINVAL; + goto out_iomap_end; + } + + if (!wait_for_completion && unlikely(!inode->i_sb->s_dio_done_wq)) { + ret = sb_init_dio_done_wq(inode->i_sb); + if (ret < 0) + goto out_iomap_end; + } + + trace_iomap_dio_rw_begin(iocb, iter, dio_flags, 0); + + if (user_backed_iter(iter)) + dio_flags |= IOMAP_DIO_USER_BACKED; + + bio = bio_alloc_bioset(iomi.iomap.bdev, + bio_iov_vecs_to_alloc(iter, BIO_MAX_VECS), + REQ_OP_READ, GFP_KERNEL, &iomap_dio_simple_pool); + sr = container_of(bio, struct iomap_dio_simple, bio); + sr->iocb = iocb; + sr->dio_flags = dio_flags; + + bio->bi_iter.bi_sector = iomap_sector(&iomi.iomap, iomi.pos); + bio->bi_ioprio = iocb->ki_ioprio; + + ret = bio_iov_iter_get_pages(bio, iter, alignment - 1); + if (unlikely(ret)) + goto out_bio_put; + + if (bio->bi_iter.bi_size != count) { + iov_iter_revert(iter, bio->bi_iter.bi_size); + ret = -ENOTBLK; + goto out_bio_release_pages; + } + + sr->size = bio->bi_iter.bi_size; + + if (dio_flags & IOMAP_DIO_USER_BACKED) + bio_set_pages_dirty(bio); + + if (iocb->ki_flags & IOCB_NOWAIT) + bio->bi_opf |= REQ_NOWAIT; + if ((iocb->ki_flags & IOCB_HIPRI) && !wait_for_completion) { + bio->bi_opf |= REQ_POLLED; + WRITE_ONCE(iocb->private, bio); + } + + if (ops->iomap_end) + ops->iomap_end(inode, iomi.pos, count, count, iomi.flags, + &iomi.iomap); + + if (!wait_for_completion) { + bio->bi_end_io = iomap_dio_simple_end_io; + submit_bio(bio); + trace_iomap_dio_rw_queued(inode, iomi.pos, count); + return -EIOCBQUEUED; + } + + submit_bio_wait(bio); + return iomap_dio_simple_complete(sr); + +out_bio_release_pages: + bio_release_pages(bio, false); +out_bio_put: + bio_put(bio); +out_iomap_end: + if (ops->iomap_end) + ops->iomap_end(inode, iomi.pos, count, 0, iomi.flags, + &iomi.iomap); + inode_dio_end(inode); + return ret; +} + ssize_t iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter, const struct iomap_ops *ops, const struct iomap_dio_ops *dops, unsigned int dio_flags, void *private, size_t done_before) { struct iomap_dio *dio; + ssize_t ret; + + if (iomap_dio_simple_supported(iocb, iter, dops, dio_flags, + done_before)) { + ret = iomap_dio_simple(iocb, iter, ops, private, dio_flags); + if (ret != -ENOTBLK) + return ret; + } dio = __iomap_dio_rw(iocb, iter, ops, dops, dio_flags, private, done_before); @@ -907,3 +1178,11 @@ iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter, return iomap_dio_complete(dio); } EXPORT_SYMBOL_GPL(iomap_dio_rw); + +static int __init iomap_dio_init(void) +{ + return bioset_init(&iomap_dio_simple_pool, 4, + offsetof(struct iomap_dio_simple, bio), + BIOSET_NEED_BVECS | BIOSET_PERCPU_CACHE); +} +fs_initcall(iomap_dio_init); diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index 0565328764c1..2ec755a89228 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -13,6 +13,7 @@ struct bio_set iomap_ioend_bioset; EXPORT_SYMBOL_GPL(iomap_ioend_bioset); +static struct bio_set iomap_ioend_split_bioset; struct iomap_ioend *iomap_init_ioend(struct inode *inode, struct bio *bio, loff_t file_offset, u16 ioend_flags) @@ -486,7 +487,8 @@ struct iomap_ioend *iomap_split_ioend(struct iomap_ioend *ioend, sector_offset = ALIGN_DOWN(sector_offset << SECTOR_SHIFT, i_blocksize(ioend->io_inode)) >> SECTOR_SHIFT; - split = bio_split(bio, sector_offset, GFP_NOFS, &iomap_ioend_bioset); + split = bio_split(bio, sector_offset, GFP_NOFS, + &iomap_ioend_split_bioset); if (IS_ERR(split)) return ERR_CAST(split); split->bi_private = bio->bi_private; @@ -509,8 +511,23 @@ EXPORT_SYMBOL_GPL(iomap_split_ioend); static int __init iomap_ioend_init(void) { - return bioset_init(&iomap_ioend_bioset, 4 * (PAGE_SIZE / SECTOR_SIZE), + const unsigned int nr_mempool_entries = 4 * (PAGE_SIZE / SECTOR_SIZE); + int error; + + error = bioset_init(&iomap_ioend_bioset, nr_mempool_entries, offsetof(struct iomap_ioend, io_bio), BIOSET_NEED_BVECS); + if (error) + return error; + error = bioset_init(&iomap_ioend_split_bioset, nr_mempool_entries, + offsetof(struct iomap_ioend, io_bio), + BIOSET_NEED_BVECS); + if (error) + goto out_exit_ioend_bioset; + return 0; + +out_exit_ioend_bioset: + bioset_exit(&iomap_ioend_bioset); + return error; } fs_initcall(iomap_ioend_init); diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index c4088c3b4ac0..656c920864c5 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -26,7 +26,7 @@ static int jffs2_readdir (struct file *, struct dir_context *); static int jffs2_create (struct mnt_idmap *, struct inode *, - struct dentry *, umode_t, bool); + struct dentry *, umode_t); static struct dentry *jffs2_lookup (struct inode *,struct dentry *, unsigned int); static int jffs2_link (struct dentry *,struct inode *,struct dentry *); @@ -163,7 +163,7 @@ static int jffs2_readdir(struct file *file, struct dir_context *ctx) static int jffs2_create(struct mnt_idmap *idmap, struct inode *dir_i, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct jffs2_raw_inode *ri; struct jffs2_inode_info *f, *dir_f; @@ -462,8 +462,6 @@ static struct dentry *jffs2_mkdir (struct mnt_idmap *idmap, struct inode *dir_i, uint32_t alloclen; int ret; - mode |= S_IFDIR; - ri = jffs2_alloc_raw_inode(); if (!ri) return ERR_PTR(-ENOMEM); diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c index 442d62679262..8a36c218f0f7 100644 --- a/fs/jfs/namei.c +++ b/fs/jfs/namei.c @@ -61,7 +61,7 @@ static inline void free_ea_wmap(struct inode *inode) * */ static int jfs_create(struct mnt_idmap *idmap, struct inode *dip, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { int rc = 0; tid_t tid; /* transaction id */ @@ -223,7 +223,7 @@ static struct dentry *jfs_mkdir(struct mnt_idmap *idmap, struct inode *dip, * block there while holding dtree page, so we allocate the inode & * begin the transaction before we search the directory. */ - ip = ialloc(dip, S_IFDIR | mode); + ip = ialloc(dip, mode); if (IS_ERR(ip)) { rc = PTR_ERR(ip); goto out2; diff --git a/fs/kernel_read_file.c b/fs/kernel_read_file.c index de32c95d823d..9c2ba9240083 100644 --- a/fs/kernel_read_file.c +++ b/fs/kernel_read_file.c @@ -150,18 +150,13 @@ ssize_t kernel_read_file_from_path_initns(const char *path, loff_t offset, enum kernel_read_file_id id) { struct file *file; - struct path root; ssize_t ret; if (!path || !*path) return -EINVAL; - task_lock(&init_task); - get_fs_root(init_task.fs, &root); - task_unlock(&init_task); - - file = file_open_root(&root, path, O_RDONLY, 0); - path_put(&root); + scoped_with_init_fs() + file = filp_open(path, O_RDONLY, 0); if (IS_ERR(file)) return PTR_ERR(file); diff --git a/fs/minix/namei.c b/fs/minix/namei.c index 263e4ba8b1c8..5525ba367ed7 100644 --- a/fs/minix/namei.c +++ b/fs/minix/namei.c @@ -64,7 +64,7 @@ static int minix_tmpfile(struct mnt_idmap *idmap, struct inode *dir, } static int minix_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return minix_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); } @@ -110,7 +110,7 @@ static struct dentry *minix_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct inode * inode; int err; - inode = minix_new_inode(dir, S_IFDIR | mode); + inode = minix_new_inode(dir, mode); if (IS_ERR(inode)) return ERR_CAST(inode); diff --git a/fs/namei.c b/fs/namei.c index 19ce43c9a6e6..e4ef48583a3b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4140,11 +4140,6 @@ EXPORT_SYMBOL(end_renaming); * after setgid stripping allows the same ordering for both non-POSIX ACL and * POSIX ACL supporting filesystems. * - * Note that it's currently valid for @type to be 0 if a directory is created. - * Filesystems raise that flag individually and we need to check whether each - * filesystem can deal with receiving S_IFDIR from the vfs before we enforce a - * non-zero type. - * * Returns: mode to be passed to the filesystem */ static inline umode_t vfs_prepare_mode(struct mnt_idmap *idmap, @@ -4190,7 +4185,7 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode, return error; if (!dir->i_op->create) - return -EACCES; /* shouldn't it be ENOSYS? */ + return -EOPNOTSUPP; mode = vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG); error = security_inode_create(dir, dentry, mode); @@ -4199,7 +4194,7 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode, error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di); if (error) return error; - error = dir->i_op->create(idmap, dir, dentry, mode, true); + error = dir->i_op->create(idmap, dir, dentry, mode); if (!error) fsnotify_create(dir, dentry); return error; @@ -4501,12 +4496,11 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, file->f_mode |= FMODE_CREATED; audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); if (!dir_inode->i_op->create) { - error = -EACCES; + error = -EOPNOTSUPP; goto out_dput; } - error = dir_inode->i_op->create(idmap, dir_inode, dentry, - mode, open_flag & O_EXCL); + error = dir_inode->i_op->create(idmap, dir_inode, dentry, mode); if (error) goto out_dput; } @@ -5117,7 +5111,7 @@ int vfs_mknod(struct mnt_idmap *idmap, struct inode *dir, return -EPERM; if (!dir->i_op->mknod) - return -EPERM; + return -EOPNOTSUPP; mode = vfs_prepare_mode(idmap, dir, mode, mode, mode); error = devcgroup_inode_mknod(mode, dev); @@ -5256,11 +5250,11 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (error) goto err; - error = -EPERM; + error = -EOPNOTSUPP; if (!dir->i_op->mkdir) goto err; - mode = vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, 0); + mode = vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, S_IFDIR); error = security_inode_mkdir(dir, dentry, mode); if (error) goto err; @@ -5360,7 +5354,7 @@ int vfs_rmdir(struct mnt_idmap *idmap, struct inode *dir, return error; if (!dir->i_op->rmdir) - return -EPERM; + return -EOPNOTSUPP; dget(dentry); inode_lock(dentry->d_inode); @@ -5496,7 +5490,7 @@ int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir, return error; if (!dir->i_op->unlink) - return -EPERM; + return -EOPNOTSUPP; inode_lock(target); if (IS_SWAPFILE(target)) @@ -5647,7 +5641,7 @@ int vfs_symlink(struct mnt_idmap *idmap, struct inode *dir, return error; if (!dir->i_op->symlink) - return -EPERM; + return -EOPNOTSUPP; error = security_inode_symlink(dir, dentry, oldname); if (error) @@ -5769,7 +5763,7 @@ int vfs_link(struct dentry *old_dentry, struct mnt_idmap *idmap, if (HAS_UNMAPPED_ID(idmap, inode)) return -EPERM; if (!dir->i_op->link) - return -EPERM; + return -EOPNOTSUPP; if (S_ISDIR(inode->i_mode)) return -EPERM; @@ -5978,7 +5972,7 @@ int vfs_rename(struct renamedata *rd) return error; if (!old_dir->i_op->rename) - return -EPERM; + return -EOPNOTSUPP; /* * If we are going to change the parent - check write permissions, diff --git a/fs/namespace.c b/fs/namespace.c index 3d5cd5bf3b05..6b81b77f9303 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2908,6 +2908,9 @@ static int do_change_type(const struct path *path, int ms_flags) for (m = mnt; m; m = (recurse ? next_mnt(m, mnt) : NULL)) change_mnt_propagation(m, type); + guard(mount_locked_reader)(); + touch_mnt_namespace(mnt->mnt_ns); + return 0; } @@ -3481,6 +3484,10 @@ static int do_set_group(const struct path *from_path, const struct path *to_path list_add(&to->mnt_share, &from->mnt_share); set_mnt_shared(to); } + + guard(mount_locked_reader)(); + touch_mnt_namespace(to->mnt_ns); + return 0; } @@ -6184,12 +6191,14 @@ static void __init init_mount_tree(void) struct path root; /* - * We create two mounts: + * We create three mounts: * * (1) nullfs with mount id 1 * (2) mutable rootfs with mount id 2 + * (3) private nullfs for kthreads (SB_KERNMOUNT) * - * with (2) mounted on top of (1). + * with (2) mounted on top of (1). The init_task's root and pwd + * are pointed at (3) so all kthreads start isolated in nullfs. */ nullfs_mnt = vfs_kern_mount(&nullfs_fs_type, 0, "nullfs", NULL); if (IS_ERR(nullfs_mnt)) @@ -6229,12 +6238,14 @@ static void __init init_mount_tree(void) init_mnt_ns.nr_mounts++; } + nullfs_mnt = kern_mount(&nullfs_fs_type); + if (IS_ERR(nullfs_mnt)) + panic("VFS: Failed to create private nullfs instance"); + root.mnt = nullfs_mnt; + root.dentry = nullfs_mnt->mnt_root; + init_task.nsproxy->mnt_ns = &init_mnt_ns; get_mnt_ns(&init_mnt_ns); - - /* The root and pwd always point to the mutable rootfs. */ - root.mnt = mnt; - root.dentry = mnt->mnt_root; set_fs_pwd(current->fs, &root); set_fs_root(current->fs, &root); @@ -6262,6 +6273,8 @@ void __init mnt_init(void) if (!mount_hashtable || !mountpoint_hashtable) panic("Failed to allocate mount hash table\n"); + super_dev_init(); + kernfs_init(); err = sysfs_init(); diff --git a/fs/nfs/blocklayout/dev.c b/fs/nfs/blocklayout/dev.c index bb35f88501ce..368d20daf67b 100644 --- a/fs/nfs/blocklayout/dev.c +++ b/fs/nfs/blocklayout/dev.c @@ -4,6 +4,7 @@ */ #include <linux/sunrpc/svc.h> #include <linux/blkdev.h> +#include <linux/fs_struct.h> #include <linux/nfs4.h> #include <linux/nfs_fs.h> #include <linux/nfs_xdr.h> @@ -363,15 +364,22 @@ static struct file * bl_open_path(struct pnfs_block_volume *v, const char *prefix) { struct file *bdev_file; - const char *devname; + const char *devname __free(kfree) = NULL; devname = kasprintf(GFP_KERNEL, "/dev/disk/by-id/%s%*phN", prefix, v->scsi.designator_len, v->scsi.designator); if (!devname) return ERR_PTR(-ENOMEM); - bdev_file = bdev_file_open_by_path(devname, - BLK_OPEN_READ | BLK_OPEN_WRITE, NULL, NULL); + if (tsk_is_kthread(current)) { + scoped_with_init_fs() + bdev_file = bdev_file_open_by_path(devname, + BLK_OPEN_READ | BLK_OPEN_WRITE, + NULL, NULL); + } else { + bdev_file = bdev_file_open_by_path(devname, + BLK_OPEN_READ | BLK_OPEN_WRITE, NULL, NULL); + } if (IS_ERR(bdev_file)) { dprintk("failed to open device %s (%ld)\n", devname, PTR_ERR(bdev_file)); @@ -380,7 +388,6 @@ bl_open_path(struct pnfs_block_volume *v, const char *prefix) file_bdev(bdev_file)->bd_disk->disk_name); } - kfree(devname); return bdev_file; } diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index c7b723c18620..50acc0a9d2b2 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2427,9 +2427,9 @@ out_err: } int nfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { - return nfs_do_create(dir, dentry, mode, excl ? O_EXCL : 0); + return nfs_do_create(dir, dentry, mode, O_EXCL); } EXPORT_SYMBOL_GPL(nfs_create); @@ -2474,7 +2474,7 @@ struct dentry *nfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, dir->i_sb->s_id, dir->i_ino, dentry); attr.ia_valid = ATTR_MODE; - attr.ia_mode = mode | S_IFDIR; + attr.ia_mode = mode; trace_nfs_mkdir_enter(dir, dentry); ret = NFS_PROTO(dir)->mkdir(dir, dentry, &attr); diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index acaeff7ddfdf..dd77d5e80d7b 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -395,7 +395,7 @@ extern unsigned long nfs_access_cache_scan(struct shrinker *shrink, struct dentry *nfs_lookup(struct inode *, struct dentry *, unsigned int); void nfs_d_prune_case_insensitive_aliases(struct inode *inode); int nfs_create(struct mnt_idmap *, struct inode *, struct dentry *, - umode_t, bool); + umode_t); struct dentry *nfs_mkdir(struct mnt_idmap *, struct inode *, struct dentry *, umode_t); int nfs_rmdir(struct inode *, struct dentry *); diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c index e2fe95de3d71..77c5f7f74fbf 100644 --- a/fs/nilfs2/namei.c +++ b/fs/nilfs2/namei.c @@ -86,7 +86,7 @@ nilfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) * with d_instantiate(). */ static int nilfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; struct nilfs_transaction_info ti; @@ -231,7 +231,7 @@ static struct dentry *nilfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, inc_nlink(dir); - inode = nilfs_new_inode(dir, S_IFDIR | mode); + inode = nilfs_new_inode(dir, mode); err = PTR_ERR(inode); if (IS_ERR(inode)) goto out_dir; diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 5ff25e9aaa32..a7a7f9532a16 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -736,7 +736,7 @@ err_out: } static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct ntfs_volume *vol = NTFS_SB(dir->i_sb); struct ntfs_inode *ni; @@ -1082,7 +1082,7 @@ static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, if (!(vol->vol_flags & VOLUME_IS_DIRTY)) ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); - ni = __ntfs_create(idmap, dir, uname, uname_len, S_IFDIR | mode, 0, NULL, 0); + ni = __ntfs_create(idmap, dir, uname, uname_len, mode, 0, NULL, 0); kmem_cache_free(ntfs_name_cache, uname); if (IS_ERR(ni)) { err = PTR_ERR(ni); diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index c59de5f2fa97..5ab45d17333e 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -105,7 +105,7 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry, * ntfs_create - inode_operations::create */ static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return ntfs_create_inode(idmap, dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0, NULL); @@ -213,7 +213,7 @@ static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ERR_PTR(ntfs_create_inode(idmap, dir, dentry, NULL, - S_IFDIR | mode, 0, NULL, 0, NULL)); + mode, 0, NULL, 0, NULL)); } /* diff --git a/fs/nullfs.c b/fs/nullfs.c index fdbd3e5d3d71..e06352c7b2cc 100644 --- a/fs/nullfs.c +++ b/fs/nullfs.c @@ -4,6 +4,8 @@ #include <linux/fs_context.h> #include <linux/magic.h> +#include "mount.h" + static const struct super_operations nullfs_super_operations = { .statfs = simple_statfs, }; @@ -40,14 +42,9 @@ static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc) return 0; } -/* - * For now this is a single global instance. If needed we can make it - * mountable by userspace at which point we will need to make it - * multi-instance. - */ static int nullfs_fs_get_tree(struct fs_context *fc) { - return get_tree_single(fc, nullfs_fs_fill_super); + return get_tree_nodev(fc, nullfs_fs_fill_super); } static const struct fs_context_operations nullfs_fs_context_ops = { @@ -57,9 +54,8 @@ static const struct fs_context_operations nullfs_fs_context_ops = { static int nullfs_init_fs_context(struct fs_context *fc) { fc->ops = &nullfs_fs_context_ops; - fc->global = true; - fc->sb_flags = SB_NOUSER; - fc->s_iflags = SB_I_NOEXEC | SB_I_NODEV; + fc->sb_flags |= SB_NOUSER; + fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; return 0; } diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c index 5821e33df78f..53df5dd10ad0 100644 --- a/fs/ocfs2/dlmfs/dlmfs.c +++ b/fs/ocfs2/dlmfs/dlmfs.c @@ -422,7 +422,7 @@ static struct dentry *dlmfs_mkdir(struct mnt_idmap * idmap, goto bail; } - inode = dlmfs_get_inode(dir, dentry, mode | S_IFDIR); + inode = dlmfs_get_inode(dir, dentry, mode); if (!inode) { status = -ENOMEM; mlog_errno(status); @@ -453,8 +453,7 @@ bail: static int dlmfs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool excl) + umode_t mode) { int status = 0; struct inode *inode; diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 1277666c77cd..b23dd678a7e0 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -657,7 +657,7 @@ static struct dentry *ocfs2_mkdir(struct mnt_idmap *idmap, trace_ocfs2_mkdir(dir, dentry, dentry->d_name.len, dentry->d_name.name, OCFS2_I(dir)->ip_blkno, mode); - ret = ocfs2_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFDIR, 0); + ret = ocfs2_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); if (ret) mlog_errno(ret); @@ -667,8 +667,7 @@ static struct dentry *ocfs2_mkdir(struct mnt_idmap *idmap, static int ocfs2_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool excl) + umode_t mode) { int ret; diff --git a/fs/ocfs2/super.c b/fs/ocfs2/super.c index bcac614ff02a..c62e389d4dd6 100644 --- a/fs/ocfs2/super.c +++ b/fs/ocfs2/super.c @@ -1882,7 +1882,6 @@ static void ocfs2_dismount_volume(struct super_block *sb, int mnt_err) ocfs2_delete_osb(osb); kfree(osb); - sb->s_dev = 0; sb->s_fs_info = NULL; } diff --git a/fs/omfs/dir.c b/fs/omfs/dir.c index 2ed541fccf33..692297cf84e7 100644 --- a/fs/omfs/dir.c +++ b/fs/omfs/dir.c @@ -282,11 +282,11 @@ out_free_inode: static struct dentry *omfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - return ERR_PTR(omfs_add_node(dir, dentry, mode | S_IFDIR)); + return ERR_PTR(omfs_add_node(dir, dentry, mode)); } static int omfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return omfs_add_node(dir, dentry, mode | S_IFREG); } diff --git a/fs/orangefs/namei.c b/fs/orangefs/namei.c index 75e65e72c2d6..8ebc34e112d5 100644 --- a/fs/orangefs/namei.c +++ b/fs/orangefs/namei.c @@ -18,8 +18,7 @@ static int orangefs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool exclusive) + umode_t mode) { struct orangefs_inode_s *parent = ORANGEFS_I(dir); struct orangefs_kernel_op_s *new_op; @@ -333,7 +332,7 @@ static struct dentry *orangefs_mkdir(struct mnt_idmap *idmap, struct inode *dir, ref = new_op->downcall.resp.mkdir.refn; - inode = orangefs_new_inode(dir->i_sb, dir, S_IFDIR | mode, 0, &ref); + inode = orangefs_new_inode(dir->i_sb, dir, mode, 0, &ref); if (IS_ERR(inode)) { gossip_err("*** Failed to allocate orangefs dir inode\n"); ret = PTR_ERR(inode); diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index a033743dbf51..610c0116252c 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -689,8 +689,8 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, return err; } -static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, - const char *link) +static int ovl_create_object(struct mnt_idmap *idmap, struct dentry *dentry, + int mode, dev_t rdev, const char *link) { int err; struct inode *inode; @@ -717,7 +717,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, inode_state_set(inode, I_CREATING); spin_unlock(&inode->i_lock); - inode_init_owner(&nop_mnt_idmap, inode, dentry->d_parent->d_inode, mode); + inode_init_owner(idmap, inode, dentry->d_parent->d_inode, mode); attr.mode = inode->i_mode; err = ovl_create_or_link(dentry, inode, &attr, false); @@ -732,15 +732,15 @@ out: } static int ovl_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { - return ovl_create_object(dentry, (mode & 07777) | S_IFREG, 0, NULL); + return ovl_create_object(idmap, dentry, (mode & 07777) | S_IFREG, 0, NULL); } static struct dentry *ovl_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - return ERR_PTR(ovl_create_object(dentry, (mode & 07777) | S_IFDIR, 0, NULL)); + return ERR_PTR(ovl_create_object(idmap, dentry, (mode & 07777) | S_IFDIR, 0, NULL)); } static int ovl_mknod(struct mnt_idmap *idmap, struct inode *dir, @@ -750,13 +750,13 @@ static int ovl_mknod(struct mnt_idmap *idmap, struct inode *dir, if (S_ISCHR(mode) && rdev == WHITEOUT_DEV) return -EPERM; - return ovl_create_object(dentry, mode, rdev, NULL); + return ovl_create_object(idmap, dentry, mode, rdev, NULL); } static int ovl_symlink(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *link) { - return ovl_create_object(dentry, S_IFLNK, 0, link); + return ovl_create_object(idmap, dentry, S_IFLNK, 0, link); } static int ovl_set_link_redirect(struct dentry *dentry) @@ -1444,7 +1444,7 @@ static int ovl_tmpfile(struct mnt_idmap *idmap, struct inode *dir, if (!inode) goto drop_write; - inode_init_owner(&nop_mnt_idmap, inode, dir, mode); + inode_init_owner(idmap, inode, dir, mode); err = ovl_create_tmpfile(file, dentry, inode, inode->i_mode); if (err) goto put_inode; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index bc71231cad53..401cb8c75520 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -26,10 +26,18 @@ int ovl_setattr(struct mnt_idmap *idmap, struct dentry *dentry, bool full_copy_up = false; struct dentry *upperdentry; - err = setattr_prepare(&nop_mnt_idmap, dentry, attr); + err = setattr_prepare(idmap, dentry, attr); if (err) return err; + /* Rebase ownership from the mount idmap into overlay id space. */ + if (attr->ia_valid & ATTR_UID) + attr->ia_vfsuid = VFSUIDT_INIT(from_vfsuid(idmap, + i_user_ns(d_inode(dentry)), attr->ia_vfsuid)); + if (attr->ia_valid & ATTR_GID) + attr->ia_vfsgid = VFSGIDT_INIT(from_vfsgid(idmap, + i_user_ns(d_inode(dentry)), attr->ia_vfsgid)); + if (attr->ia_valid & ATTR_SIZE) { /* Truncate should trigger data copy up as well */ full_copy_up = true; @@ -172,6 +180,8 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path, int fsid = 0; int err; bool metacopy_blocks = false; + vfsuid_t vfsuid; + vfsgid_t vfsgid; metacopy_blocks = ovl_is_metacopy_dentry(dentry); @@ -284,6 +294,12 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path, if (!is_dir && ovl_test_flag(OVL_INDEX, d_inode(dentry))) stat->nlink = dentry->d_inode->i_nlink; + /* Map ownership of the real inode through the overlay mount idmap. */ + vfsuid = make_vfsuid(idmap, i_user_ns(inode), stat->uid); + vfsgid = make_vfsgid(idmap, i_user_ns(inode), stat->gid); + stat->uid = vfsuid_into_kuid(vfsuid); + stat->gid = vfsgid_into_kgid(vfsgid); + return err; } @@ -306,7 +322,7 @@ int ovl_permission(struct mnt_idmap *idmap, * Check overlay inode with the creds of task and underlying inode * with creds of mounter */ - err = generic_permission(&nop_mnt_idmap, inode, mask); + err = generic_permission(idmap, inode, mask); if (err) return err; @@ -534,7 +550,7 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, return -EOPNOTSUPP; if (type == ACL_TYPE_DEFAULT && !S_ISDIR(inode->i_mode)) return acl ? -EACCES : 0; - if (!inode_owner_or_capable(&nop_mnt_idmap, inode)) + if (!inode_owner_or_capable(idmap, inode)) return -EPERM; /* @@ -542,8 +558,8 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, * be done with mounter's capabilities and so that won't do it for us). */ if (unlikely(inode->i_mode & S_ISGID) && type == ACL_TYPE_ACCESS && - !in_group_p(inode->i_gid) && - !capable_wrt_inode_uidgid(&nop_mnt_idmap, inode, CAP_FSETID)) { + !in_group_or_capable(idmap, inode, + i_gid_into_vfsgid(idmap, inode))) { struct iattr iattr = { .ia_valid = ATTR_KILL_SGID }; err = ovl_setattr(&nop_mnt_idmap, dentry, &iattr); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index b75df37f70ac..e0d8c6152e9f 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -320,6 +320,7 @@ static inline int ovl_do_setxattr(struct ovl_fs *ofs, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { + /* Use vfs_setxattr(), not __vfs_setxattr(): it idmaps the security.capability rootid. */ int err = vfs_setxattr(ovl_upper_mnt_idmap(ofs), dentry, name, value, size, flags); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 60f0b7ceef0a..f2889cf9bc07 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1573,7 +1573,7 @@ struct file_system_type ovl_fs_type = { .name = "overlay", .init_fs_context = ovl_init_fs_context, .parameters = ovl_parameter_spec, - .fs_flags = FS_USERNS_MOUNT, + .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP, .kill_sb = kill_anon_super, }; MODULE_ALIAS_FS("overlay"); diff --git a/fs/overlayfs/xattrs.c b/fs/overlayfs/xattrs.c index aa95855c7023..811c94d2d9e9 100644 --- a/fs/overlayfs/xattrs.c +++ b/fs/overlayfs/xattrs.c @@ -84,6 +84,7 @@ static int ovl_xattr_get(struct dentry *dentry, struct inode *inode, const char struct path realpath; ovl_i_path_real(inode, &realpath); + /* Use vfs_getxattr(), not __vfs_getxattr(): it idmaps the security.capability rootid. */ with_ovl_creds(dentry->d_sb) return vfs_getxattr(mnt_idmap(realpath.mnt), realpath.dentry, name, value, size); } diff --git a/fs/posix_acl.c b/fs/posix_acl.c index b4bfe4ddf64e..6df5de23aac4 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -740,8 +740,6 @@ static int posix_acl_fix_xattr_common(const void *value, size_t size) count = posix_acl_xattr_count(size); if (count < 0) return -EINVAL; - if (count == 0) - return 0; return count; } diff --git a/fs/proc/array.c b/fs/proc/array.c index 479ea8cb4ef4..f6f75d206762 100644 --- a/fs/proc/array.c +++ b/fs/proc/array.c @@ -168,8 +168,8 @@ static inline void task_state(struct seq_file *m, struct pid_namespace *ns, cred = get_task_cred(p); task_lock(p); - if (p->fs) - umask = p->fs->umask; + if (p->real_fs) + umask = p->real_fs->umask; if (p->files) max_fds = files_fdtable(p->files)->max_fds; task_unlock(p); diff --git a/fs/proc/base.c b/fs/proc/base.c index 780f81259052..6a39de424f62 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -211,8 +211,8 @@ static int get_task_root(struct task_struct *task, struct path *root) int result = -ENOENT; task_lock(task); - if (task->fs) { - get_fs_root(task->fs, root); + if (task->real_fs) { + get_fs_root(task->real_fs, root); result = 0; } task_unlock(task); @@ -225,8 +225,8 @@ static int proc_cwd_link(struct dentry *dentry, struct path *path, int result = -ENOENT; task_lock(task); - if (task->fs) { - get_fs_pwd(task->fs, path); + if (task->real_fs) { + get_fs_pwd(task->real_fs, path); result = 0; } task_unlock(task); diff --git a/fs/proc_namespace.c b/fs/proc_namespace.c index 5c555db68aa2..036356c0a55b 100644 --- a/fs/proc_namespace.c +++ b/fs/proc_namespace.c @@ -254,13 +254,13 @@ static int mounts_open_common(struct inode *inode, struct file *file, } ns = nsp->mnt_ns; get_mnt_ns(ns); - if (!task->fs) { + if (!task->real_fs) { task_unlock(task); put_task_struct(task); ret = -ENOENT; goto err_put_ns; } - get_fs_root(task->fs, &root); + get_fs_root(task->real_fs, &root); task_unlock(task); put_task_struct(task); diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c index 3987639ed132..0a88ede48e0a 100644 --- a/fs/ramfs/inode.c +++ b/fs/ramfs/inode.c @@ -121,14 +121,14 @@ out: static struct dentry *ramfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - int retval = ramfs_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFDIR, 0); + int retval = ramfs_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); if (!retval) inc_nlink(dir); return ERR_PTR(retval); } static int ramfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return ramfs_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFREG, 0); } diff --git a/fs/romfs/super.c b/fs/romfs/super.c index ac55193bf398..43eb897197c0 100644 --- a/fs/romfs/super.c +++ b/fs/romfs/super.c @@ -587,7 +587,7 @@ static void romfs_kill_sb(struct super_block *sb) #ifdef CONFIG_ROMFS_ON_BLOCK if (sb->s_bdev) { sync_blockdev(sb->s_bdev); - bdev_fput(sb->s_bdev_file); + fs_bdev_file_release(sb->s_bdev_file, sb); } #endif } diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 854e672a4e37..287c632392c4 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -54,7 +54,7 @@ void cifs_sb_deactive(struct super_block *sb); extern const struct inode_operations cifs_dir_inode_ops; struct inode *cifs_root_iget(struct super_block *sb); int cifs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *direntry, umode_t mode, bool excl); + struct dentry *direntry, umode_t mode); int cifs_atomic_open(struct inode *dir, struct dentry *direntry, struct file *file, unsigned int oflags, umode_t mode); int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 88a4a1787ff0..7803bd5bd01f 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -645,7 +645,7 @@ out_free_xid: * hashed-positive by calling d_instantiate(). */ int cifs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *direntry, umode_t mode, bool excl) + struct dentry *direntry, umode_t mode) { struct cifs_sb_info *cifs_sb = CIFS_SB(dir); int rc; diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 2ed1c79c1132..10de74937911 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2282,6 +2282,13 @@ struct dentry *cifs_mkdir(struct mnt_idmap *idmap, struct inode *inode, const char *full_path; void *page; + /* + * vfs_mkdir() now passes S_IFDIR in @mode, but @mode is forwarded + * verbatim to the server and in the past only contained permission + * bits. Strip the type bit until SMB is verified to deal with it. + */ + mode &= ~S_IFDIR; + cifs_dbg(FYI, "In cifs_mkdir, mode = %04ho inode = 0x%p\n", mode, inode); diff --git a/fs/smb/server/mgmt/share_config.c b/fs/smb/server/mgmt/share_config.c index 6f97f8d39657..e00aee155935 100644 --- a/fs/smb/server/mgmt/share_config.c +++ b/fs/smb/server/mgmt/share_config.c @@ -9,6 +9,7 @@ #include <linux/rwsem.h> #include <linux/parser.h> #include <linux/namei.h> +#include <linux/fs_struct.h> #include <linux/sched.h> #include <linux/mm.h> @@ -193,7 +194,8 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work, goto out; } - ret = kern_path(share->path, 0, &share->vfs_path); + scoped_with_init_fs() + ret = kern_path(share->path, 0, &share->vfs_path); ksmbd_revert_fsids(work); if (ret) { ksmbd_debug(SMB, "failed to access '%s'\n", diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 097f51fc7ed6..1f11ece7e74d 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -9,6 +9,7 @@ #include <net/addrconf.h> #include <linux/syscalls.h> #include <linux/namei.h> +#include <linux/fs_struct.h> #include <linux/statfs.h> #include <linux/ethtool.h> #include <linux/falloc.h> @@ -5868,7 +5869,8 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work, if (!share->path) return -EIO; - rc = kern_path(share->path, LOOKUP_NO_SYMLINKS, &path); + scoped_with_init_fs() + rc = kern_path(share->path, LOOKUP_NO_SYMLINKS, &path); if (rc) { pr_err("cannot create vfs path\n"); return -EIO; diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index d0a0ad15d803..2797782c0623 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -7,6 +7,7 @@ #include <crypto/sha2.h> #include <linux/kernel.h> #include <linux/fs.h> +#include <linux/fs_struct.h> #include <linux/filelock.h> #include <linux/uaccess.h> #include <linux/backing-dev.h> @@ -67,8 +68,9 @@ static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf, } CLASS(filename_kernel, filename)(pathname); - err = vfs_path_parent_lookup(filename, flags, path, &last, - root_share_path); + scoped_with_init_fs() + err = vfs_path_parent_lookup(filename, flags, path, &last, + root_share_path); if (err) return err; @@ -623,7 +625,8 @@ int ksmbd_vfs_link(struct ksmbd_work *work, const char *oldname, if (ksmbd_override_fsids(work)) return -ENOMEM; - err = kern_path(oldname, LOOKUP_NO_SYMLINKS, &oldpath); + scoped_with_init_fs() + err = kern_path(oldname, LOOKUP_NO_SYMLINKS, &oldpath); if (err) { pr_err("cannot get linux path for %s, err = %d\n", oldname, err); diff --git a/fs/super.c b/fs/super.c index a8fd61136aaf..9f82e6b38f07 100644 --- a/fs/super.c +++ b/fs/super.c @@ -24,6 +24,8 @@ #include <linux/export.h> #include <linux/slab.h> #include <linux/blkdev.h> +#include <linux/memcontrol.h> +#include <linux/rhashtable.h> #include <linux/mount.h> #include <linux/security.h> #include <linux/writeback.h> /* for the emergency remount stuff */ @@ -102,7 +104,7 @@ static bool super_flags(const struct super_block *sb, unsigned int flags) * creation will succeed and SB_BORN is set by vfs_get_tree() or we're * woken and we'll see SB_DYING. * - * The caller must have acquired a temporary reference on @sb->s_count. + * The caller must have acquired a temporary reference on @sb->s_passive. * * Return: The function returns true if SB_BORN was set and with * s_umount held. The function returns false if SB_DYING was @@ -170,6 +172,19 @@ static void super_wake(struct super_block *sb, unsigned int flag) } /* + * The s_op->nr_cached_objects hooks (used for example by btrfs and xfs) + * operate on filesystem-global state and ignore sc->memcg. Driving them + * from per-memcg shrink_slab_memcg() invocations only burns CPU walking + * per-cpu counters and queueing duplicate work: the actual reclaim happens on + * the global path (kswapd or root direct reclaim) regardless. Restrict them + * to that path. + */ +static inline bool super_fs_objects_eligible(struct shrink_control *sc) +{ + return !sc->memcg || mem_cgroup_is_root(sc->memcg); +} + +/* * One thing we have to be careful of with a per-sb shrinker is that we don't * drop the last active reference to the superblock from within the shrinker. * If that happens we could trigger unregistering the shrinker from within the @@ -198,7 +213,7 @@ static unsigned long super_cache_scan(struct shrinker *shrink, if (!super_trylock_shared(sb)) return SHRINK_STOP; - if (sb->s_op->nr_cached_objects) + if (sb->s_op->nr_cached_objects && super_fs_objects_eligible(sc)) fs_objects = sb->s_op->nr_cached_objects(sb, sc); inodes = list_lru_shrink_count(&sb->s_inode_lru, sc); @@ -259,7 +274,8 @@ static unsigned long super_cache_count(struct shrinker *shrink, return 0; smp_rmb(); - if (sb->s_op && sb->s_op->nr_cached_objects) + if (sb->s_op && sb->s_op->nr_cached_objects && + super_fs_objects_eligible(sc)) total_objects = sb->s_op->nr_cached_objects(sb, sc); total_objects += list_lru_shrink_count(&sb->s_dentry_lru, sc); @@ -272,6 +288,8 @@ static unsigned long super_cache_count(struct shrinker *shrink, return total_objects; } +static struct super_dev *super_dev_alloc(dev_t dev, struct super_block *sb); + static void destroy_super_work(struct work_struct *work) { struct super_block *s = container_of(work, struct super_block, @@ -279,6 +297,8 @@ static void destroy_super_work(struct work_struct *work) fsnotify_sb_free(s); security_sb_free(s); put_user_ns(s->s_user_ns); + /* Only an unregistered entry is still owned by the superblock. */ + kfree(s->s_super_dev); kfree(s->s_subtype); for (int i = 0; i < SB_FREEZE_LEVELS; i++) percpu_free_rwsem(&s->s_writers.rw_sem[i]); @@ -367,7 +387,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags, spin_lock_init(&s->s_inode_wblist_lock); fserror_mount(s); - s->s_count = 1; + refcount_set(&s->s_passive, 1); atomic_set(&s->s_active, 1); mutex_init(&s->s_vfs_rename_mutex); lockdep_set_class(&s->s_vfs_rename_mutex, &type->s_vfs_rename_key); @@ -392,6 +412,10 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags, goto fail; if (list_lru_init_memcg(&s->s_inode_lru, s->s_shrink)) goto fail; + s->s_super_dev = super_dev_alloc(0, s); + if (!s->s_super_dev) + goto fail; + s->s_min_writeback_pages = MIN_WRITEBACK_PAGES; return s; @@ -403,12 +427,17 @@ fail: /* Superblock refcounting */ /* - * Drop a superblock's refcount. The caller must hold sb_lock. + * Drop a superblock's passive reference. Must be called WITHOUT sb_lock held; + * put_super() acquires sb_lock itself when the final reference is dropped. */ -static void __put_super(struct super_block *s) +void put_super(struct super_block *s) { - if (!--s->s_count) { + if (refcount_dec_and_test(&s->s_passive)) { + + spin_lock(&sb_lock); list_del_init(&s->s_list); + spin_unlock(&sb_lock); + WARN_ON(s->s_dentry_lru.node); WARN_ON(s->s_inode_lru.node); WARN_ON(s->s_mounts); @@ -416,18 +445,109 @@ static void __put_super(struct super_block *s) } } -/** - * put_super - drop a temporary reference to superblock - * @sb: superblock in question - * - * Drops a temporary reference, frees superblock if there's no - * references left. - */ -void put_super(struct super_block *sb) +struct super_dev { + dev_t sd_dev; + struct super_block *sd_sb; + refcount_t sd_ref; + struct rhlist_head sd_node; + struct rcu_head sd_rcu; +}; + +static struct rhltable super_dev_table; +static const struct rhashtable_params super_dev_params = { + .key_len = sizeof(dev_t), + .key_offset = offsetof(struct super_dev, sd_dev), + .head_offset = offsetof(struct super_dev, sd_node), +}; + +static struct super_dev *super_dev_alloc(dev_t dev, struct super_block *sb) { - spin_lock(&sb_lock); - __put_super(sb); - spin_unlock(&sb_lock); + struct super_dev *fsd; + + fsd = kzalloc_obj(*fsd); + if (!fsd) + return NULL; + fsd->sd_dev = dev; + fsd->sd_sb = sb; + refcount_set(&fsd->sd_ref, 1); + return fsd; +} + +static void super_dev_put(struct super_dev *fsd) +{ + /* Unlink only once unpinned, so a cursor never resumes from a removed node. */ + if (fsd && refcount_dec_and_test(&fsd->sd_ref)) { + rhltable_remove(&super_dev_table, &fsd->sd_node, super_dev_params); + put_super(fsd->sd_sb); + kfree_rcu(fsd, sd_rcu); + } +} + +void __init super_dev_init(void) +{ + if (rhltable_init(&super_dev_table, &super_dev_params)) + panic("VFS: Cannot initialise super_dev_table\n"); +} + +static int super_dev_insert(struct super_dev *fsd) +{ + int err; + + err = rhltable_insert(&super_dev_table, &fsd->sd_node, super_dev_params); + if (!err) + refcount_inc(&fsd->sd_sb->s_passive); + return err; +} + +/* Register @sb under @sb->s_dev as the final fallible act of a set callback. */ +static int super_dev_register(struct super_block *sb) +{ + struct super_dev *fsd = sb->s_super_dev; + int err; + + lockdep_assert_held(&sb_lock); + VFS_WARN_ON_ONCE(!sb->s_dev); + VFS_WARN_ON_ONCE(!fsd || fsd->sd_dev); + + fsd->sd_dev = sb->s_dev; + err = super_dev_insert(fsd); + if (err) + fsd->sd_dev = 0; + return err; +} + +static struct super_dev *super_dev_get(struct rhlist_head *pos) +{ + struct super_dev *sb_dev; + + for (; pos; pos = rcu_dereference_all(pos->next)) { + sb_dev = container_of(pos, struct super_dev, sd_node); + if (refcount_inc_not_zero(&sb_dev->sd_ref)) + return sb_dev; + } + return NULL; +} + +static struct super_dev *super_dev_first(dev_t dev) +{ + struct super_dev *sb_dev; + + rcu_read_lock(); + sb_dev = super_dev_get(rhltable_lookup(&super_dev_table, &dev, super_dev_params)); + rcu_read_unlock(); + return sb_dev; +} + +static struct super_dev *super_dev_next(struct super_dev *prev) +{ + struct super_dev *sb_dev; + + rcu_read_lock(); + sb_dev = super_dev_get(rcu_dereference_all(prev->sd_node.next)); + rcu_read_unlock(); + + super_dev_put(prev); + return sb_dev; } static void kill_super_notify(struct super_block *sb) @@ -449,6 +569,12 @@ static void kill_super_notify(struct super_block *sb) hlist_del_init(&sb->s_instances); spin_unlock(&sb_lock); + /* Drop sget_fc()'s claim; a never-registered entry stays with the sb. */ + if (sb->s_super_dev->sd_dev) { + super_dev_put(sb->s_super_dev); + sb->s_super_dev = NULL; + } + /* * Let concurrent mounts know that this thing is really dead. * We don't need @sb->s_umount here as every concurrent caller @@ -478,11 +604,7 @@ void deactivate_locked_super(struct super_block *s) kill_super_notify(s); - /* - * Since list_lru_destroy() may sleep, we cannot call it from - * put_super(), where we hold the sb_lock. Therefore we destroy - * the lru lists right now. - */ + /* list_lru_destroy() may sleep; put_super() callers may not. */ list_lru_destroy(&s->s_dentry_lru); list_lru_destroy(&s->s_inode_lru); @@ -529,7 +651,7 @@ static bool grab_super(struct super_block *sb) { bool locked; - sb->s_count++; + refcount_inc(&sb->s_passive); spin_unlock(&sb_lock); locked = super_lock_excl(sb); if (locked) { @@ -556,7 +678,7 @@ static bool grab_super(struct super_block *sb) * lock held in read mode in case of success. On successful return, * the caller must drop the s_umount lock when done. * - * Note that unlike get_super() et.al. this one does *not* bump ->s_count. + * Note that unlike get_super() et.al. this one does *not* bump ->s_passive. * The reason why it's safe is that we are OK with doing trylock instead * of down_read(). There's a couple of places that are OK with that, but * it's very much not a general-purpose interface. @@ -763,6 +885,7 @@ retry: } if (!s) { spin_unlock(&sb_lock); + s = alloc_super(fc->fs_type, fc->sb_flags, user_ns); if (!s) return ERR_PTR(-ENOMEM); @@ -772,11 +895,13 @@ retry: s->s_fs_info = fc->s_fs_info; err = set(s, fc); if (err) { + VFS_WARN_ON_ONCE(s->s_super_dev->sd_dev); s->s_fs_info = NULL; spin_unlock(&sb_lock); destroy_unused_super(s); return ERR_PTR(err); } + VFS_WARN_ON_ONCE(!s->s_super_dev->sd_dev); fc->s_fs_info = NULL; s->s_type = fc->fs_type; s->s_iflags |= fc->s_iflags; @@ -851,14 +976,17 @@ static void __iterate_supers(void (*f)(struct super_block *, void *), void *arg, struct super_block *sb, *p = NULL; bool excl = flags & SUPER_ITER_EXCL; - guard(spinlock)(&sb_lock); + spin_lock(&sb_lock); for (sb = first_super(flags); !list_entry_is_head(sb, &super_blocks, s_list); sb = next_super(sb, flags)) { if (super_flags(sb, SB_DYING)) continue; - sb->s_count++; + + if (!refcount_inc_not_zero(&sb->s_passive)) + continue; + spin_unlock(&sb_lock); if (flags & SUPER_ITER_UNLOCKED) { @@ -868,13 +996,14 @@ static void __iterate_supers(void (*f)(struct super_block *, void *), void *arg, super_unlock(sb, excl); } - spin_lock(&sb_lock); if (p) - __put_super(p); + put_super(p); p = sb; + spin_lock(&sb_lock); } + spin_unlock(&sb_lock); if (p) - __put_super(p); + put_super(p); } void iterate_supers(void (*f)(struct super_block *, void *), void *arg) @@ -903,7 +1032,9 @@ void iterate_supers_type(struct file_system_type *type, if (super_flags(sb, SB_DYING)) continue; - sb->s_count++; + if (!refcount_inc_not_zero(&sb->s_passive)) + continue; + spin_unlock(&sb_lock); locked = super_lock_shared(sb); @@ -912,41 +1043,33 @@ void iterate_supers_type(struct file_system_type *type, super_unlock_shared(sb); } - spin_lock(&sb_lock); if (p) - __put_super(p); + put_super(p); p = sb; + spin_lock(&sb_lock); } - if (p) - __put_super(p); spin_unlock(&sb_lock); + if (p) + put_super(p); } EXPORT_SYMBOL(iterate_supers_type); struct super_block *user_get_super(dev_t dev, bool excl) { - struct super_block *sb; + struct super_dev *sb_dev; - spin_lock(&sb_lock); - list_for_each_entry(sb, &super_blocks, s_list) { - bool locked; + for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) { + struct super_block *sb = sb_dev->sd_sb; - if (sb->s_dev != dev) + if (!super_lock(sb, excl)) continue; - sb->s_count++; - spin_unlock(&sb_lock); - - locked = super_lock(sb, excl); - if (locked) - return sb; - - spin_lock(&sb_lock); - __put_super(sb); - break; + /* The pinned entry holds a passive reference, take our own. */ + refcount_inc(&sb->s_passive); + super_dev_put(sb_dev); + return sb; } - spin_unlock(&sb_lock); return NULL; } @@ -1222,7 +1345,16 @@ EXPORT_SYMBOL(free_anon_bdev); int set_anon_super(struct super_block *s, void *data) { - return get_anon_bdev(&s->s_dev); + int error; + + error = get_anon_bdev(&s->s_dev); + if (error) + return error; + + error = super_dev_register(s); + if (error) + free_anon_bdev(s->s_dev); + return error; } EXPORT_SYMBOL(set_anon_super); @@ -1308,7 +1440,7 @@ EXPORT_SYMBOL(get_tree_keyed); static int set_bdev_super(struct super_block *s, void *data) { s->s_dev = *(dev_t *)data; - return 0; + return super_dev_register(s); } static int super_s_dev_set(struct super_block *s, struct fs_context *fc) @@ -1350,197 +1482,313 @@ struct super_block *sget_dev(struct fs_context *fc, dev_t dev) EXPORT_SYMBOL(sget_dev); #ifdef CONFIG_BLOCK -/* - * Lock the superblock that is holder of the bdev. Returns the superblock - * pointer if we successfully locked the superblock and it is alive. Otherwise - * we return NULL and just unlock bdev->bd_holder_lock. - * - * The function must be called with bdev->bd_holder_lock and releases it. - */ -static struct super_block *bdev_super_lock(struct block_device *bdev, bool excl) - __releases(&bdev->bd_holder_lock) +static int fs_super_freeze(struct super_block *sb) { - struct super_block *sb = bdev->bd_holder; - bool locked; - - lockdep_assert_held(&bdev->bd_holder_lock); - lockdep_assert_not_held(&sb->s_umount); - lockdep_assert_not_held(&bdev->bd_disk->open_mutex); - - /* Make sure sb doesn't go away from under us */ - spin_lock(&sb_lock); - sb->s_count++; - spin_unlock(&sb_lock); - - mutex_unlock(&bdev->bd_holder_lock); - - locked = super_lock(sb, excl); - - /* - * If the superblock wasn't already SB_DYING then we hold - * s_umount and can safely drop our temporary reference. - */ - put_super(sb); - - if (!locked) - return NULL; - - if (!sb->s_root || !(sb->s_flags & SB_ACTIVE)) { - super_unlock(sb, excl); - return NULL; - } + if (sb->s_op->freeze_super) + return sb->s_op->freeze_super(sb, + FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); + return freeze_super(sb, FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); +} - return sb; +static int fs_super_thaw(struct super_block *sb) +{ + if (sb->s_op->thaw_super) + return sb->s_op->thaw_super(sb, + FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); + return thaw_super(sb, FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); } static void fs_bdev_mark_dead(struct block_device *bdev, bool surprise) { - struct super_block *sb; + struct super_dev *sb_dev; + dev_t dev = bdev->bd_dev; - sb = bdev_super_lock(bdev, false); - if (!sb) - return; + mutex_unlock(&bdev->bd_holder_lock); - if (sb->s_op->remove_bdev) { - int ret; + for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) { + struct super_block *sb = sb_dev->sd_sb; - ret = sb->s_op->remove_bdev(sb, bdev); - if (!ret) { - super_unlock_shared(sb); - return; + if (!super_lock_shared(sb)) + continue; + if (sb->s_root && (sb->s_flags & SB_ACTIVE)) { + if (!sb->s_op->remove_bdev || + sb->s_op->remove_bdev(sb, bdev)) { + if (!surprise) + sync_filesystem(sb); + shrink_dcache_sb(sb); + evict_inodes(sb); + if (sb->s_op->shutdown) + sb->s_op->shutdown(sb); + } } - /* Fallback to shutdown. */ + super_unlock_shared(sb); } - - if (!surprise) - sync_filesystem(sb); - shrink_dcache_sb(sb); - evict_inodes(sb); - if (sb->s_op->shutdown) - sb->s_op->shutdown(sb); - - super_unlock_shared(sb); } static void fs_bdev_sync(struct block_device *bdev) { - struct super_block *sb; - - sb = bdev_super_lock(bdev, false); - if (!sb) - return; + struct super_dev *sb_dev; + dev_t dev = bdev->bd_dev; - sync_filesystem(sb); - super_unlock_shared(sb); -} + mutex_unlock(&bdev->bd_holder_lock); -static struct super_block *get_bdev_super(struct block_device *bdev) -{ - bool active = false; - struct super_block *sb; + for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) { + struct super_block *sb = sb_dev->sd_sb; - sb = bdev_super_lock(bdev, true); - if (sb) { - active = atomic_inc_not_zero(&sb->s_active); - super_unlock_excl(sb); + if (!super_lock_shared(sb)) + continue; + if (sb->s_root && (sb->s_flags & SB_ACTIVE)) + sync_filesystem(sb); + super_unlock_shared(sb); } - if (!active) - return NULL; - return sb; } /** - * fs_bdev_freeze - freeze owning filesystem of block device + * fs_bdev_freeze - freeze every superblock using a block device * @bdev: block device * - * Freeze the filesystem that owns this block device if it is still - * active. - * - * A filesystem that owns multiple block devices may be frozen from each - * block device and won't be unfrozen until all block devices are - * unfrozen. Each block device can only freeze the filesystem once as we - * nest freezes for block devices in the block layer. + * Freeze each live superblock using @bdev. A superblock owning several block + * devices is frozen once per device and stays frozen until all are thawed; the + * block layer nests these freezes so the count stays balanced. * - * Return: If the freeze was successful zero is returned. If the freeze - * failed a negative error code is returned. + * Return: 0, or the error from the one superblock on a single-fs device. When + * several superblocks share @bdev a per-superblock failure is swallowed + * (see below), but a sync_blockdev() failure is always reported. */ static int fs_bdev_freeze(struct block_device *bdev) { - struct super_block *sb; - int error = 0; + dev_t dev = bdev->bd_dev; + struct super_dev *sb_dev; + unsigned int count = 0; + int error = 0, err; lockdep_assert_held(&bdev->bd_fsfreeze_mutex); - sb = get_bdev_super(bdev); - if (!sb) - return -EINVAL; + mutex_unlock(&bdev->bd_holder_lock); - if (sb->s_op->freeze_super) - error = sb->s_op->freeze_super(sb, - FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); - else - error = freeze_super(sb, - FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); + for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) { + if (!get_active_super(sb_dev->sd_sb)) + continue; + err = fs_super_freeze(sb_dev->sd_sb); + if (err && !error) + error = err; + deactivate_super(sb_dev->sd_sb); + count++; + } + + /* + * When several superblocks share the device, keep it frozen even if some + * of them failed to freeze and swallow the error: rolling the rest back + * via thaw_super() can fail too, so neither is a clear win. A single + * filesystem (count == 1) still reports its error. + */ + if (error && count > 1) + error = 0; if (!error) error = sync_blockdev(bdev); - deactivate_super(sb); return error; } /** - * fs_bdev_thaw - thaw owning filesystem of block device + * fs_bdev_thaw - thaw every superblock using a block device * @bdev: block device * - * Thaw the filesystem that owns this block device. - * - * A filesystem that owns multiple block devices may be frozen from each - * block device and won't be unfrozen until all block devices are - * unfrozen. Each block device can only freeze the filesystem once as we - * nest freezes for block devices in the block layer. + * The counterpart to fs_bdev_freeze(): thaw each live superblock using @bdev. + * A zero return does not imply a superblock is fully unfrozen; it may have been + * frozen more than once (by the kernel or via another device). * - * Return: If the thaw was successful zero is returned. If the thaw - * failed a negative error code is returned. If this function - * returns zero it doesn't mean that the filesystem is unfrozen - * as it may have been frozen multiple times (kernel may hold a - * freeze or might be frozen from other block devices). + * Return: 0, or the first error on a single-fs device; a shared device swallows + * per-superblock errors, as fs_bdev_freeze() does. */ static int fs_bdev_thaw(struct block_device *bdev) { - struct super_block *sb; - int error; + dev_t dev = bdev->bd_dev; + struct super_dev *sb_dev; + unsigned int count = 0; + int error = 0, err; lockdep_assert_held(&bdev->bd_fsfreeze_mutex); - /* - * The block device may have been frozen before it was claimed by a - * filesystem. Concurrently another process might try to mount that - * frozen block device and has temporarily claimed the block device for - * that purpose causing a concurrent fs_bdev_thaw() to end up here. The - * mounter is already about to abort mounting because they still saw an - * elevanted bdev->bd_fsfreeze_count so get_bdev_super() will return - * NULL in that case. - */ - sb = get_bdev_super(bdev); - if (!sb) - return -EINVAL; + mutex_unlock(&bdev->bd_holder_lock); - if (sb->s_op->thaw_super) - error = sb->s_op->thaw_super(sb, - FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); - else - error = thaw_super(sb, - FREEZE_MAY_NEST | FREEZE_HOLDER_USERSPACE, NULL); - deactivate_super(sb); + for (sb_dev = super_dev_first(dev); sb_dev; sb_dev = super_dev_next(sb_dev)) { + if (!get_active_super(sb_dev->sd_sb)) + continue; + err = fs_super_thaw(sb_dev->sd_sb); + if (err && !error) + error = err; + deactivate_super(sb_dev->sd_sb); + count++; + } + + /* Shared device: swallow per-superblock errors, like fs_bdev_freeze(). */ + if (error && count > 1) + error = 0; return error; } -const struct blk_holder_ops fs_holder_ops = { +static const struct blk_holder_ops fs_holder_ops = { .mark_dead = fs_bdev_mark_dead, .sync = fs_bdev_sync, .freeze = fs_bdev_freeze, .thaw = fs_bdev_thaw, }; -EXPORT_SYMBOL_GPL(fs_holder_ops); + +static struct super_dev *super_dev_lookup(dev_t dev, struct super_block *sb) +{ + struct super_dev *it; + struct rhlist_head *list, *pos; + + RCU_LOCKDEP_WARN(!rcu_read_lock_held(), "suspicious super_dev_lookup() usage"); + VFS_WARN_ON_ONCE(!dev); + VFS_WARN_ON_ONCE(!sb); + + list = rhltable_lookup(&super_dev_table, &dev, super_dev_params); + rhl_for_each_entry_rcu(it, pos, list, sd_node) { + if (it->sd_sb == sb) + return it; + } + + return NULL; +} + +static int fs_bdev_register(struct file *bdev_file, struct super_block *sb) +{ + struct super_dev *sb_dev __free(kfree) = NULL; + dev_t dev = file_bdev(bdev_file)->bd_dev; + int err; + + scoped_guard(rcu) { + sb_dev = super_dev_lookup(dev, sb); + if (sb_dev && refcount_inc_not_zero(&sb_dev->sd_ref)) { + retain_and_null_ptr(sb_dev); + return 0; + } + } + + sb_dev = super_dev_alloc(dev, sb); + if (!sb_dev) + return -ENOMEM; + + err = super_dev_insert(sb_dev); + if (err) + return err; + + /* Publish the entry before reading the count; pairs with bdev_freeze(). */ + smp_mb(); + if (atomic_read(&file_bdev(bdev_file)->bd_fsfreeze_count) > 0) { + err = -EBUSY; + super_dev_put(sb_dev); + } + + retain_and_null_ptr(sb_dev); + return err; +} + +/** + * fs_bdev_file_open_by_dev - claim a block device on behalf of a superblock + * @dev: block device number + * @mode: open mode + * @holder: block-layer exclusivity token (a superblock, or the file_system_type + * when the device may be shared by several superblocks of that type) + * @sb: superblock to drive fs_holder_ops events for + * + * Open @dev with &fs_holder_ops and register that @sb uses it, so device + * removal/sync/freeze/thaw are propagated to @sb (and any other superblock + * sharing @dev). Must be paired with fs_bdev_file_release(). + * + * Return: an opened block-device file or an ERR_PTR(). + */ +struct file *fs_bdev_file_open_by_dev(dev_t dev, blk_mode_t mode, void *holder, + struct super_block *sb) +{ + struct file *bdev_file; + int err; + + bdev_file = bdev_file_open_by_dev(dev, mode, holder, &fs_holder_ops); + if (IS_ERR(bdev_file)) + return bdev_file; + + err = fs_bdev_register(bdev_file, sb); + if (err) { + bdev_fput(bdev_file); + return ERR_PTR(err); + } + return bdev_file; +} +EXPORT_SYMBOL_GPL(fs_bdev_file_open_by_dev); + +/** + * fs_bdev_file_open_by_path - claim a block device on behalf of a superblock + * @path: path to the block device + * @mode: open mode + * @holder: block-layer exclusivity token (a superblock, or the file_system_type + * when the device may be shared by several superblocks of that type) + * @sb: superblock to drive fs_holder_ops events for + * + * Open the block device at @path with &fs_holder_ops and register that @sb + * uses it, so device removal/sync/freeze/thaw are propagated to @sb (and any + * other superblock sharing the device). Must be paired with + * fs_bdev_file_release(). + * + * Return: an opened block-device file or an ERR_PTR(). + */ +struct file *fs_bdev_file_open_by_path(const char *path, blk_mode_t mode, + void *holder, struct super_block *sb) +{ + struct file *bdev_file; + int err; + + bdev_file = bdev_file_open_by_path(path, mode, holder, &fs_holder_ops); + if (IS_ERR(bdev_file)) + return bdev_file; + + err = fs_bdev_register(bdev_file, sb); + if (err) { + bdev_fput(bdev_file); + return ERR_PTR(err); + } + return bdev_file; +} +EXPORT_SYMBOL_GPL(fs_bdev_file_open_by_path); + +/** + * fs_bdev_unregister - drop a superblock's claim on a block device + * @bdev_file: file returned by fs_bdev_file_open_by_{dev,path}() + * @sb: superblock the device was claimed for + * + * The inverse of fs_bdev_register(): drop one claim on the {dev, @sb} entry + * (the last claim unregisters it; a pinning cursor defers the actual unlink) + * without closing the device. A caller that must act on the still-open device + * between unregistering and closing - e.g. re-allow freezing one denied for a + * membership change - pairs this with bdev_fput(). fs_bdev_file_release() is + * the common unregister-and-close. + */ +void fs_bdev_unregister(struct file *bdev_file, struct super_block *sb) +{ + dev_t dev = file_bdev(bdev_file)->bd_dev; + struct super_dev *sb_dev; + + rcu_read_lock(); + sb_dev = super_dev_lookup(dev, sb); + rcu_read_unlock(); + super_dev_put(sb_dev); +} +EXPORT_SYMBOL_GPL(fs_bdev_unregister); + +/** + * fs_bdev_file_release - release a block device claimed for a superblock + * @bdev_file: file returned by fs_bdev_file_open_by_{dev,path}() + * @sb: superblock the device was claimed for + * + * Unregister the {dev, @sb} entry, then close the block device. + */ +void fs_bdev_file_release(struct file *bdev_file, struct super_block *sb) +{ + fs_bdev_unregister(bdev_file, sb); + bdev_fput(bdev_file); +} +EXPORT_SYMBOL_GPL(fs_bdev_file_release); int setup_bdev_super(struct super_block *sb, int sb_flags, struct fs_context *fc) @@ -1549,7 +1797,7 @@ int setup_bdev_super(struct super_block *sb, int sb_flags, struct file *bdev_file; struct block_device *bdev; - bdev_file = bdev_file_open_by_dev(sb->s_dev, mode, sb, &fs_holder_ops); + bdev_file = fs_bdev_file_open_by_dev(sb->s_dev, mode, sb, sb); if (IS_ERR(bdev_file)) { if (fc) errorf(fc, "%s: Can't open blockdev", fc->source); @@ -1563,20 +1811,19 @@ int setup_bdev_super(struct super_block *sb, int sb_flags, * writable from userspace even for a read-only block device. */ if ((mode & BLK_OPEN_WRITE) && bdev_read_only(bdev)) { - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, sb); return -EACCES; } - /* - * It is enough to check bdev was not frozen before we set - * s_bdev as freezing will wait until SB_BORN is set. - */ + /* The sget_fc() entry is already published; pairs with bdev_freeze(). */ + smp_mb(); if (atomic_read(&bdev->bd_fsfreeze_count) > 0) { if (fc) warnf(fc, "%pg: Can't mount, blockdev is frozen", bdev); - bdev_fput(bdev_file); + fs_bdev_file_release(bdev_file, sb); return -EBUSY; } + spin_lock(&sb_lock); sb->s_bdev_file = bdev_file; sb->s_bdev = bdev; @@ -1665,7 +1912,7 @@ void kill_block_super(struct super_block *sb) generic_shutdown_super(sb); if (bdev) { sync_blockdev(bdev); - bdev_fput(sb->s_bdev_file); + fs_bdev_file_release(sb->s_bdev_file, sb); } } diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 86d41e077e4d..23ec924162d6 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -303,7 +303,7 @@ static int ubifs_prepare_create(struct inode *dir, struct dentry *dentry, } static int ubifs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode; struct ubifs_info *c = dir->i_sb->s_fs_info; @@ -1031,7 +1031,7 @@ static struct dentry *ubifs_mkdir(struct mnt_idmap *idmap, struct inode *dir, sz_change = CALC_DENT_SIZE(fname_len(&nm)); - inode = ubifs_new_inode(c, dir, S_IFDIR | mode, false); + inode = ubifs_new_inode(c, dir, mode, false); if (IS_ERR(inode)) { err = PTR_ERR(inode); goto out_fname; diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 9a3b7cef3606..b90841ac0a40 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -371,7 +371,7 @@ static int udf_add_nondir(struct dentry *dentry, struct inode *inode) } static int udf_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { struct inode *inode = udf_new_inode(dir, mode); @@ -428,7 +428,7 @@ static struct dentry *udf_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct udf_inode_info *dinfo = UDF_I(dir); struct udf_inode_info *iinfo; - inode = udf_new_inode(dir, S_IFDIR | mode); + inode = udf_new_inode(dir, mode); if (IS_ERR(inode)) return ERR_CAST(inode); diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c index 5b3c85c93242..6703f3bcf76f 100644 --- a/fs/ufs/namei.c +++ b/fs/ufs/namei.c @@ -70,8 +70,7 @@ static struct dentry *ufs_lookup(struct inode * dir, struct dentry *dentry, unsi * with d_instantiate(). */ static int ufs_create (struct mnt_idmap * idmap, - struct inode * dir, struct dentry * dentry, umode_t mode, - bool excl) + struct inode * dir, struct dentry * dentry, umode_t mode) { struct inode *inode; @@ -174,7 +173,7 @@ static struct dentry *ufs_mkdir(struct mnt_idmap * idmap, struct inode * dir, inode_inc_link_count(dir); - inode = ufs_new_inode(dir, S_IFDIR|mode); + inode = ufs_new_inode(dir, mode); err = PTR_ERR(inode); if (IS_ERR(inode)) goto out_dir; diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c index c5bd3271aa96..0b9eab157432 100644 --- a/fs/vboxsf/dir.c +++ b/fs/vboxsf/dir.c @@ -298,9 +298,9 @@ out: static int vboxsf_dir_mkfile(struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, - umode_t mode, bool excl) + umode_t mode) { - return vboxsf_dir_create(parent, dentry, mode, false, excl, NULL); + return vboxsf_dir_create(parent, dentry, mode, false, true, NULL); } static struct dentry *vboxsf_dir_mkdir(struct mnt_idmap *idmap, diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index e1465e950acc..8c210ebb66bc 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -1631,7 +1631,7 @@ xfs_free_buftarg( fs_put_dax(btp->bt_daxdev, btp->bt_mount); /* the main block device is closed by kill_block_super */ if (btp->bt_bdev != btp->bt_mount->m_super->s_bdev) - bdev_fput(btp->bt_file); + fs_bdev_file_release(btp->bt_file, btp->bt_mount->m_super); kfree(btp); } diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 6339f4956ecb..4a3299abf774 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -293,8 +293,7 @@ xfs_vn_create( struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, - umode_t mode, - bool flags) + umode_t mode) { return xfs_generic_create(idmap, dir, dentry, mode, 0, NULL); } @@ -306,7 +305,7 @@ xfs_vn_mkdir( struct dentry *dentry, umode_t mode) { - return ERR_PTR(xfs_generic_create(idmap, dir, dentry, mode | S_IFDIR, 0, NULL)); + return ERR_PTR(xfs_generic_create(idmap, dir, dentry, mode, 0, NULL)); } STATIC struct dentry * @@ -338,7 +337,7 @@ STATIC struct dentry * xfs_vn_ci_lookup( struct inode *dir, struct dentry *dentry, - unsigned int flags) + unsigned int flags) { struct xfs_inode *ip; struct xfs_name xname; diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index 8531d526fc44..d1c622f0a957 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -400,8 +400,8 @@ xfs_blkdev_get( blk_mode_t mode; mode = sb_open_mode(mp->m_super->s_flags); - *bdev_filep = bdev_file_open_by_path(name, mode, - mp->m_super, &fs_holder_ops); + *bdev_filep = fs_bdev_file_open_by_path(name, mode, + mp->m_super, mp->m_super); if (IS_ERR(*bdev_filep)) { error = PTR_ERR(*bdev_filep); *bdev_filep = NULL; @@ -526,7 +526,7 @@ xfs_open_devices( mp->m_logdev_targp = mp->m_ddev_targp; /* Handle won't be used, drop it */ if (logdev_file) - bdev_fput(logdev_file); + fs_bdev_file_release(logdev_file, mp->m_super); } return 0; @@ -541,10 +541,10 @@ xfs_open_devices( mp->m_ddev_targp = NULL; out_close_rtdev: if (rtdev_file) - bdev_fput(rtdev_file); + fs_bdev_file_release(rtdev_file, mp->m_super); out_close_logdev: if (logdev_file) - bdev_fput(logdev_file); + fs_bdev_file_release(logdev_file, mp->m_super); return error; } diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h index 8808ee76e73c..5a725a0cd35f 100644 --- a/include/linux/blk_types.h +++ b/include/linux/blk_types.h @@ -66,7 +66,7 @@ struct block_device { int bd_holders; struct kobject *bd_holder_dir; - atomic_t bd_fsfreeze_count; /* number of freeze requests */ + atomic_t bd_fsfreeze_count; /* >0 freeze requests, <0 freeze deniers */ struct mutex bd_fsfreeze_mutex; /* serialize freeze/thaw */ struct partition_meta_info *bd_meta_info; diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 9213a5716f95..dbb549cdfb77 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -126,8 +126,6 @@ struct blk_integrity { unsigned char pi_tuple_size; }; -typedef unsigned int __bitwise blk_mode_t; - /* open for reading */ #define BLK_OPEN_READ ((__force blk_mode_t)(1 << 0)) /* open for writing */ @@ -1771,13 +1769,6 @@ struct blk_holder_ops { }; /* - * For filesystems using @fs_holder_ops, the @holder argument passed to - * helpers used to open and claim block devices via - * bd_prepare_to_claim() must point to a superblock. - */ -extern const struct blk_holder_ops fs_holder_ops; - -/* * Return the correct open flags for blkdev_get_by_* for super block flags * as stored in sb->s_flags. */ @@ -1837,7 +1828,10 @@ static inline int early_lookup_bdev(const char *pathname, dev_t *dev) int bdev_freeze(struct block_device *bdev); int bdev_thaw(struct block_device *bdev); +int bdev_deny_freeze(struct block_device *bdev); +void bdev_allow_freeze(struct block_device *bdev); void bdev_fput(struct file *bdev_file); +void bdev_yield_claim(struct file *bdev_file); struct io_comp_batch { struct rq_list req_list; diff --git a/include/linux/efs_vh.h b/include/linux/efs_vh.h deleted file mode 100644 index 206c5270f7b8..000000000000 --- a/include/linux/efs_vh.h +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * efs_vh.h - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from IRIX header files (c) 1985 MIPS Computer Systems, Inc. - */ - -#ifndef __EFS_VH_H__ -#define __EFS_VH_H__ - -#define VHMAGIC 0xbe5a941 /* volume header magic number */ -#define NPARTAB 16 /* 16 unix partitions */ -#define NVDIR 15 /* max of 15 directory entries */ -#define BFNAMESIZE 16 /* max 16 chars in boot file name */ -#define VDNAMESIZE 8 - -struct volume_directory { - char vd_name[VDNAMESIZE]; /* name */ - __be32 vd_lbn; /* logical block number */ - __be32 vd_nbytes; /* file length in bytes */ -}; - -struct partition_table { /* one per logical partition */ - __be32 pt_nblks; /* # of logical blks in partition */ - __be32 pt_firstlbn; /* first lbn of partition */ - __be32 pt_type; /* use of partition */ -}; - -struct volume_header { - __be32 vh_magic; /* identifies volume header */ - __be16 vh_rootpt; /* root partition number */ - __be16 vh_swappt; /* swap partition number */ - char vh_bootfile[BFNAMESIZE]; /* name of file to boot */ - char pad[48]; /* device param space */ - struct volume_directory vh_vd[NVDIR]; /* other vol hdr contents */ - struct partition_table vh_pt[NPARTAB]; /* device partition layout */ - __be32 vh_csum; /* volume header checksum */ - __be32 vh_fill; /* fill out to 512 bytes */ -}; - -/* partition type sysv is used for EFS format CD-ROM partitions */ -#define SGI_SYSV 0x05 -#define SGI_EFS 0x07 -#define IS_EFS(x) (((x) == SGI_EFS) || ((x) == SGI_SYSV)) - -struct pt_types { - int pt_type; - char *pt_name; -}; - -#endif /* __EFS_VH_H__ */ - diff --git a/include/linux/fs.h b/include/linux/fs.h index d10897b3a1e3..30e0f54d8828 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1916,8 +1916,6 @@ struct dir_context { struct io_uring_cmd; struct offset_ctx; -typedef unsigned int __bitwise fop_flags_t; - struct file_operations { struct module *owner; fop_flags_t fop_flags; @@ -2002,7 +2000,7 @@ struct inode_operations { int (*readlink) (struct dentry *, char __user *,int); int (*create) (struct mnt_idmap *, struct inode *,struct dentry *, - umode_t, bool); + umode_t); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); int (*symlink) (struct mnt_idmap *, struct inode *,struct dentry *, diff --git a/include/linux/fs/super.h b/include/linux/fs/super.h index 405612678115..733d439f01ed 100644 --- a/include/linux/fs/super.h +++ b/include/linux/fs/super.h @@ -237,4 +237,12 @@ int thaw_super(struct super_block *super, enum freeze_holder who, int sb_init_dio_done_wq(struct super_block *sb); +struct file; +struct file *fs_bdev_file_open_by_dev(dev_t dev, blk_mode_t mode, void *holder, + struct super_block *sb); +struct file *fs_bdev_file_open_by_path(const char *path, blk_mode_t mode, + void *holder, struct super_block *sb); +void fs_bdev_unregister(struct file *bdev_file, struct super_block *sb); +void fs_bdev_file_release(struct file *bdev_file, struct super_block *sb); + #endif /* _LINUX_FS_SUPER_H */ diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h index ef7941e9dc79..c8172558750f 100644 --- a/include/linux/fs/super_types.h +++ b/include/linux/fs/super_types.h @@ -30,6 +30,7 @@ struct mount; struct mtd_info; struct quotactl_ops; struct shrinker; +struct super_dev; struct unicode_map; struct user_namespace; struct workqueue_struct; @@ -132,6 +133,7 @@ struct super_operations { struct super_block { struct list_head s_list; /* Keep this first */ dev_t s_dev; /* search index; _not_ kdev_t */ + struct super_dev *s_super_dev; /* sget_fc()'s device table claim */ unsigned char s_blocksize_bits; unsigned long s_blocksize; loff_t s_maxbytes; /* Max file size */ @@ -145,7 +147,7 @@ struct super_block { unsigned long s_magic; struct dentry *s_root; struct rw_semaphore s_umount; - int s_count; + refcount_t s_passive; atomic_t s_active; #ifdef CONFIG_SECURITY void *s_security; diff --git a/include/linux/fs_struct.h b/include/linux/fs_struct.h index 0070764b790a..97eef8d3863d 100644 --- a/include/linux/fs_struct.h +++ b/include/linux/fs_struct.h @@ -6,6 +6,7 @@ #include <linux/path.h> #include <linux/spinlock.h> #include <linux/seqlock.h> +#include <linux/vfsdebug.h> struct fs_struct { int users; @@ -16,6 +17,7 @@ struct fs_struct { } __randomize_layout; extern struct kmem_cache *fs_cachep; +extern struct fs_struct *userspace_init_fs; extern void exit_fs(struct task_struct *); extern void set_fs_root(struct fs_struct *, const struct path *); @@ -40,6 +42,8 @@ static inline void get_fs_pwd(struct fs_struct *fs, struct path *pwd) read_sequnlock_excl(&fs->seq); } +struct fs_struct *switch_fs_struct(struct fs_struct *new_fs); + extern bool current_chrooted(void); static inline int current_umask(void) @@ -47,4 +51,34 @@ static inline int current_umask(void) return current->fs->umask; } +/* + * Temporarily use userspace_init_fs for path resolution in kthreads. + * Callers should use scoped_with_init_fs() which automatically + * restores the original fs_struct at scope exit. + */ +static inline struct fs_struct *__override_init_fs(void) +{ + struct fs_struct *old_fs; + + old_fs = current->fs; + WRITE_ONCE(current->fs, userspace_init_fs); + return old_fs; +} + +static inline void __revert_init_fs(struct fs_struct *old_fs) +{ + VFS_WARN_ON_ONCE(current->fs != userspace_init_fs); + WRITE_ONCE(current->fs, old_fs); +} + +DEFINE_CLASS(__override_init_fs, + struct fs_struct *, + __revert_init_fs(_T), + __override_init_fs(), void) + +#define scoped_with_init_fs() \ + scoped_class(__override_init_fs, __UNIQUE_ID(label)) + +void __init init_userspace_fs(void); + #endif /* _LINUX_FS_STRUCT_H */ diff --git a/include/linux/init_task.h b/include/linux/init_task.h index a6cb241ea00c..61536be773f5 100644 --- a/include/linux/init_task.h +++ b/include/linux/init_task.h @@ -24,6 +24,7 @@ extern struct files_struct init_files; extern struct fs_struct init_fs; +extern struct fs_struct *userspace_init_fs; extern struct nsproxy init_nsproxy; #ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE diff --git a/include/linux/net.h b/include/linux/net.h index f268f395ce47..fdcf9956805c 100644 --- a/include/linux/net.h +++ b/include/linux/net.h @@ -285,6 +285,7 @@ int sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags); struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname); struct socket *sockfd_lookup(int fd, int *err); struct socket *sock_from_file(struct file *file); +int sock_read_xattr(struct socket *sock, const char *name, void *value, size_t size); #define sockfd_put(sock) fput(sock->file) int net_ratelimit(void); diff --git a/include/linux/sched.h b/include/linux/sched.h index 373bcc0598d1..1e4136c2b2a3 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1191,6 +1191,7 @@ struct task_struct { unsigned long last_switch_time; #endif /* Filesystem information: */ + struct fs_struct *real_fs; struct fs_struct *fs; /* Open file information: */ diff --git a/include/linux/sched/task.h b/include/linux/sched/task.h index 41ed884cffc9..e0c1ca8c6a18 100644 --- a/include/linux/sched/task.h +++ b/include/linux/sched/task.h @@ -31,6 +31,7 @@ struct kernel_clone_args { u32 io_thread:1; u32 user_worker:1; u32 no_files:1; + u32 umh:1; unsigned long stack; unsigned long stack_size; unsigned long tls; diff --git a/include/linux/types.h b/include/linux/types.h index 93166b0b0617..bc5dda2a3d86 100644 --- a/include/linux/types.h +++ b/include/linux/types.h @@ -163,6 +163,8 @@ typedef u32 dma_addr_t; typedef unsigned int __bitwise gfp_t; typedef unsigned int __bitwise slab_flags_t; typedef unsigned int __bitwise fmode_t; +typedef unsigned int __bitwise blk_mode_t; +typedef unsigned int __bitwise fop_flags_t; #ifdef CONFIG_PHYS_ADDR_T_64BIT typedef u64 phys_addr_t; diff --git a/include/uapi/asm-generic/errno.h b/include/uapi/asm-generic/errno.h index bd78e69e0a43..c84ebf89c8b6 100644 --- a/include/uapi/asm-generic/errno.h +++ b/include/uapi/asm-generic/errno.h @@ -76,7 +76,7 @@ #define ENOPROTOOPT 92 /* Protocol not available */ #define EPROTONOSUPPORT 93 /* Protocol not supported */ #define ESOCKTNOSUPPORT 94 /* Socket type not supported */ -#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ +#define EOPNOTSUPP 95 /* Operation not supported */ #define EPFNOSUPPORT 96 /* Protocol family not supported */ #define EAFNOSUPPORT 97 /* Address family not supported by protocol */ #define EADDRINUSE 98 /* Address already in use */ diff --git a/include/uapi/linux/efs_fs_sb.h b/include/uapi/linux/efs_fs_sb.h deleted file mode 100644 index 6bad29a10faa..000000000000 --- a/include/uapi/linux/efs_fs_sb.h +++ /dev/null @@ -1,63 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * efs_fs_sb.h - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from IRIX header files (c) 1988 Silicon Graphics - */ - -#ifndef __EFS_FS_SB_H__ -#define __EFS_FS_SB_H__ - -#include <linux/types.h> -#include <linux/magic.h> - -/* EFS superblock magic numbers */ -#define EFS_MAGIC 0x072959 -#define EFS_NEWMAGIC 0x07295a - -#define IS_EFS_MAGIC(x) ((x == EFS_MAGIC) || (x == EFS_NEWMAGIC)) - -#define EFS_SUPER 1 -#define EFS_ROOTINODE 2 - -/* efs superblock on disk */ -struct efs_super { - __be32 fs_size; /* size of filesystem, in sectors */ - __be32 fs_firstcg; /* bb offset to first cg */ - __be32 fs_cgfsize; /* size of cylinder group in bb's */ - __be16 fs_cgisize; /* bb's of inodes per cylinder group */ - __be16 fs_sectors; /* sectors per track */ - __be16 fs_heads; /* heads per cylinder */ - __be16 fs_ncg; /* # of cylinder groups in filesystem */ - __be16 fs_dirty; /* fs needs to be fsck'd */ - __be32 fs_time; /* last super-block update */ - __be32 fs_magic; /* magic number */ - char fs_fname[6]; /* file system name */ - char fs_fpack[6]; /* file system pack name */ - __be32 fs_bmsize; /* size of bitmap in bytes */ - __be32 fs_tfree; /* total free data blocks */ - __be32 fs_tinode; /* total free inodes */ - __be32 fs_bmblock; /* bitmap location. */ - __be32 fs_replsb; /* Location of replicated superblock. */ - __be32 fs_lastialloc; /* last allocated inode */ - char fs_spare[20]; /* space for expansion - MUST BE ZERO */ - __be32 fs_checksum; /* checksum of volume portion of fs */ -}; - -/* efs superblock information in memory */ -struct efs_sb_info { - __u32 fs_magic; /* superblock magic number */ - __u32 fs_start; /* first block of filesystem */ - __u32 first_block; /* first data block in filesystem */ - __u32 total_blocks; /* total number of blocks in filesystem */ - __u32 group_size; /* # of blocks a group consists of */ - __u32 data_free; /* # of free data blocks */ - __u32 inode_free; /* # of free inodes */ - __u16 inode_blocks; /* # of blocks used for inodes in every grp */ - __u16 total_groups; /* # of groups */ -}; - -#endif /* __EFS_FS_SB_H__ */ - diff --git a/init/init_task.c b/init/init_task.c index b67ef6040a65..ba5c2523f7e0 100644 --- a/init/init_task.c +++ b/init/init_task.c @@ -162,6 +162,7 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { RCU_POINTER_INITIALIZER(cred, &init_cred), .comm = INIT_TASK_COMM, .thread = INIT_THREAD, + .real_fs = &init_fs, .fs = &init_fs, .files = &init_files, #ifdef CONFIG_IO_URING diff --git a/init/initramfs.c b/init/initramfs.c index 20a18fcda48e..4e27b97a8844 100644 --- a/init/initramfs.c +++ b/init/initramfs.c @@ -6,6 +6,7 @@ #include <linux/fcntl.h> #include <linux/file.h> #include <linux/fs.h> +#include <linux/fs_struct.h> #include <linux/hex.h> #include <linux/init.h> #include <linux/init_syscalls.h> @@ -716,7 +717,7 @@ static void __init populate_initrd_image(char *err) } #endif /* CONFIG_BLK_DEV_RAM */ -static void __init do_populate_rootfs(void *unused, async_cookie_t cookie) +static void __init unpack_initramfs(async_cookie_t cookie) { /* Load the built in initramfs */ char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size); @@ -724,7 +725,7 @@ static void __init do_populate_rootfs(void *unused, async_cookie_t cookie) panic_show_mem("%s", err); /* Failed to decompress INTERNAL initramfs */ if (!initrd_start || IS_ENABLED(CONFIG_INITRAMFS_FORCE)) - goto done; + return; if (IS_ENABLED(CONFIG_BLK_DEV_RAM)) printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n"); @@ -739,9 +740,14 @@ static void __init do_populate_rootfs(void *unused, async_cookie_t cookie) printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err); #endif } +} -done: - security_initramfs_populated(); +static void __init do_populate_rootfs(void *unused, async_cookie_t cookie) +{ + scoped_with_init_fs() { + unpack_initramfs(cookie); + security_initramfs_populated(); + } /* * If the initrd region is overlapped with crashkernel reserved region, diff --git a/init/initramfs_test.c b/init/initramfs_test.c index bc55306d226d..1fc990a66563 100644 --- a/init/initramfs_test.c +++ b/init/initramfs_test.c @@ -3,6 +3,7 @@ #include <linux/fcntl.h> #include <linux/file.h> #include <linux/fs.h> +#include <linux/fs_struct.h> #include <linux/init.h> #include <linux/init_syscalls.h> #include <linux/initrd.h> @@ -116,38 +117,41 @@ static void __init initramfs_test_extract(struct kunit *test) GFP_KERNEL); len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); - ktime_get_real_ts64(&ts_before); - err = unpack_to_rootfs(cpio_srcbuf, len); - ktime_get_real_ts64(&ts_after); - if (err) { - KUNIT_FAIL(test, "unpack failed %s", err); - goto out; + /* Tests run in a nullfs kthread; borrow the init fs for path resolution. */ + scoped_with_init_fs() { + ktime_get_real_ts64(&ts_before); + err = unpack_to_rootfs(cpio_srcbuf, len); + ktime_get_real_ts64(&ts_after); + if (err) { + KUNIT_FAIL(test, "unpack failed %s", err); + goto out; + } + + KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st, 0), 0); + KUNIT_EXPECT_TRUE(test, S_ISREG(st.mode)); + KUNIT_EXPECT_TRUE(test, uid_eq(st.uid, KUIDT_INIT(c[0].uid))); + KUNIT_EXPECT_TRUE(test, gid_eq(st.gid, KGIDT_INIT(c[0].gid))); + KUNIT_EXPECT_EQ(test, st.nlink, 1); + if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) { + KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[0].mtime); + } else { + KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec); + KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec); + } + KUNIT_EXPECT_EQ(test, st.blocks, c[0].filesize); + + KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st, 0), 0); + KUNIT_EXPECT_TRUE(test, S_ISDIR(st.mode)); + if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) { + KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[1].mtime); + } else { + KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec); + KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec); + } + + KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); + KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0); } - - KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st, 0), 0); - KUNIT_EXPECT_TRUE(test, S_ISREG(st.mode)); - KUNIT_EXPECT_TRUE(test, uid_eq(st.uid, KUIDT_INIT(c[0].uid))); - KUNIT_EXPECT_TRUE(test, gid_eq(st.gid, KGIDT_INIT(c[0].gid))); - KUNIT_EXPECT_EQ(test, st.nlink, 1); - if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) { - KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[0].mtime); - } else { - KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec); - KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec); - } - KUNIT_EXPECT_EQ(test, st.blocks, c[0].filesize); - - KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st, 0), 0); - KUNIT_EXPECT_TRUE(test, S_ISDIR(st.mode)); - if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) { - KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[1].mtime); - } else { - KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec); - KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec); - } - - KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); - KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0); out: kfree(cpio_srcbuf); } @@ -197,8 +201,11 @@ static void __init initramfs_test_fname_overrun(struct kunit *test) suffix_off--; } - err = unpack_to_rootfs(cpio_srcbuf, len); - KUNIT_EXPECT_NOT_NULL(test, err); + /* Tests run in a nullfs kthread; borrow the init fs for path resolution. */ + scoped_with_init_fs() { + err = unpack_to_rootfs(cpio_srcbuf, len); + KUNIT_EXPECT_NOT_NULL(test, err); + } kfree(cpio_srcbuf); } @@ -233,22 +240,25 @@ static void __init initramfs_test_data(struct kunit *test) len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); - err = unpack_to_rootfs(cpio_srcbuf, len); - KUNIT_EXPECT_NULL(test, err); + /* Tests run in a nullfs kthread; borrow the init fs for path resolution. */ + scoped_with_init_fs() { + err = unpack_to_rootfs(cpio_srcbuf, len); + KUNIT_EXPECT_NULL(test, err); - file = filp_open(c[0].fname, O_RDONLY, 0); - if (IS_ERR(file)) { - KUNIT_FAIL(test, "open failed"); - goto out; - } + file = filp_open(c[0].fname, O_RDONLY, 0); + if (IS_ERR(file)) { + KUNIT_FAIL(test, "open failed"); + goto out; + } - /* read back file contents into @cpio_srcbuf and confirm match */ - len = kernel_read(file, cpio_srcbuf, c[0].filesize, NULL); - KUNIT_EXPECT_EQ(test, len, c[0].filesize); - KUNIT_EXPECT_MEMEQ(test, cpio_srcbuf, c[0].data, len); + /* read back file contents into @cpio_srcbuf and confirm match */ + len = kernel_read(file, cpio_srcbuf, c[0].filesize, NULL); + KUNIT_EXPECT_EQ(test, len, c[0].filesize); + KUNIT_EXPECT_MEMEQ(test, cpio_srcbuf, c[0].data, len); - fput(file); - KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); + fput(file); + KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); + } out: kfree(cpio_srcbuf); } @@ -288,25 +298,28 @@ static void __init initramfs_test_csum(struct kunit *test) len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); - err = unpack_to_rootfs(cpio_srcbuf, len); - KUNIT_EXPECT_NULL(test, err); + /* Tests run in a nullfs kthread; borrow the init fs for path resolution. */ + scoped_with_init_fs() { + err = unpack_to_rootfs(cpio_srcbuf, len); + KUNIT_EXPECT_NULL(test, err); - KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); - KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0); + KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); + KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0); - /* mess up the csum and confirm that unpack fails */ - c[0].csum--; - len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); + /* mess up the csum and confirm that unpack fails */ + c[0].csum--; + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); - err = unpack_to_rootfs(cpio_srcbuf, len); - KUNIT_EXPECT_NOT_NULL(test, err); + err = unpack_to_rootfs(cpio_srcbuf, len); + KUNIT_EXPECT_NOT_NULL(test, err); - /* - * file (with content) is still retained in case of bad-csum abort. - * Perhaps we should change this. - */ - KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); - KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), -ENOENT); + /* + * file (with content) is still retained in case of bad-csum abort. + * Perhaps we should change this. + */ + KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); + KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), -ENOENT); + } kfree(cpio_srcbuf); } @@ -344,17 +357,20 @@ static void __init initramfs_test_hardlink(struct kunit *test) len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); - err = unpack_to_rootfs(cpio_srcbuf, len); - KUNIT_EXPECT_NULL(test, err); + /* Tests run in a nullfs kthread; borrow the init fs for path resolution. */ + scoped_with_init_fs() { + err = unpack_to_rootfs(cpio_srcbuf, len); + KUNIT_EXPECT_NULL(test, err); - KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0); - KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0); - KUNIT_EXPECT_EQ(test, st0.ino, st1.ino); - KUNIT_EXPECT_EQ(test, st0.nlink, 2); - KUNIT_EXPECT_EQ(test, st1.nlink, 2); + KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0); + KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0); + KUNIT_EXPECT_EQ(test, st0.ino, st1.ino); + KUNIT_EXPECT_EQ(test, st0.nlink, 2); + KUNIT_EXPECT_EQ(test, st1.nlink, 2); - KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); - KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0); + KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); + KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0); + } kfree(cpio_srcbuf); } @@ -387,12 +403,15 @@ static void __init initramfs_test_many(struct kunit *test) } len = p - cpio_srcbuf; - err = unpack_to_rootfs(cpio_srcbuf, len); - KUNIT_EXPECT_NULL(test, err); - - for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) { - sprintf(thispath, "initramfs_test_many-%d", i); - KUNIT_EXPECT_EQ(test, init_unlink(thispath), 0); + /* Tests run in a nullfs kthread; borrow the init fs for path resolution. */ + scoped_with_init_fs() { + err = unpack_to_rootfs(cpio_srcbuf, len); + KUNIT_EXPECT_NULL(test, err); + + for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) { + sprintf(thispath, "initramfs_test_many-%d", i); + KUNIT_EXPECT_EQ(test, init_unlink(thispath), 0); + } } kfree(cpio_srcbuf); @@ -439,22 +458,25 @@ static void __init initramfs_test_fname_pad(struct kunit *test) memcpy(tbufs->padded_fname, "padded_fname", sizeof("padded_fname")); len = fill_cpio(c, ARRAY_SIZE(c), false, tbufs->cpio_srcbuf); - err = unpack_to_rootfs(tbufs->cpio_srcbuf, len); - KUNIT_EXPECT_NULL(test, err); + /* Tests run in a nullfs kthread; borrow the init fs for path resolution. */ + scoped_with_init_fs() { + err = unpack_to_rootfs(tbufs->cpio_srcbuf, len); + KUNIT_EXPECT_NULL(test, err); - file = filp_open(c[0].fname, O_RDONLY, 0); - if (IS_ERR(file)) { - KUNIT_FAIL(test, "open failed"); - goto out; - } + file = filp_open(c[0].fname, O_RDONLY, 0); + if (IS_ERR(file)) { + KUNIT_FAIL(test, "open failed"); + goto out; + } - /* read back file contents into @cpio_srcbuf and confirm match */ - len = kernel_read(file, tbufs->cpio_srcbuf, c[0].filesize, NULL); - KUNIT_EXPECT_EQ(test, len, c[0].filesize); - KUNIT_EXPECT_MEMEQ(test, tbufs->cpio_srcbuf, c[0].data, len); + /* read back file contents into @cpio_srcbuf and confirm match */ + len = kernel_read(file, tbufs->cpio_srcbuf, c[0].filesize, NULL); + KUNIT_EXPECT_EQ(test, len, c[0].filesize); + KUNIT_EXPECT_MEMEQ(test, tbufs->cpio_srcbuf, c[0].data, len); - fput(file); - KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); + fput(file); + KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); + } out: kfree(tbufs); } @@ -495,13 +517,16 @@ static void __init initramfs_test_fname_path_max(struct kunit *test) memcpy(tbufs->fname_ok, "fname_ok", sizeof("fname_ok") - 1); len = fill_cpio(c, ARRAY_SIZE(c), false, tbufs->cpio_src); - /* unpack skips over fname_oversize instead of returning an error */ - err = unpack_to_rootfs(tbufs->cpio_src, len); - KUNIT_EXPECT_NULL(test, err); + /* Tests run in a nullfs kthread; borrow the init fs for path resolution. */ + scoped_with_init_fs() { + /* unpack skips over fname_oversize instead of returning an error */ + err = unpack_to_rootfs(tbufs->cpio_src, len); + KUNIT_EXPECT_NULL(test, err); - KUNIT_EXPECT_EQ(test, init_stat("fname_oversize", &st0, 0), -ENOENT); - KUNIT_EXPECT_EQ(test, init_stat("fname_ok", &st1, 0), 0); - KUNIT_EXPECT_EQ(test, init_rmdir("fname_ok"), 0); + KUNIT_EXPECT_EQ(test, init_stat("fname_oversize", &st0, 0), -ENOENT); + KUNIT_EXPECT_EQ(test, init_stat("fname_ok", &st1, 0), 0); + KUNIT_EXPECT_EQ(test, init_rmdir("fname_ok"), 0); + } kfree(tbufs); } @@ -539,8 +564,11 @@ static void __init initramfs_test_hdr_hex(struct kunit *test) /* inject_ox=true to add "0x" cpio field prefixes */ len = fill_cpio(c, ARRAY_SIZE(c), true, tbufs->cpio_src); - err = unpack_to_rootfs(tbufs->cpio_src, len); - KUNIT_EXPECT_NOT_NULL(test, err); + /* Tests run in a nullfs kthread; borrow the init fs for path resolution. */ + scoped_with_init_fs() { + err = unpack_to_rootfs(tbufs->cpio_src, len); + KUNIT_EXPECT_NOT_NULL(test, err); + } kfree(tbufs); } diff --git a/init/main.c b/init/main.c index e363232b428b..92d34e496a33 100644 --- a/init/main.c +++ b/init/main.c @@ -103,6 +103,7 @@ #include <linux/stackdepot.h> #include <linux/randomize_kstack.h> #include <linux/pidfs.h> +#include <linux/fs_struct.h> #include <linux/ptdump.h> #include <linux/time_namespace.h> #include <linux/unaligned.h> @@ -670,6 +671,11 @@ static __initdata DECLARE_COMPLETION(kthreadd_done); static noinline void __ref __noreturn rest_init(void) { + struct kernel_clone_args init_args = { + .flags = (CLONE_VM | CLONE_UNTRACED), + .fn = kernel_init, + .fn_arg = NULL, + }; struct task_struct *tsk; int pid; @@ -679,7 +685,7 @@ static noinline void __ref __noreturn rest_init(void) * the init task will end up wanting to create kthreads, which, if * we schedule it before we create kthreadd, will OOPS. */ - pid = user_mode_thread(kernel_init, NULL, CLONE_FS); + pid = kernel_clone(&init_args); /* * Pin init on the boot CPU. Task migration is not properly working * until sched_init_smp() has been run. It will set the allowed @@ -1540,6 +1546,8 @@ static int __ref kernel_init(void *unused) { int ret; + init_userspace_fs(); + /* * Wait until kthreadd is all set-up. */ diff --git a/ipc/mqueue.c b/ipc/mqueue.c index 4798b375972b..d1dd36a651b0 100644 --- a/ipc/mqueue.c +++ b/ipc/mqueue.c @@ -608,7 +608,7 @@ out_unlock: } static int mqueue_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return mqueue_create_attr(dentry, mode, NULL); } @@ -790,7 +790,6 @@ static void __do_notify(struct mqueue_inode_info *info) struct kernel_siginfo sig_i; struct task_struct *task; - /* do_mq_notify() accepts sigev_signo == 0, why?? */ if (!info->notify.sigev_signo) break; @@ -1281,9 +1280,9 @@ static int do_mq_notify(mqd_t mqdes, const struct sigevent *notification) notification->sigev_notify != SIGEV_THREAD)) return -EINVAL; if (notification->sigev_notify == SIGEV_SIGNAL && - !valid_signal(notification->sigev_signo)) { + (!notification->sigev_signo || + !valid_signal(notification->sigev_signo))) return -EINVAL; - } if (notification->sigev_notify == SIGEV_THREAD) { long timeo; diff --git a/kernel/exit.c b/kernel/exit.c index 1056422bc101..7ac52196d819 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1111,7 +1111,7 @@ void __noreturn make_task_dead(int signr) SYSCALL_DEFINE1(exit, int, error_code) { - do_exit((error_code&0xff)<<8); + do_exit((error_code & 0xff) << 8); } /* diff --git a/kernel/fork.c b/kernel/fork.c index f0e2e131a9a5..94e021eabf1d 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1618,9 +1618,27 @@ static int copy_exec_state(u64 clone_flags, struct task_struct *tsk) return task_exec_state_copy(tsk); } -static int copy_fs(u64 clone_flags, struct task_struct *tsk) +static int copy_fs(u64 clone_flags, struct task_struct *tsk, bool umh) { - struct fs_struct *fs = current->fs; + struct fs_struct *fs; + + /* + * Usermodehelper may copy userspace_init_fs filesystem state but + * they don't get to create mount namespaces, share the + * filesystem state, or be started from a non-initial mount + * namespace. + */ + if (umh) { + if (clone_flags & (CLONE_NEWNS | CLONE_FS)) + return -EINVAL; + if (current->nsproxy->mnt_ns != &init_mnt_ns) + return -EINVAL; + fs = userspace_init_fs; + } else { + fs = current->fs; + VFS_WARN_ON_ONCE(current->fs != current->real_fs); + } + if (clone_flags & CLONE_FS) { /* tsk->fs is already what we want */ read_seqlock_excl(&fs->seq); @@ -1633,7 +1651,7 @@ static int copy_fs(u64 clone_flags, struct task_struct *tsk) read_sequnlock_excl(&fs->seq); return 0; } - tsk->fs = copy_fs_struct(fs); + tsk->real_fs = tsk->fs = copy_fs_struct(fs); if (!tsk->fs) return -ENOMEM; return 0; @@ -2277,7 +2295,7 @@ __latent_entropy struct task_struct *copy_process( retval = copy_files(clone_flags, p, args->no_files); if (retval) goto bad_fork_cleanup_semundo; - retval = copy_fs(clone_flags, p); + retval = copy_fs(clone_flags, p, args->umh); if (retval) goto bad_fork_cleanup_files; retval = copy_sighand(clone_flags, p); @@ -2819,6 +2837,7 @@ pid_t user_mode_thread(int (*fn)(void *), void *arg, unsigned long flags) .exit_signal = (flags & CSIGNAL), .fn = fn, .fn_arg = arg, + .umh = 1, }; return kernel_clone(&args); @@ -3216,7 +3235,7 @@ static int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp */ int ksys_unshare(unsigned long unshare_flags) { - struct fs_struct *fs, *new_fs = NULL; + struct fs_struct *new_fs = NULL; struct files_struct *new_fd = NULL; struct cred *new_cred = NULL; struct nsproxy *new_nsproxy = NULL; @@ -3247,6 +3266,10 @@ int ksys_unshare(unsigned long unshare_flags) if (unshare_flags & CLONE_NEWNS) unshare_flags |= CLONE_FS; + /* No unsharing with overriden fs state */ + VFS_WARN_ON_ONCE(unshare_flags & (CLONE_NEWNS | CLONE_FS) && + current->fs != current->real_fs); + err = check_unshare_flags(unshare_flags); if (err) goto bad_unshare_out; @@ -3294,23 +3317,13 @@ int ksys_unshare(unsigned long unshare_flags) new_nsproxy = NULL; } - task_lock(current); + if (new_fs) + new_fs = switch_fs_struct(new_fs); - if (new_fs) { - fs = current->fs; - read_seqlock_excl(&fs->seq); - current->fs = new_fs; - if (--fs->users) - new_fs = NULL; - else - new_fs = fs; - read_sequnlock_excl(&fs->seq); - } - - if (new_fd) + if (new_fd) { + guard(task_lock)(current); swap(current->files, new_fd); - - task_unlock(current); + } if (new_cred) { /* Install the new user namespace */ diff --git a/kernel/kcmp.c b/kernel/kcmp.c index 7c1a65bd5f8d..76476aeee067 100644 --- a/kernel/kcmp.c +++ b/kernel/kcmp.c @@ -186,7 +186,7 @@ SYSCALL_DEFINE5(kcmp, pid_t, pid1, pid_t, pid2, int, type, ret = kcmp_ptr(task1->files, task2->files, KCMP_FILES); break; case KCMP_FS: - ret = kcmp_ptr(task1->fs, task2->fs, KCMP_FS); + ret = kcmp_ptr(task1->real_fs, task2->real_fs, KCMP_FS); break; case KCMP_SIGHAND: ret = kcmp_ptr(task1->sighand, task2->sighand, KCMP_SIGHAND); diff --git a/kernel/umh.c b/kernel/umh.c index 48117c569e1a..6e2c7bb315c6 100644 --- a/kernel/umh.c +++ b/kernel/umh.c @@ -71,10 +71,8 @@ static int call_usermodehelper_exec_async(void *data) spin_unlock_irq(¤t->sighand->siglock); /* - * Initial kernel threads share ther FS with init, in order to - * get the init root directory. But we've now created a new - * thread that is going to execve a user process and has its own - * 'struct fs_struct'. Reset umask to the default. + * Usermodehelper threads get a copy of userspace init's + * fs_struct. Reset umask to the default. */ current->fs->umask = 0022; diff --git a/mm/shmem.c b/mm/shmem.c index b51f83c970bb..5789a0f5a346 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -3864,7 +3864,7 @@ static struct dentry *shmem_mkdir(struct mnt_idmap *idmap, struct inode *dir, } static int shmem_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) + struct dentry *dentry, umode_t mode) { return shmem_mknod(idmap, dir, dentry, mode | S_IFREG, 0); } diff --git a/net/socket.c b/net/socket.c index 63c69a0fa74e..b0256cd222f8 100644 --- a/net/socket.c +++ b/net/socket.c @@ -465,6 +465,31 @@ static const struct xattr_handler sockfs_user_xattr_handler = { .set = sockfs_user_xattr_set, }; +/** + * sock_read_xattr - read a user.* xattr from a socket's sockfs inode + * @sock: socket whose inode holds the xattr + * @name: full xattr name, e.g. "user.bpf_test" + * @value: output buffer + * @size: size of @value in bytes + * + * SOCK_INODE() is valid only for sockfs sockets; sock_from_file() rejects + * anything else (e.g. tun, tap). + * Lockless: simple_xattr_get() looks up the value under RCU, no inode lock. + * + * Return: length of the value on success, a negative errno on error. + */ +int sock_read_xattr(struct socket *sock, const char *name, void *value, size_t size) +{ + struct file *file = sock->file; + struct sockfs_inode *si; + + if (!file || sock_from_file(file) != sock) + return -EOPNOTSUPP; + + si = SOCKFS_I(SOCK_INODE(sock)); + return simple_xattr_get(&sockfs_xa_cache, &si->xattrs, name, value, size); +} + static const struct xattr_handler * const sockfs_xattr_handlers[] = { &sockfs_xattr_handler, &sockfs_security_xattr_handler, diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index f7a9d55eee8a..92a768cc9ecf 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -1196,17 +1196,12 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len, unix_mkname_bsd(sunaddr, addr_len); if (flags & SOCK_COREDUMP) { - struct path root; - - task_lock(&init_task); - get_fs_root(init_task.fs, &root); - task_unlock(&init_task); - - scoped_with_kernel_creds() - err = vfs_path_lookup(root.dentry, root.mnt, sunaddr->sun_path, - LOOKUP_BENEATH | LOOKUP_NO_SYMLINKS | - LOOKUP_NO_MAGICLINKS, &path); - path_put(&root); + scoped_with_init_fs() { + scoped_with_kernel_creds() + err = kern_path(sunaddr->sun_path, + LOOKUP_NO_SYMLINKS | + LOOKUP_NO_MAGICLINKS, &path); + } if (err) goto fail; } else { diff --git a/tools/testing/selftests/bpf/bpf_experimental.h b/tools/testing/selftests/bpf/bpf_experimental.h index 67ff7882299e..63e4e472fe36 100644 --- a/tools/testing/selftests/bpf/bpf_experimental.h +++ b/tools/testing/selftests/bpf/bpf_experimental.h @@ -364,6 +364,9 @@ extern void bpf_iter_dmabuf_destroy(struct bpf_iter_dmabuf *it) __weak __ksym; extern int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__str, struct bpf_dynptr *value_p) __weak __ksym; +extern int bpf_sock_read_xattr(struct socket *sock, const char *name__str, + struct bpf_dynptr *value_p) __weak __ksym; + #define PREEMPT_BITS 8 #define SOFTIRQ_BITS 8 #define HARDIRQ_BITS 4 diff --git a/tools/testing/selftests/bpf/prog_tests/sock_xattr.c b/tools/testing/selftests/bpf/prog_tests/sock_xattr.c new file mode 100644 index 000000000000..b5816e90f01a --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/sock_xattr.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2026 Christian Brauner */ + +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/xattr.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <test_progs.h> + +#include "sock_read_xattr.skel.h" + +static const char xattr_value[] = "bpf_sock_value"; +static const char xattr_name[] = "user.bpf_test"; + +static void test_read_sock_xattr(void) +{ + struct sockaddr_in addr = {}; + struct sock_read_xattr *skel = NULL; + struct bpf_link *link = NULL; + int sock_fd = -1, err; + + sock_fd = socket(AF_INET, SOCK_STREAM, 0); + if (!ASSERT_OK_FD(sock_fd, "socket")) + return; + + err = fsetxattr(sock_fd, xattr_name, xattr_value, sizeof(xattr_value), 0); + if (!ASSERT_OK(err, "fsetxattr")) + goto out; + + skel = sock_read_xattr__open_and_load(); + if (!ASSERT_OK_PTR(skel, "sock_read_xattr__open_and_load")) + goto out; + + skel->bss->monitored_pid = sys_gettid(); + + /* Only attach the functional program; the verifier-only programs + * above are not pid-gated and would clobber the shared globals. + */ + link = bpf_program__attach(skel->progs.read_sock_xattr); + if (!ASSERT_OK_PTR(link, "attach read_sock_xattr")) + goto out; + + addr.sin_family = AF_INET; + addr.sin_port = htons(1234); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + /* Only the lsm/socket_connect hook matters; the connect may fail. */ + connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)); + + ASSERT_EQ(skel->data->read_ret, sizeof(xattr_value), "read_ret"); + ASSERT_STREQ(skel->bss->value, xattr_value, "value"); + +out: + bpf_link__destroy(link); + if (sock_fd >= 0) + close(sock_fd); + sock_read_xattr__destroy(skel); +} + +void test_sock_xattr(void) +{ + RUN_TESTS(sock_read_xattr); + + if (test__start_subtest("read_sock_xattr")) + test_read_sock_xattr(); +} diff --git a/tools/testing/selftests/bpf/progs/sock_read_xattr.c b/tools/testing/selftests/bpf/progs/sock_read_xattr.c new file mode 100644 index 000000000000..c4a8eae8cc3c --- /dev/null +++ b/tools/testing/selftests/bpf/progs/sock_read_xattr.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Christian Brauner */ + +#include <vmlinux.h> +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_core_read.h> +#include "bpf_experimental.h" +#include "bpf_misc.h" + +char _license[] SEC("license") = "GPL"; + +char value[16]; +int read_ret = -1; +__u32 monitored_pid = 0; + +static __always_inline void read_xattr(struct socket *sock) +{ + struct bpf_dynptr value_ptr; + + bpf_dynptr_from_mem(value, sizeof(value), 0, &value_ptr); + bpf_sock_read_xattr(sock, "user.bpf_test", &value_ptr); +} + +SEC("lsm.s/socket_connect") +__success +int BPF_PROG(trusted_sock_ptr_sleepable, struct socket *sock) +{ + read_xattr(sock); + return 0; +} + +SEC("lsm/socket_connect") +__success +int BPF_PROG(trusted_sock_ptr_non_sleepable, struct socket *sock) +{ + read_xattr(sock); + return 0; +} + +SEC("lsm.s/socket_connect") +__success +int BPF_PROG(read_sock_xattr, struct socket *sock) +{ + struct bpf_dynptr value_ptr; + __u32 pid = bpf_get_current_pid_tgid() >> 32; + + if (pid != monitored_pid) + return 0; + + bpf_dynptr_from_mem(value, sizeof(value), 0, &value_ptr); + read_ret = bpf_sock_read_xattr(sock, "user.bpf_test", &value_ptr); + return 0; +} diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index a78f894157de..9eb185fb2f9d 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -6,3 +6,4 @@ file_stressor anon_inode_test kernfs_test idmapped_tmpfile +ustat_test diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index a7ec2ba2dd83..03be337c1f35 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 CFLAGS += $(KHDR_INCLUDES) -TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog +TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test TEST_GEN_PROGS += idmapped_tmpfile TEST_GEN_PROGS_EXTENDED := dnotify_test diff --git a/tools/testing/selftests/filesystems/overlayfs/.gitignore b/tools/testing/selftests/filesystems/overlayfs/.gitignore index e23a18c8b37f..077f7a128168 100644 --- a/tools/testing/selftests/filesystems/overlayfs/.gitignore +++ b/tools/testing/selftests/filesystems/overlayfs/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only dev_in_maps set_layers_via_fds +idmapped_mounts diff --git a/tools/testing/selftests/filesystems/overlayfs/Makefile b/tools/testing/selftests/filesystems/overlayfs/Makefile index d3ad4a77db9b..b3185f684add 100644 --- a/tools/testing/selftests/filesystems/overlayfs/Makefile +++ b/tools/testing/selftests/filesystems/overlayfs/Makefile @@ -8,7 +8,9 @@ LOCAL_HDRS += ../wrappers.h log.h TEST_GEN_PROGS := dev_in_maps TEST_GEN_PROGS += set_layers_via_fds +TEST_GEN_PROGS += idmapped_mounts include ../../lib.mk $(OUTPUT)/set_layers_via_fds: ../utils.c +$(OUTPUT)/idmapped_mounts: ../utils.c diff --git a/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c new file mode 100644 index 000000000000..44a75839f4ed --- /dev/null +++ b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE + +#include <fcntl.h> +#include <limits.h> +#include <sched.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/syscall.h> + +#include <linux/mount.h> +#include <linux/types.h> + +#include "kselftest_harness.h" +#include "../wrappers.h" +#include "../utils.h" + +/* + * An idmapping that maps the mount-visible id range [0, ID_RANGE) onto the + * host/overlay-final id range [ID_HOST, ID_HOST + ID_RANGE). Through such an + * idmapped overlay mount, an overlay-final id of ID_HOST + n is reported as n, + * and an id of n requested through the mount is stored as ID_HOST + n. + */ +#define ID_NS 0 +#define ID_HOST 10000 +#define ID_RANGE 10000 + +/* + * For the composition test the lower layer's on-disk ids live in a + * separate range and are mapped by an idmapped lower layer onto the + * overlay-final range [ID_HOST, ID_HOST + ID_RANGE). + */ +#define LAYER_HOST 20000 + +#ifndef MOUNT_ATTR_IDMAP +#define MOUNT_ATTR_IDMAP 0x00100000 +#endif + +#ifndef __NR_mount_setattr +#define __NR_mount_setattr 442 +#endif + +static inline int sys_mount_setattr(int dfd, const char *path, + unsigned int flags, + struct mount_attr *attr, size_t size) +{ + return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); +} + +static bool ovl_supported(void) +{ + int fd = sys_fsopen("overlay", 0); + + if (fd < 0) + return false; + close(fd); + return true; +} + +/* base/{l,u,w} owned by ID_HOST so they map to ID_NS through the idmap. */ +static int setup_layers(const char *base) +{ + static const char *sub[] = { "", "/l", "/u", "/w" }; + char path[PATH_MAX]; + + for (size_t i = 0; i < ARRAY_SIZE(sub); i++) { + snprintf(path, sizeof(path), "%s%s", base, sub[i]); + if (mkdir(path, 0755) && errno != EEXIST) + return -1; + if (i && chown(path, ID_HOST, ID_HOST)) + return -1; + } + return 0; +} + +static int ovl_mount(const char *base, bool nfs_export) +{ + char lower[PATH_MAX], upper[PATH_MAX], work[PATH_MAX]; + int fsfd, ovl; + + snprintf(lower, sizeof(lower), "%s/l", base); + snprintf(upper, sizeof(upper), "%s/u", base); + snprintf(work, sizeof(work), "%s/w", base); + + fsfd = sys_fsopen("overlay", 0); + if (fsfd < 0) + return -1; + + if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "lowerdir", lower, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "upperdir", upper, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "workdir", work, 0)) + goto err; + if (nfs_export && + (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "index", "on", 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "nfs_export", "on", 0))) + goto err; + if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) + goto err; + + ovl = sys_fsmount(fsfd, 0, 0); + close(fsfd); + return ovl; +err: + close(fsfd); + return -1; +} + +/* Idmap the (still detached, not yet visible) overlay mount @mfd. */ +static int ovl_idmap(int mfd) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + int ret, userns_fd; + + /* + * get_userns_fd(fs_id, mount_id, range): a file whose filesystem id + * is fs_id + n is shown through the idmapped mount as mount_id + n. + * Here the overlay-final (fs side) range is [ID_HOST, ..) and the + * caller-visible (mount side) range is [ID_NS, ..). + */ + userns_fd = get_userns_fd(ID_HOST, ID_NS, ID_RANGE); + if (userns_fd < 0) + return -1; + + attr.userns_fd = userns_fd; + ret = sys_mount_setattr(mfd, "", AT_EMPTY_PATH, &attr, sizeof(attr)); + close(userns_fd); + return ret; +} + +/* Clone @path into a detached, idmapped mount usable as an overlay layer. */ +static int idmapped_layer_fd(const char *path, int nsid, int hostid, int range) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + int fd_tree, userns_fd; + + fd_tree = sys_open_tree(AT_FDCWD, path, + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (fd_tree < 0) + return -1; + userns_fd = get_userns_fd(nsid, hostid, range); + if (userns_fd < 0) { + close(fd_tree); + return -1; + } + attr.userns_fd = userns_fd; + if (sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH, &attr, + sizeof(attr))) { + close(userns_fd); + close(fd_tree); + return -1; + } + close(userns_fd); + return fd_tree; +} + +/* Overlay with a layer passed by fd (idmapped) plus a plain upper/work. */ +static int ovl_mount_lower_fd(const char *upper, const char *work, int fd_lower) +{ + int fsfd, ovl; + + fsfd = sys_fsopen("overlay", 0); + if (fsfd < 0) + return -1; + + if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "upperdir", upper, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "workdir", work, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_FD, "lowerdir+", NULL, fd_lower) || + sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) + goto err; + + ovl = sys_fsmount(fsfd, 0, 0); + close(fsfd); + return ovl; +err: + close(fsfd); + return -1; +} + +/* + * Mount an overlay inside user namespace @u1 (so the overlay sb's s_user_ns is + * not the initial namespace) and idmap that overlay mount with @u2. Runs in a + * child that joins @u1; returns 0 on success. + */ +static int userns_overlay_child(int u1) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + struct stat st; + int ovl, u2; + + /* Become root in the overlay sb's user namespace u1. */ + if (!switch_userns(u1, 0, 0, false)) + return fprintf(stderr, "userns: switch_userns: %m\n"), -1; + if (unshare(CLONE_NEWNS) || + sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL)) + return fprintf(stderr, "userns: unshare/slave: %m\n"), -1; + if (sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL)) + return fprintf(stderr, "userns: mount tmpfs: %m\n"), -1; + if (setup_layers("/tmp/ovl")) + return fprintf(stderr, "userns: setup_layers: %m\n"), -1; + if (mknod("/tmp/ovl/l/file", S_IFREG | 0644, 0) || + chown("/tmp/ovl/l/file", ID_HOST + 5, ID_HOST + 5)) + return fprintf(stderr, "userns: lower file: %m\n"), -1; + + ovl = ovl_mount("/tmp/ovl", false); + if (ovl < 0) + return fprintf(stderr, "userns: ovl_mount: %m\n"), -1; + + /* + * mount_setattr() requires CAP_SYS_ADMIN over the idmap user + * namespace, so it must be a child of u1. Create it now, from + * inside u1. + */ + u2 = get_userns_fd(ID_HOST, ID_NS, ID_RANGE); + if (u2 < 0) + return fprintf(stderr, "userns: get_userns_fd: %m\n"), -1; + attr.userns_fd = u2; + if (sys_mount_setattr(ovl, "", AT_EMPTY_PATH, &attr, sizeof(attr))) + return fprintf(stderr, "userns: mount_setattr: %m\n"), -1; + close(u2); + + if (fstatat(ovl, "file", &st, 0)) + return fprintf(stderr, "userns: fstatat: %m\n"), -1; + if (st.st_uid != ID_NS + 5 || st.st_gid != ID_NS + 5) { + fprintf(stderr, "userns: got %u:%u expected %u:%u\n", + st.st_uid, st.st_gid, ID_NS + 5, ID_NS + 5); + return -1; + } + return 0; +} + +FIXTURE(idmapped_overlay) { + char base[64]; +}; + +FIXTURE_SETUP(idmapped_overlay) +{ + /* Private mount namespace so test mounts need no cleanup. */ + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0); + + /* tmpfs for the layers so we can chown them to arbitrary ids. */ + ASSERT_EQ(sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL), 0); + + snprintf(self->base, sizeof(self->base), "/tmp/ovl"); + ASSERT_EQ(setup_layers(self->base), 0); +} + +FIXTURE_TEARDOWN(idmapped_overlay) +{ +} + +/* A file owned by ID_HOST + 5 is reported as ID_NS + 5 through the idmap. */ +TEST_F(idmapped_overlay, getattr) +{ + char path[PATH_MAX]; + struct stat st; + int ovl; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + snprintf(path, sizeof(path), "%s/l/file", self->base); + ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0); + ASSERT_EQ(chown(path, ID_HOST + 5, ID_HOST + 5), 0); + + ovl = ovl_mount(self->base, false); + ASSERT_GE(ovl, 0); + ASSERT_EQ(ovl_idmap(ovl), 0); + + ASSERT_EQ(fstatat(ovl, "file", &st, 0), 0); + EXPECT_EQ(st.st_uid, ID_NS + 5); + EXPECT_EQ(st.st_gid, ID_NS + 5); + + EXPECT_EQ(close(ovl), 0); +} + +/* + * Every creation path initializes the new owner through the mount idmap: + * created as caller id ID_NS, stored on the upper layer as overlay-final + * ID_HOST. Covers ovl_create() (regular file), ovl_mkdir(), ovl_mknod() + * and ovl_symlink() (which share ovl_create_object()), plus the separate + * ovl_tmpfile() path. + */ +TEST_F(idmapped_overlay, create) +{ + static const char *names[] = { "reg", "dir", "fifo", "lnk" }; + char path[PATH_MAX]; + struct stat st; + int ovl, fd; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + ovl = ovl_mount(self->base, false); + ASSERT_GE(ovl, 0); + ASSERT_EQ(ovl_idmap(ovl), 0); + + /* One object per creation operation, all as caller id ID_NS. */ + fd = openat(ovl, "reg", O_CREAT | O_WRONLY | O_EXCL, 0644); + ASSERT_GE(fd, 0); + EXPECT_EQ(close(fd), 0); + ASSERT_EQ(mkdirat(ovl, "dir", 0755), 0); + ASSERT_EQ(mknodat(ovl, "fifo", S_IFIFO | 0644, 0), 0); + ASSERT_EQ(symlinkat("target", ovl, "lnk"), 0); + + for (size_t i = 0; i < ARRAY_SIZE(names); i++) { + /* Reported as ID_NS through the idmapped mount ... */ + ASSERT_EQ(fstatat(ovl, names[i], &st, AT_SYMLINK_NOFOLLOW), 0); + EXPECT_EQ(st.st_uid, ID_NS); + EXPECT_EQ(st.st_gid, ID_NS); + /* ... and stored as ID_HOST on the upper layer. */ + snprintf(path, sizeof(path), "%s/u/%s", self->base, names[i]); + ASSERT_EQ(lstat(path, &st), 0); + EXPECT_EQ(st.st_uid, ID_HOST); + EXPECT_EQ(st.st_gid, ID_HOST); + } + + /* O_TMPFILE goes through the separate ovl_tmpfile() path. */ + fd = openat(ovl, ".", O_TMPFILE | O_WRONLY, 0644); + ASSERT_GE(fd, 0); + /* Inside the mount: caller id ID_NS. */ + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_EQ(st.st_uid, ID_NS); + EXPECT_EQ(st.st_gid, ID_NS); + /* Link it in so the upper backing file can be inspected too. */ + ASSERT_EQ(linkat(fd, "", ovl, "tmp", AT_EMPTY_PATH), 0); + EXPECT_EQ(close(fd), 0); + snprintf(path, sizeof(path), "%s/u/tmp", self->base); + ASSERT_EQ(lstat(path, &st), 0); + EXPECT_EQ(st.st_uid, ID_HOST); + EXPECT_EQ(st.st_gid, ID_HOST); + + EXPECT_EQ(close(ovl), 0); +} + +/* chown through the idmapped mount round-trips: ID_NS + 5 <-> ID_HOST + 5. */ +TEST_F(idmapped_overlay, chown) +{ + char path[PATH_MAX]; + struct stat st; + int ovl, fd; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + ovl = ovl_mount(self->base, false); + ASSERT_GE(ovl, 0); + ASSERT_EQ(ovl_idmap(ovl), 0); + + fd = openat(ovl, "f", O_CREAT | O_WRONLY | O_EXCL, 0644); + ASSERT_GE(fd, 0); + EXPECT_EQ(close(fd), 0); + + ASSERT_EQ(fchownat(ovl, "f", ID_NS + 5, ID_NS + 5, 0), 0); + + ASSERT_EQ(fstatat(ovl, "f", &st, 0), 0); + EXPECT_EQ(st.st_uid, ID_NS + 5); + EXPECT_EQ(st.st_gid, ID_NS + 5); + + snprintf(path, sizeof(path), "%s/u/f", self->base); + ASSERT_EQ(stat(path, &st), 0); + EXPECT_EQ(st.st_uid, ID_HOST + 5); + EXPECT_EQ(st.st_gid, ID_HOST + 5); + + EXPECT_EQ(close(ovl), 0); +} + +/* + * Composition: an idmapped lower layer underneath an idmapped overlay mount. + * An on-disk id is mapped by the layer idmap into the overlay-final range and + * then by the mount idmap into the caller's range: + * + * on-disk LAYER_HOST+7 --layer--> ID_HOST+7 --mount--> ID_NS+7 + */ +TEST_F(idmapped_overlay, composition) +{ + char lower[PATH_MAX], upper[PATH_MAX], work[PATH_MAX], path[PATH_MAX]; + struct stat st; + int ovl, fd_lower; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + snprintf(lower, sizeof(lower), "%s/l", self->base); + snprintf(upper, sizeof(upper), "%s/u", self->base); + snprintf(work, sizeof(work), "%s/w", self->base); + + /* Put the lower layer's ids in the on-disk [LAYER_HOST, ..) range. */ + ASSERT_EQ(chown(lower, LAYER_HOST, LAYER_HOST), 0); + snprintf(path, sizeof(path), "%s/l/file", self->base); + ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0); + ASSERT_EQ(chown(path, LAYER_HOST + 7, LAYER_HOST + 7), 0); + + /* Idmapped lower: on-disk LAYER_HOST <-> overlay-final ID_HOST. */ + fd_lower = idmapped_layer_fd(lower, LAYER_HOST, ID_HOST, ID_RANGE); + ASSERT_GE(fd_lower, 0); + + ovl = ovl_mount_lower_fd(upper, work, fd_lower); + ASSERT_GE(ovl, 0); + EXPECT_EQ(close(fd_lower), 0); + + /* Idmap the overlay mount: overlay-final ID_HOST <-> caller ID_NS. */ + ASSERT_EQ(ovl_idmap(ovl), 0); + + ASSERT_EQ(fstatat(ovl, "file", &st, 0), 0); + EXPECT_EQ(st.st_uid, ID_NS + 7); + EXPECT_EQ(st.st_gid, ID_NS + 7); + + EXPECT_EQ(close(ovl), 0); +} + +/* An idmapped overlay mount whose sb lives inside a user namespace. */ +TEST_F(idmapped_overlay, userns) +{ + int u1; + pid_t pid; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + /* u1 backs the overlay sb: identity-mapped, but not the init ns. */ + u1 = get_userns_fd(0, 0, 65536); + if (u1 < 0) + SKIP(return, "user namespaces not available"); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + int ret = userns_overlay_child(u1); + + _exit(ret ? EXIT_FAILURE : EXIT_SUCCESS); + } + EXPECT_EQ(wait_for_pid(pid), 0); + + EXPECT_EQ(close(u1), 0); +} + +/* + * An nfs_export overlay can be idmapped, and decodable file handles round-trip + * through the idmapped mount with correctly mapped ownership. Overlay file + * handles encode object identity, not ownership, so the mount idmap does not + * affect them; it only maps the owner reported once a handle is reopened. + */ +TEST_F(idmapped_overlay, nfs_export_handles) +{ + char path[PATH_MAX], mnt[128]; + union { + struct file_handle fh; + char buf[sizeof(struct file_handle) + MAX_HANDLE_SZ]; + } fhu; + struct file_handle *fh = &fhu.fh; + struct stat st; + int ovl, mfd, fd, mount_id; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + snprintf(path, sizeof(path), "%s/l/file", self->base); + ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0); + ASSERT_EQ(chown(path, ID_HOST + 7, ID_HOST + 7), 0); + + /* nfs_export=on gives decodable overlay file handles. */ + ovl = ovl_mount(self->base, true); + if (ovl < 0) + SKIP(return, "overlayfs nfs_export not supported"); + ASSERT_EQ(ovl_idmap(ovl), 0); + + /* Attach the idmapped mount so handles can be resolved against it. */ + snprintf(mnt, sizeof(mnt), "%s/mnt", self->base); + ASSERT_EQ(mkdir(mnt, 0755), 0); + ASSERT_EQ(sys_move_mount(ovl, "", AT_FDCWD, mnt, + MOVE_MOUNT_F_EMPTY_PATH), 0); + + snprintf(path, sizeof(path), "%s/file", mnt); + fh->handle_bytes = MAX_HANDLE_SZ; + ASSERT_EQ(name_to_handle_at(AT_FDCWD, path, fh, &mount_id, 0), 0); + + mfd = open(mnt, O_RDONLY | O_DIRECTORY); + ASSERT_GE(mfd, 0); + fd = open_by_handle_at(mfd, fh, O_RDONLY); + EXPECT_EQ(close(mfd), 0); + ASSERT_GE(fd, 0); + + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_EQ(st.st_uid, ID_NS + 7); + EXPECT_EQ(st.st_gid, ID_NS + 7); + + EXPECT_EQ(close(fd), 0); + EXPECT_EQ(close(ovl), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c b/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c index 3c0b93183348..7a293544233d 100644 --- a/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c +++ b/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c @@ -624,7 +624,7 @@ TEST_F(set_layers_via_fds, set_layers_via_detached_mount_fds) ASSERT_EQ(sys_move_mount(fd_tmpfs, "", -EBADF, "/set_layers_via_fds_tmpfs", MOVE_MOUNT_F_EMPTY_PATH), 0); - fd_tmp = open_tree(fd_tmpfs, "u", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + fd_tmp = sys_open_tree(fd_tmpfs, "u", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(fd_tmp, 0); layer_fds[0] = openat(fd_tmp, "upper", O_CLOEXEC | O_DIRECTORY | O_PATH); @@ -633,25 +633,25 @@ TEST_F(set_layers_via_fds, set_layers_via_detached_mount_fds) layer_fds[1] = openat(fd_tmp, "work", O_CLOEXEC | O_DIRECTORY | O_PATH); ASSERT_GE(layer_fds[1], 0); - layer_fds[2] = open_tree(fd_tmpfs, "l1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[2] = sys_open_tree(fd_tmpfs, "l1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[2], 0); - layer_fds[3] = open_tree(fd_tmpfs, "l2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[3] = sys_open_tree(fd_tmpfs, "l2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[3], 0); - layer_fds[4] = open_tree(fd_tmpfs, "l3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[4] = sys_open_tree(fd_tmpfs, "l3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[4], 0); - layer_fds[5] = open_tree(fd_tmpfs, "l4", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[5] = sys_open_tree(fd_tmpfs, "l4", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[5], 0); - layer_fds[6] = open_tree(fd_tmpfs, "d1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[6] = sys_open_tree(fd_tmpfs, "d1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[6], 0); - layer_fds[7] = open_tree(fd_tmpfs, "d2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[7] = sys_open_tree(fd_tmpfs, "d2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[7], 0); - layer_fds[8] = open_tree(fd_tmpfs, "d3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[8] = sys_open_tree(fd_tmpfs, "d3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[8], 0); ASSERT_EQ(close(fd_tmpfs), 0); diff --git a/tools/testing/selftests/filesystems/ustat_test.c b/tools/testing/selftests/filesystems/ustat_test.c new file mode 100644 index 000000000000..d429fd18d779 --- /dev/null +++ b/tools/testing/selftests/filesystems/ustat_test.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test ustat(2): looking up superblocks by device number. + * + * ustat() resolves a device number to a mounted superblock via + * user_get_super(). Check that the device number of a mounted tmpfs (an + * anonymous device) resolves, that it stops resolving once the filesystem + * is unmounted and that bogus device numbers report EINVAL. + */ +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "../kselftest_harness.h" + +/* struct ustat is not exported through UAPI, mirror include/linux/types.h. */ +struct ustat_buf { + int f_tfree; + unsigned long f_tinode; + char f_fname[6]; + char f_fpack[6]; + /* slack in case an architecture lays the struct out differently */ + char pad[64]; +}; + +#ifdef __NR_ustat + +/* + * The kernel decodes @dev with new_decode_dev(), which matches the low 32 + * bits of the st_dev encoding stat(2) returns for any major below 4096. + */ +static int sys_ustat(unsigned int dev, struct ustat_buf *buf) +{ + return syscall(__NR_ustat, dev, buf); +} + +static int write_string(const char *path, const char *string) +{ + ssize_t len = strlen(string); + int fd; + + fd = open(path, O_WRONLY); + if (fd < 0) + return -1; + if (write(fd, string, len) != len) { + close(fd); + return -1; + } + return close(fd); +} + +/* Enter namespaces in which mounting a tmpfs instance is allowed. */ +static int setup_namespaces(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[64]; + + if (unshare(CLONE_NEWNS | (uid ? CLONE_NEWUSER : 0))) + return -1; + + if (uid) { + if (write_string("/proc/self/setgroups", "deny")) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_string("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_string("/proc/self/gid_map", map)) + return -1; + } + + return mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +TEST(resolves_mounted_superblock) +{ + char dir[] = "/tmp/ustat_test.XXXXXX"; + struct ustat_buf ub; + struct stat st; + + ASSERT_NE(NULL, mkdtemp(dir)); + + if (setup_namespaces()) { + rmdir(dir); + SKIP(return, "cannot set up namespaces: %s", strerror(errno)); + } + + ASSERT_EQ(0, mount("ustat_test", dir, "tmpfs", 0, NULL)); + ASSERT_EQ(0, stat(dir, &st)); + + memset(&ub, 0xff, sizeof(ub)); + ASSERT_EQ(0, sys_ustat(st.st_dev, &ub)) + TH_LOG("ustat(%u): %s", (unsigned int)st.st_dev, + strerror(errno)); + + ASSERT_EQ(0, umount(dir)); + + /* The unmount removed the superblock, the device is gone. */ + ASSERT_EQ(-1, sys_ustat(st.st_dev, &ub)); + ASSERT_EQ(EINVAL, errno); + + rmdir(dir); +} + +TEST(bogus_device_numbers) +{ + struct ustat_buf ub; + + ASSERT_EQ(-1, sys_ustat(0, &ub)); + ASSERT_EQ(EINVAL, errno); + + /* major 4095, minor 1048575: nothing plausible lives there */ + ASSERT_EQ(-1, sys_ustat((0xfffu << 8) | 0xffu | (0xfff00u << 12), &ub)); + ASSERT_EQ(EINVAL, errno); +} + +#else /* !__NR_ustat */ + +TEST(unsupported) +{ + SKIP(return, "ustat(2) is not available on this architecture"); +} + +#endif /* __NR_ustat */ + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/proc/proc-pidns.c b/tools/testing/selftests/proc/proc-pidns.c index 25b9a2933c45..6f7c10fe97b3 100644 --- a/tools/testing/selftests/proc/proc-pidns.c +++ b/tools/testing/selftests/proc/proc-pidns.c @@ -6,6 +6,7 @@ #include <assert.h> #include <errno.h> +#include <fcntl.h> #include <sched.h> #include <stdbool.h> #include <stdlib.h> |
