summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-26 21:03:00 +0100
committerMark Brown <broonie@kernel.org>2026-07-26 21:03:00 +0100
commit9162650d34ab9dfe48f73cd255c41a6084669df3 (patch)
tree75578b321f8e890195a7238c296c13498cac58e2 /fs
parent4834b6805ec130608e7416797f779a76d6a11fd7 (diff)
parentce76d9d4f5f36501013bd94bb65dd067f04b2913 (diff)
downloadlinux-next-9162650d34ab9dfe48f73cd255c41a6084669df3.tar.gz
linux-next-9162650d34ab9dfe48f73cd255c41a6084669df3.zip
next-20260723/vfs-brauner
Diffstat (limited to 'fs')
-rw-r--r--fs/9p/vfs_inode.c5
-rw-r--r--fs/9p/vfs_inode_dotl.c4
-rw-r--r--fs/Kconfig1
-rw-r--r--fs/Kconfig.binfmt14
-rw-r--r--fs/Makefile2
-rw-r--r--fs/affs/affs.h2
-rw-r--r--fs/affs/namei.c4
-rw-r--r--fs/affs/super.c2
-rw-r--r--fs/afs/dir.c6
-rw-r--r--fs/autofs/root.c2
-rw-r--r--fs/bad_inode.c2
-rw-r--r--fs/bfs/dir.c2
-rw-r--r--fs/binfmt_misc.c1047
-rw-r--r--fs/binfmt_misc_bpf.c354
-rw-r--r--fs/bpf_fs_kfuncs.c65
-rw-r--r--fs/btrfs/dev-replace.c65
-rw-r--r--fs/btrfs/inode.c4
-rw-r--r--fs/btrfs/ioctl.c4
-rw-r--r--fs/btrfs/volumes.c116
-rw-r--r--fs/btrfs/volumes.h6
-rw-r--r--fs/ceph/dir.c3
-rw-r--r--fs/coda/dir.c9
-rw-r--r--fs/coredump.c11
-rw-r--r--fs/cramfs/inode.c2
-rw-r--r--fs/ecryptfs/inode.c2
-rw-r--r--fs/efivarfs/inode.c2
-rw-r--r--fs/efs/Kconfig16
-rw-r--r--fs/efs/Makefile8
-rw-r--r--fs/efs/dir.c105
-rw-r--r--fs/efs/efs.h144
-rw-r--r--fs/efs/file.c42
-rw-r--r--fs/efs/inode.c315
-rw-r--r--fs/efs/namei.c120
-rw-r--r--fs/efs/super.c368
-rw-r--r--fs/efs/symlink.c50
-rw-r--r--fs/erofs/super.c35
-rw-r--r--fs/exec.c2
-rw-r--r--fs/exfat/namei.c2
-rw-r--r--fs/ext2/namei.c4
-rw-r--r--fs/ext4/extents-test.c9
-rw-r--r--fs/ext4/mballoc-test.c9
-rw-r--r--fs/ext4/namei.c4
-rw-r--r--fs/ext4/super.c12
-rw-r--r--fs/f2fs/namei.c4
-rw-r--r--fs/f2fs/super.c6
-rw-r--r--fs/fat/namei_msdos.c5
-rw-r--r--fs/fat/namei_vfat.c2
-rw-r--r--fs/fs_struct.c103
-rw-r--r--fs/fuse/dir.c10
-rw-r--r--fs/gfs2/inode.c7
-rw-r--r--fs/hfs/dir.c4
-rw-r--r--fs/hfs/super.c2
-rw-r--r--fs/hfsplus/dir.c4
-rw-r--r--fs/hfsplus/super.c2
-rw-r--r--fs/hostfs/hostfs_kern.c2
-rw-r--r--fs/hpfs/namei.c6
-rw-r--r--fs/hugetlbfs/inode.c4
-rw-r--r--fs/inode.c23
-rw-r--r--fs/internal.h1
-rw-r--r--fs/iomap/buffered-io.c6
-rw-r--r--fs/iomap/direct-io.c293
-rw-r--r--fs/iomap/ioend.c21
-rw-r--r--fs/jffs2/dir.c6
-rw-r--r--fs/jffs2/wbuf.c2
-rw-r--r--fs/jfs/namei.c4
-rw-r--r--fs/kernel_read_file.c9
-rw-r--r--fs/minix/namei.c4
-rw-r--r--fs/namei.c30
-rw-r--r--fs/namespace.c25
-rw-r--r--fs/nfs/blocklayout/dev.c15
-rw-r--r--fs/nfs/dir.c6
-rw-r--r--fs/nfs/internal.h2
-rw-r--r--fs/nilfs2/namei.c4
-rw-r--r--fs/ntfs/namei.c4
-rw-r--r--fs/ntfs3/namei.c4
-rw-r--r--fs/nullfs.c14
-rw-r--r--fs/ocfs2/dlmfs/dlmfs.c5
-rw-r--r--fs/ocfs2/namei.c5
-rw-r--r--fs/ocfs2/super.c1
-rw-r--r--fs/omfs/dir.c4
-rw-r--r--fs/orangefs/namei.c5
-rw-r--r--fs/overlayfs/dir.c18
-rw-r--r--fs/overlayfs/inode.c26
-rw-r--r--fs/overlayfs/overlayfs.h1
-rw-r--r--fs/overlayfs/super.c2
-rw-r--r--fs/overlayfs/xattrs.c1
-rw-r--r--fs/posix_acl.c2
-rw-r--r--fs/proc/array.c4
-rw-r--r--fs/proc/base.c8
-rw-r--r--fs/proc_namespace.c4
-rw-r--r--fs/ramfs/inode.c4
-rw-r--r--fs/romfs/super.c10
-rw-r--r--fs/smb/client/cifsfs.h2
-rw-r--r--fs/smb/client/dir.c2
-rw-r--r--fs/smb/client/inode.c7
-rw-r--r--fs/smb/server/mgmt/share_config.c4
-rw-r--r--fs/smb/server/smb2pdu.c4
-rw-r--r--fs/smb/server/vfs.c9
-rw-r--r--fs/super.c639
-rw-r--r--fs/ubifs/dir.c4
-rw-r--r--fs/udf/namei.c4
-rw-r--r--fs/ufs/namei.c5
-rw-r--r--fs/ufs/super.c2
-rw-r--r--fs/vboxsf/dir.c4
-rw-r--r--fs/xfs/xfs_buf.c2
-rw-r--r--fs/xfs/xfs_iops.c7
-rw-r--r--fs/xfs/xfs_super.c10
107 files changed, 2345 insertions, 2075 deletions
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/Kconfig.binfmt b/fs/Kconfig.binfmt
index 1949e25c7741..daeac4889d03 100644
--- a/fs/Kconfig.binfmt
+++ b/fs/Kconfig.binfmt
@@ -168,6 +168,20 @@ config BINFMT_MISC
you have use for it; the module is called binfmt_misc. If you
don't know what to answer at this point, say Y.
+config BINFMT_MISC_BPF
+ bool "BPF-selected interpreters for misc binaries"
+ depends on BINFMT_MISC=y
+ depends on BPF_SYSCALL && BPF_JIT && DEBUG_INFO_BTF
+ help
+ Allow binfmt_misc binary type handlers to be implemented as bpf
+ struct_ops programs. Instead of matching a fixed magic and
+ redirecting to a fixed interpreter recorded at registration time
+ such handlers match binaries programmatically and compute the
+ interpreter to use per binary, e.g. relative to the location of
+ the binary itself.
+
+ If you don't know what to answer at this point, say N.
+
config COREDUMP
bool "Enable core dump support" if EXPERT
default y
diff --git a/fs/Makefile b/fs/Makefile
index 89a8a9d207d1..447a1f7c5732 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_FS_ENCRYPTION) += crypto/
obj-$(CONFIG_FS_VERITY) += verity/
obj-$(CONFIG_FILE_LOCKING) += locks.o
obj-$(CONFIG_BINFMT_MISC) += binfmt_misc.o
+obj-$(CONFIG_BINFMT_MISC_BPF) += binfmt_misc_bpf.o
obj-$(CONFIG_BINFMT_SCRIPT) += binfmt_script.o
obj-$(CONFIG_BINFMT_ELF) += binfmt_elf.o
obj-$(CONFIG_COMPAT_BINFMT_ELF) += compat_binfmt_elf.o
@@ -92,7 +93,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/affs/super.c b/fs/affs/super.c
index b232251aa7bb..4f331f784db2 100644
--- a/fs/affs/super.c
+++ b/fs/affs/super.c
@@ -88,7 +88,7 @@ void affs_mark_sb_dirty(struct super_block *sb)
spin_lock(&sbi->work_lock);
if (!sbi->work_queued) {
delay = msecs_to_jiffies(dirty_writeback_interval * 10);
- queue_delayed_work(system_long_wq, &sbi->sb_work, delay);
+ queue_delayed_work(system_dfl_long_wq, &sbi->sb_work, delay);
sbi->work_queued = 1;
}
spin_unlock(&sbi->work_lock);
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/binfmt_misc.c b/fs/binfmt_misc.c
index 5de615ca7a75..c06cea00a6d6 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -10,45 +10,49 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/hex.h>
-#include <linux/init.h>
-#include <linux/sched/mm.h>
-#include <linux/magic.h>
+#include <linux/binfmt_misc.h>
#include <linux/binfmts.h>
-#include <linux/slab.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/cred.h>
#include <linux/ctype.h>
-#include <linux/string_helpers.h>
#include <linux/file.h>
-#include <linux/pagemap.h>
-#include <linux/namei.h>
-#include <linux/mount.h>
-#include <linux/fs_context.h>
-#include <linux/syscalls.h>
#include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/init.h>
+#include <linux/kstrtox.h>
+#include <linux/magic.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/rculist.h>
+#include <linux/refcount.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/srcu.h>
+#include <linux/string.h>
+#include <linux/string_helpers.h>
#include <linux/uaccess.h>
+#include <linux/user_namespace.h>
-#include "internal.h"
-
-#ifdef DEBUG
-# define USE_DEBUG 1
-#else
-# define USE_DEBUG 0
-#endif
-
-enum {
- VERBOSE_STATUS = 1 /* make it zero to save 400 bytes kernel memory */
+/* Entry status and match type bit numbers. */
+enum binfmt_misc_entry_bits {
+ MISC_FMT_ENABLED_BIT = 0,
+ MISC_FMT_MAGIC_BIT = 1,
+ MISC_FMT_BPF_BIT = 2,
};
-enum {Enabled, Magic};
-#define MISC_FMT_PRESERVE_ARGV0 (1UL << 31)
-#define MISC_FMT_OPEN_BINARY (1UL << 30)
-#define MISC_FMT_CREDENTIALS (1UL << 29)
-#define MISC_FMT_OPEN_FILE (1UL << 28)
+/* Entry behavior flags, fixed at registration time. */
+enum binfmt_misc_entry_flags {
+ MISC_FMT_PRESERVE_ARGV0 = (1U << 31),
+ MISC_FMT_OPEN_BINARY = (1U << 30),
+ MISC_FMT_CREDENTIALS = (1U << 29),
+ MISC_FMT_OPEN_FILE = (1U << 28),
+};
-typedef struct {
- struct list_head list;
+struct binfmt_misc_entry {
+ struct hlist_node node;
unsigned long flags; /* type, status, etc. */
int offset; /* offset of magic */
int size; /* size of magic/mask */
@@ -58,10 +62,12 @@ typedef struct {
char *name;
struct dentry *dentry;
struct file *interp_file;
+ const struct binfmt_misc_ops *bpf_ops; /* bpf-backed handler ('B') */
+ const char *bpf_ops_name;
refcount_t users; /* sync removal with load_misc_binary() */
-} Node;
-
-static struct file_system_type bm_fs_type;
+ struct rcu_head rcu;
+ char buf[]; /* register string, fields point in here */
+};
/*
* Max length of the register string. Determined by:
@@ -74,54 +80,83 @@ static struct file_system_type bm_fs_type;
* - interp: ~50 bytes
* - flags: 5 bytes
* Round that up a bit, and then back off to hold the internal data
- * (like struct Node).
+ * (like struct binfmt_misc_entry).
*/
#define MAX_REGISTER_LENGTH 1920
+/* Trailing delimiter pad so field parsing always terminates at a delimiter. */
+#define MISC_DELIM_PAD 8
+
+/* Protects the entry walk in load_misc_binary(), which may sleep in it. */
+DEFINE_STATIC_SRCU_FAST(bm_entries_srcu);
+
+/* Check if @e's magic matches @bprm's buffer, applying the mask if set. */
+static bool entry_matches_magic(const struct binfmt_misc_entry *e,
+ const struct linux_binprm *bprm)
+{
+ const char *s = bprm->buf + e->offset;
+ int i;
+
+ if (!e->mask)
+ return !memcmp(s, e->magic, e->size);
+
+ for (i = 0; i < e->size; i++)
+ if ((s[i] ^ e->magic[i]) & e->mask[i])
+ return false;
+ return true;
+}
+
+/* Check if @e's registered extension matches @ext, NULL if there is none. */
+static bool entry_matches_extension(const struct binfmt_misc_entry *e,
+ const char *ext)
+{
+ return ext && !strcmp(e->magic, ext);
+}
+
/**
* search_binfmt_handler - search for a binary handler for @bprm
* @misc: handle to binfmt_misc instance
* @bprm: binary for which we are looking for a handler
*
* Search for a binary type handler for @bprm in the list of registered binary
- * type handlers.
+ * type handlers. A 'B' entry's match program decides whether the handler
+ * applies; it may sleep to read the binary. The matched entry is returned
+ * with a reference taken while the walk still held it; a dying entry -
+ * unlinked with its last reference gone - cannot be matched and the walk
+ * moves on.
*
- * Return: binary type list entry on success, NULL on failure
+ * The caller must hold the bm_entries_srcu read lock, which allows an
+ * entry's evaluation to sleep.
+ *
+ * Return: referenced binary type list entry on success, NULL on failure
*/
-static Node *search_binfmt_handler(struct binfmt_misc *misc,
- struct linux_binprm *bprm)
+static struct binfmt_misc_entry *
+search_binfmt_handler(struct binfmt_misc *misc, struct linux_binprm *bprm)
{
- char *p = strrchr(bprm->interp, '.');
- Node *e;
+ char *dot = strrchr(bprm->interp, '.');
+ const char *ext = dot ? dot + 1 : NULL;
+ struct binfmt_misc_entry *e;
/* Walk all the registered handlers. */
- list_for_each_entry(e, &misc->entries, list) {
- char *s;
- int j;
-
+ hlist_for_each_entry_rcu(e, &misc->entries, node,
+ srcu_read_lock_held(&bm_entries_srcu)) {
/* Make sure this one is currently enabled. */
- if (!test_bit(Enabled, &e->flags))
- continue;
-
- /* Do matching based on extension if applicable. */
- if (!test_bit(Magic, &e->flags)) {
- if (p && !strcmp(e->magic, p + 1))
- return e;
+ if (!test_bit(MISC_FMT_ENABLED_BIT, &e->flags))
continue;
- }
- /* Do matching based on magic & mask. */
- s = bprm->buf + e->offset;
- if (e->mask) {
- for (j = 0; j < e->size; j++)
- if ((*s++ ^ e->magic[j]) & e->mask[j])
- break;
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) {
+ if (!e->bpf_ops->match(bprm))
+ continue;
+ } else if (test_bit(MISC_FMT_MAGIC_BIT, &e->flags)) {
+ if (!entry_matches_magic(e, bprm))
+ continue;
} else {
- for (j = 0; j < e->size; j++)
- if ((*s++ ^ e->magic[j]))
- break;
+ if (!entry_matches_extension(e, ext))
+ continue;
}
- if (j == e->size)
+
+ /* A dying entry cannot be matched, walk on. */
+ if (refcount_inc_not_zero(&e->users))
return e;
}
@@ -133,157 +168,244 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc,
* @misc: handle to binfmt_misc instance
* @bprm: binary for which we are looking for a handler
*
- * Try to find a binfmt handler for the binary type. If one is found take a
- * reference to protect against removal via bm_{entry,status}_write().
+ * Try to find a binfmt handler for the binary type. If one is found it is
+ * returned with a reference protecting it against removal via
+ * bm_{entry,status}_write().
*
* Return: binary type list entry on success, NULL on failure
*/
-static Node *get_binfmt_handler(struct binfmt_misc *misc,
- struct linux_binprm *bprm)
+static struct binfmt_misc_entry *get_binfmt_handler(struct binfmt_misc *misc,
+ struct linux_binprm *bprm)
+{
+ guard(srcu_fast)(&bm_entries_srcu);
+ return search_binfmt_handler(misc, bprm);
+}
+
+static void bm_entry_free_rcu(struct rcu_head *rcu)
{
- Node *e;
-
- read_lock(&misc->entries_lock);
- e = search_binfmt_handler(misc, bprm);
- if (e)
- refcount_inc(&e->users);
- read_unlock(&misc->entries_lock);
- return e;
+ struct binfmt_misc_entry *e = container_of(rcu, struct binfmt_misc_entry, rcu);
+
+ /* No walker that could sleep in the handler's programs is left. */
+ if (e->bpf_ops)
+ binfmt_misc_put_ops(e->bpf_ops);
+ kfree(e);
}
/**
- * put_binfmt_handler - put binary handler node
- * @e: node to put
+ * put_binfmt_handler - put binary handler entry
+ * @e: entry to put
*
- * Free node syncing with load_misc_binary() and defer final free to
+ * Free entry syncing with load_misc_binary() and defer final free to
* load_misc_binary() in case it is using the binary type handler we were
* requested to remove.
*/
-static void put_binfmt_handler(Node *e)
+static void put_binfmt_handler(struct binfmt_misc_entry *e)
{
if (refcount_dec_and_test(&e->users)) {
- if (e->flags & MISC_FMT_OPEN_FILE)
+ if (e->flags & MISC_FMT_OPEN_FILE) {
+ exe_file_allow_write_access(e->interp_file);
filp_close(e->interp_file, NULL);
- kfree(e);
+ }
+ /* Walkers may still dereference this entry, even sleeping. */
+ call_srcu(&bm_entries_srcu, &e->rcu, bm_entry_free_rcu);
}
}
+DEFINE_FREE(put_binfmt_handler, struct binfmt_misc_entry *, if (_T) put_binfmt_handler(_T))
+
/**
- * load_binfmt_misc - load the binfmt_misc of the caller's user namespace
+ * current_binfmt_misc - get the binfmt_misc instance of the caller's user namespace
*
- * To be called in load_misc_binary() to load the relevant struct binfmt_misc.
- * If a user namespace doesn't have its own binfmt_misc mount it can make use
- * of its ancestor's binfmt_misc handlers. This mimicks the behavior of
- * pre-namespaced binfmt_misc where all registered binfmt_misc handlers where
- * available to all user and user namespaces on the system.
+ * If a user namespace doesn't have its own binfmt_misc mount it uses the
+ * handlers of its closest ancestor with one. This mimics the behavior of
+ * pre-namespaced binfmt_misc where all registered handlers were available
+ * to all users and user namespaces on the system. The init user namespace
+ * instance is statically set up so the fallback is never reached in
+ * practice.
*
* Return: the binfmt_misc instance of the caller's user namespace
*/
-static struct binfmt_misc *load_binfmt_misc(void)
+static struct binfmt_misc *current_binfmt_misc(void)
{
const struct user_namespace *user_ns;
struct binfmt_misc *misc;
- user_ns = current_user_ns();
- while (user_ns) {
+ for (user_ns = current_user_ns(); user_ns; user_ns = user_ns->parent) {
/* Pairs with smp_store_release() in bm_fill_super(). */
misc = smp_load_acquire(&user_ns->binfmt_misc);
if (misc)
return misc;
-
- user_ns = user_ns->parent;
}
return &init_binfmt_misc;
}
+/**
+ * entry_select_interpreter - get the interpreter for the matched @e
+ * @e: matched binary type handler
+ * @bprm: binary that is being executed
+ *
+ * A static entry carries its interpreter path, for a 'B' entry the
+ * handler's load program selects it. The match is committed, so a failing
+ * program fails the exec.
+ *
+ * Return: the interpreter on success, an ERR_PTR on failure
+ */
+static const char *entry_select_interpreter(const struct binfmt_misc_entry *e,
+ struct linux_binprm *bprm)
+{
+ int retval;
+
+ if (!test_bit(MISC_FMT_BPF_BIT, &e->flags))
+ return e->interpreter;
+
+ /* Drop any interpreter or flags a previous chain level staged. */
+ kfree(bprm->bpf_interp);
+ bprm->bpf_interp = NULL;
+ bprm->bpf_flags = 0;
+
+ retval = e->bpf_ops->load(bprm);
+ if (retval) {
+ /* Keep a program-supplied error within errno range. */
+ if (retval > 0 || retval < -MAX_ERRNO)
+ retval = -ENOEXEC;
+ goto drop_staged;
+ }
+
+ /* Selecting an interpreter is part of the contract. */
+ if (!bprm->bpf_interp) {
+ retval = -ENOEXEC;
+ goto drop_staged;
+ }
+
+ return bprm->bpf_interp;
+
+drop_staged:
+ /* A failing load leaves nothing behind for later entries. */
+ kfree(bprm->bpf_interp_arg);
+ bprm->bpf_interp_arg = NULL;
+ bprm->bpf_flags = 0;
+ return ERR_PTR(retval);
+}
+
/*
* the loader itself
*/
static int load_misc_binary(struct linux_binprm *bprm)
{
- Node *fmt;
- struct file *interp_file = NULL;
- int retval = -ENOEXEC;
+ struct binfmt_misc_entry *fmt __free(put_binfmt_handler) = NULL;
+ const char *interpreter;
+ struct file *interp_file;
struct binfmt_misc *misc;
+ bool preserve_argv0, want_execfd, want_creds;
+ int retval;
- misc = load_binfmt_misc();
- if (!misc->enabled)
- return retval;
+ misc = current_binfmt_misc();
+ if (!READ_ONCE(misc->enabled))
+ return -ENOEXEC;
fmt = get_binfmt_handler(misc, bprm);
if (!fmt)
- return retval;
+ return -ENOEXEC;
/* Need to be able to load the file after exec */
- retval = -ENOENT;
if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE)
- goto ret;
+ return -ENOENT;
+
+ interpreter = entry_select_interpreter(fmt, bprm);
+ if (IS_ERR(interpreter))
+ return PTR_ERR(interpreter);
+
+ /*
+ * The invocation flags are fixed at registration for a static handler
+ * and chosen per exec by the load program, via bpf_binprm_set_flags(),
+ * for a bpf one.
+ */
+ if (test_bit(MISC_FMT_BPF_BIT, &fmt->flags)) {
+ u64 f = bprm->bpf_flags;
- if (fmt->flags & MISC_FMT_PRESERVE_ARGV0) {
+ /* Clear so it can't accumulate into a nested interpreter level. */
+ bprm->bpf_flags = 0;
+
+ preserve_argv0 = f & BPF_BINPRM_PRESERVE_ARGV0;
+ want_creds = f & BPF_BINPRM_CREDENTIALS;
+ want_execfd = f & (BPF_BINPRM_CREDENTIALS | BPF_BINPRM_EXECFD);
+ } else {
+ preserve_argv0 = fmt->flags & MISC_FMT_PRESERVE_ARGV0;
+ want_creds = fmt->flags & MISC_FMT_CREDENTIALS;
+ want_execfd = fmt->flags & MISC_FMT_OPEN_BINARY;
+ }
+
+ /* The entry's own choice - not one accumulated from an earlier level. */
+ if (preserve_argv0) {
bprm->interp_flags |= BINPRM_FLAGS_PRESERVE_ARGV0;
} else {
retval = remove_arg_zero(bprm);
if (retval)
- goto ret;
+ return retval;
}
- /* make argv[1] be the path to the binary */
+ /* make the binary the last argument to the interpreter */
retval = copy_string_kernel(bprm->interp, bprm);
if (retval < 0)
- goto ret;
+ return retval;
bprm->argc++;
+ /*
+ * A single optional argument to the interpreter, inserted between it
+ * and the binary just like the argument of a #! interpreter line.
+ */
+ if (bprm->bpf_interp_arg) {
+ retval = copy_string_kernel(bprm->bpf_interp_arg, bprm);
+ if (retval < 0)
+ return retval;
+ bprm->argc++;
+ /* Consumed - don't let it leak into a nested interpreter's argv. */
+ kfree(bprm->bpf_interp_arg);
+ bprm->bpf_interp_arg = NULL;
+ }
+
/* add the interp as argv[0] */
- retval = copy_string_kernel(fmt->interpreter, bprm);
+ retval = copy_string_kernel(interpreter, bprm);
if (retval < 0)
- goto ret;
+ return retval;
bprm->argc++;
/* Update interp in case binfmt_script needs it. */
- retval = bprm_change_interp(fmt->interpreter, bprm);
+ retval = bprm_change_interp(interpreter, bprm);
if (retval < 0)
- goto ret;
+ return retval;
if (fmt->flags & MISC_FMT_OPEN_FILE) {
interp_file = file_clone_open(fmt->interp_file);
- if (!IS_ERR(interp_file))
- deny_write_access(interp_file);
+ if (!IS_ERR(interp_file)) {
+ int err = exe_file_deny_write_access(interp_file);
+
+ if (err) {
+ fput(interp_file);
+ interp_file = ERR_PTR(err);
+ }
+ }
} else {
- interp_file = open_exec(fmt->interpreter);
+ interp_file = open_exec(interpreter);
}
- retval = PTR_ERR(interp_file);
if (IS_ERR(interp_file))
- goto ret;
+ return PTR_ERR(interp_file);
bprm->interpreter = interp_file;
- if (fmt->flags & MISC_FMT_OPEN_BINARY)
+ if (want_execfd)
bprm->have_execfd = 1;
- if (fmt->flags & MISC_FMT_CREDENTIALS)
+ if (want_creds)
bprm->execfd_creds = 1;
-
- retval = 0;
-ret:
-
- /*
- * If we actually put the node here all concurrent calls to
- * load_misc_binary() will have finished. We also know
- * that for the refcount to be zero someone must have concurently
- * removed the binary type handler from the list and it's our job to
- * free it.
- */
- put_binfmt_handler(fmt);
-
- return retval;
+ return 0;
}
/* Command parsers */
/*
- * parses and copies one argument enclosed in del from *sp to *dp,
- * recognising the \x special.
- * returns pointer to the copied argument or NULL in case of an
- * error (and sets err) or null argument length.
+ * Scan the argument starting at @s up to the delimiter @del, recognising
+ * the \x escape. Terminates the argument with a NUL and returns a pointer
+ * past it or NULL on a malformed escape.
*/
static char *scanarg(char *s, char del)
{
@@ -298,45 +420,143 @@ static char *scanarg(char *s, char del)
return NULL;
}
}
- s[-1] ='\0';
+ s[-1] = '\0';
return s;
}
-static char *check_special_flags(char *sfs, Node *e)
+static char *check_special_flags(char *p, struct binfmt_misc_entry *e)
{
- char *p = sfs;
- int cont = 1;
-
- /* special flags */
- while (cont) {
+ for (;; p++) {
switch (*p) {
case 'P':
pr_debug("register: flag: P (preserve argv0)\n");
- p++;
e->flags |= MISC_FMT_PRESERVE_ARGV0;
break;
case 'O':
pr_debug("register: flag: O (open binary)\n");
- p++;
e->flags |= MISC_FMT_OPEN_BINARY;
break;
case 'C':
pr_debug("register: flag: C (preserve creds)\n");
- p++;
- /* this flags also implies the
- open-binary flag */
- e->flags |= (MISC_FMT_CREDENTIALS |
- MISC_FMT_OPEN_BINARY);
+ /* C implies O */
+ e->flags |= MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_BINARY;
break;
case 'F':
pr_debug("register: flag: F: open interpreter file now\n");
- p++;
e->flags |= MISC_FMT_OPEN_FILE;
break;
default:
- cont = 0;
+ return p;
}
}
+}
+
+/* Parse the 'offset', 'magic' and 'mask' fields of an 'M' entry. */
+static char *parse_magic_fields(struct binfmt_misc_entry *e, char *p, char del)
+{
+ char *s;
+
+ /* Parse the 'offset' field. */
+ s = strchr(p, del);
+ if (!s)
+ return NULL;
+ *s = '\0';
+ if (p != s) {
+ if (kstrtoint(p, 10, &e->offset) || e->offset < 0)
+ return NULL;
+ }
+ p = s + 1;
+ pr_debug("register: offset: %#x\n", e->offset);
+
+ /* Parse the 'magic' field. */
+ e->magic = p;
+ p = scanarg(p, del);
+ if (!p || !e->magic[0])
+ return NULL;
+ print_hex_dump_debug(
+ KBUILD_MODNAME ": register: magic[raw]: ",
+ DUMP_PREFIX_NONE, 16, 1, e->magic, p - e->magic, true);
+
+ /* Parse the 'mask' field. */
+ e->mask = p;
+ p = scanarg(p, del);
+ if (!p)
+ return NULL;
+ if (!e->mask[0]) {
+ e->mask = NULL;
+ pr_debug("register: mask[raw]: none\n");
+ } else {
+ print_hex_dump_debug(
+ KBUILD_MODNAME ": register: mask[raw]: ",
+ DUMP_PREFIX_NONE, 16, 1, e->mask, p - e->mask, true);
+ }
+
+ /*
+ * Decode the magic & mask fields. Note: while we might have accepted
+ * embedded NUL bytes from above, the unescape helpers will stop at
+ * the first one they encounter.
+ */
+ e->size = string_unescape_inplace(e->magic, UNESCAPE_HEX);
+ if (e->mask && string_unescape_inplace(e->mask, UNESCAPE_HEX) != e->size)
+ return NULL;
+ if (e->size > BINPRM_BUF_SIZE || BINPRM_BUF_SIZE - e->size < e->offset)
+ return NULL;
+ pr_debug("register: magic/mask length: %i\n", e->size);
+ print_hex_dump_debug(
+ KBUILD_MODNAME ": register: magic[decoded]: ",
+ DUMP_PREFIX_NONE, 16, 1, e->magic, e->size, true);
+ if (e->mask)
+ print_hex_dump_debug(
+ KBUILD_MODNAME ": register: mask[decoded]: ",
+ DUMP_PREFIX_NONE, 16, 1, e->mask, e->size, true);
+ return p;
+}
+
+/* Parse the 'magic' field of an 'E' entry: the filename extension. */
+static char *parse_extension_fields(struct binfmt_misc_entry *e, char *p,
+ char del)
+{
+ /* Skip the 'offset' field. */
+ p = strchr(p, del);
+ if (!p)
+ return NULL;
+ *p++ = '\0';
+
+ /* Parse the 'magic' field. */
+ e->magic = p;
+ p = strchr(p, del);
+ if (!p)
+ return NULL;
+ *p++ = '\0';
+ if (!e->magic[0] || strchr(e->magic, '/'))
+ return NULL;
+ pr_debug("register: extension: {%s}\n", e->magic);
+
+ /* Skip the 'mask' field. */
+ p = strchr(p, del);
+ if (!p)
+ return NULL;
+ *p++ = '\0';
+ return p;
+}
+
+/*
+ * Parse the fields of a 'B' entry: the 'offset', 'magic' and 'mask' fields
+ * must be empty. The handler name is carried in the 'interpreter' field.
+ */
+static char *parse_bpf_fields(struct binfmt_misc_entry *e, char *p, char del)
+{
+ /* The 'offset' field must be empty. */
+ if (*p++ != del)
+ return NULL;
+
+ /* The 'magic' field must be empty. */
+ if (*p++ != del)
+ return NULL;
+
+ /* The 'mask' field must be empty. */
+ if (*p++ != del)
+ return NULL;
return p;
}
@@ -346,50 +566,52 @@ static char *check_special_flags(char *sfs, Node *e)
* ':name:type:offset:magic:mask:interpreter:flags'
* where the ':' is the IFS, that can be chosen with the first char
*/
-static Node *create_entry(const char __user *buffer, size_t count)
+static struct binfmt_misc_entry *create_entry(const char __user *buffer,
+ size_t count)
{
- Node *e;
- int memsize, err;
+ struct binfmt_misc_entry *e __free(kfree) = NULL;
char *buf, *p;
char del;
pr_debug("register: received %zu bytes\n", count);
/* some sanity checks */
- err = -EINVAL;
if ((count < 11) || (count > MAX_REGISTER_LENGTH))
- goto out;
+ return ERR_PTR(-EINVAL);
- err = -ENOMEM;
- memsize = sizeof(Node) + count + 8;
- e = kmalloc(memsize, GFP_KERNEL_ACCOUNT);
+ e = kmalloc(struct_size(e, buf, count + MISC_DELIM_PAD),
+ GFP_KERNEL_ACCOUNT);
if (!e)
- goto out;
+ return ERR_PTR(-ENOMEM);
- p = buf = (char *)e + sizeof(Node);
+ p = buf = e->buf;
- memset(e, 0, sizeof(Node));
+ memset(e, 0, sizeof(*e));
if (copy_from_user(buf, buffer, count))
- goto efault;
+ return ERR_PTR(-EFAULT);
- del = *p++; /* delimeter */
+ del = *p++; /* delimiter */
pr_debug("register: delim: %#x {%c}\n", del, del);
+ /* A flag-char delimiter runs the flag scan off the buffer. */
+ if (del == 'P' || del == 'O' || del == 'C' || del == 'F')
+ return ERR_PTR(-EINVAL);
+
/* Pad the buffer with the delim to simplify parsing below. */
- memset(buf + count, del, 8);
+ memset(buf + count, del, MISC_DELIM_PAD);
/* Parse the 'name' field. */
e->name = p;
p = strchr(p, del);
if (!p)
- goto einval;
+ return ERR_PTR(-EINVAL);
*p++ = '\0';
if (!e->name[0] ||
!strcmp(e->name, ".") ||
!strcmp(e->name, "..") ||
strchr(e->name, '/'))
- goto einval;
+ return ERR_PTR(-EINVAL);
pr_debug("register: name: {%s}\n", e->name);
@@ -397,159 +619,84 @@ static Node *create_entry(const char __user *buffer, size_t count)
switch (*p++) {
case 'E':
pr_debug("register: type: E (extension)\n");
- e->flags = 1 << Enabled;
+ e->flags = BIT(MISC_FMT_ENABLED_BIT);
break;
case 'M':
pr_debug("register: type: M (magic)\n");
- e->flags = (1 << Enabled) | (1 << Magic);
+ e->flags = BIT(MISC_FMT_ENABLED_BIT) | BIT(MISC_FMT_MAGIC_BIT);
+ break;
+ case 'B':
+ pr_debug("register: type: B (bpf)\n");
+ if (!IS_ENABLED(CONFIG_BINFMT_MISC_BPF))
+ return ERR_PTR(-EINVAL);
+ e->flags = BIT(MISC_FMT_ENABLED_BIT) | BIT(MISC_FMT_BPF_BIT);
break;
default:
- goto einval;
+ return ERR_PTR(-EINVAL);
}
if (*p++ != del)
- goto einval;
-
- if (test_bit(Magic, &e->flags)) {
- /* Handle the 'M' (magic) format. */
- char *s;
-
- /* Parse the 'offset' field. */
- s = strchr(p, del);
- if (!s)
- goto einval;
- *s = '\0';
- if (p != s) {
- int r = kstrtoint(p, 10, &e->offset);
- if (r != 0 || e->offset < 0)
- goto einval;
- }
- p = s;
- if (*p++)
- goto einval;
- pr_debug("register: offset: %#x\n", e->offset);
-
- /* Parse the 'magic' field. */
- e->magic = p;
- p = scanarg(p, del);
- if (!p)
- goto einval;
- if (!e->magic[0])
- goto einval;
- if (USE_DEBUG)
- print_hex_dump_bytes(
- KBUILD_MODNAME ": register: magic[raw]: ",
- DUMP_PREFIX_NONE, e->magic, p - e->magic);
-
- /* Parse the 'mask' field. */
- e->mask = p;
- p = scanarg(p, del);
- if (!p)
- goto einval;
- if (!e->mask[0]) {
- e->mask = NULL;
- pr_debug("register: mask[raw]: none\n");
- } else if (USE_DEBUG)
- print_hex_dump_bytes(
- KBUILD_MODNAME ": register: mask[raw]: ",
- DUMP_PREFIX_NONE, e->mask, p - e->mask);
-
- /*
- * Decode the magic & mask fields.
- * Note: while we might have accepted embedded NUL bytes from
- * above, the unescape helpers here will stop at the first one
- * it encounters.
- */
- e->size = string_unescape_inplace(e->magic, UNESCAPE_HEX);
- if (e->mask &&
- string_unescape_inplace(e->mask, UNESCAPE_HEX) != e->size)
- goto einval;
- if (e->size > BINPRM_BUF_SIZE ||
- BINPRM_BUF_SIZE - e->size < e->offset)
- goto einval;
- pr_debug("register: magic/mask length: %i\n", e->size);
- if (USE_DEBUG) {
- print_hex_dump_bytes(
- KBUILD_MODNAME ": register: magic[decoded]: ",
- DUMP_PREFIX_NONE, e->magic, e->size);
-
- if (e->mask) {
- int i;
- char *masked = kmalloc(e->size, GFP_KERNEL_ACCOUNT);
-
- print_hex_dump_bytes(
- KBUILD_MODNAME ": register: mask[decoded]: ",
- DUMP_PREFIX_NONE, e->mask, e->size);
-
- if (masked) {
- for (i = 0; i < e->size; ++i)
- masked[i] = e->magic[i] & e->mask[i];
- print_hex_dump_bytes(
- KBUILD_MODNAME ": register: magic[masked]: ",
- DUMP_PREFIX_NONE, masked, e->size);
-
- kfree(masked);
- }
- }
- }
- } else {
- /* Handle the 'E' (extension) format. */
-
- /* Skip the 'offset' field. */
- p = strchr(p, del);
- if (!p)
- goto einval;
- *p++ = '\0';
-
- /* Parse the 'magic' field. */
- e->magic = p;
- p = strchr(p, del);
- if (!p)
- goto einval;
- *p++ = '\0';
- if (!e->magic[0] || strchr(e->magic, '/'))
- goto einval;
- pr_debug("register: extension: {%s}\n", e->magic);
-
- /* Skip the 'mask' field. */
- p = strchr(p, del);
- if (!p)
- goto einval;
- *p++ = '\0';
- }
+ return ERR_PTR(-EINVAL);
+
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags))
+ p = parse_bpf_fields(e, p, del);
+ else if (test_bit(MISC_FMT_MAGIC_BIT, &e->flags))
+ p = parse_magic_fields(e, p, del);
+ else
+ p = parse_extension_fields(e, p, del);
+ if (!p)
+ return ERR_PTR(-EINVAL);
/* Parse the 'interpreter' field. */
e->interpreter = p;
p = strchr(p, del);
if (!p)
- goto einval;
+ return ERR_PTR(-EINVAL);
*p++ = '\0';
- if (!e->interpreter[0])
- goto einval;
- pr_debug("register: interpreter: {%s}\n", e->interpreter);
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) {
+ /* The 'interpreter' field carries the handler name. */
+ e->bpf_ops_name = e->interpreter;
+ e->interpreter = NULL;
+ if (!e->bpf_ops_name[0])
+ return ERR_PTR(-EINVAL);
+ pr_debug("register: bpf handler: {%s}\n", e->bpf_ops_name);
+ } else if (!e->interpreter[0]) {
+ return ERR_PTR(-EINVAL);
+ } else {
+ pr_debug("register: interpreter: {%s}\n", e->interpreter);
+ }
/* Parse the 'flags' field. */
p = check_special_flags(p, e);
if (*p == '\n')
p++;
if (p != buf + count)
- goto einval;
-
- return e;
+ return ERR_PTR(-EINVAL);
-out:
- return ERR_PTR(err);
+ /*
+ * A bpf handler decides the invocation flags per exec with
+ * bpf_binprm_set_flags() rather than fixing them at registration, so a
+ * 'B' entry carries no flags: 'P', 'C' and 'O' become per-exec choices
+ * and 'F' (pre-open a fixed interpreter) is meaningless for it.
+ */
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags) &&
+ (e->flags & (MISC_FMT_PRESERVE_ARGV0 | MISC_FMT_OPEN_BINARY |
+ MISC_FMT_CREDENTIALS | MISC_FMT_OPEN_FILE)))
+ return ERR_PTR(-EINVAL);
-efault:
- kfree(e);
- return ERR_PTR(-EFAULT);
-einval:
- kfree(e);
- return ERR_PTR(-EINVAL);
+ return no_free_ptr(e);
}
+/* Commands accepted by the /status and /<entry> files. */
+enum bm_command {
+ BM_CMD_IGNORE, /* empty write */
+ BM_CMD_DISABLE, /* "0" */
+ BM_CMD_ENABLE, /* "1" */
+ BM_CMD_REMOVE, /* "-1" */
+};
+
/*
- * Set status of entry/binfmt_misc:
- * '1' enables, '0' disables and '-1' clears entry/binfmt_misc
+ * Parse what userspace wrote to /status or an entry file: '1' enables,
+ * '0' disables and '-1' removes the entry or all entries.
*/
static int parse_command(const char __user *buffer, size_t count)
{
@@ -560,62 +707,69 @@ static int parse_command(const char __user *buffer, size_t count)
if (copy_from_user(s, buffer, count))
return -EFAULT;
if (!count)
- return 0;
+ return BM_CMD_IGNORE;
if (s[count - 1] == '\n')
count--;
if (count == 1 && s[0] == '0')
- return 1;
+ return BM_CMD_DISABLE;
if (count == 1 && s[0] == '1')
- return 2;
+ return BM_CMD_ENABLE;
if (count == 2 && s[0] == '-' && s[1] == '1')
- return 3;
+ return BM_CMD_REMOVE;
return -EINVAL;
}
/* generic stuff */
-static void entry_status(Node *e, char *page)
+static void bm_seq_hex(struct seq_file *m, const u8 *data, int size)
{
- char *dp = page;
- const char *status = "disabled";
+ for (int i = 0; i < size; i++)
+ seq_printf(m, "%02x", data[i]);
+}
- if (test_bit(Enabled, &e->flags))
- status = "enabled";
+static int bm_entry_show(struct seq_file *m, void *unused)
+{
+ struct binfmt_misc_entry *e = m->private;
- if (!VERBOSE_STATUS) {
- sprintf(page, "%s\n", status);
- return;
- }
+ if (test_bit(MISC_FMT_ENABLED_BIT, &e->flags))
+ seq_puts(m, "enabled\n");
+ else
+ seq_puts(m, "disabled\n");
- dp += sprintf(dp, "%s\ninterpreter %s\n", status, e->interpreter);
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags))
+ seq_printf(m, "bpf %s\n", e->bpf_ops->name);
+ else
+ seq_printf(m, "interpreter %s\n", e->interpreter);
/* print the special flags */
- dp += sprintf(dp, "flags: ");
+ seq_puts(m, "flags: ");
if (e->flags & MISC_FMT_PRESERVE_ARGV0)
- *dp++ = 'P';
+ seq_putc(m, 'P');
if (e->flags & MISC_FMT_OPEN_BINARY)
- *dp++ = 'O';
+ seq_putc(m, 'O');
if (e->flags & MISC_FMT_CREDENTIALS)
- *dp++ = 'C';
+ seq_putc(m, 'C');
if (e->flags & MISC_FMT_OPEN_FILE)
- *dp++ = 'F';
- *dp++ = '\n';
+ seq_putc(m, 'F');
+ seq_putc(m, '\n');
- if (!test_bit(Magic, &e->flags)) {
- sprintf(dp, "extension .%s\n", e->magic);
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) {
+ /* The program does the matching. */
+ } else if (!test_bit(MISC_FMT_MAGIC_BIT, &e->flags)) {
+ seq_printf(m, "extension .%s\n", e->magic);
} else {
- dp += sprintf(dp, "offset %i\nmagic ", e->offset);
- dp = bin2hex(dp, e->magic, e->size);
+ seq_printf(m, "offset %i\nmagic ", e->offset);
+ bm_seq_hex(m, e->magic, e->size);
if (e->mask) {
- dp += sprintf(dp, "\nmask ");
- dp = bin2hex(dp, e->mask, e->size);
+ seq_puts(m, "\nmask ");
+ bm_seq_hex(m, e->mask, e->size);
}
- *dp++ = '\n';
- *dp = '\0';
+ seq_putc(m, '\n');
}
+ return 0;
}
-static struct inode *bm_get_inode(struct super_block *sb, int mode)
+static struct inode *bm_get_inode(struct super_block *sb, umode_t mode)
{
struct inode *inode = new_inode(sb);
@@ -651,14 +805,14 @@ static struct binfmt_misc *i_binfmt_misc(struct inode *inode)
* entry is removed or the filesystem is unmounted and the super block is
* shutdown.
*
- * If the ->evict call was not caused by a super block shutdown but by a write
- * to remove the entry or all entries via bm_{entry,status}_write() the entry
- * will have already been removed from the list. We keep the list_empty() check
- * to make that explicit.
+ * If the ->evict call was not caused by a super block shutdown but by
+ * removing the entry via bm_{entry,status}_write() or unlink(2) the entry
+ * will have already been removed from the list. We keep the hlist_unhashed()
+ * check to make that explicit.
*/
static void bm_evict_inode(struct inode *inode)
{
- Node *e = inode->i_private;
+ struct binfmt_misc_entry *e = inode->i_private;
clear_inode(inode);
@@ -666,89 +820,132 @@ static void bm_evict_inode(struct inode *inode)
struct binfmt_misc *misc;
misc = i_binfmt_misc(inode);
- write_lock(&misc->entries_lock);
- if (!list_empty(&e->list))
- list_del_init(&e->list);
- write_unlock(&misc->entries_lock);
+ spin_lock(&misc->entries_lock);
+ if (!hlist_unhashed(&e->node))
+ hlist_del_init_rcu(&e->node);
+ spin_unlock(&misc->entries_lock);
put_binfmt_handler(e);
}
}
/**
+ * unlink_binfmt_handler - unhash a binary type handler
+ * @misc: handle to binfmt_misc instance
+ * @e: binary type handler to unhash
+ *
+ * Adding and removing entries via bm_{entry,register,status}_write() and
+ * unlink(2) happens under the exclusively held inode lock of the root
+ * dentry keeping the list stable for writers. load_misc_binary() walks it
+ * concurrently under SRCU. The entries_lock is only held around the actual
+ * unlink to serialize against bm_evict_inode() which unlinks entries
+ * during umount without holding the root inode lock.
+ */
+static void unlink_binfmt_handler(struct binfmt_misc *misc,
+ struct binfmt_misc_entry *e)
+{
+ spin_lock(&misc->entries_lock);
+ hlist_del_init_rcu(&e->node);
+ spin_unlock(&misc->entries_lock);
+}
+
+/**
* remove_binfmt_handler - remove a binary type handler
* @misc: handle to binfmt_misc instance
* @e: binary type handler to remove
*
* Remove a binary type handler from the list of binary type handlers and
- * remove its associated dentry. This is called from
- * binfmt_{entry,status}_write(). In the future, we might want to think about
- * adding a proper ->unlink() method to binfmt_misc instead of forcing caller's
- * to use writes to files in order to delete binary type handlers. But it has
- * worked for so long that it's not a pressing issue.
+ * remove its associated dentry.
*/
-static void remove_binfmt_handler(struct binfmt_misc *misc, Node *e)
+static void remove_binfmt_handler(struct binfmt_misc *misc,
+ struct binfmt_misc_entry *e)
{
- write_lock(&misc->entries_lock);
- list_del_init(&e->list);
- write_unlock(&misc->entries_lock);
+ unlink_binfmt_handler(misc, e);
locked_recursive_removal(e->dentry, NULL);
}
-/* /<entry> */
+/* Remove @e unless it was already removed. */
+static void bm_remove_entry(struct binfmt_misc_entry *e, struct super_block *sb)
+{
+ struct inode *root = d_inode(sb->s_root);
-static ssize_t
-bm_entry_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
+ inode_lock_nested(root, I_MUTEX_PARENT);
+ if (!hlist_unhashed(&e->node))
+ remove_binfmt_handler(i_binfmt_misc(root), e);
+ inode_unlock(root);
+}
+
+/* Remove all entries of the binfmt_misc instance @misc belonging to @sb. */
+static void bm_remove_all_entries(struct binfmt_misc *misc,
+ struct super_block *sb)
{
- Node *e = file_inode(file)->i_private;
- ssize_t res;
- char *page;
+ struct inode *root = d_inode(sb->s_root);
+ struct binfmt_misc_entry *e;
+ struct hlist_node *next;
+
+ inode_lock_nested(root, I_MUTEX_PARENT);
+ hlist_for_each_entry_safe(e, next, &misc->entries, node)
+ remove_binfmt_handler(misc, e);
+ inode_unlock(root);
+}
- page = kmalloc(PAGE_SIZE, GFP_KERNEL);
- if (!page)
- return -ENOMEM;
+/**
+ * bm_unlink - remove a binary type handler via unlink(2)
+ * @dir: inode of the root directory
+ * @dentry: entry file to remove
+ *
+ * Removing the entry file removes its binary type handler, exactly like
+ * writing -1 to it does. The status and register control files can't be
+ * removed. The VFS calls this with the root inode lock held which
+ * serializes against the write based add and remove paths.
+ */
+static int bm_unlink(struct inode *dir, struct dentry *dentry)
+{
+ struct binfmt_misc_entry *e = d_inode(dentry)->i_private;
+
+ if (!e)
+ return -EPERM;
+
+ unlink_binfmt_handler(i_binfmt_misc(dir), e);
+ return simple_unlink(dir, dentry);
+}
+
+static const struct inode_operations bm_dir_inode_operations = {
+ .lookup = simple_lookup,
+ .unlink = bm_unlink,
+};
+
+/* /<entry> */
- entry_status(e, page);
+static int bm_entry_open(struct inode *inode, struct file *file)
+{
+ int ret;
- res = simple_read_from_buffer(buf, nbytes, ppos, page, strlen(page));
+ ret = single_open(file, bm_entry_show, inode->i_private);
+ if (ret)
+ return ret;
- kfree(page);
- return res;
+ /* seq_open() clears FMODE_PWRITE, bm_entry_write() takes any offset */
+ if (file->f_mode & FMODE_WRITE)
+ file->f_mode |= FMODE_PWRITE;
+ return 0;
}
static ssize_t bm_entry_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct inode *inode = file_inode(file);
- Node *e = inode->i_private;
+ struct binfmt_misc_entry *e = inode->i_private;
int res = parse_command(buffer, count);
switch (res) {
- case 1:
- /* Disable this handler. */
- clear_bit(Enabled, &e->flags);
+ case BM_CMD_DISABLE:
+ clear_bit(MISC_FMT_ENABLED_BIT, &e->flags);
break;
- case 2:
- /* Enable this handler. */
- set_bit(Enabled, &e->flags);
+ case BM_CMD_ENABLE:
+ set_bit(MISC_FMT_ENABLED_BIT, &e->flags);
break;
- case 3:
- /* Delete this handler. */
- inode = d_inode(inode->i_sb->s_root);
- inode_lock_nested(inode, I_MUTEX_PARENT);
-
- /*
- * In order to add new element or remove elements from the list
- * via bm_{entry,register,status}_write() inode_lock() on the
- * root inode must be held.
- * The lock is exclusive ensuring that the list can't be
- * modified. Only load_misc_binary() can access but does so
- * read-only. So we only need to take the write lock when we
- * actually remove the entry from the list.
- */
- if (!list_empty(&e->list))
- remove_binfmt_handler(i_binfmt_misc(inode), e);
-
- inode_unlock(inode);
+ case BM_CMD_REMOVE:
+ bm_remove_entry(e, inode->i_sb);
break;
default:
return res;
@@ -758,15 +955,17 @@ static ssize_t bm_entry_write(struct file *file, const char __user *buffer,
}
static const struct file_operations bm_entry_operations = {
- .read = bm_entry_read,
+ .open = bm_entry_open,
+ .read = seq_read,
.write = bm_entry_write,
- .llseek = default_llseek,
+ .llseek = seq_lseek,
+ .release = single_release,
};
/* /register */
/* add to filesystem */
-static int add_entry(Node *e, struct super_block *sb)
+static int add_entry(struct binfmt_misc_entry *e, struct super_block *sb)
{
struct dentry *dentry = simple_start_creating(sb->s_root, e->name);
struct inode *inode;
@@ -788,9 +987,9 @@ static int add_entry(Node *e, struct super_block *sb)
d_make_persistent(dentry, inode);
misc = i_binfmt_misc(inode);
- write_lock(&misc->entries_lock);
- list_add(&e->list, &misc->entries);
- write_unlock(&misc->entries_lock);
+ spin_lock(&misc->entries_lock);
+ hlist_add_head_rcu(&e->node, &misc->entries);
+ spin_unlock(&misc->entries_lock);
simple_done_creating(dentry);
return 0;
}
@@ -798,16 +997,24 @@ static int add_entry(Node *e, struct super_block *sb)
static ssize_t bm_register_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
- Node *e;
+ struct binfmt_misc_entry *e __free(kfree) = NULL;
struct super_block *sb = file_inode(file)->i_sb;
- int err = 0;
struct file *f = NULL;
+ int err;
e = create_entry(buffer, count);
-
if (IS_ERR(e))
return PTR_ERR(e);
+ if (test_bit(MISC_FMT_BPF_BIT, &e->flags)) {
+ e->bpf_ops = binfmt_misc_get_ops(sb->s_user_ns, e->bpf_ops_name);
+ if (!e->bpf_ops) {
+ pr_notice("register: no bpf handler named %s\n",
+ e->bpf_ops_name);
+ return -ENOENT;
+ }
+ }
+
if (e->flags & MISC_FMT_OPEN_FILE) {
/*
* Now that we support unprivileged binfmt_misc mounts make
@@ -821,7 +1028,6 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,
if (IS_ERR(f)) {
pr_notice("register: failed to install interpreter file %s\n",
e->interpreter);
- kfree(e);
return PTR_ERR(f);
}
e->interp_file = f;
@@ -833,9 +1039,13 @@ static ssize_t bm_register_write(struct file *file, const char __user *buffer,
exe_file_allow_write_access(f);
filp_close(f, NULL);
}
- kfree(e);
+ if (e->bpf_ops)
+ binfmt_misc_put_ops(e->bpf_ops);
return err;
}
+
+ /* The entry is owned by its inode now. */
+ retain_and_null_ptr(e);
return count;
}
@@ -850,10 +1060,10 @@ static ssize_t
bm_status_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
struct binfmt_misc *misc;
- char *s;
+ const char *s;
misc = i_binfmt_misc(file_inode(file));
- s = misc->enabled ? "enabled\n" : "disabled\n";
+ s = READ_ONCE(misc->enabled) ? "enabled\n" : "disabled\n";
return simple_read_from_buffer(buf, nbytes, ppos, s, strlen(s));
}
@@ -862,37 +1072,17 @@ static ssize_t bm_status_write(struct file *file, const char __user *buffer,
{
struct binfmt_misc *misc;
int res = parse_command(buffer, count);
- Node *e, *next;
- struct inode *inode;
misc = i_binfmt_misc(file_inode(file));
switch (res) {
- case 1:
- /* Disable all handlers. */
- misc->enabled = false;
+ case BM_CMD_DISABLE:
+ WRITE_ONCE(misc->enabled, false);
break;
- case 2:
- /* Enable all handlers. */
- misc->enabled = true;
+ case BM_CMD_ENABLE:
+ WRITE_ONCE(misc->enabled, true);
break;
- case 3:
- /* Delete all handlers. */
- inode = d_inode(file_inode(file)->i_sb->s_root);
- inode_lock_nested(inode, I_MUTEX_PARENT);
-
- /*
- * In order to add new element or remove elements from the list
- * via bm_{entry,register,status}_write() inode_lock() on the
- * root inode must be held.
- * The lock is exclusive ensuring that the list can't be
- * modified. Only load_misc_binary() can access but does so
- * read-only. So we only need to take the write lock when we
- * actually remove the entry from the list.
- */
- list_for_each_entry_safe(e, next, &misc->entries, list)
- remove_binfmt_handler(misc, e);
-
- inode_unlock(inode);
+ case BM_CMD_REMOVE:
+ bm_remove_all_entries(misc, file_inode(file)->i_sb);
break;
default:
return res;
@@ -917,7 +1107,7 @@ static void bm_put_super(struct super_block *sb)
put_user_ns(user_ns);
}
-static const struct super_operations s_ops = {
+static const struct super_operations bm_super_ops = {
.statfs = simple_statfs,
.evict_inode = bm_evict_inode,
.put_super = bm_put_super,
@@ -964,10 +1154,10 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
if (!misc)
return -ENOMEM;
- INIT_LIST_HEAD(&misc->entries);
- rwlock_init(&misc->entries_lock);
+ INIT_HLIST_HEAD(&misc->entries);
+ spin_lock_init(&misc->entries_lock);
- /* Pairs with smp_load_acquire() in load_binfmt_misc(). */
+ /* Pairs with smp_load_acquire() in current_binfmt_misc(). */
smp_store_release(&user_ns->binfmt_misc, misc);
}
@@ -982,12 +1172,15 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
* someone mounts binfmt_misc for the first time or again we simply
* reset ->enabled to true.
*/
- misc->enabled = true;
+ WRITE_ONCE(misc->enabled, true);
err = simple_fill_super(sb, BINFMTFS_MAGIC, bm_files);
- if (!err)
- sb->s_op = &s_ops;
- return err;
+ if (err)
+ return err;
+
+ sb->s_op = &bm_super_ops;
+ d_inode(sb->s_root)->i_op = &bm_dir_inode_operations;
+ return 0;
}
static void bm_free(struct fs_context *fc)
@@ -1038,6 +1231,8 @@ static void __exit exit_misc_binfmt(void)
{
unregister_binfmt(&misc_format);
unregister_filesystem(&bm_fs_type);
+ /* Flush pending bm_entry_free_rcu() callbacks before the text goes. */
+ srcu_barrier(&bm_entries_srcu);
}
core_initcall(init_misc_binfmt);
diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c
new file mode 100644
index 000000000000..65a3b8313fbe
--- /dev/null
+++ b/fs/binfmt_misc_bpf.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * BPF-backed binary type handlers for binfmt_misc.
+ *
+ * A handler is a struct binfmt_misc_ops struct_ops map. Loading and
+ * registering it makes the handler available under its name in the user
+ * namespace it was registered in. A binfmt_misc 'B' entry activates it:
+ *
+ * echo ':entry:B::::<handler-name>:' > <binfmt_misc>/register
+ */
+
+#include <linux/binfmt_misc.h>
+#include <linux/binfmts.h>
+#include <linux/bpf.h>
+#include <linux/bpf_verifier.h>
+#include <linux/btf.h>
+#include <linux/btf_ids.h>
+#include <linux/cred.h>
+#include <linux/init.h>
+#include <linux/limits.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/user_namespace.h>
+
+struct bm_bpf_ops_reg {
+ struct list_head list;
+ const struct binfmt_misc_ops *ops;
+ struct bpf_link *link;
+ struct user_namespace *user_ns;
+};
+
+static DEFINE_SPINLOCK(bm_bpf_ops_lock);
+static LIST_HEAD(bm_bpf_ops_list);
+
+static struct bpf_struct_ops bpf_binfmt_misc_ops;
+
+static struct bm_bpf_ops_reg *bm_bpf_ops_find(const struct user_namespace *user_ns,
+ const char *name)
+{
+ struct bm_bpf_ops_reg *reg;
+
+ lockdep_assert_held(&bm_bpf_ops_lock);
+
+ list_for_each_entry(reg, &bm_bpf_ops_list, list) {
+ if (reg->user_ns == user_ns && !strcmp(reg->ops->name, name))
+ return reg;
+ }
+ return NULL;
+}
+
+/**
+ * binfmt_misc_get_ops - look up a bpf binary type handler by name
+ * @user_ns: user namespace of the binfmt_misc instance
+ * @name: name the handler was registered under
+ *
+ * Search @user_ns and its ancestors for a handler named @name, mirroring
+ * the instance lookup in current_binfmt_misc(). The returned handler stays
+ * callable until binfmt_misc_put_ops() even if the backing struct_ops map
+ * is detached or deleted in the meantime.
+ *
+ * Return: the handler on success, NULL on failure
+ */
+const struct binfmt_misc_ops *binfmt_misc_get_ops(struct user_namespace *user_ns,
+ const char *name)
+{
+ const struct user_namespace *ns;
+ struct bm_bpf_ops_reg *reg;
+
+ guard(spinlock)(&bm_bpf_ops_lock);
+
+ for (ns = user_ns; ns; ns = ns->parent) {
+ reg = bm_bpf_ops_find(ns, name);
+ if (!reg)
+ continue;
+ if (!bpf_struct_ops_get(reg->ops))
+ return NULL;
+ return reg->ops;
+ }
+ return NULL;
+}
+
+void binfmt_misc_put_ops(const struct binfmt_misc_ops *ops)
+{
+ bpf_struct_ops_put(ops);
+}
+
+bool bpf_prog_is_binfmt_misc_ops(const struct bpf_prog *prog)
+{
+ return prog->type == BPF_PROG_TYPE_STRUCT_OPS &&
+ prog->aux->st_ops == &bpf_binfmt_misc_ops;
+}
+
+__bpf_kfunc_start_defs();
+
+/**
+ * bpf_binprm_set_interp - select the interpreter for the current exec
+ * @bprm: binary that is being executed
+ * @path: absolute path to the interpreter
+ * @path__sz: size of the @path buffer, including the terminating NUL
+ *
+ * To be called from the load program of a struct binfmt_misc_ops handler
+ * before returning zero; the verifier rejects the call from any other
+ * program, including the handler's own match program. The path is opened
+ * with the credentials of the task doing the exec after the program
+ * returns.
+ *
+ * Return: 0 on success, a negative errno on failure
+ */
+__bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm,
+ const char *path, size_t path__sz)
+{
+ size_t len;
+ char *interp;
+
+ if (!path__sz)
+ return -EINVAL;
+ len = strnlen(path, path__sz);
+ if (len == path__sz)
+ return -EINVAL;
+ if (path[0] != '/')
+ return -EINVAL;
+ if (len >= PATH_MAX)
+ return -ENAMETOOLONG;
+
+ interp = kmemdup_nul(path, len, GFP_KERNEL);
+ if (!interp)
+ return -ENOMEM;
+
+ kfree(bprm->bpf_interp);
+ bprm->bpf_interp = interp;
+ return 0;
+}
+
+/**
+ * bpf_binprm_set_interp_arg - set a single argument for the interpreter
+ * @bprm: binary that is being executed
+ * @arg: argument to pass to the interpreter
+ * @arg__sz: size of the @arg buffer, including the terminating NUL
+ *
+ * To be called from the load program of a struct binfmt_misc_ops handler. The
+ * argument is passed to the interpreter ahead of the binary, mirroring the
+ * single optional argument of a #! interpreter line. Calling it again
+ * replaces the argument.
+ *
+ * Return: 0 on success, a negative errno on failure
+ */
+__bpf_kfunc int bpf_binprm_set_interp_arg(struct linux_binprm *bprm,
+ const char *arg, size_t arg__sz)
+{
+ size_t len;
+ char *val;
+
+ if (!arg__sz)
+ return -EINVAL;
+ len = strnlen(arg, arg__sz);
+ if (len == arg__sz)
+ return -EINVAL;
+ if (!len)
+ return -EINVAL;
+
+ val = kmemdup_nul(arg, len, GFP_KERNEL);
+ if (!val)
+ return -ENOMEM;
+
+ kfree(bprm->bpf_interp_arg);
+ bprm->bpf_interp_arg = val;
+ return 0;
+}
+
+/**
+ * bpf_binprm_set_flags - choose the interpreter invocation flags for this exec
+ * @bprm: binary that is being executed
+ * @flags: an OR of enum bpf_binprm_flags values
+ *
+ * To be called from the load program of a struct binfmt_misc_ops handler. It
+ * decides per exec what a static entry fixes at registration with the P, C and
+ * O flags: BPF_BINPRM_PRESERVE_ARGV0 keeps the caller's argv[0],
+ * BPF_BINPRM_CREDENTIALS computes credentials from the binary, and
+ * BPF_BINPRM_EXECFD hands the binary to the interpreter through AT_EXECFD.
+ * Calling it again replaces the flags, passing zero clears them again.
+ *
+ * Return: 0 on success, -EINVAL if @flags contains an unknown bit
+ */
+__bpf_kfunc int bpf_binprm_set_flags(struct linux_binprm *bprm,
+ enum bpf_binprm_flags flags)
+{
+ if (flags & ~(BPF_BINPRM_PRESERVE_ARGV0 | BPF_BINPRM_CREDENTIALS |
+ BPF_BINPRM_EXECFD))
+ return -EINVAL;
+
+ bprm->bpf_flags = flags;
+ return 0;
+}
+
+__bpf_kfunc_end_defs();
+
+BTF_KFUNCS_START(bm_bpf_kfunc_ids)
+BTF_ID_FLAGS(func, bpf_binprm_set_interp, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_binprm_set_interp_arg, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_binprm_set_flags, KF_SLEEPABLE)
+BTF_KFUNCS_END(bm_bpf_kfunc_ids)
+
+static int bm_bpf_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+ if (!btf_id_set8_contains(&bm_bpf_kfunc_ids, kfunc_id))
+ return 0;
+ if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
+ return -EACCES;
+ /* ->st_ops is unset during the cfg pass; enforced once it is set. */
+ if (!prog->aux->st_ops)
+ return 0;
+ /* Only the load program decides how a binary is run. */
+ if (bpf_prog_is_binfmt_misc_ops(prog) &&
+ prog->aux->attach_st_ops_member_off == offsetof(struct binfmt_misc_ops, load))
+ return 0;
+ return -EACCES;
+}
+
+static const struct btf_kfunc_id_set bm_bpf_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &bm_bpf_kfunc_ids,
+ .filter = bm_bpf_kfunc_filter,
+};
+
+static bool bm_bpf_ops__match(struct linux_binprm *bprm)
+{
+ return false;
+}
+
+static int bm_bpf_ops__load(struct linux_binprm *bprm)
+{
+ return 0;
+}
+
+static struct binfmt_misc_ops bm_bpf_ops_stubs = {
+ .match = bm_bpf_ops__match,
+ .load = bm_bpf_ops__load,
+};
+
+static int bm_bpf_init(struct btf *btf)
+{
+ return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
+ &bm_bpf_kfunc_set);
+}
+
+static int bm_bpf_check_member(const struct btf_type *t,
+ const struct btf_member *member,
+ const struct bpf_prog *prog)
+{
+ u32 moff = __btf_member_bit_offset(t, member) / 8;
+
+ switch (moff) {
+ case offsetof(struct binfmt_misc_ops, match):
+ case offsetof(struct binfmt_misc_ops, load):
+ /* Reliable file reads at exec time require sleeping. */
+ if (!prog->sleepable)
+ return -EINVAL;
+ break;
+ }
+ return 0;
+}
+
+static int bm_bpf_init_member(const struct btf_type *t,
+ const struct btf_member *member,
+ void *kdata, const void *udata)
+{
+ const struct binfmt_misc_ops *uops = udata;
+ struct binfmt_misc_ops *ops = kdata;
+ u32 moff = __btf_member_bit_offset(t, member) / 8;
+
+ switch (moff) {
+ case offsetof(struct binfmt_misc_ops, name):
+ if (bpf_obj_name_cpy(ops->name, uops->name,
+ sizeof(ops->name)) <= 0)
+ return -EINVAL;
+ return 1;
+ }
+ return 0;
+}
+
+static int bm_bpf_validate(void *kdata)
+{
+ struct binfmt_misc_ops *ops = kdata;
+
+ if (!ops->match || !ops->load)
+ return -EINVAL;
+ return 0;
+}
+
+static int bm_bpf_reg(void *kdata, struct bpf_link *link)
+{
+ struct binfmt_misc_ops *ops = kdata;
+ struct bm_bpf_ops_reg *reg;
+
+ reg = kzalloc_obj(*reg, GFP_KERNEL_ACCOUNT);
+ if (!reg)
+ return -ENOMEM;
+
+ reg->ops = ops;
+ reg->link = link;
+ reg->user_ns = get_user_ns(current_user_ns());
+
+ guard(spinlock)(&bm_bpf_ops_lock);
+
+ if (bm_bpf_ops_find(reg->user_ns, ops->name)) {
+ put_user_ns(reg->user_ns);
+ kfree(reg);
+ return -EEXIST;
+ }
+
+ list_add(&reg->list, &bm_bpf_ops_list);
+ return 0;
+}
+
+static void bm_bpf_unreg(void *kdata, struct bpf_link *link)
+{
+ struct bm_bpf_ops_reg *reg;
+
+ guard(spinlock)(&bm_bpf_ops_lock);
+
+ list_for_each_entry(reg, &bm_bpf_ops_list, list) {
+ if (reg->ops == kdata && reg->link == link) {
+ list_del(&reg->list);
+ put_user_ns(reg->user_ns);
+ kfree(reg);
+ return;
+ }
+ }
+}
+
+static const struct bpf_verifier_ops bm_bpf_verifier_ops = {
+ .get_func_proto = bpf_base_func_proto,
+ .is_valid_access = bpf_tracing_btf_ctx_access,
+};
+
+static struct bpf_struct_ops bpf_binfmt_misc_ops = {
+ .verifier_ops = &bm_bpf_verifier_ops,
+ .init = bm_bpf_init,
+ .check_member = bm_bpf_check_member,
+ .init_member = bm_bpf_init_member,
+ .validate = bm_bpf_validate,
+ .reg = bm_bpf_reg,
+ .unreg = bm_bpf_unreg,
+ .cfi_stubs = &bm_bpf_ops_stubs,
+ .name = "binfmt_misc_ops",
+ .owner = THIS_MODULE,
+};
+
+static int __init bm_bpf_struct_ops_init(void)
+{
+ return register_bpf_struct_ops(&bpf_binfmt_misc_ops, binfmt_misc_ops);
+}
+late_initcall(bm_bpf_struct_ops_init);
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index f1863a891db6..6cb877267978 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2024 Google LLC. */
+#include <linux/binfmt_misc.h>
#include <linux/bpf.h>
#include <linux/bpf_lsm.h>
#include <linux/btf.h>
@@ -11,6 +12,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 +361,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,12 +425,30 @@ 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)
+/* Side-effecting kfuncs that stay exclusive to LSM programs. */
+BTF_SET_START(bpf_fs_kfunc_lsm_only_ids)
+BTF_ID(func, bpf_set_dentry_xattr)
+BTF_ID(func, bpf_remove_dentry_xattr)
+BTF_SET_END(bpf_fs_kfunc_lsm_only_ids)
+
static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)
{
- if (!btf_id_set8_contains(&bpf_fs_kfunc_set_ids, kfunc_id) ||
- prog->type == BPF_PROG_TYPE_LSM)
+ if (!btf_id_set8_contains(&bpf_fs_kfunc_set_ids, kfunc_id))
+ return 0;
+ if (prog->type == BPF_PROG_TYPE_LSM)
+ return 0;
+ if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
+ return -EACCES;
+ /* ->st_ops is unset during the cfg pass; enforced once it is set. */
+ if (!prog->aux->st_ops)
+ return 0;
+ if (bpf_prog_is_binfmt_misc_ops(prog) &&
+ !btf_id_set_contains(&bpf_fs_kfunc_lsm_only_ids, kfunc_id))
return 0;
return -EACCES;
}
@@ -438,7 +491,13 @@ static const struct btf_kfunc_id_set bpf_fs_kfunc_set = {
static int __init bpf_fs_kfuncs_init(void)
{
- return register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set);
+ int ret;
+
+ ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set);
+ if (ret || !IS_ENABLED(CONFIG_BINFMT_MISC_BPF))
+ return ret;
+ return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
+ &bpf_fs_kfunc_set);
}
late_initcall(bpf_fs_kfuncs_init);
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 636196705fa3..0fcbfc393946 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -6843,7 +6843,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;
@@ -6947,7 +6947,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 5cb927c6e53d..07ce84070efd 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -2627,7 +2627,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;
@@ -2677,7 +2677,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 a8e27db8e4bc..9b66eb584ece 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;
}
@@ -1052,7 +1061,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--;
@@ -1089,7 +1098,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;
@@ -1099,7 +1119,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)
@@ -1120,7 +1145,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;
@@ -2090,8 +2115,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);
}
@@ -2338,6 +2371,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);
@@ -2364,6 +2404,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;
}
@@ -2455,6 +2497,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,
@@ -2499,7 +2543,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);
@@ -2520,7 +2565,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;
@@ -2541,7 +2587,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);
}
@@ -2810,6 +2856,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;
@@ -2828,8 +2905,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);
@@ -3000,8 +3077,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)
@@ -3009,8 +3088,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;
@@ -3030,6 +3111,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:
@@ -3059,7 +3141,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 eaf23c0dcbf6..0415d74cad9b 100644
--- a/fs/btrfs/volumes.h
+++ b/fs/btrfs/volumes.h
@@ -750,6 +750,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);
@@ -774,6 +775,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);
@@ -794,7 +797,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 ef9e92e362d3..dfa590a220ea 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -983,7 +983,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);
}
@@ -1147,7 +1147,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 bc55be84d945..8ead1646f329 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;
@@ -841,28 +841,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);
@@ -874,8 +880,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 = {
@@ -929,7 +940,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;
}
@@ -941,7 +952,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/exec.c b/fs/exec.c
index c7b8f2d6366c..41e1684d999c 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1418,6 +1418,8 @@ static void free_bprm(struct linux_binprm *bprm)
/* If a binfmt changed the interp, free it. */
if (bprm->interp != bprm->filename)
kfree(bprm->interp);
+ kfree(bprm->bpf_interp);
+ kfree(bprm->bpf_interp_arg);
kfree(bprm->fdpath);
kfree(bprm);
}
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 3b9740c1c16d..a6386c1d237f 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -2813,7 +2813,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;
@@ -3011,7 +3011,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 32c8e33b5036..8e7c93a5e458 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -5816,7 +5816,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);
@@ -6000,9 +6000,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",
@@ -6062,7 +6062,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);
}
@@ -6101,7 +6101,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);
}
@@ -7519,7 +7519,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 769d16d54997..de99eead6f8d 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
@@ -4901,8 +4901,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(&current->fs->seq);
+ current->fs->users++;
+ read_sequnlock_excl(&current->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/hfs/super.c b/fs/hfs/super.c
index a466c401f6bb..ecdafc658928 100644
--- a/fs/hfs/super.c
+++ b/fs/hfs/super.c
@@ -82,7 +82,7 @@ void hfs_mark_mdb_dirty(struct super_block *sb)
spin_lock(&sbi->work_lock);
if (!sbi->work_queued) {
delay = msecs_to_jiffies(dirty_writeback_interval * 10);
- queue_delayed_work(system_long_wq, &sbi->mdb_work, delay);
+ queue_delayed_work(system_dfl_long_wq, &sbi->mdb_work, delay);
sbi->work_queued = 1;
}
spin_unlock(&sbi->work_lock);
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/hfsplus/super.c b/fs/hfsplus/super.c
index 5777e31de45a..ff7d6b3336a6 100644
--- a/fs/hfsplus/super.c
+++ b/fs/hfsplus/super.c
@@ -312,7 +312,7 @@ void hfsplus_mark_mdb_dirty(struct super_block *sb)
spin_lock(&sbi->work_lock);
if (!sbi->work_queued) {
delay = msecs_to_jiffies(dirty_writeback_interval * 10);
- queue_delayed_work(system_long_wq, &sbi->sync_work, delay);
+ queue_delayed_work(system_dfl_long_wq, &sbi->sync_work, delay);
sbi->work_queued = 1;
}
spin_unlock(&sbi->work_lock);
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 6d9a2efd4bee..f669fcdca2f0 100644
--- a/fs/iomap/buffered-io.c
+++ b/fs/iomap/buffered-io.c
@@ -792,7 +792,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;
@@ -1182,7 +1182,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);
@@ -1278,12 +1277,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 30468d51b5ad..fb636dce43af 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)
@@ -488,7 +489,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;
@@ -511,8 +513,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/jffs2/wbuf.c b/fs/jffs2/wbuf.c
index 8ff7a0b6add2..3b7803c75d58 100644
--- a/fs/jffs2/wbuf.c
+++ b/fs/jffs2/wbuf.c
@@ -1177,7 +1177,7 @@ void jffs2_dirty_trigger(struct jffs2_sb_info *c)
return;
delay = msecs_to_jiffies(dirty_writeback_interval * 10);
- if (queue_delayed_work(system_long_wq, &c->wbuf_dwork, delay))
+ if (queue_delayed_work(system_dfl_long_wq, &c->wbuf_dwork, delay))
jffs2_dbg(1, "%s()\n", __func__);
}
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 c7caffb31935..36f2e8588922 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 e4533f583632..7f96a258af76 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 aee7aabaf059..0de5b63ef407 100644
--- a/fs/ntfs/namei.c
+++ b/fs/ntfs/namei.c
@@ -737,7 +737,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;
@@ -1083,7 +1083,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 937d349e841d..7bf889a4ca35 100644
--- a/fs/ntfs3/namei.c
+++ b/fs/ntfs3/namei.c
@@ -110,7 +110,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);
@@ -218,7 +218,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 859e80ae6f40..5ae44b9c8790 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 3dc62c1c2708..18b302f94174 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -747,8 +747,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..7f783d6173e8 100644
--- a/fs/romfs/super.c
+++ b/fs/romfs/super.c
@@ -240,6 +240,8 @@ static struct dentry *romfs_lookup(struct inode *dir, struct dentry *dentry,
if ((be32_to_cpu(ri.next) & ROMFH_TYPE) == ROMFH_HRD)
offset = be32_to_cpu(ri.spec) & ROMFH_MASK;
inode = romfs_iget(dir->i_sb, offset);
+ if (IS_ERR(inode))
+ return ERR_CAST(inode);
break;
}
@@ -262,6 +264,8 @@ static const struct inode_operations romfs_dir_inode_operations = {
.lookup = romfs_lookup,
};
+#define ROMFS_MAX_HARDLINK_DEPTH 64
+
/*
* get a romfs inode based on its position in the image (which doubles as the
* inode number)
@@ -273,6 +277,7 @@ static struct inode *romfs_iget(struct super_block *sb, unsigned long pos)
struct inode *i;
unsigned long nlen;
unsigned nextfh;
+ unsigned int depth = 0;
int ret;
umode_t mode;
@@ -289,6 +294,9 @@ static struct inode *romfs_iget(struct super_block *sb, unsigned long pos)
if ((nextfh & ROMFH_TYPE) != ROMFH_HRD)
break;
+ if (++depth > ROMFS_MAX_HARDLINK_DEPTH)
+ return ERR_PTR(-ELOOP);
+
pos = be32_to_cpu(ri.spec) & ROMFH_MASK;
}
@@ -587,7 +595,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 b2806371bfde..dc64474d16f6 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -2287,6 +2287,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 add6194cd565..0caf1598aae7 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>
@@ -6057,7 +6058,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 6600c2f5a404..2f111b1923d7 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;
@@ -621,7 +623,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 ffdcc6a2e0de..5feecf5d9038 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;
}
@@ -1228,7 +1351,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);
@@ -1314,7 +1446,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)
@@ -1356,197 +1488,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)
@@ -1555,7 +1803,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);
@@ -1569,20 +1817,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;
@@ -1671,7 +1918,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/ufs/super.c b/fs/ufs/super.c
index c4831a8b9b3f..6dcf6d048cce 100644
--- a/fs/ufs/super.c
+++ b/fs/ufs/super.c
@@ -672,7 +672,7 @@ void ufs_mark_sb_dirty(struct super_block *sb)
spin_lock(&sbi->work_lock);
if (!sbi->work_queued) {
delay = msecs_to_jiffies(dirty_writeback_interval * 10);
- queue_delayed_work(system_long_wq, &sbi->sync_work, delay);
+ queue_delayed_work(system_dfl_long_wq, &sbi->sync_work, delay);
sbi->work_queued = 1;
}
spin_unlock(&sbi->work_lock);
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 48d7dfd3e15f..21d67af781da 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 63c4bcbe6c2b..4b2eeb7783f7 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;
}