diff --git a/Documentation/filesystems/adding-new-filesystems.rst b/Documentation/filesystems/adding-new-filesystems.rst new file mode 100644 index 000000000000..a3d0bf16f73a --- /dev/null +++ b/Documentation/filesystems/adding-new-filesystems.rst @@ -0,0 +1,195 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. _adding_new_filesystems: + +Adding New Filesystems +====================== + +This document describes what is involved in adding a new filesystem to the +Linux kernel. + +Every filesystem merged into the kernel becomes the collective responsibility +of the VFS maintainers and the wider filesystem development community. +Experience has shown that filesystems which become unmaintained impose a +significant and ongoing burden: they are hard or impossible to test, they +block infrastructure changes because someone must update or preserve old APIs +for code that nobody is actively looking after, and they accumulate unfixed +bugs. The requirements and expectations described here are informed by this +experience and are intended to ensure that new filesystems enter the kernel +on a sustainable footing. + + +Do You Need a New In-Kernel Filesystem? +--------------------------------------- + +Before proposing a new in-kernel filesystem, consider whether one of the +alternatives might be more appropriate. + + - If an existing in-kernel filesystem covers the same use case, improving it + is generally preferred over adding a new implementation. The kernel + community favors incremental improvement over parallel implementations. + + - If the filesystem serves a niche audience or has a small user base, a FUSE + (Filesystem in Userspace) implementation may be a better fit. FUSE + filesystems avoid the long-term kernel maintenance commitment and can be + developed and released on their own schedule. + + - If kernel-level performance, reliability, or integration is genuinely + required, make the case explicitly. Explain who the users are, what the + use case is, and why a FUSE implementation would not be sufficient. + + +Technical Requirements +---------------------- + +New filesystems must use current kernel interfaces and practices. +Submitting a filesystem built on outdated APIs creates an unacceptable +maintenance debt and is likely to face pushback during review. + +Use modern VFS interfaces + Do not use interfaces listed in + :ref:`Documentation/process/deprecated.rst `. + + Use folios rather than raw page operations for page cache management and + iomap rather than buffer heads for block mapping and I/O. See + ``Documentation/filesystems/iomap/index.rst`` for iomap documentation. + + Block-based filesystems that need functionality not currently provided by + iomap should be prepared to explain why adding that functionality to iomap + is infeasible, rather than reimplementing their own block mapping layer. + + Network filesystems should consider using the netfs library + (``Documentation/filesystems/netfs_library.rst``), or be prepared to explain + why it is not a good fit. + +Provide userspace utilities + A ``mkfs`` tool is expected so that the filesystem can be created and used + by testers and users. A ``fsck`` tool is strongly recommended; while not + strictly required for every filesystem type, the ability to verify + consistency and repair corruption is an important part of a mature + filesystem. + +Be testable + The filesystem must be testable in a meaningful way. The + `fstests `_ + framework (also known as xfstests) is the standard testing infrastructure + for Linux filesystems and its use is highly recommended. At a minimum, + there must be a credible and documented way to test the filesystem and + detect regressions. When submitting, include a summary of test results + indicating which tests pass, fail, or are not applicable. + +Provide documentation + A documentation file under ``Documentation/filesystems/`` describing the + filesystem, its on-disk format, mount options, and any notable design + decisions is recommended. + + +Community and Maintainership Expectations +----------------------------------------- + +Merging a filesystem is a long-term commitment. The kernel community +needs confidence that the filesystem will be actively maintained after it +is merged. + +Identified maintainers + The submission must include a ``MAINTAINERS`` entry with at least one + maintainer (``M:``), a mailing list (``L:``), and a git tree (``T:``). + Having two or more maintainers is strongly preferred so that coverage + does not depend on a single person. The maintainers are expected to be + the primary points of contact for the filesystem going forward. + +Demonstrated commitment + A track record of maintaining kernel code -- for example, in other + subsystems -- significantly strengthens the case for a new filesystem. + Maintainers who are already known and trusted within the community face + less friction during review. + +Sustained backing + Major filesystems in Linux have organizational or corporate support behind + their development. Filesystems that depend entirely on volunteer effort + face higher scrutiny about their long-term viability. + +Responsiveness + The maintainer is expected to respond to bug reports, address review + feedback, and adapt the filesystem to VFS infrastructure changes such as + folio conversions, iomap migration, and mount API updates. Unresponsive + maintainership is one of the primary reasons filesystems end up on the + path to deprecation. + +User base + Clearly describe who the users of this filesystem are and the scale of the + user base. Filesystems with a very small or unclear user base face a + harder path to acceptance and a higher risk of future deprecation. + +Building your track record + A practical way to demonstrate many of the qualities above is to maintain + the filesystem out-of-tree for a period before requesting a merge. This + shows sustained commitment, builds a visible user base, and gives reviewers + confidence that the code and its maintainer will persist after merging. + That said, it is recognized that for some filesystems the user base grows + significantly only after upstreaming, so a compelling case for expected + adoption can substitute for a large existing user base. + + +Submission Process +------------------ + +This section covers what is specific to filesystem submissions, over and +above the normal submission advice in +:ref:`Documentation/process/submitting-patches.rst ` and +:ref:`Documentation/process/submit-checklist.rst `. + + - Send patches to the linux-fsdevel mailing list + (``linux-fsdevel@vger.kernel.org``). CC the relevant VFS maintainers as + listed in the ``MAINTAINERS`` file under + ``FILESYSTEMS (VFS and infrastructure)``. + + - Structure the submission logically. It is neither acceptable to send one + large patch containing the entire filesystem, nor is a replay of the full + development history helpful to reviewers. Instead, split the series by + topic -- for example: superblock and mount handling, inode operations, + directory operations, address space operations, and so on -- so that each + patch is reviewable in isolation. + + - Separate any filesystem-specific ioctls into their own patches with + dedicated justification. Interfaces beyond those already common across + other filesystems will receive additional scrutiny because they are hard + to maintain and may conflict with future generic interfaces. + + - Expect thorough review. Filesystem code interacts deeply with the VFS, + memory management, and block layers, so reviewers will examine the code + carefully. Address all review feedback and be prepared for multiple + revision cycles. + + - It may be appropriate to mark the filesystem as experimental in its Kconfig + help text for the first few releases to set expectations while the code + stabilizes in-tree. + + +Ongoing Obligations +------------------- + +Merging is not the finish line. Maintaining a filesystem in the kernel is an +ongoing commitment. + + - Adapt to VFS infrastructure changes. The VFS layer evolves continuously; + maintainers are expected to keep up with conversions such as folio + migration, iomap adoption, and mount API updates. + + - Maintain test coverage. As test suites evolve, the filesystem's test + results should be kept current. + + - Handle security issues and regression promptly. Both those reported + by ordinary users and those reported by test bots and fuzzing tools. + The filesystem must handle corrupted input gracefully without corrupting + memory, hanging, or crashing the kernel. + + - Engage with the wider filesystem community. Participate on linux-fsdevel, + share approaches to common problems, and look for opportunities to reuse + shared infrastructure. It is inappropriate to develop in isolation on a + private list and surface patches only at merge time. + + - Filesystems that become unmaintained -- where the maintainer stops + responding, infrastructure changes go unadapted, and testing becomes + impossible -- are candidates for deprecation and eventual removal from + the kernel. diff --git a/Documentation/filesystems/index.rst b/Documentation/filesystems/index.rst index fc7254d01a2b..1f71cf159547 100644 --- a/Documentation/filesystems/index.rst +++ b/Documentation/filesystems/index.rst @@ -43,6 +43,7 @@ algorithms work. caching/index porting + adding-new-filesystems Filesystem support layers ========================= diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst index fdf074429cd3..f546b1d3897f 100644 --- a/Documentation/filesystems/porting.rst +++ b/Documentation/filesystems/porting.rst @@ -1297,7 +1297,6 @@ Several functions are renamed: - kern_path_locked -> start_removing_path - kern_path_create -> start_creating_path - user_path_create -> start_creating_user_path -- user_path_locked_at -> start_removing_user_path_at - done_path_create -> end_creating_path --- diff --git a/arch/alpha/include/uapi/asm/fcntl.h b/arch/alpha/include/uapi/asm/fcntl.h index 50bdc8e8a271..c7e1c5cf646d 100644 --- a/arch/alpha/include/uapi/asm/fcntl.h +++ b/arch/alpha/include/uapi/asm/fcntl.h @@ -2,20 +2,20 @@ #ifndef _ALPHA_FCNTL_H #define _ALPHA_FCNTL_H -#define O_CREAT 01000 /* not fcntl */ -#define O_TRUNC 02000 /* not fcntl */ -#define O_EXCL 04000 /* not fcntl */ -#define O_NOCTTY 010000 /* not fcntl */ +#define O_CREAT (1 << 9) /* not fcntl */ +#define O_TRUNC (1 << 10) /* not fcntl */ +#define O_EXCL (1 << 11) /* not fcntl */ +#define O_NOCTTY (1 << 12) /* not fcntl */ -#define O_NONBLOCK 00004 -#define O_APPEND 00010 -#define O_DSYNC 040000 /* used to be O_SYNC, see below */ -#define O_DIRECTORY 0100000 /* must be a directory */ -#define O_NOFOLLOW 0200000 /* don't follow links */ -#define O_LARGEFILE 0400000 /* will be set by the kernel on every open */ -#define O_DIRECT 02000000 /* direct disk access - should check with OSF/1 */ -#define O_NOATIME 04000000 -#define O_CLOEXEC 010000000 /* set close_on_exec */ +#define O_NONBLOCK (1 << 2) +#define O_APPEND (1 << 3) +#define O_DSYNC (1 << 14) /* used to be O_SYNC, see below */ +#define O_DIRECTORY (1 << 15) /* must be a directory */ +#define O_NOFOLLOW (1 << 16) /* don't follow links */ +#define O_LARGEFILE (1 << 17) /* will be set by the kernel on every open */ +#define O_DIRECT (1 << 19) /* direct disk access - should check with OSF/1 */ +#define O_NOATIME (1 << 20) +#define O_CLOEXEC (1 << 21) /* set close_on_exec */ /* * Before Linux 2.6.33 only O_DSYNC semantics were implemented, but using * the O_SYNC flag. We continue to use the existing numerical value @@ -29,11 +29,11 @@ * * Note: __O_SYNC must never be used directly. */ -#define __O_SYNC 020000000 +#define __O_SYNC (1 << 22) #define O_SYNC (__O_SYNC|O_DSYNC) -#define O_PATH 040000000 -#define __O_TMPFILE 0100000000 +#define O_PATH (1 << 23) +#define __O_TMPFILE (1 << 24) #define F_GETLK 7 #define F_SETLK 8 diff --git a/arch/arm/include/uapi/asm/fcntl.h b/arch/arm/include/uapi/asm/fcntl.h index e6b5d7141c05..b576ff00beb2 100644 --- a/arch/arm/include/uapi/asm/fcntl.h +++ b/arch/arm/include/uapi/asm/fcntl.h @@ -2,10 +2,10 @@ #ifndef _ARM_FCNTL_H #define _ARM_FCNTL_H -#define O_DIRECTORY 040000 /* must be a directory */ -#define O_NOFOLLOW 0100000 /* don't follow links */ -#define O_DIRECT 0200000 /* direct disk access hint - currently ignored */ -#define O_LARGEFILE 0400000 +#define O_DIRECTORY (1 << 14) /* must be a directory */ +#define O_NOFOLLOW (1 << 15) /* don't follow links */ +#define O_DIRECT (1 << 16) /* direct disk access hint - currently ignored */ +#define O_LARGEFILE (1 << 17) #include diff --git a/arch/arm64/include/uapi/asm/fcntl.h b/arch/arm64/include/uapi/asm/fcntl.h index f8db34f2622d..e503fdb74ecb 100644 --- a/arch/arm64/include/uapi/asm/fcntl.h +++ b/arch/arm64/include/uapi/asm/fcntl.h @@ -20,10 +20,10 @@ /* * Using our own definitions for AArch32 (compat) support. */ -#define O_DIRECTORY 040000 /* must be a directory */ -#define O_NOFOLLOW 0100000 /* don't follow links */ -#define O_DIRECT 0200000 /* direct disk access hint - currently ignored */ -#define O_LARGEFILE 0400000 +#define O_DIRECTORY (1 << 14) /* must be a directory */ +#define O_NOFOLLOW (1 << 15) /* don't follow links */ +#define O_DIRECT (1 << 16) /* direct disk access hint - currently ignored */ +#define O_LARGEFILE (1 << 17) #include diff --git a/arch/m68k/include/uapi/asm/fcntl.h b/arch/m68k/include/uapi/asm/fcntl.h index c6861e6ee313..66c0e5515105 100644 --- a/arch/m68k/include/uapi/asm/fcntl.h +++ b/arch/m68k/include/uapi/asm/fcntl.h @@ -2,10 +2,10 @@ #ifndef _M68K_FCNTL_H #define _M68K_FCNTL_H -#define O_DIRECTORY 040000 /* must be a directory */ -#define O_NOFOLLOW 0100000 /* don't follow links */ -#define O_DIRECT 0200000 /* direct disk access hint - currently ignored */ -#define O_LARGEFILE 0400000 +#define O_DIRECTORY (1 << 14) /* must be a directory */ +#define O_NOFOLLOW (1 << 15) /* don't follow links */ +#define O_DIRECT (1 << 16) /* direct disk access hint - currently ignored */ +#define O_LARGEFILE (1 << 17) #include diff --git a/arch/mips/include/uapi/asm/fcntl.h b/arch/mips/include/uapi/asm/fcntl.h index 0369a38e3d4f..549fc65d849d 100644 --- a/arch/mips/include/uapi/asm/fcntl.h +++ b/arch/mips/include/uapi/asm/fcntl.h @@ -11,15 +11,15 @@ #include -#define O_APPEND 0x0008 -#define O_DSYNC 0x0010 /* used to be O_SYNC, see below */ -#define O_NONBLOCK 0x0080 -#define O_CREAT 0x0100 /* not fcntl */ -#define O_TRUNC 0x0200 /* not fcntl */ -#define O_EXCL 0x0400 /* not fcntl */ -#define O_NOCTTY 0x0800 /* not fcntl */ -#define FASYNC 0x1000 /* fcntl, for BSD compatibility */ -#define O_LARGEFILE 0x2000 /* allow large file opens */ +#define O_APPEND (1 << 3) +#define O_DSYNC (1 << 4) /* used to be O_SYNC, see below */ +#define O_NONBLOCK (1 << 7) +#define O_CREAT (1 << 8) /* not fcntl */ +#define O_TRUNC (1 << 9) /* not fcntl */ +#define O_EXCL (1 << 10) /* not fcntl */ +#define O_NOCTTY (1 << 11) /* not fcntl */ +#define FASYNC (1 << 12) /* fcntl, for BSD compatibility */ +#define O_LARGEFILE (1 << 13) /* allow large file opens */ /* * Before Linux 2.6.33 only O_DSYNC semantics were implemented, but using * the O_SYNC flag. We continue to use the existing numerical value @@ -33,9 +33,9 @@ * * Note: __O_SYNC must never be used directly. */ -#define __O_SYNC 0x4000 +#define __O_SYNC (1 << 14) #define O_SYNC (__O_SYNC|O_DSYNC) -#define O_DIRECT 0x8000 /* direct disk access hint */ +#define O_DIRECT (1 << 15) /* direct disk access hint */ #define F_GETLK 14 #define F_SETLK 6 diff --git a/arch/parisc/include/uapi/asm/fcntl.h b/arch/parisc/include/uapi/asm/fcntl.h index 03dee816cb13..2e1bb18eefb8 100644 --- a/arch/parisc/include/uapi/asm/fcntl.h +++ b/arch/parisc/include/uapi/asm/fcntl.h @@ -2,23 +2,23 @@ #ifndef _PARISC_FCNTL_H #define _PARISC_FCNTL_H -#define O_APPEND 000000010 -#define O_CREAT 000000400 /* not fcntl */ -#define O_EXCL 000002000 /* not fcntl */ -#define O_LARGEFILE 000004000 -#define __O_SYNC 000100000 +#define O_APPEND (1 << 3) +#define O_CREAT (1 << 8) /* not fcntl */ +#define O_EXCL (1 << 10) /* not fcntl */ +#define O_LARGEFILE (1 << 11) +#define __O_SYNC (1 << 15) #define O_SYNC (__O_SYNC|O_DSYNC) -#define O_NONBLOCK 000200000 -#define O_NOCTTY 000400000 /* not fcntl */ -#define O_DSYNC 001000000 -#define O_NOATIME 004000000 -#define O_CLOEXEC 010000000 /* set close_on_exec */ +#define O_NONBLOCK (1 << 16) +#define O_NOCTTY (1 << 17) /* not fcntl */ +#define O_DSYNC (1 << 18) +#define O_NOATIME (1 << 20) +#define O_CLOEXEC (1 << 21) /* set close_on_exec */ -#define O_DIRECTORY 000010000 /* must be a directory */ -#define O_NOFOLLOW 000000200 /* don't follow links */ +#define O_DIRECTORY (1 << 12) /* must be a directory */ +#define O_NOFOLLOW (1 << 7) /* don't follow links */ -#define O_PATH 020000000 -#define __O_TMPFILE 040000000 +#define O_PATH (1 << 22) +#define __O_TMPFILE (1 << 23) #define F_GETLK64 8 #define F_SETLK64 9 diff --git a/arch/powerpc/include/uapi/asm/fcntl.h b/arch/powerpc/include/uapi/asm/fcntl.h index 65ce08322a89..003bc5ea78e1 100644 --- a/arch/powerpc/include/uapi/asm/fcntl.h +++ b/arch/powerpc/include/uapi/asm/fcntl.h @@ -2,10 +2,10 @@ #ifndef _ASM_FCNTL_H #define _ASM_FCNTL_H -#define O_DIRECTORY 040000 /* must be a directory */ -#define O_NOFOLLOW 0100000 /* don't follow links */ -#define O_LARGEFILE 0200000 -#define O_DIRECT 0400000 /* direct disk access hint */ +#define O_DIRECTORY (1 << 14) /* must be a directory */ +#define O_NOFOLLOW (1 << 15) /* don't follow links */ +#define O_LARGEFILE (1 << 16) +#define O_DIRECT (1 << 17) /* direct disk access hint */ #include diff --git a/arch/sparc/include/uapi/asm/fcntl.h b/arch/sparc/include/uapi/asm/fcntl.h index 67dae75e5274..29c5639bc3fa 100644 --- a/arch/sparc/include/uapi/asm/fcntl.h +++ b/arch/sparc/include/uapi/asm/fcntl.h @@ -2,23 +2,23 @@ #ifndef _SPARC_FCNTL_H #define _SPARC_FCNTL_H -#define O_APPEND 0x0008 -#define FASYNC 0x0040 /* fcntl, for BSD compatibility */ -#define O_CREAT 0x0200 /* not fcntl */ -#define O_TRUNC 0x0400 /* not fcntl */ -#define O_EXCL 0x0800 /* not fcntl */ -#define O_DSYNC 0x2000 /* used to be O_SYNC, see below */ -#define O_NONBLOCK 0x4000 +#define O_APPEND (1 << 3) +#define FASYNC (1 << 6) /* fcntl, for BSD compatibility */ +#define O_CREAT (1 << 9) /* not fcntl */ +#define O_TRUNC (1 << 10) /* not fcntl */ +#define O_EXCL (1 << 11) /* not fcntl */ +#define O_DSYNC (1 << 13) /* used to be O_SYNC, see below */ +#define O_NONBLOCK (1 << 14) #if defined(__sparc__) && defined(__arch64__) -#define O_NDELAY 0x0004 +#define O_NDELAY (1 << 2) #else -#define O_NDELAY (0x0004 | O_NONBLOCK) +#define O_NDELAY ((1 << 2) | O_NONBLOCK) #endif -#define O_NOCTTY 0x8000 /* not fcntl */ -#define O_LARGEFILE 0x40000 -#define O_DIRECT 0x100000 /* direct disk access hint */ -#define O_NOATIME 0x200000 -#define O_CLOEXEC 0x400000 +#define O_NOCTTY (1 << 15) /* not fcntl */ +#define O_LARGEFILE (1 << 18) +#define O_DIRECT (1 << 20) /* direct disk access hint */ +#define O_NOATIME (1 << 21) +#define O_CLOEXEC (1 << 22) /* * Before Linux 2.6.33 only O_DSYNC semantics were implemented, but using * the O_SYNC flag. We continue to use the existing numerical value @@ -32,11 +32,11 @@ * * Note: __O_SYNC must never be used directly. */ -#define __O_SYNC 0x800000 +#define __O_SYNC (1 << 23) #define O_SYNC (__O_SYNC|O_DSYNC) -#define O_PATH 0x1000000 -#define __O_TMPFILE 0x2000000 +#define O_PATH (1 << 24) +#define __O_TMPFILE (1 << 25) #define F_GETOWN 5 /* for sockets. */ #define F_SETOWN 6 /* for sockets. */ diff --git a/fs/affs/affs.h b/fs/affs/affs.h index a0caf6ace860..44a3f69d275f 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -227,11 +227,6 @@ static inline bool affs_validblock(struct super_block *sb, int block) block < AFFS_SB(sb)->s_partition_size); } -static inline void -affs_set_blocksize(struct super_block *sb, int size) -{ - sb_set_blocksize(sb, size); -} static inline struct buffer_head * affs_bread(struct super_block *sb, int block) { diff --git a/fs/affs/super.c b/fs/affs/super.c index 079f36e1ddec..b232251aa7bb 100644 --- a/fs/affs/super.c +++ b/fs/affs/super.c @@ -358,7 +358,8 @@ static int affs_fill_super(struct super_block *sb, struct fs_context *fc) size = bdev_nr_sectors(sb->s_bdev); pr_debug("initial blocksize=%d, #blocks=%d\n", 512, size); - affs_set_blocksize(sb, PAGE_SIZE); + if (!sb_set_blocksize(sb, PAGE_SIZE)) + return -EINVAL; /* Try to find root block. Its location depends on the block size. */ i = bdev_logical_block_size(sb->s_bdev); @@ -374,7 +375,8 @@ static int affs_fill_super(struct super_block *sb, struct fs_context *fc) if (ctx->root_block < 0) sbi->s_root_block = (ctx->reserved + size - 1) / 2; pr_debug("setting blocksize to %d\n", blocksize); - affs_set_blocksize(sb, blocksize); + if (!sb_set_blocksize(sb, blocksize)) + return -EINVAL; sbi->s_partition_size = size; /* The root block location that was calculated above is not diff --git a/fs/aio.c b/fs/aio.c index 722476560848..f57fa21a2503 100644 --- a/fs/aio.c +++ b/fs/aio.c @@ -318,7 +318,6 @@ static int aio_init_fs_context(struct fs_context *fc) pfc = init_pseudo(fc, AIO_RING_MAGIC); if (!pfc) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC; pfc->ops = &aio_super_operations; return 0; } diff --git a/fs/anon_inodes.c b/fs/anon_inodes.c index b8381c7fb636..a7b9b948e33d 100644 --- a/fs/anon_inodes.c +++ b/fs/anon_inodes.c @@ -86,8 +86,6 @@ static int anon_inodefs_init_fs_context(struct fs_context *fc) struct pseudo_fs_context *ctx = init_pseudo(fc, ANON_INODE_FS_MAGIC); if (!ctx) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC; - fc->s_iflags |= SB_I_NODEV; ctx->dops = &anon_inodefs_dentry_operations; return 0; } diff --git a/fs/backing-file.c b/fs/backing-file.c index 1f3bbfc75882..080c99696cd0 100644 --- a/fs/backing-file.c +++ b/fs/backing-file.c @@ -18,17 +18,18 @@ /** * backing_file_open - open a backing file for kernel internal use - * @user_path: path that the user reuqested to open + * @user_file: file the user requested to open * @flags: open flags * @real_path: path of the backing file * @cred: credentials for open * * Open a backing file for a stackable filesystem (e.g., overlayfs). - * @user_path may be on the stackable filesystem and @real_path on the - * underlying filesystem. In this case, we want to be able to return the - * @user_path of the stackable filesystem. This is done by embedding the - * returned file into a container structure that also stores the stacked - * file's path, which can be retrieved using backing_file_user_path(). + * @user_file->f_path may be on the stackable filesystem and @real_path + * on the underlying filesystem. In this case, we want to be able to + * return the path of the stackable filesystem. This is done by + * embedding the returned file into a container structure that also + * stores the stacked file's path, which can be retrieved using + * backing_file_user_path(). */ struct file *backing_file_open(const struct file *user_file, int flags, const struct path *real_path, diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c index c12caae9a967..ee0cbae521b9 100644 --- a/fs/befs/linuxvfs.c +++ b/fs/befs/linuxvfs.c @@ -860,7 +860,8 @@ befs_fill_super(struct super_block *sb, struct fs_context *fc) */ sb->s_magic = BEFS_SUPER_MAGIC; /* Set real blocksize of fs */ - sb_set_blocksize(sb, (ulong) befs_sb->block_size); + if (!sb_set_blocksize(sb, (ulong) befs_sb->block_size)) + goto unacquire_priv_sbp; sb->s_op = &befs_sops; sb->s_export_op = &befs_export_operations; sb->s_time_min = 0; diff --git a/fs/bfs/inode.c b/fs/bfs/inode.c index 19e49c8cf750..e41efdd35db9 100644 --- a/fs/bfs/inode.c +++ b/fs/bfs/inode.c @@ -311,7 +311,7 @@ void bfs_dump_imap(const char *prefix, struct super_block *s) { #ifdef DEBUG int i; - char *tmpbuf = (char *)get_zeroed_page(GFP_KERNEL); + char *tmpbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!tmpbuf) return; @@ -323,7 +323,7 @@ void bfs_dump_imap(const char *prefix, struct super_block *s) strcat(tmpbuf, "0"); } printf("%s: lasti=%08lx <%s>\n", prefix, BFS_SB(s)->si_lasti, tmpbuf); - free_page((unsigned long)tmpbuf); + kfree(tmpbuf); #endif } @@ -346,7 +346,8 @@ static int bfs_fill_super(struct super_block *s, struct fs_context *fc) s->s_time_min = 0; s->s_time_max = U32_MAX; - sb_set_blocksize(s, BFS_BSIZE); + if (!sb_set_blocksize(s, BFS_BSIZE)) + goto out; sbh = sb_bread(s, 0); if (!sbh) diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index b3d8fd70e8b1..84349fcb93f1 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -704,7 +704,7 @@ bm_entry_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) ssize_t res; char *page; - page = (char *) __get_free_page(GFP_KERNEL); + page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!page) return -ENOMEM; @@ -712,7 +712,7 @@ bm_entry_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) res = simple_read_from_buffer(buf, nbytes, ppos, page, strlen(page)); - free_page((unsigned long) page); + kfree(page); return res; } diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index 606319dd69e8..11841c3d4260 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -359,6 +359,21 @@ __bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__s } #endif /* CONFIG_CGROUPS */ +/** + * bpf_real_inode - get the real inode backing a dentry + * @dentry: dentry to resolve + * + * If the dentry is on a union/overlay filesystem, return the underlying, real + * inode that hosts the data. Otherwise return the inode attached to the + * dentry itself. + * + * Return: The real inode backing the dentry, or NULL for a negative dentry. + */ +__bpf_kfunc struct inode *bpf_real_inode(struct dentry *dentry) +{ + return d_real_inode(dentry); +} + __bpf_kfunc_end_defs(); BTF_KFUNCS_START(bpf_fs_kfunc_set_ids) @@ -369,6 +384,7 @@ BTF_ID_FLAGS(func, bpf_get_dentry_xattr, KF_SLEEPABLE) 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_inode, KF_SLEEPABLE | KF_RET_NULL) BTF_KFUNCS_END(bpf_fs_kfunc_set_ids) static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id) diff --git a/fs/buffer.c b/fs/buffer.c index 7ed8dd77d221..9af5f061a1f8 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -71,9 +71,7 @@ EXPORT_SYMBOL(__lock_buffer); void unlock_buffer(struct buffer_head *bh) { - clear_bit_unlock(BH_Lock, &bh->b_state); - smp_mb__after_atomic(); - wake_up_bit(&bh->b_state, BH_Lock); + clear_and_wake_up_bit(BH_Lock, &bh->b_state); } EXPORT_SYMBOL(unlock_buffer); diff --git a/fs/configfs/file.c b/fs/configfs/file.c index ef8c3cd10cc6..a48cece775a3 100644 --- a/fs/configfs/file.c +++ b/fs/configfs/file.c @@ -59,7 +59,7 @@ static int fill_read_buffer(struct file *file, struct configfs_buffer *buffer) ssize_t count = -ENOENT; if (!buffer->page) - buffer->page = (char *) get_zeroed_page(GFP_KERNEL); + buffer->page = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buffer->page) return -ENOMEM; @@ -184,7 +184,7 @@ static int fill_write_buffer(struct configfs_buffer *buffer, int copied; if (!buffer->page) - buffer->page = (char *)__get_free_pages(GFP_KERNEL, 0); + buffer->page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!buffer->page) return -ENOMEM; @@ -381,8 +381,7 @@ static int configfs_release(struct inode *inode, struct file *filp) struct configfs_buffer *buffer = filp->private_data; module_put(buffer->owner); - if (buffer->page) - free_page((unsigned long)buffer->page); + kfree(buffer->page); mutex_destroy(&buffer->mutex); kfree(buffer); return 0; diff --git a/fs/coredump.c b/fs/coredump.c index e943569e9b6d..e68a76ff92a3 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -1482,7 +1482,8 @@ static int proc_dostring_coredump(const struct ctl_table *table, int write, return -EINVAL; } - validate_coredump_safety(); + if (strncmp(old_core_pattern, core_pattern, CORENAME_MAX_SIZE)) + validate_coredump_safety(); return error; } diff --git a/fs/dcache.c b/fs/dcache.c index c8cc7c26b725..d6f505313205 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -426,9 +426,16 @@ static inline void __d_clear_type_and_inode(struct dentry *dentry) this_cpu_inc(nr_dentry_negative); } +#define DENTRY_WARN_ONCE(condition, dentry) \ + WARN_ONCE((condition), "dentry=%p d_flags=0x%x\n", (dentry), (dentry)->d_flags) +#define D_FLAG_VERIFY(dentry, x) \ + DENTRY_WARN_ONCE(((dentry)->d_flags & (DCACHE_LRU_LIST | DCACHE_SHRINK_LIST)) != (x), (dentry)) + static void dentry_free(struct dentry *dentry) { - WARN_ON(d_really_is_positive(dentry)); + DENTRY_WARN_ONCE(d_really_is_positive(dentry), dentry); + DENTRY_WARN_ONCE(dentry->d_lockref.count >= 0, dentry); + D_FLAG_VERIFY(dentry, 0); if (unlikely(dname_external(dentry))) { struct external_name *p = external_name(dentry); if (likely(atomic_dec_and_test(&p->count))) { @@ -495,7 +502,6 @@ static void dentry_unlink_inode(struct dentry * dentry) * These helper functions make sure we always follow the * rules. d_lock must be held by the caller. */ -#define D_FLAG_VERIFY(dentry,x) WARN_ON_ONCE(((dentry)->d_flags & (DCACHE_LRU_LIST | DCACHE_SHRINK_LIST)) != (x)) static void d_lru_add(struct dentry *dentry) { D_FLAG_VERIFY(dentry, 0); @@ -1820,10 +1826,10 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name) name = &slash_name; dname = dentry->d_shortname.string; } else if (name->len > DNAME_INLINE_LEN-1) { - size_t size = offsetof(struct external_name, name[1]); - struct external_name *p = kmalloc(size + name->len, - GFP_KERNEL_ACCOUNT | - __GFP_RECLAIMABLE); + struct external_name *p; + + p = kmalloc_flex(*p, name, name->len + 1, + GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE); if (!p) { kmem_cache_free(dentry_cache, dentry); return NULL; diff --git a/fs/exec.c b/fs/exec.c index 824b46c069ae..b92fe7db176c 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1994,9 +1994,11 @@ COMPAT_SYSCALL_DEFINE5(execveat, int, fd, static int proc_dointvec_minmax_coredump(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { - int error = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + int error, old = READ_ONCE(suid_dumpable); - if (!error && write) + error = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + + if (!error && write && (old != READ_ONCE(suid_dumpable))) validate_coredump_safety(); return error; } diff --git a/fs/fcntl.c b/fs/fcntl.c index b3ea135b74d8..c158f082f1da 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -929,11 +929,11 @@ void send_sigio(struct fown_struct *fown, int fd, int band) send_sigio_to_task(p, fown, fd, band, type); rcu_read_unlock(); } else { - read_lock(&tasklist_lock); + rcu_read_lock(); do_each_pid_task(pid, type, p) { send_sigio_to_task(p, fown, fd, band, type); } while_each_pid_task(pid, type, p); - read_unlock(&tasklist_lock); + rcu_read_unlock(); } out_unlock_fown: read_unlock_irqrestore(&fown->lock, flags); @@ -975,11 +975,11 @@ int send_sigurg(struct file *file) send_sigurg_to_task(p, fown, type); rcu_read_unlock(); } else { - read_lock(&tasklist_lock); + rcu_read_lock(); do_each_pid_task(pid, type, p) { send_sigurg_to_task(p, fown, type); } while_each_pid_task(pid, type, p); - read_unlock(&tasklist_lock); + rcu_read_unlock(); } out_unlock_fown: read_unlock_irqrestore(&fown->lock, flags); diff --git a/fs/file.c b/fs/file.c index 2c81c0b162d0..628ca07dc4b1 100644 --- a/fs/file.c +++ b/fs/file.c @@ -544,24 +544,23 @@ struct files_struct init_files = { static unsigned int find_next_fd(struct fdtable *fdt, unsigned int start) { unsigned int maxfd = fdt->max_fds; /* always multiple of BITS_PER_LONG */ - unsigned int maxbit = maxfd / BITS_PER_LONG; - unsigned int bitbit = start / BITS_PER_LONG; + unsigned int max_fds_words = maxfd / BITS_PER_LONG; + unsigned int fds_word_idx = start / BITS_PER_LONG; unsigned int bit; /* * Try to avoid looking at the second level bitmap */ - bit = find_next_zero_bit(&fdt->open_fds[bitbit], BITS_PER_LONG, + bit = find_next_zero_bit(&fdt->open_fds[fds_word_idx], BITS_PER_LONG, start & (BITS_PER_LONG - 1)); if (bit < BITS_PER_LONG) - return bit + bitbit * BITS_PER_LONG; + return bit + (fds_word_idx * BITS_PER_LONG); - bitbit = find_next_zero_bit(fdt->full_fds_bits, maxbit, bitbit) * BITS_PER_LONG; - if (bitbit >= maxfd) + bit = BITS_PER_LONG * + find_next_zero_bit(fdt->full_fds_bits, max_fds_words, fds_word_idx + 1); + if (bit >= maxfd) return maxfd; - if (bitbit > start) - start = bitbit; - return find_next_zero_bit(fdt->open_fds, maxfd, start); + return find_next_zero_bit(fdt->open_fds, maxfd, bit); } /* @@ -1134,7 +1133,6 @@ struct file *fget_task(struct task_struct *task, unsigned int fd) struct file *fget_task_next(struct task_struct *task, unsigned int *ret_fd) { - /* Must be called with rcu_read_lock held */ struct files_struct *files; unsigned int fd = *ret_fd; struct file *file = NULL; diff --git a/fs/file_table.c b/fs/file_table.c index 16e52e7fc2ac..3c08832aa387 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -231,13 +231,13 @@ static int init_file(struct file *f, int flags, const struct cred *cred) } /* Find an unused file structure and return a pointer to it. - * Returns an error pointer if some error happend e.g. we over file + * Returns an error pointer if some error happened, e.g., we exceed the file * structures limit, run out of memory or operation is not permitted. * * Be very careful using this. You are responsible for * getting write access to any mount that you might assign * to this filp, if it is opened for write. If this is not - * done, you will imbalance int the mount's writer count + * done, the mount's writer count will be wrong * and a warning at __fput() time. */ struct file *alloc_empty_file(int flags, const struct cred *cred) diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index fdc175e93f74..3614ea603913 100644 --- a/fs/fuse/ioctl.c +++ b/fs/fuse/ioctl.c @@ -10,6 +10,7 @@ #include #include +#include #define FUSE_VERITY_ENABLE_ARG_MAX_PAGES 256 static ssize_t fuse_send_ioctl(struct fuse_mount *fm, struct fuse_args *args, @@ -252,7 +253,7 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg, err = -ENOMEM; ap.folios = fuse_folios_alloc(fm->fc->max_pages, GFP_KERNEL, &ap.descs); - iov_page = (struct iovec *) __get_free_page(GFP_KERNEL); + iov_page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!ap.folios || !iov_page) goto out; @@ -400,7 +401,7 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg, } err = 0; out: - free_page((unsigned long) iov_page); + kfree(iov_page); while (ap.num_folios) folio_put(ap.folios[--ap.num_folios]); kfree(ap.folios); diff --git a/fs/hpfs/super.c b/fs/hpfs/super.c index c16d5d4caead..8fbdbf080627 100644 --- a/fs/hpfs/super.c +++ b/fs/hpfs/super.c @@ -523,7 +523,8 @@ static int hpfs_fill_super(struct super_block *s, struct fs_context *fc) hpfs_lock(s); /*sbi->sb_mounting = 1;*/ - sb_set_blocksize(s, 512); + if (!sb_set_blocksize(s, 512)) + goto bail0; sbi->sb_fs_size = -1; if (!(bootblock = hpfs_map_sector(s, 0, &bh0, 0))) goto bail1; if (!(superblock = hpfs_map_sector(s, 16, &bh1, 1))) goto bail2; diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 5a107d59ae27..8d4806dc46d4 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -905,7 +905,7 @@ static int __iomap_write_begin(const struct iomap_iter *iter, if (status < 0) fserror_report_io(iter->inode, FSERR_BUFFERED_READ, pos, - len, status, GFP_NOFS); + plen, status, GFP_NOFS); if (status) return status; diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c index 55385a72a4ce..cc587cd25162 100644 --- a/fs/isofs/dir.c +++ b/fs/isofs/dir.c @@ -13,6 +13,7 @@ */ #include #include +#include #include "isofs.h" #include @@ -256,7 +257,7 @@ static int isofs_readdir(struct file *file, struct dir_context *ctx) struct iso_directory_record *tmpde; struct inode *inode = file_inode(file); - tmpname = (char *)__get_free_page(GFP_KERNEL); + tmpname = kmalloc(PAGE_SIZE, GFP_KERNEL); if (tmpname == NULL) return -ENOMEM; @@ -264,7 +265,7 @@ static int isofs_readdir(struct file *file, struct dir_context *ctx) result = do_isofs_readdir(inode, file, ctx, tmpname, tmpde); - free_page((unsigned long) tmpname); + kfree(tmpname); return result; } diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c index efee53717f1c..337836a0a170 100644 --- a/fs/isofs/inode.c +++ b/fs/isofs/inode.c @@ -818,7 +818,8 @@ static int isofs_fill_super(struct super_block *s, struct fs_context *fc) * entries. By forcing the blocksize in this way, we ensure * that we will never be required to do this. */ - sb_set_blocksize(s, orig_zonesize); + if (!sb_set_blocksize(s, orig_zonesize)) + goto out_freesbi; sbi->s_nls_iocharset = NULL; diff --git a/fs/jbd2/commit.c b/fs/jbd2/commit.c index 4e91593d27e5..d8577725a2fb 100644 --- a/fs/jbd2/commit.c +++ b/fs/jbd2/commit.c @@ -41,9 +41,7 @@ static void journal_end_buffer_io_sync(struct bio *bio) else clear_buffer_uptodate(bh); if (orig_bh) { - clear_bit_unlock(BH_Shadow, &orig_bh->b_state); - smp_mb__after_atomic(); - wake_up_bit(&orig_bh->b_state, BH_Shadow); + clear_and_wake_up_bit(BH_Shadow, &orig_bh->b_state); } unlock_buffer(bh); } diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c index 2040af8c84cb..e82798680109 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c @@ -2782,7 +2782,7 @@ void *jbd2_alloc(size_t size, gfp_t flags) if (size < PAGE_SIZE) ptr = kmem_cache_alloc(get_slab(size), flags); else - ptr = (void *)__get_free_pages(flags, get_order(size)); + ptr = kmalloc(size, flags); /* Check alignment; SLUB has gotten this wrong in the past, * and this can lead to user data corruption! */ @@ -2793,10 +2793,7 @@ void *jbd2_alloc(size_t size, gfp_t flags) void jbd2_free(void *ptr, size_t size) { - if (size < PAGE_SIZE) - kmem_cache_free(get_slab(size), ptr); - else - free_pages((unsigned long)ptr, get_order(size)); + kfree(ptr); }; /* diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c index ac0f79fafaca..8ce6e4458cc2 100644 --- a/fs/jfs/jfs_dtree.c +++ b/fs/jfs/jfs_dtree.c @@ -2729,7 +2729,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) struct ldtentry *d; struct dtslot *t; int d_namleft, len, outlen; - unsigned long dirent_buf; + void *dirent_buf; char *name_ptr; u32 dir_index; int do_index = 0; @@ -2884,7 +2884,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) } } - dirent_buf = __get_free_page(GFP_KERNEL); + dirent_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (dirent_buf == 0) { DT_PUTPAGE(mp); jfs_warn("jfs_readdir: __get_free_page failed!"); @@ -2893,7 +2893,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) } while (1) { - jfs_dirent = (struct jfs_dirent *) dirent_buf; + jfs_dirent = dirent_buf; jfs_dirents = 0; overflow = fix_page = 0; @@ -2903,7 +2903,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) if (stbl[i] < 0) { jfs_err("JFS: Invalid stbl[%d] = %d for inode %ld, block = %lld", i, stbl[i], (long)ip->i_ino, (long long)bn); - free_page(dirent_buf); + kfree(dirent_buf); DT_PUTPAGE(mp); return -EIO; } @@ -2911,7 +2911,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) d = (struct ldtentry *) & p->slot[stbl[i]]; if (((long) jfs_dirent + d->namlen + 1) > - (dirent_buf + PAGE_SIZE)) { + ((long)dirent_buf + PAGE_SIZE)) { /* DBCS codepages could overrun dirent_buf */ index = i; overflow = 1; @@ -3014,7 +3014,7 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) /* unpin previous leaf page */ DT_PUTPAGE(mp); - jfs_dirent = (struct jfs_dirent *) dirent_buf; + jfs_dirent = dirent_buf; while (jfs_dirents--) { ctx->pos = jfs_dirent->position; if (!dir_emit(ctx, jfs_dirent->name, @@ -3037,13 +3037,13 @@ int jfs_readdir(struct file *file, struct dir_context *ctx) DT_GETPAGE(ip, bn, mp, PSIZE, p, rc); if (rc) { - free_page(dirent_buf); + kfree(dirent_buf); return rc; } } out: - free_page(dirent_buf); + kfree(dirent_buf); return rc; } diff --git a/fs/jfs/super.c b/fs/jfs/super.c index 61575f7397ae..8180d83d33fe 100644 --- a/fs/jfs/super.c +++ b/fs/jfs/super.c @@ -491,7 +491,8 @@ static int jfs_fill_super(struct super_block *sb, struct fs_context *fc) /* * Initialize blocksize to 4K. */ - sb_set_blocksize(sb, PSIZE); + if (!sb_set_blocksize(sb, PSIZE)) + goto out_unload; /* * Set method vectors. diff --git a/fs/libfs.c b/fs/libfs.c index 1bbea5e7bae3..124139645f7f 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -736,6 +736,7 @@ struct pseudo_fs_context *init_pseudo(struct fs_context *fc, fc->fs_private = ctx; fc->ops = &pseudo_fs_context_ops; fc->sb_flags |= SB_NOUSER; + fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; fc->global = true; } return ctx; @@ -1258,7 +1259,7 @@ char *simple_transaction_get(struct file *file, const char __user *buf, size_t s if (size > SIMPLE_TRANSACTION_LIMIT - 1) return ERR_PTR(-EFBIG); - ar = (struct simple_transaction_argresp *)get_zeroed_page(GFP_KERNEL); + ar = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!ar) return ERR_PTR(-ENOMEM); @@ -1267,7 +1268,7 @@ char *simple_transaction_get(struct file *file, const char __user *buf, size_t s /* only one write allowed per open */ if (file->private_data) { spin_unlock(&simple_transaction_lock); - free_page((unsigned long)ar); + kfree(ar); return ERR_PTR(-EBUSY); } @@ -1294,7 +1295,7 @@ EXPORT_SYMBOL(simple_transaction_read); int simple_transaction_release(struct inode *inode, struct file *file) { - free_page((unsigned long)file->private_data); + kfree(file->private_data); return 0; } EXPORT_SYMBOL(simple_transaction_release); diff --git a/fs/minix/inode.c b/fs/minix/inode.c index 9c6bac248907..c30cc590698d 100644 --- a/fs/minix/inode.c +++ b/fs/minix/inode.c @@ -292,7 +292,8 @@ static int minix_fill_super(struct super_block *s, struct fs_context *fc) sbi->s_namelen = 60; sbi->s_version = MINIX_V3; sbi->s_mount_state = MINIX_VALID_FS; - sb_set_blocksize(s, m3s->s_blocksize); + if (!sb_set_blocksize(s, m3s->s_blocksize)) + goto out_release; s->s_max_links = MINIX2_LINK_MAX; } else goto out_no_fs; diff --git a/fs/namei.c b/fs/namei.c index 6b87b40c208f..8340dc4108be 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2960,16 +2960,17 @@ void end_dirop(struct dentry *de) EXPORT_SYMBOL(end_dirop); /* does lookup, returns the object with parent locked */ -static struct dentry *__start_removing_path(int dfd, struct filename *name, - struct path *path) +struct dentry *start_removing_path(const char *name, struct path *path) { + CLASS(filename_kernel, filename)(name); struct path parent_path __free(path_put) = {}; struct dentry *d; struct qstr last; enum last_type type; int error; - error = filename_parentat(dfd, name, 0, &parent_path, &last, &type); + error = filename_parentat(AT_FDCWD, filename, 0, &parent_path, &last, + &type); if (error) return ERR_PTR(error); if (unlikely(type != LAST_NORM)) @@ -3030,21 +3031,6 @@ struct dentry *kern_path_parent(const char *name, struct path *path) return d; } -struct dentry *start_removing_path(const char *name, struct path *path) -{ - CLASS(filename_kernel, filename)(name); - return __start_removing_path(AT_FDCWD, filename, path); -} - -struct dentry *start_removing_user_path_at(int dfd, - const char __user *name, - struct path *path) -{ - CLASS(filename, filename)(name); - return __start_removing_path(dfd, filename, path); -} -EXPORT_SYMBOL(start_removing_user_path_at); - int kern_path(const char *name, unsigned int flags, struct path *path) { CLASS(filename_kernel, filename)(name); @@ -3631,7 +3617,6 @@ int path_pts(struct path *path) */ struct dentry *parent = dget_parent(path->dentry); struct dentry *child; - struct qstr this = QSTR_INIT("pts", 3); if (unlikely(!path_connected(path->mnt, parent))) { dput(parent); @@ -3639,7 +3624,7 @@ int path_pts(struct path *path) } dput(path->dentry); path->dentry = parent; - child = d_hash_and_lookup(parent, &this); + child = d_hash_and_lookup(parent, &QSTR("pts")); if (IS_ERR_OR_NULL(child)) return -ENOENT; diff --git a/fs/namespace.c b/fs/namespace.c index 341ddd353b3a..d4cf40198e92 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3306,9 +3306,9 @@ static void mnt_warn_timestamp_expiry(const struct path *mountpoint, (ktime_get_real_seconds() + TIME_UPTIME_SEC_MAX > sb->s_time_max)) { char *buf, *mntpath; - buf = (char *)__get_free_page(GFP_KERNEL); + buf = __getname(); if (buf) - mntpath = d_path(mountpoint, buf, PAGE_SIZE); + mntpath = d_path(mountpoint, buf, PATH_MAX); else mntpath = ERR_PTR(-ENOMEM); if (IS_ERR(mntpath)) @@ -3321,8 +3321,7 @@ static void mnt_warn_timestamp_expiry(const struct path *mountpoint, (unsigned long long)sb->s_time_max); sb->s_iflags |= SB_I_TS_EXPIRY_WARNED; - if (buf) - free_page((unsigned long)buf); + __putname(buf); } } @@ -4502,6 +4501,10 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags, new_mnt = vfs_create_mount(fc); if (IS_ERR(new_mnt)) return PTR_ERR(new_mnt); + if (new_mnt->mnt_sb->s_flags & SB_NOUSER) { + mntput(new_mnt); + return -EINVAL; + } new_mnt->mnt_flags = mnt_flags; new_path.dentry = dget(fc->root); diff --git a/fs/nfs/fs_context.c b/fs/nfs/fs_context.c index c105882edd16..1967de7d1dff 100644 --- a/fs/nfs/fs_context.c +++ b/fs/nfs/fs_context.c @@ -1769,7 +1769,9 @@ struct file_system_type nfs_fs_type = { .init_fs_context = nfs_init_fs_context, .parameters = nfs_fs_parameters, .kill_sb = nfs_kill_super, - .fs_flags = FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA, + .fs_flags = FS_RENAME_DOES_D_MOVE | + FS_BINARY_MOUNTDATA | + FS_USERNS_DELEGATABLE, }; MODULE_ALIAS_FS("nfs"); EXPORT_SYMBOL_GPL(nfs_fs_type); @@ -1781,7 +1783,9 @@ struct file_system_type nfs4_fs_type = { .init_fs_context = nfs_init_fs_context, .parameters = nfs_fs_parameters, .kill_sb = nfs_kill_super, - .fs_flags = FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA, + .fs_flags = FS_RENAME_DOES_D_MOVE | + FS_BINARY_MOUNTDATA | + FS_USERNS_DELEGATABLE, }; MODULE_ALIAS_FS("nfs4"); MODULE_ALIAS("nfs4"); diff --git a/fs/nfs/nfs4namespace.c b/fs/nfs/nfs4namespace.c index 14f72baf3b30..2a03f02bba7c 100644 --- a/fs/nfs/nfs4namespace.c +++ b/fs/nfs/nfs4namespace.c @@ -481,7 +481,6 @@ int nfs4_submount(struct fs_context *fc, struct nfs_server *server) * Returns zero on success, or a negative errno value. */ static int nfs4_try_replacing_one_location(struct nfs_server *server, - char *page, char *page2, const struct nfs4_fs_location *location) { struct net *net = rpc_net_ns(server->client); @@ -541,21 +540,12 @@ static int nfs4_try_replacing_one_location(struct nfs_server *server, int nfs4_replace_transport(struct nfs_server *server, const struct nfs4_fs_locations *locations) { - char *page = NULL, *page2 = NULL; int loc, error; error = -ENOENT; if (locations == NULL || locations->nlocations <= 0) goto out; - error = -ENOMEM; - page = (char *) __get_free_page(GFP_USER); - if (!page) - goto out; - page2 = (char *) __get_free_page(GFP_USER); - if (!page2) - goto out; - for (loc = 0; loc < locations->nlocations; loc++) { const struct nfs4_fs_location *location = &locations->locations[loc]; @@ -564,14 +554,11 @@ int nfs4_replace_transport(struct nfs_server *server, location->rootpath.ncomponents == 0) continue; - error = nfs4_try_replacing_one_location(server, page, - page2, location); + error = nfs4_try_replacing_one_location(server, location); if (error == 0) break; } out: - free_page((unsigned long)page); - free_page((unsigned long)page2); return error; } diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 4cd420b14ce3..8f8a03a68d3d 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -623,7 +623,7 @@ static void show_implementation_id(struct seq_file *m, struct nfs_server *nfss) int nfs_show_devname(struct seq_file *m, struct dentry *root) { - char *page = (char *) __get_free_page(GFP_KERNEL); + char *page = kmalloc(PAGE_SIZE, GFP_KERNEL); char *devname, *dummy; int err = 0; if (!page) @@ -633,7 +633,7 @@ int nfs_show_devname(struct seq_file *m, struct dentry *root) err = PTR_ERR(devname); else seq_escape(m, devname, " \t\n\\"); - free_page((unsigned long)page); + kfree(page); return err; } EXPORT_SYMBOL_GPL(nfs_show_devname); diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index ba97e287c007..653cd6fea202 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -2408,7 +2408,7 @@ static __be32 nfsd_buffered_readdir(struct file *file, struct svc_fh *fhp, loff_t offset; struct readdir_data buf = { .ctx.actor = nfsd_buffered_filldir, - .dirent = (void *)__get_free_page(GFP_KERNEL) + .dirent = kmalloc(PAGE_SIZE, GFP_KERNEL) }; if (!buf.dirent) @@ -2459,7 +2459,7 @@ static __be32 nfsd_buffered_readdir(struct file *file, struct svc_fh *fhp, offset = vfs_llseek(file, 0, SEEK_CUR); } - free_page((unsigned long)(buf.dirent)); + kfree((buf.dirent)); if (host_err) return nfserrno(host_err); diff --git a/fs/nilfs2/ioctl.c b/fs/nilfs2/ioctl.c index e0a606643e87..b73f2c5d10f0 100644 --- a/fs/nilfs2/ioctl.c +++ b/fs/nilfs2/ioctl.c @@ -69,7 +69,7 @@ static int nilfs_ioctl_wrap_copy(struct the_nilfs *nilfs, if (argv->v_index > ~(__u64)0 - argv->v_nmembs) return -EINVAL; - buf = (void *)get_zeroed_page(GFP_NOFS); + buf = kzalloc(PAGE_SIZE, GFP_NOFS); if (unlikely(!buf)) return -ENOMEM; maxmembs = PAGE_SIZE / argv->v_size; @@ -107,7 +107,7 @@ static int nilfs_ioctl_wrap_copy(struct the_nilfs *nilfs, } argv->v_nmembs = total; - free_pages((unsigned long)buf, 0); + kfree(buf); return ret; } diff --git a/fs/nsfs.c b/fs/nsfs.c index 160018c4fb36..c3b6ae76594a 100644 --- a/fs/nsfs.c +++ b/fs/nsfs.c @@ -664,7 +664,6 @@ static int nsfs_init_fs_context(struct fs_context *fc) struct pseudo_fs_context *ctx = init_pseudo(fc, NSFS_MAGIC); if (!ctx) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; ctx->s_d_flags |= DCACHE_DONTCACHE; ctx->ops = &nsfs_ops; ctx->eops = &nsfs_export_operations; diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c index 004f59937559..3305fe406cb2 100644 --- a/fs/ntfs3/super.c +++ b/fs/ntfs3/super.c @@ -1174,7 +1174,10 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size, rec->total = cpu_to_le32(sbi->record_size); ((struct ATTRIB *)Add2Ptr(rec, ao))->type = ATTR_END; - sb_set_blocksize(sb, min_t(u32, sbi->cluster_size, PAGE_SIZE)); + if (!sb_set_blocksize(sb, min_t(u32, sbi->cluster_size, PAGE_SIZE))) { + err = -EINVAL; + goto out; + } sbi->block_mask = sb->s_blocksize - 1; sbi->blocks_per_cluster = sbi->cluster_size >> sb->s_blocksize_bits; @@ -1225,7 +1228,8 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size, /* * Try alternative boot (last sector) */ - sb_set_blocksize(sb, block_size); + if (!sb_set_blocksize(sb, block_size)) + return -EINVAL; hint = "Alternative boot"; dev_size = dev_size0; /* restore original size. */ goto read_boot; diff --git a/fs/ocfs2/dlm/dlmdebug.c b/fs/ocfs2/dlm/dlmdebug.c index fe4fdd09bae3..6ca8b3b68eef 100644 --- a/fs/ocfs2/dlm/dlmdebug.c +++ b/fs/ocfs2/dlm/dlmdebug.c @@ -260,10 +260,10 @@ void dlm_print_one_mle(struct dlm_master_list_entry *mle) { char *buf; - buf = (char *) get_zeroed_page(GFP_ATOMIC); + buf = kzalloc(PAGE_SIZE, GFP_ATOMIC); if (buf) { dump_mle(mle, buf, PAGE_SIZE - 1); - free_page((unsigned long)buf); + kfree(buf); } } @@ -280,7 +280,7 @@ static struct dentry *dlm_debugfs_root; /* begin - utils funcs */ static int debug_release(struct inode *inode, struct file *file) { - free_page((unsigned long)file->private_data); + kfree(file->private_data); return 0; } @@ -327,17 +327,15 @@ static int debug_purgelist_open(struct inode *inode, struct file *file) struct dlm_ctxt *dlm = inode->i_private; char *buf = NULL; - buf = (char *) get_zeroed_page(GFP_NOFS); + buf = kzalloc(PAGE_SIZE, GFP_NOFS); if (!buf) - goto bail; + return -ENOMEM; i_size_write(inode, debug_purgelist_print(dlm, buf, PAGE_SIZE - 1)); file->private_data = buf; return 0; -bail: - return -ENOMEM; } static const struct file_operations debug_purgelist_fops = { @@ -384,17 +382,15 @@ static int debug_mle_open(struct inode *inode, struct file *file) struct dlm_ctxt *dlm = inode->i_private; char *buf = NULL; - buf = (char *) get_zeroed_page(GFP_NOFS); + buf = kzalloc(PAGE_SIZE, GFP_NOFS); if (!buf) - goto bail; + return -ENOMEM; i_size_write(inode, debug_mle_print(dlm, buf, PAGE_SIZE - 1)); file->private_data = buf; return 0; -bail: - return -ENOMEM; } static const struct file_operations debug_mle_fops = { @@ -775,17 +771,15 @@ static int debug_state_open(struct inode *inode, struct file *file) struct dlm_ctxt *dlm = inode->i_private; char *buf = NULL; - buf = (char *) get_zeroed_page(GFP_NOFS); + buf = kzalloc(PAGE_SIZE, GFP_NOFS); if (!buf) - goto bail; + return -ENOMEM; i_size_write(inode, debug_state_print(dlm, buf, PAGE_SIZE - 1)); file->private_data = buf; return 0; -bail: - return -ENOMEM; } static const struct file_operations debug_state_fops = { diff --git a/fs/ocfs2/dlm/dlmdomain.c b/fs/ocfs2/dlm/dlmdomain.c index dc9da9133c8e..97bb9400e24b 100644 --- a/fs/ocfs2/dlm/dlmdomain.c +++ b/fs/ocfs2/dlm/dlmdomain.c @@ -63,7 +63,7 @@ static inline void byte_copymap(u8 dmap[], unsigned long smap[], static void dlm_free_pagevec(void **vec, int pages) { while (pages--) - free_page((unsigned long)vec[pages]); + kfree(vec[pages]); kfree(vec); } @@ -75,9 +75,11 @@ static void **dlm_alloc_pagevec(int pages) if (!vec) return NULL; - for (i = 0; i < pages; i++) - if (!(vec[i] = (void *)__get_free_page(GFP_KERNEL))) + for (i = 0; i < pages; i++) { + vec[i] = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!vec[i]) goto out_free; + } mlog(0, "Allocated DLM hash pagevec; %d pages (%lu expected), %lu buckets per page\n", pages, (unsigned long)DLM_HASH_PAGES, diff --git a/fs/ocfs2/dlm/dlmmaster.c b/fs/ocfs2/dlm/dlmmaster.c index 93eff38fdadd..aee3b4c56dcc 100644 --- a/fs/ocfs2/dlm/dlmmaster.c +++ b/fs/ocfs2/dlm/dlmmaster.c @@ -2548,7 +2548,7 @@ static int dlm_migrate_lockres(struct dlm_ctxt *dlm, /* preallocate up front. if this fails, abort */ ret = -ENOMEM; - mres = (struct dlm_migratable_lockres *) __get_free_page(GFP_NOFS); + mres = kmalloc(PAGE_SIZE, GFP_NOFS); if (!mres) { mlog_errno(ret); goto leave; @@ -2725,8 +2725,7 @@ static int dlm_migrate_lockres(struct dlm_ctxt *dlm, if (wake) wake_up(&res->wq); - if (mres) - free_page((unsigned long)mres); + kfree(mres); dlm_put(dlm); diff --git a/fs/ocfs2/dlm/dlmrecovery.c b/fs/ocfs2/dlm/dlmrecovery.c index 128872bd945d..9b97bf73df22 100644 --- a/fs/ocfs2/dlm/dlmrecovery.c +++ b/fs/ocfs2/dlm/dlmrecovery.c @@ -837,7 +837,7 @@ int dlm_request_all_locks_handler(struct o2net_msg *msg, u32 len, void *data, } /* this will get freed by dlm_request_all_locks_worker */ - buf = (char *) __get_free_page(GFP_NOFS); + buf = kmalloc(PAGE_SIZE, GFP_NOFS); if (!buf) { kfree(item); dlm_put(dlm); @@ -933,7 +933,7 @@ static void dlm_request_all_locks_worker(struct dlm_work_item *item, void *data) } } leave: - free_page((unsigned long)data); + kfree(data); } diff --git a/fs/omfs/inode.c b/fs/omfs/inode.c index 834cae1e6223..1d915ef72119 100644 --- a/fs/omfs/inode.c +++ b/fs/omfs/inode.c @@ -478,7 +478,8 @@ static int omfs_fill_super(struct super_block *sb, struct fs_context *fc) sb->s_time_min = 0; sb->s_time_max = U64_MAX / MSEC_PER_SEC; - sb_set_blocksize(sb, 0x200); + if (!sb_set_blocksize(sb, 0x200)) + goto end; bh = sb_bread(sb, 0); if (!bh) @@ -530,7 +531,8 @@ static int omfs_fill_super(struct super_block *sb, struct fs_context *fc) * Use sys_blocksize as the fs block since it is smaller than a * page while the fs blocksize can be larger. */ - sb_set_blocksize(sb, sbi->s_sys_blocksize); + if (!sb_set_blocksize(sb, sbi->s_sys_blocksize)) + goto out_brelse_bh; /* * ...and the difference goes into a shift. sys_blocksize is always diff --git a/fs/pidfs.c b/fs/pidfs.c index ae307b713dbd..fdd75c3eaff8 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -1089,8 +1089,6 @@ static int pidfs_init_fs_context(struct fs_context *fc) if (!ctx) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC; - fc->s_iflags |= SB_I_NODEV; ctx->s_d_flags |= DCACHE_DONTCACHE; ctx->ops = &pidfs_sops; ctx->eops = &pidfs_export_operations; diff --git a/fs/pipe.c b/fs/pipe.c index 9841648c9cf3..429b0714ec57 100644 --- a/fs/pipe.c +++ b/fs/pipe.c @@ -111,16 +111,76 @@ void pipe_double_lock(struct pipe_inode_info *pipe1, pipe_lock(pipe2); } -static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe) +#define PIPE_PREALLOC_MAX 8 + +struct anon_pipe_prealloc { + struct page *pages[PIPE_PREALLOC_MAX]; + unsigned int count; +}; + +/* + * Pre-allocate pages outside pipe->mutex for multi-page writes. + * alloc_page() with GFP_HIGHUSER can sleep in reclaim and runs memcg + * charging; doing it under the mutex stalls a concurrent reader. + * + * Loop alloc_page() instead of alloc_pages_bulk_*(): the bulk path refuses + * __GFP_ACCOUNT under memcg (see commit 8dcb3060d81d "memcg: page_alloc: + * skip bulk allocator for __GFP_ACCOUNT") and silently degrades to a single + * page. A per-page loop keeps memcg accounting and the task NUMA mempolicy + * honoured for every page; the per-call overhead is small compared to the + * pipe->mutex hold-time being shrunk. Any shortfall is covered by the + * in-lock alloc_page() fallback in anon_pipe_get_page(). + */ +static void anon_pipe_get_page_prealloc(struct anon_pipe_prealloc *prealloc, + size_t total_len) { + unsigned int want, i; + struct page *page; + + prealloc->count = 0; + if (total_len <= PAGE_SIZE) + return; + + want = min_t(unsigned int, DIV_ROUND_UP(total_len, PAGE_SIZE), + PIPE_PREALLOC_MAX); + + for (i = 0; i < want; i++) { + page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT); + if (!page) + break; + prealloc->pages[prealloc->count++] = page; + } +} + +static struct page *anon_pipe_prealloc_pop(struct anon_pipe_prealloc *prealloc) +{ + if (!prealloc->count) + return NULL; + + prealloc->count--; + + return prealloc->pages[prealloc->count]; +} + +static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe, + struct anon_pipe_prealloc *prealloc) +{ + struct page *page; + + /* Drain prealloc first to keep tmp_page[] hot for later small writes. */ + page = anon_pipe_prealloc_pop(prealloc); + if (page) + return page; + for (int i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) { if (pipe->tmp_page[i]) { - struct page *page = pipe->tmp_page[i]; + page = pipe->tmp_page[i]; pipe->tmp_page[i] = NULL; return page; } } + /* FWIW: This is called with pipe->mutex held */ return alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT); } @@ -139,6 +199,38 @@ static void anon_pipe_put_page(struct pipe_inode_info *pipe, put_page(page); } +/* + * Stash leftover prealloc pages in tmp_page[] so the next write to this + * pipe gets a hot page without entering the allocator. + */ +static void anon_pipe_refill_tmp_pages(struct pipe_inode_info *pipe, + struct anon_pipe_prealloc *prealloc) +{ + int i, idx; + + if (!prealloc->count) + return; + + for (i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) { + if (pipe->tmp_page[i]) + continue; + if (!prealloc->count) + return; + idx = --prealloc->count; + pipe->tmp_page[i] = prealloc->pages[idx]; + prealloc->pages[idx] = NULL; + } +} + +/* Runs after mutex_unlock() to keep put_page() out of the critical section. */ +static void anon_pipe_free_pages(struct anon_pipe_prealloc *prealloc) +{ + while (prealloc->count) { + prealloc->count--; + put_page(prealloc->pages[prealloc->count]); + } +} + static void anon_pipe_buf_release(struct pipe_inode_info *pipe, struct pipe_buffer *buf) { @@ -432,6 +524,7 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from) { struct file *filp = iocb->ki_filp; struct pipe_inode_info *pipe = filp->private_data; + struct anon_pipe_prealloc prealloc; unsigned int head; ssize_t ret = 0; size_t total_len = iov_iter_count(from); @@ -455,6 +548,8 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from) if (unlikely(total_len == 0)) return 0; + anon_pipe_get_page_prealloc(&prealloc, total_len); + mutex_lock(&pipe->mutex); if (!pipe->readers) { @@ -512,7 +607,7 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from) struct page *page; int copied; - page = anon_pipe_get_page(pipe); + page = anon_pipe_get_page(pipe, &prealloc); if (unlikely(!page)) { if (!ret) ret = -ENOMEM; @@ -576,9 +671,11 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from) wake_next_writer = true; } out: + anon_pipe_refill_tmp_pages(pipe, &prealloc); if (pipe_is_full(pipe)) wake_next_writer = false; mutex_unlock(&pipe->mutex); + anon_pipe_free_pages(&prealloc); /* * If we do do a wakeup event, we do a 'sync' wakeup, because we @@ -664,7 +761,8 @@ pipe_poll(struct file *filp, poll_table *wait) union pipe_index idx; /* Epoll has some historical nasty semantics, this enables them */ - WRITE_ONCE(pipe->poll_usage, true); + if (unlikely(!READ_ONCE(pipe->poll_usage))) + WRITE_ONCE(pipe->poll_usage, true); /* * Reading pipe state only -- no need for acquiring the semaphore. diff --git a/fs/proc/base.c b/fs/proc/base.c index 65f56136ec3f..126b98419adb 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -262,7 +262,7 @@ static ssize_t get_mm_proctitle(struct mm_struct *mm, char __user *buf, if (pos >= PAGE_SIZE) return 0; - page = (char *)__get_free_page(GFP_KERNEL); + page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!page) return -ENOMEM; @@ -285,7 +285,7 @@ static ssize_t get_mm_proctitle(struct mm_struct *mm, char __user *buf, ret = len; } } - free_page((unsigned long)page); + kfree(page); return ret; } @@ -348,7 +348,7 @@ static ssize_t get_mm_cmdline(struct mm_struct *mm, char __user *buf, if (count > arg_end - pos) count = arg_end - pos; - page = (char *)__get_free_page(GFP_KERNEL); + page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!page) return -ENOMEM; @@ -372,7 +372,7 @@ static ssize_t get_mm_cmdline(struct mm_struct *mm, char __user *buf, count -= got; } - free_page((unsigned long)page); + kfree(page); return len; } @@ -909,7 +909,7 @@ static ssize_t mem_rw(struct file *file, char __user *buf, if (!mm) return 0; - page = (char *)__get_free_page(GFP_KERNEL); + page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!page) return -ENOMEM; @@ -950,7 +950,7 @@ static ssize_t mem_rw(struct file *file, char __user *buf, mmput(mm); free: - free_page((unsigned long) page); + kfree(page); return copied; } @@ -1017,7 +1017,7 @@ static ssize_t environ_read(struct file *file, char __user *buf, if (!mm || !mm->env_end) return 0; - page = (char *)__get_free_page(GFP_KERNEL); + page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!page) return -ENOMEM; @@ -1063,7 +1063,7 @@ static ssize_t environ_read(struct file *file, char __user *buf, mmput(mm); free: - free_page((unsigned long) page); + kfree(page); return ret; } diff --git a/fs/qnx4/inode.c b/fs/qnx4/inode.c index 4deb0eeadbde..42fcd500fad2 100644 --- a/fs/qnx4/inode.c +++ b/fs/qnx4/inode.c @@ -202,7 +202,8 @@ static int qnx4_fill_super(struct super_block *s, struct fs_context *fc) return -ENOMEM; s->s_fs_info = qs; - sb_set_blocksize(s, QNX4_BLOCK_SIZE); + if (!sb_set_blocksize(s, QNX4_BLOCK_SIZE)) + return -EINVAL; s->s_op = &qnx4_sops; s->s_magic = QNX4_SUPER_MAGIC; diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 64cf42721496..9850de3955d3 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -3022,7 +3022,7 @@ static const struct ctl_table fs_dqstats_table[] = { static int __init dquot_init(void) { int i, ret; - unsigned long nr_hash, order; + unsigned long nr_hash; struct shrinker *dqcache_shrinker; printk(KERN_NOTICE "VFS: Disk quotas %s\n", __DQUOT_VERSION__); @@ -3035,8 +3035,7 @@ static int __init dquot_init(void) SLAB_PANIC), NULL); - order = 0; - dquot_hash = (struct hlist_head *)__get_free_pages(GFP_KERNEL, order); + dquot_hash = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!dquot_hash) panic("Cannot create dquot hash table"); @@ -3046,7 +3045,7 @@ static int __init dquot_init(void) panic("Cannot create dquot stat counters"); /* Find power-of-two hlist_heads which can fit into allocation */ - nr_hash = (1UL << order) * PAGE_SIZE / sizeof(struct hlist_head); + nr_hash = PAGE_SIZE / sizeof(struct hlist_head); dq_hash_bits = ilog2(nr_hash); nr_hash = 1UL << dq_hash_bits; @@ -3054,8 +3053,8 @@ static int __init dquot_init(void) for (i = 0; i < nr_hash; i++) INIT_HLIST_HEAD(dquot_hash + i); - pr_info("VFS: Dquot-cache hash table entries: %ld (order %ld," - " %ld bytes)\n", nr_hash, order, (PAGE_SIZE << order)); + pr_info("VFS: Dquot-cache hash table entries: %ld (%ld bytes)\n", + nr_hash, PAGE_SIZE); dqcache_shrinker = shrinker_alloc(0, "dquota-cache"); if (!dqcache_shrinker) diff --git a/fs/read_write.c b/fs/read_write.c index 50bff7edc91f..e8c14e2760b2 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -641,13 +641,12 @@ ssize_t __kernel_write(struct file *file, const void *buf, size_t count, loff_t return __kernel_write_iter(file, &iter, pos); } /* - * This "EXPORT_SYMBOL_GPL()" is more of a "EXPORT_SYMBOL_DONTUSE()", - * but autofs is one of the few internal kernel users that actually + * autofs is one of the few internal kernel users that actually * wants this _and_ can be built as a module. So we need to export * this symbol for autofs, even though it really isn't appropriate * for any other kernel modules. */ -EXPORT_SYMBOL_GPL(__kernel_write); +EXPORT_SYMBOL_FOR_MODULES(__kernel_write, "autofs4"); ssize_t kernel_write(struct file *file, const void *buf, size_t count, loff_t *pos) diff --git a/fs/select.c b/fs/select.c index bf71c9838dfe..95d76531015a 100644 --- a/fs/select.c +++ b/fs/select.c @@ -150,7 +150,7 @@ void poll_freewait(struct poll_wqueues *pwq) } while (entry > p->entries); old = p; p = p->next; - free_page((unsigned long) old); + kfree(old); } } EXPORT_SYMBOL(poll_freewait); @@ -165,7 +165,7 @@ static struct poll_table_entry *poll_get_entry(struct poll_wqueues *p) if (!table || POLL_TABLE_FULL(table)) { struct poll_table_page *new_table; - new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL); + new_table = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!new_table) { p->error = -ENOMEM; return NULL; diff --git a/fs/super.c b/fs/super.c index 5fe8cea9f8fe..a117e1f0dfe3 100644 --- a/fs/super.c +++ b/fs/super.c @@ -741,12 +741,13 @@ struct super_block *sget_fc(struct fs_context *fc, int err; /* - * Never allow s_user_ns != &init_user_ns when FS_USERNS_MOUNT is - * not set, as the filesystem is likely unprepared to handle it. - * This can happen when fsconfig() is called from init_user_ns with - * an fs_fd opened in another user namespace. + * Never allow s_user_ns != &init_user_ns when FS_USERNS_MOUNT or + * FS_USERNS_DELEGATABLE is not set, as the filesystem is likely + * unprepared to handle it. This can happen when fsconfig() is called + * from init_user_ns with an fs_fd opened in another user namespace. */ - if (user_ns != &init_user_ns && !(fc->fs_type->fs_flags & FS_USERNS_MOUNT)) { + if (user_ns != &init_user_ns && + !(fc->fs_type->fs_flags & (FS_USERNS_MOUNT | FS_USERNS_DELEGATABLE))) { errorfc(fc, "VFS: Mounting from non-initial user namespace is not allowed"); return ERR_PTR(-EPERM); } @@ -821,7 +822,6 @@ void drop_super_exclusive(struct super_block *sb) super_unlock_excl(sb); put_super(sb); } -EXPORT_SYMBOL(drop_super_exclusive); enum super_iter_flags_t { SUPER_ITER_EXCL = (1U << 0), diff --git a/fs/sync.c b/fs/sync.c index 942a60cfedfb..4a84dd837b86 100644 --- a/fs/sync.c +++ b/fs/sync.c @@ -266,8 +266,7 @@ int sync_file_range(struct file *file, loff_t offset, loff_t nbytes, i_mode = file_inode(file)->i_mode; ret = -ESPIPE; - if (!S_ISREG(i_mode) && !S_ISBLK(i_mode) && !S_ISDIR(i_mode) && - !S_ISLNK(i_mode)) + if (!S_ISREG(i_mode) && !S_ISBLK(i_mode) && !S_ISDIR(i_mode)) goto out; mapping = file->f_mapping; diff --git a/include/linux/filelock.h b/include/linux/filelock.h index 7a7a6e8a9a08..ec11cc6b4c58 100644 --- a/include/linux/filelock.h +++ b/include/linux/filelock.h @@ -584,7 +584,7 @@ static inline bool is_delegated(struct delegated_inode *di) return false; } -static inline int break_lease(struct inode *inode, bool wait) +static inline int break_lease(struct inode *inode, unsigned int mode) { return 0; } diff --git a/include/linux/fs.h b/include/linux/fs.h index 2a6d2cb674db..9674c3d1cb3f 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2294,6 +2294,7 @@ struct file_system_type { #define FS_MGTIME 64 /* FS uses multigrain timestamps */ #define FS_LBS 128 /* FS supports LBS */ #define FS_POWER_FREEZE 256 /* Always freeze on suspend/hibernate */ +#define FS_USERNS_DELEGATABLE 1024 /* Can be mounted inside userns from outside */ #define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */ int (*init_fs_context)(struct fs_context *); const struct fs_parameter_spec *parameters; diff --git a/include/linux/kstrtox.h b/include/linux/kstrtox.h index 6ea897222af1..6c9282866770 100644 --- a/include/linux/kstrtox.h +++ b/include/linux/kstrtox.h @@ -142,10 +142,9 @@ static inline int __must_check kstrtos32_from_user(const char __user *s, size_t * Keep in mind above caveat. */ -extern unsigned long simple_strtoul(const char *,char **,unsigned int); -extern unsigned long simple_strntoul(const char *,char **,unsigned int,size_t); -extern long simple_strtol(const char *,char **,unsigned int); -extern unsigned long long simple_strtoull(const char *,char **,unsigned int); -extern long long simple_strtoll(const char *,char **,unsigned int); +unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base); +long simple_strtol(const char *cp, char **endp, unsigned int base); +unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base); +long long simple_strtoll(const char *cp, char **endp, unsigned int base); #endif /* _LINUX_KSTRTOX_H */ diff --git a/include/linux/namei.h b/include/linux/namei.h index 3941b9f1dec7..ebe6e29f7e93 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -56,7 +56,6 @@ extern struct dentry *start_creating_path(int, const char *, struct path *, unsi extern struct dentry *start_creating_user_path(int, const char __user *, struct path *, unsigned int); extern void end_creating_path(const struct path *, struct dentry *); extern struct dentry *start_removing_path(const char *, struct path *); -extern struct dentry *start_removing_user_path_at(int , const char __user *, struct path *); static inline void end_removing_path(const struct path *path , struct dentry *dentry) { end_creating_path(path, dentry); diff --git a/include/linux/sockptr.h b/include/linux/sockptr.h index 3e6c8e9d67ae..9c2429c1a570 100644 --- a/include/linux/sockptr.h +++ b/include/linux/sockptr.h @@ -87,24 +87,10 @@ static inline int copy_safe_from_sockptr(void *dst, size_t ksize, static inline int copy_struct_from_sockptr(void *dst, size_t ksize, sockptr_t src, size_t usize) { - size_t size = min(ksize, usize); - size_t rest = max(ksize, usize) - size; - if (!sockptr_is_kernel(src)) - return copy_struct_from_user(dst, ksize, src.user, size); + return copy_struct_from_user(dst, ksize, src.user, usize); - if (usize < ksize) { - memset(dst + size, 0, rest); - } else if (usize > ksize) { - char *p = src.kernel; - - while (rest--) { - if (*p++) - return -E2BIG; - } - } - memcpy(dst, src.kernel, size); - return 0; + return copy_struct_from_bounce_buffer(dst, ksize, src.kernel, usize); } static inline int copy_to_sockptr_offset(sockptr_t dst, size_t offset, @@ -121,6 +107,16 @@ static inline int copy_to_sockptr(sockptr_t dst, const void *src, size_t size) return copy_to_sockptr_offset(dst, 0, src, size); } +static inline int +copy_struct_to_sockptr(sockptr_t dst, size_t usize, const void *src, + size_t ksize, bool *ignored_trailing) +{ + if (!sockptr_is_kernel(dst)) + return copy_struct_to_user(dst.user, usize, src, ksize, ignored_trailing); + + return copy_struct_to_bounce_buffer(dst.kernel, usize, src, ksize, ignored_trailing); +} + static inline void *memdup_sockptr_noprof(sockptr_t src, size_t len) { void *p = kmalloc_track_caller_noprof(len, GFP_USER | __GFP_NOWARN); diff --git a/include/linux/uaccess.h b/include/linux/uaccess.h index 56328601218c..e4a64976f1c5 100644 --- a/include/linux/uaccess.h +++ b/include/linux/uaccess.h @@ -510,7 +510,7 @@ copy_struct_to_user(void __user *dst, size_t usize, const void *src, return -EFAULT; } if (ignored_trailing) - *ignored_trailing = ksize < usize && + *ignored_trailing = usize < ksize && memchr_inv(src + size, 0, rest) != NULL; /* Copy the interoperable parts of the struct. */ if (copy_to_user(dst, src, size)) @@ -518,6 +518,69 @@ copy_struct_to_user(void __user *dst, size_t usize, const void *src, return 0; } +static __always_inline void +__copy_struct_generic_bounce_buffer(void *dst, size_t dstsize, + const void *src, size_t srcsize, + bool *ignored_trailing) +{ + size_t size = min(dstsize, srcsize); + size_t rest = max(dstsize, srcsize) - size; + + /* Deal with trailing bytes. */ + if (dstsize > srcsize) + memset(dst + size, 0, rest); + if (ignored_trailing) + *ignored_trailing = dstsize < srcsize && + memchr_inv(src + size, 0, rest) != NULL; + /* Copy the interoperable parts of the struct. */ + memcpy(dst, src, size); +} + +/** + * This is like copy_struct_from_user(), but the + * src buffer was already copied into a kernel + * bounce buffer, so it will never return -EFAULT. + */ +static __always_inline __must_check int +copy_struct_from_bounce_buffer(void *dst, size_t dstsize, + const void *src, size_t srcsize) +{ + bool ignored_trailing; + + /* Double check if ksize is larger than a known object size. */ + if (WARN_ON_ONCE(dstsize > __builtin_object_size(dst, 1))) + return -E2BIG; + + __copy_struct_generic_bounce_buffer(dst, dstsize, + src, srcsize, + &ignored_trailing); + if (unlikely(ignored_trailing)) + return -E2BIG; + + return 0; +} + +/** + * This is like copy_struct_to_user(), but the + * dst buffer is a kernel bounce buffer instead + * of a direct userspace buffer, so it will never return -EFAULT. + */ +static __always_inline __must_check int +copy_struct_to_bounce_buffer(void *dst, size_t dstsize, + const void *src, + size_t srcsize, + bool *ignored_trailing) +{ + /* Double check if srcsize is larger than a known object size. */ + if (WARN_ON_ONCE(srcsize > __builtin_object_size(src, 1))) + return -E2BIG; + + __copy_struct_generic_bounce_buffer(dst, dstsize, + src, srcsize, + ignored_trailing); + return 0; +} + bool copy_from_kernel_nofault_allowed(const void *unsafe_src, size_t size); long copy_from_kernel_nofault(void *dst, const void *src, size_t size); diff --git a/include/uapi/asm-generic/fcntl.h b/include/uapi/asm-generic/fcntl.h index bfc68156b45a..883cfd7de2ef 100644 --- a/include/uapi/asm-generic/fcntl.h +++ b/include/uapi/asm-generic/fcntl.h @@ -15,51 +15,55 @@ * When introducing new O_* bits, please check its uniqueness in fcntl_init(). */ -#define O_ACCMODE 00000003 -#define O_RDONLY 00000000 -#define O_WRONLY 00000001 -#define O_RDWR 00000002 +#define O_ACCMODE 3 +#define O_RDONLY 0 +#define O_WRONLY (1 << 0) +#define O_RDWR (1 << 1) +/* (1 << 2) must not be used -- it collides with flags on alpha, sparc */ +/* (1 << 3) must not be used -- it collides with flags on alpha, mips, parisc, sparc */ +/* (1 << 4) must not be used -- it collides with flags on mips */ +/* (1 << 5) is free */ #ifndef O_CREAT -#define O_CREAT 00000100 /* not fcntl */ +#define O_CREAT (1 << 6) /* not fcntl */ #endif #ifndef O_EXCL -#define O_EXCL 00000200 /* not fcntl */ +#define O_EXCL (1 << 7) /* not fcntl */ #endif #ifndef O_NOCTTY -#define O_NOCTTY 00000400 /* not fcntl */ +#define O_NOCTTY (1 << 8) /* not fcntl */ #endif #ifndef O_TRUNC -#define O_TRUNC 00001000 /* not fcntl */ +#define O_TRUNC (1 << 9) /* not fcntl */ #endif #ifndef O_APPEND -#define O_APPEND 00002000 +#define O_APPEND (1 << 10) #endif #ifndef O_NONBLOCK -#define O_NONBLOCK 00004000 +#define O_NONBLOCK (1 << 11) #endif #ifndef O_DSYNC -#define O_DSYNC 00010000 /* used to be O_SYNC, see below */ +#define O_DSYNC (1 << 12) /* used to be O_SYNC, see below */ #endif #ifndef FASYNC -#define FASYNC 00020000 /* fcntl, for BSD compatibility */ +#define FASYNC (1 << 13) /* fcntl, for BSD compatibility */ #endif #ifndef O_DIRECT -#define O_DIRECT 00040000 /* direct disk access hint */ +#define O_DIRECT (1 << 14) /* direct disk access hint */ #endif #ifndef O_LARGEFILE -#define O_LARGEFILE 00100000 +#define O_LARGEFILE (1 << 15) #endif #ifndef O_DIRECTORY -#define O_DIRECTORY 00200000 /* must be a directory */ +#define O_DIRECTORY (1 << 16) /* must be a directory */ #endif #ifndef O_NOFOLLOW -#define O_NOFOLLOW 00400000 /* don't follow links */ +#define O_NOFOLLOW (1 << 17) /* don't follow links */ #endif #ifndef O_NOATIME -#define O_NOATIME 01000000 +#define O_NOATIME (1 << 18) #endif #ifndef O_CLOEXEC -#define O_CLOEXEC 02000000 /* set close_on_exec */ +#define O_CLOEXEC (1 << 19) /* set close_on_exec */ #endif /* @@ -76,16 +80,16 @@ * Note: __O_SYNC must never be used directly. */ #ifndef O_SYNC -#define __O_SYNC 04000000 +#define __O_SYNC (1 << 20) #define O_SYNC (__O_SYNC|O_DSYNC) #endif #ifndef O_PATH -#define O_PATH 010000000 +#define O_PATH (1 << 21) #endif #ifndef __O_TMPFILE -#define __O_TMPFILE 020000000 +#define __O_TMPFILE (1 << 22) #endif #ifndef O_EMPTYPATH @@ -99,6 +103,10 @@ #define O_NDELAY O_NONBLOCK #endif +/* (1 << 23) must not be used -- it collides with flags on alpha, parisc, sparc */ +/* (1 << 24) must not be used -- it collides with flags on alpha, sparc */ +/* (1 << 25) must not be used -- it collides with flags on sparc */ + #define F_DUPFD 0 /* dup */ #define F_GETFD 1 /* get close_on_exec */ #define F_SETFD 2 /* set/clear close_on_exec */ diff --git a/init/do_mounts.c b/init/do_mounts.c index 55ed3ac0b70f..95e0b3a0f711 100644 --- a/init/do_mounts.c +++ b/init/do_mounts.c @@ -143,16 +143,14 @@ static int __init do_mount_root(const char *name, const char *fs, const int flags, const void *data) { struct super_block *s; - struct page *p = NULL; char *data_page = NULL; int ret; if (data) { /* init_mount() requires a full page as fifth argument */ - p = alloc_page(GFP_KERNEL); - if (!p) + data_page = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!data_page) return -ENOMEM; - data_page = page_address(p); strscpy_pad(data_page, data, PAGE_SIZE); } @@ -170,19 +168,20 @@ static int __init do_mount_root(const char *name, const char *fs, MAJOR(ROOT_DEV), MINOR(ROOT_DEV)); out: - if (p) - put_page(p); + kfree(data_page); return ret; } void __init mount_root_generic(char *name, char *pretty_name, int flags) { - struct page *page = alloc_page(GFP_KERNEL); - char *fs_names = page_address(page); + char *fs_names = kmalloc(PAGE_SIZE, GFP_KERNEL); char *p; char b[BDEVNAME_SIZE]; int num_fs, i; + if (!fs_names) + panic("VFS: Unable to mount root fs: not enough memory"); + scnprintf(b, BDEVNAME_SIZE, "unknown-block(%u,%u)", MAJOR(ROOT_DEV), MINOR(ROOT_DEV)); if (root_fs_names) @@ -242,7 +241,7 @@ void __init mount_root_generic(char *name, char *pretty_name, int flags) printk("\n"); panic("VFS: Unable to mount root fs on \"%s\" or %s", pretty_name, b); out: - put_page(page); + kfree(fs_names); } #ifdef CONFIG_ROOT_NFS @@ -343,7 +342,7 @@ static int __init mount_nodev_root(char *root_device_name) int err = -EINVAL; int num_fs, i; - fs_names = (void *)__get_free_page(GFP_KERNEL); + fs_names = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!fs_names) return -EINVAL; num_fs = split_fs_names(fs_names, PAGE_SIZE); @@ -360,7 +359,7 @@ static int __init mount_nodev_root(char *root_device_name) break; } - free_page((unsigned long)fs_names); + kfree(fs_names); return err; } diff --git a/init/initramfs.c b/init/initramfs.c index 58db15fb18fd..20a18fcda48e 100644 --- a/init/initramfs.c +++ b/init/initramfs.c @@ -1,25 +1,28 @@ // SPDX-License-Identifier: GPL-2.0 -#include #include -#include -#include -#include -#include -#include #include -#include #include -#include -#include +#include +#include #include +#include +#include +#include +#include #include #include #include #include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include + +#include #include "do_mounts.h" #include "initramfs_internal.h" @@ -190,26 +193,30 @@ static __initdata gid_t gid; static __initdata unsigned rdev; static __initdata u32 hdr_csum; -static void __init parse_header(char *s) +static int __init parse_header(char *s) { - unsigned long parsed[13]; - int i; + __be32 header[13]; + int ret; - for (i = 0, s += 6; i < 13; i++, s += 8) - parsed[i] = simple_strntoul(s, NULL, 16, 8); + ret = hex2bin((u8 *)header, s + 6, sizeof(header)); + if (ret) { + error("damaged header"); + return ret; + } - ino = parsed[0]; - mode = parsed[1]; - uid = parsed[2]; - gid = parsed[3]; - nlink = parsed[4]; - mtime = parsed[5]; /* breaks in y2106 */ - body_len = parsed[6]; - major = parsed[7]; - minor = parsed[8]; - rdev = new_encode_dev(MKDEV(parsed[9], parsed[10])); - name_len = parsed[11]; - hdr_csum = parsed[12]; + ino = be32_to_cpu(header[0]); + mode = be32_to_cpu(header[1]); + uid = be32_to_cpu(header[2]); + gid = be32_to_cpu(header[3]); + nlink = be32_to_cpu(header[4]); + mtime = be32_to_cpu(header[5]); /* breaks in y2106 */ + body_len = be32_to_cpu(header[6]); + major = be32_to_cpu(header[7]); + minor = be32_to_cpu(header[8]); + rdev = new_encode_dev(MKDEV(be32_to_cpu(header[9]), be32_to_cpu(header[10]))); + name_len = be32_to_cpu(header[11]); + hdr_csum = be32_to_cpu(header[12]); + return 0; } /* Finite-state machine */ @@ -289,7 +296,8 @@ static int __init do_header(void) error("no cpio magic"); return 1; } - parse_header(collected); + if (parse_header(collected)) + return 1; next_header = this_header + N_ALIGN(name_len) + body_len; next_header = (next_header + 3) & ~3; state = SkipIt; diff --git a/init/initramfs_test.c b/init/initramfs_test.c index 2ce38d9a8fd0..bc55306d226d 100644 --- a/init/initramfs_test.c +++ b/init/initramfs_test.c @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include "initramfs_internal.h" @@ -27,7 +29,18 @@ struct initramfs_test_cpio { char *data; }; -static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, char *out) +/* regular newc header format */ +#define CPIO_HDR_FMT "%s%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%s" +/* + * Bogus newc header with "0x" prefixes on the uid, gid, and namesize values. + * parse_header()/simple_str[n]toul() accepted this, contrary to the initramfs + * specification. hex2bin() now fails. + */ +#define CPIO_HDR_OX_INJECT \ + "%s%08x%08x0x%06x0X%06x%08x%08x%08x%08x%08x%08x%08x0x%06x%08x%s" + +static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, + bool inject_ox, char *out) { int i; size_t off = 0; @@ -38,9 +51,8 @@ static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, char *out) size_t thislen; /* +1 to account for nulterm */ - thislen = sprintf(pos, "%s" - "%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x" - "%s", + thislen = sprintf(pos, + inject_ox ? CPIO_HDR_OX_INJECT : CPIO_HDR_FMT, c->magic, c->ino, c->mode, c->uid, c->gid, c->nlink, c->mtime, c->filesize, c->devmajor, c->devminor, c->rdevmajor, c->rdevminor, c->namesize, c->csum, @@ -102,7 +114,7 @@ static void __init initramfs_test_extract(struct kunit *test) /* +3 to cater for any 4-byte end-alignment */ cpio_srcbuf = kzalloc(ARRAY_SIZE(c) * (CPIO_HDRLEN + PATH_MAX + 3), GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); ktime_get_real_ts64(&ts_before); err = unpack_to_rootfs(cpio_srcbuf, len); @@ -177,7 +189,7 @@ static void __init initramfs_test_fname_overrun(struct kunit *test) /* limit overrun to avoid crashes / filp_open() ENAMETOOLONG */ cpio_srcbuf[CPIO_HDRLEN + strlen(c[0].fname) + 20] = '\0'; - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); /* overwrite trailing fname terminator and padding */ suffix_off = len - 1; while (cpio_srcbuf[suffix_off] == '\0') { @@ -219,7 +231,7 @@ static void __init initramfs_test_data(struct kunit *test) cpio_srcbuf = kmalloc(CPIO_HDRLEN + c[0].namesize + c[0].filesize + 6, GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -274,7 +286,7 @@ static void __init initramfs_test_csum(struct kunit *test) cpio_srcbuf = kmalloc(8192, GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -284,7 +296,7 @@ static void __init initramfs_test_csum(struct kunit *test) /* mess up the csum and confirm that unpack fails */ c[0].csum--; - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NOT_NULL(test, err); @@ -306,7 +318,7 @@ static void __init initramfs_test_hardlink(struct kunit *test) { char *err, *cpio_srcbuf; size_t len; - struct kstat st0, st1; + struct kstat st0 = {}, st1 = {}; struct initramfs_test_cpio c[] = { { .magic = "070701", .ino = 1, @@ -330,7 +342,7 @@ static void __init initramfs_test_hardlink(struct kunit *test) cpio_srcbuf = kmalloc(8192, GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -371,7 +383,7 @@ static void __init initramfs_test_many(struct kunit *test) }; c.namesize = 1 + sprintf(thispath, "initramfs_test_many-%d", i); - p += fill_cpio(&c, 1, p); + p += fill_cpio(&c, 1, false, p); } len = p - cpio_srcbuf; @@ -425,7 +437,7 @@ static void __init initramfs_test_fname_pad(struct kunit *test) } }; memcpy(tbufs->padded_fname, "padded_fname", sizeof("padded_fname")); - len = fill_cpio(c, ARRAY_SIZE(c), tbufs->cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, tbufs->cpio_srcbuf); err = unpack_to_rootfs(tbufs->cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -451,7 +463,7 @@ static void __init initramfs_test_fname_path_max(struct kunit *test) { char *err; size_t len; - struct kstat st0, st1; + struct kstat st0 = {}, st1 = {}; char fdata[] = "this file data will not be unpacked"; struct test_fname_path_max { char fname_oversize[PATH_MAX + 1]; @@ -481,7 +493,7 @@ static void __init initramfs_test_fname_path_max(struct kunit *test) memcpy(tbufs->fname_oversize, "fname_oversize", sizeof("fname_oversize") - 1); memcpy(tbufs->fname_ok, "fname_ok", sizeof("fname_ok") - 1); - len = fill_cpio(c, ARRAY_SIZE(c), tbufs->cpio_src); + len = fill_cpio(c, ARRAY_SIZE(c), false, tbufs->cpio_src); /* unpack skips over fname_oversize instead of returning an error */ err = unpack_to_rootfs(tbufs->cpio_src, len); @@ -494,6 +506,45 @@ static void __init initramfs_test_fname_path_max(struct kunit *test) kfree(tbufs); } +static void __init initramfs_test_hdr_hex(struct kunit *test) +{ + char *err; + size_t len; + char fdata[] = "this file data will not be unpacked"; + struct initramfs_test_bufs { + char cpio_src[(CPIO_HDRLEN + PATH_MAX + 3 + sizeof(fdata)) * 2]; + } *tbufs = kzalloc(sizeof(struct initramfs_test_bufs), GFP_KERNEL); + struct initramfs_test_cpio c[] = { { + .magic = "070701", + .ino = 1, + .mode = S_IFREG | 0777, + .uid = 0x123456, + .gid = 0x123457, + .nlink = 1, + .namesize = sizeof("initramfs_test_hdr_hex_0"), + .fname = "initramfs_test_hdr_hex_0", + .filesize = sizeof(fdata), + .data = fdata, + }, { + .magic = "070701", + .ino = 2, + .mode = S_IFDIR | 0777, + .uid = 0x000056, + .gid = 0x000057, + .nlink = 1, + .namesize = sizeof("initramfs_test_hdr_hex_1"), + .fname = "initramfs_test_hdr_hex_1", + } }; + + /* inject_ox=true to add "0x" cpio field prefixes */ + len = fill_cpio(c, ARRAY_SIZE(c), true, tbufs->cpio_src); + + err = unpack_to_rootfs(tbufs->cpio_src, len); + KUNIT_EXPECT_NOT_NULL(test, err); + + kfree(tbufs); +} + /* * The kunit_case/_suite struct cannot be marked as __initdata as this will be * used in debugfs to retrieve results after test has run. @@ -507,11 +558,25 @@ static struct kunit_case __refdata initramfs_test_cases[] = { KUNIT_CASE(initramfs_test_many), KUNIT_CASE(initramfs_test_fname_pad), KUNIT_CASE(initramfs_test_fname_path_max), + KUNIT_CASE(initramfs_test_hdr_hex), {}, }; -static struct kunit_suite initramfs_test_suite = { +static int __init initramfs_test_init(struct kunit_suite *suite) +{ + /* + * unpack_to_rootfs() uses module-static state (victim, byte_count, + * state, ...). The boot-time async do_populate_rootfs() may still be + * running, so wait for it to finish before we call unpack_to_rootfs() + * from the test thread, otherwise the two writers race and crash. + */ + wait_for_initramfs(); + return 0; +} + +static struct kunit_suite __refdata initramfs_test_suite = { .name = "initramfs", + .suite_init = initramfs_test_init, .test_cases = initramfs_test_cases, }; kunit_test_init_section_suites(&initramfs_test_suite); diff --git a/lib/iov_iter.c b/lib/iov_iter.c index 243662af1af7..273919b16161 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -1224,13 +1224,13 @@ const void *dup_iter(struct iov_iter *new, struct iov_iter *old, gfp_t flags) { *new = *old; if (iov_iter_is_bvec(new)) - return new->bvec = kmemdup(new->bvec, - new->nr_segs * sizeof(struct bio_vec), + return new->bvec = kmemdup_array(new->bvec, + new->nr_segs, sizeof(struct bio_vec), flags); else if (iov_iter_is_kvec(new) || iter_is_iovec(new)) /* iovec and kvec have identical layout */ - return new->__iov = kmemdup(new->__iov, - new->nr_segs * sizeof(struct iovec), + return new->__iov = kmemdup_array(new->__iov, + new->nr_segs, sizeof(struct iovec), flags); return NULL; } diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 9f359b31c8d1..a6169e9bcdc9 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -129,13 +129,6 @@ unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base) } EXPORT_SYMBOL(simple_strtoul); -unsigned long simple_strntoul(const char *cp, char **endp, unsigned int base, - size_t max_chars) -{ - return simple_strntoull(cp, endp, base, max_chars); -} -EXPORT_SYMBOL(simple_strntoul); - /** * simple_strtol - convert a string to a signed long * @cp: The start of the string diff --git a/mm/secretmem.c b/mm/secretmem.c index 5f57ac4720d3..4877c262cb1f 100644 --- a/mm/secretmem.c +++ b/mm/secretmem.c @@ -245,8 +245,6 @@ static int secretmem_init_fs_context(struct fs_context *fc) if (!ctx) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC; - fc->s_iflags |= SB_I_NODEV; return 0; } diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 641a180fb35f..8d4db2241cc2 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -91,6 +91,7 @@ TARGETS += pcie_bwctrl TARGETS += perf_events TARGETS += pidfd TARGETS += pid_namespace +TARGETS += pipe TARGETS += power_supply TARGETS += powerpc TARGETS += prctl diff --git a/tools/testing/selftests/namespaces/listns_efault_test.c b/tools/testing/selftests/namespaces/listns_efault_test.c index b570746e917c..26b452c98c66 100644 --- a/tools/testing/selftests/namespaces/listns_efault_test.c +++ b/tools/testing/selftests/namespaces/listns_efault_test.c @@ -38,7 +38,7 @@ TEST(listns_partial_fault_with_ns_cleanup) __u64 *ns_ids; ssize_t ret; long page_size; - pid_t pid, iter_pid; + pid_t pid, iter_pid, ns_pids[5]; int pidfds[5]; int sv[5][2]; int iter_pidfd; @@ -114,6 +114,7 @@ TEST(listns_partial_fault_with_ns_cleanup) pid = create_child(&pidfds[i], CLONE_NEWNS); ASSERT_NE(pid, -1); + ns_pids[i] = pid; if (pid == 0) { close(sv[i][0]); /* Close parent end */ @@ -164,7 +165,7 @@ TEST(listns_partial_fault_with_ns_cleanup) /* Wait for all mount namespace children to exit and cleanup */ for (i = 0; i < 5; i++) { - waitpid(-1, NULL, 0); + waitpid(ns_pids[i], NULL, 0); close(sv[i][0]); close(pidfds[i]); } @@ -175,6 +176,12 @@ TEST(listns_partial_fault_with_ns_cleanup) ASSERT_EQ(ret, iter_pid); close(iter_pidfd); + /* If listns() is not supported the iterator exits cleanly via ENOSYS */ + if (WIFEXITED(status) && WEXITSTATUS(status) == PIDFD_SKIP) { + munmap(map, page_size); + SKIP(return, "listns() not supported"); + } + /* Should have been killed */ ASSERT_TRUE(WIFSIGNALED(status)); ASSERT_EQ(WTERMSIG(status), SIGKILL); @@ -250,7 +257,7 @@ TEST(listns_late_fault_with_ns_cleanup) __u64 *ns_ids; ssize_t ret; long page_size; - pid_t pid, iter_pid; + pid_t pid, iter_pid, ns_pids[10]; int pidfds[10]; int sv[10][2]; int iter_pidfd; @@ -320,6 +327,7 @@ TEST(listns_late_fault_with_ns_cleanup) pid = create_child(&pidfds[i], CLONE_NEWNS); ASSERT_NE(pid, -1); + ns_pids[i] = pid; if (pid == 0) { close(sv[i][0]); /* Close parent end */ @@ -373,7 +381,7 @@ TEST(listns_late_fault_with_ns_cleanup) /* Wait for all children and cleanup */ for (i = 0; i < 10; i++) { - waitpid(-1, NULL, 0); + waitpid(ns_pids[i], NULL, 0); close(sv[i][0]); close(pidfds[i]); } @@ -384,6 +392,12 @@ TEST(listns_late_fault_with_ns_cleanup) ASSERT_EQ(ret, iter_pid); close(iter_pidfd); + /* If listns() is not supported the iterator exits cleanly via ENOSYS */ + if (WIFEXITED(status) && WEXITSTATUS(status) == PIDFD_SKIP) { + munmap(map, page_size); + SKIP(return, "listns() not supported"); + } + /* Should have been killed */ ASSERT_TRUE(WIFSIGNALED(status)); ASSERT_EQ(WTERMSIG(status), SIGKILL); @@ -402,7 +416,7 @@ TEST(listns_mnt_ns_cleanup_on_fault) __u64 *ns_ids; ssize_t ret; long page_size; - pid_t pid, iter_pid; + pid_t pid, iter_pid, ns_pids[8]; int pidfds[8]; int sv[8][2]; int iter_pidfd; @@ -462,6 +476,7 @@ TEST(listns_mnt_ns_cleanup_on_fault) pid = create_child(&pidfds[i], CLONE_NEWNS); ASSERT_NE(pid, -1); + ns_pids[i] = pid; if (pid == 0) { close(sv[i][0]); /* Close parent end */ @@ -508,7 +523,7 @@ TEST(listns_mnt_ns_cleanup_on_fault) /* Wait for children and cleanup */ for (i = 0; i < 8; i++) { - waitpid(-1, NULL, 0); + waitpid(ns_pids[i], NULL, 0); close(sv[i][0]); close(pidfds[i]); } @@ -519,6 +534,12 @@ TEST(listns_mnt_ns_cleanup_on_fault) ASSERT_EQ(ret, iter_pid); close(iter_pidfd); + /* If listns() is not supported the iterator exits cleanly via ENOSYS */ + if (WIFEXITED(status) && WEXITSTATUS(status) == PIDFD_SKIP) { + munmap(map, page_size); + SKIP(return, "listns() not supported"); + } + /* Should have been killed */ ASSERT_TRUE(WIFSIGNALED(status)); ASSERT_EQ(WTERMSIG(status), SIGKILL); diff --git a/tools/testing/selftests/namespaces/nsid_test.c b/tools/testing/selftests/namespaces/nsid_test.c index b4a14c6693a5..46dc838cba82 100644 --- a/tools/testing/selftests/namespaces/nsid_test.c +++ b/tools/testing/selftests/namespaces/nsid_test.c @@ -25,14 +25,24 @@ /* Fixture for tests that create child processes */ FIXTURE(nsid) { pid_t child_pid; + pid_t grandchild_pid; }; FIXTURE_SETUP(nsid) { self->child_pid = 0; + self->grandchild_pid = 0; } FIXTURE_TEARDOWN(nsid) { - /* Clean up any child process that may still be running */ + /* + * Kill grandchild first: timens_separate and pidns_separate fork a + * grandchild that calls pause(). It is reparented to init on child + * exit and keeps the test runner's tap pipe open, hanging the runner. + */ + if (self->grandchild_pid > 0) { + kill(self->grandchild_pid, SIGKILL); + waitpid(self->grandchild_pid, NULL, 0); + } if (self->child_pid > 0) { kill(self->child_pid, SIGKILL); waitpid(self->child_pid, NULL, 0); @@ -676,6 +686,7 @@ TEST_F(nsid, timens_separate) pid_t grandchild_pid; ASSERT_EQ(read(pipefd[0], &grandchild_pid, sizeof(grandchild_pid)), sizeof(grandchild_pid)); + self->grandchild_pid = grandchild_pid; close(pipefd[0]); /* Open grandchild's time namespace */ @@ -797,6 +808,7 @@ TEST_F(nsid, pidns_separate) pid_t grandchild_pid; ASSERT_EQ(read(pipefd[0], &grandchild_pid, sizeof(grandchild_pid)), sizeof(grandchild_pid)); + self->grandchild_pid = grandchild_pid; close(pipefd[0]); /* Open grandchild's PID namespace */ diff --git a/tools/testing/selftests/pipe/.gitignore b/tools/testing/selftests/pipe/.gitignore new file mode 100644 index 000000000000..20b549361a15 --- /dev/null +++ b/tools/testing/selftests/pipe/.gitignore @@ -0,0 +1 @@ +pipe_bench diff --git a/tools/testing/selftests/pipe/Makefile b/tools/testing/selftests/pipe/Makefile new file mode 100644 index 000000000000..1810c680117b --- /dev/null +++ b/tools/testing/selftests/pipe/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2026 Meta Platforms, Inc. and affiliates +# Copyright (c) 2026 Breno Leitao + +CFLAGS += -O2 -Wall -Wextra -pthread + +TEST_GEN_PROGS := pipe_bench + +include ../lib.mk diff --git a/tools/testing/selftests/pipe/pipe_bench.c b/tools/testing/selftests/pipe/pipe_bench.c new file mode 100644 index 000000000000..7e96429b8fb4 --- /dev/null +++ b/tools/testing/selftests/pipe/pipe_bench.c @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * pipe_bench - exercise concurrent pipe operation + * + * N writer threads hammer a single pipe with multi-page writes; M reader + * threads drain it. Each writer records its own write() latency histogram. + * Multi-page writes (msgsize >= PAGE_SIZE) force the loop in + * anon_pipe_write() to call alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT) under + * pipe->mutex, which is the critical section the patch shrinks. + * + * By default the benchmark sweeps writers in {1, 2, 5} x readers in + * {1, 5, 10} and prints one block per configuration so two runs (e.g. + * baseline vs patched) can be diffed directly. Pass -w and -r to run a + * single configuration instead. Pass --memory-pressure to spawn stress-ng + * alongside the sweep so the per-page alloc_page() path under pipe->mutex + * has to dip into reclaim. + * + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates + * Copyright (c) 2026 Breno Leitao + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define HIST_BUCKETS 32 + +static size_t g_msgsize = 16 * 4096; +static int g_duration = 3; +static int g_pipe_size = 1024 * 1024; +static int g_memory_pressure; + +static atomic_int g_stop; +static int g_pipe[2]; + +struct wstats { + uint64_t writes; + uint64_t bytes; + uint64_t lat_sum_ns; + uint64_t lat_max_ns; + uint64_t lat_hist[HIST_BUCKETS]; + char *buf; +}; + +struct rstats { + char *buf; +}; + +struct hist_totals { + uint64_t writes; + uint64_t bytes; + uint64_t lat_sum; + uint64_t lat_max; +}; + +static inline uint64_t now_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; +} + +static inline int log2_bucket(uint64_t v) +{ + int b = 0; + + if (!v) + return 0; + while (v >>= 1) + b++; + return b < HIST_BUCKETS ? b : HIST_BUCKETS - 1; +} + +static void *writer(void *arg) +{ + struct wstats *s = arg; + + while (!atomic_load_explicit(&g_stop, memory_order_relaxed)) { + uint64_t t0 = now_ns(); + ssize_t n = write(g_pipe[1], s->buf, g_msgsize); + uint64_t dt = now_ns() - t0; + + if (n > 0) { + s->writes++; + s->bytes += (uint64_t)n; + s->lat_sum_ns += dt; + if (dt > s->lat_max_ns) + s->lat_max_ns = dt; + s->lat_hist[log2_bucket(dt)]++; + } else if (n < 0 && (errno == EPIPE || errno == EBADF)) { + break; + } + } + return NULL; +} + +static void *reader(void *arg) +{ + struct rstats *s = arg; + + /* + * Drain until EOF (write end closed by main). g_stop is not checked + * here on purpose: writers may be blocked in write() with the pipe + * full when g_stop is set, so the reader must keep draining until + * main closes the write end. + */ + for (;;) { + ssize_t n = read(g_pipe[0], s->buf, g_msgsize); + + if (n <= 0) + break; + } + return NULL; +} + +/* Sum per-writer stats and per-bucket counts into the caller's aggregates. */ +static void aggregate_wstats(struct wstats *all, int nw, + uint64_t agg[HIST_BUCKETS], + struct hist_totals *t) +{ + memset(t, 0, sizeof(*t)); + for (int i = 0; i < nw; i++) { + t->writes += all[i].writes; + t->bytes += all[i].bytes; + t->lat_sum += all[i].lat_sum_ns; + if (all[i].lat_max_ns > t->lat_max) + t->lat_max = all[i].lat_max_ns; + for (int b = 0; b < HIST_BUCKETS; b++) + agg[b] += all[i].lat_hist[b]; + } +} + +/* + * Walk @agg in order, returning the inclusive upper bound (in ns) of the + * log2 bucket where the running sum first reaches @target. + * + * A percentile is undefined with zero samples, and with very low sample + * counts integer truncation could make @target zero -- then "cum >= 0" + * would latch on the first (possibly empty) bucket. Callers must pass + * @target >= 1. + */ +static uint64_t bucket_at(const uint64_t agg[HIST_BUCKETS], uint64_t target) +{ + uint64_t cum = 0; + + for (int b = 0; b < HIST_BUCKETS; b++) { + /* HIST_BUCKETS <= 63, so (b + 1) is always a safe shift. */ + uint64_t upper = (1ULL << (b + 1)) - 1; + + cum += agg[b]; + if (cum >= target) + return upper; + } + return 0; +} + +static void compute_p50_p99(const uint64_t agg[HIST_BUCKETS], uint64_t writes, + uint64_t *p50, uint64_t *p99) +{ + uint64_t p50_target, p99_target; + + *p50 = *p99 = 0; + if (!writes) + return; + + p50_target = writes * 50 / 100; + p99_target = writes * 99 / 100; + if (!p50_target) + p50_target = 1; + if (!p99_target) + p99_target = 1; + + *p50 = bucket_at(agg, p50_target); + *p99 = bucket_at(agg, p99_target); +} + +static void print_summary(int nw, int nr, const struct hist_totals *t, + uint64_t p50, uint64_t p99) +{ + double sec = g_duration; + uint64_t avg_ns = t->writes ? t->lat_sum / t->writes : 0; + + printf("config: writers=%d readers=%d msgsize=%zu duration=%d pipe_size=%d memory_pressure=%s\n", + nw, nr, g_msgsize, g_duration, g_pipe_size, + g_memory_pressure ? "yes" : "no"); + printf("writes: total=%llu rate=%.0f/s\n", + (unsigned long long)t->writes, (double)t->writes / sec); + printf("throughput_MBps: %.2f\n", + ((double)t->bytes / sec) / (1024.0 * 1024.0)); + printf("lat_avg_ns: %llu\n", (unsigned long long)avg_ns); + printf("lat_p50_ns_upper: %llu\n", (unsigned long long)p50); + printf("lat_p99_ns_upper: %llu\n", (unsigned long long)p99); + printf("lat_max_ns: %llu\n", (unsigned long long)t->lat_max); +} + +static void summarize(struct wstats *all, int nw, int nr) +{ + uint64_t agg[HIST_BUCKETS] = {0}; + struct hist_totals t; + uint64_t p50, p99; + + aggregate_wstats(all, nw, agg, &t); + compute_p50_p99(agg, t.writes, &p50, &p99); + print_summary(nw, nr, &t, p50, p99); +} + +/* + * Child branch of fork(): restore SIGPIPE to default (parent ignores it), + * exec stress-ng, and on failure write the reason into @hs_wr before + * exiting. The parent observes EOF on hs_wr (closed via O_CLOEXEC) when + * exec succeeds. + */ +static void stress_ng_child(int hs_wr) __attribute__((noreturn)); +static void stress_ng_child(int hs_wr) +{ + char errbuf[256]; + + signal(SIGPIPE, SIG_DFL); + execlp("stress-ng", "stress-ng", + "--vm", "4", "--vm-bytes", "80%", + "--vm-method", "all", + (char *)NULL); + snprintf(errbuf, sizeof(errbuf), + "exec stress-ng failed: %s\n", strerror(errno)); + (void)!write(hs_wr, errbuf, strlen(errbuf)); + _exit(127); +} + +/* + * Read from the O_CLOEXEC handshake pipe. Anything readable means the + * child wrote an error before exec; EOF (n == 0) means the write-end + * closed because exec succeeded. Returns 0 on exec success, -1 if the + * child failed and was reaped. + */ +static int stress_ng_wait_handshake(int hs_rd, pid_t pid) +{ + struct pollfd pfd = { .fd = hs_rd, .events = POLLIN }; + char errbuf[256]; + int status; + int ret; + + ret = poll(&pfd, 1, 500); + if (ret <= 0) + return 0; + + ssize_t n = read(hs_rd, errbuf, sizeof(errbuf) - 1); + + if (n > 0) { + errbuf[n] = '\0'; + fputs(errbuf, stderr); + waitpid(pid, &status, 0); + return -1; + } + return 0; +} + +static pid_t spawn_stress_ng(void) +{ + int hs[2]; + pid_t pid; + + /* + * Handshake pipe: child writes one byte and _exit()s on exec + * failure. On exec success the O_CLOEXEC flag closes the write + * end, which the parent observes as EOF. This makes the "is + * stress-ng on $PATH?" check fail fast rather than silently. + */ + if (pipe2(hs, O_CLOEXEC) < 0) { + perror("pipe2"); + return -1; + } + + pid = fork(); + if (pid < 0) { + perror("fork"); + close(hs[0]); + close(hs[1]); + return -1; + } + if (pid == 0) { + close(hs[0]); + stress_ng_child(hs[1]); + } + + close(hs[1]); + if (stress_ng_wait_handshake(hs[0], pid) < 0) { + close(hs[0]); + return -1; + } + close(hs[0]); + + /* Give stress-ng a moment to map its VM regions before measuring. */ + sleep(1); + return pid; +} + +static void kill_stress_ng(pid_t pid) +{ + int status; + + if (pid <= 0) + return; + kill(pid, SIGTERM); + for (int i = 0; i < 20; i++) { + if (waitpid(pid, &status, WNOHANG) > 0) + return; + usleep(100 * 1000); + } + kill(pid, SIGKILL); + waitpid(pid, &status, 0); +} + +/* + * Allocate per-thread page-aligned buffers in main so a failed + * aligned_alloc() aborts the run before any thread starts. Workers used + * to allocate their own buffer and return NULL on failure, which left + * peers blocked in write()/read() with nobody to unblock them. + */ +static int alloc_thread_bufs(struct wstats *ws, int nw, + struct rstats *rs, int nr) +{ + for (int i = 0; i < nw; i++) { + ws[i].buf = aligned_alloc(4096, g_msgsize); + if (!ws[i].buf) { + fprintf(stderr, "writer %d: aligned_alloc(%zu) failed\n", + i, g_msgsize); + return -1; + } + memset(ws[i].buf, 0xAA, g_msgsize); + } + for (int i = 0; i < nr; i++) { + rs[i].buf = aligned_alloc(4096, g_msgsize); + if (!rs[i].buf) { + fprintf(stderr, "reader %d: aligned_alloc(%zu) failed\n", + i, g_msgsize); + return -1; + } + } + return 0; +} + +static void free_thread_bufs(struct wstats *ws, int nw, + struct rstats *rs, int nr) +{ + if (ws) + for (int i = 0; i < nw; i++) + free(ws[i].buf); + if (rs) + for (int i = 0; i < nr; i++) + free(rs[i].buf); +} + +static int start_readers(pthread_t *rt, struct rstats *rs, int nr, + int *created) +{ + for (int i = 0; i < nr; i++) { + int err = pthread_create(&rt[i], NULL, reader, &rs[i]); + + if (err) { + fprintf(stderr, "pthread_create reader %d: %s\n", + i, strerror(err)); + return -1; + } + (*created)++; + } + return 0; +} + +static int start_writers(pthread_t *wt, struct wstats *ws, int nw, + int *created) +{ + for (int i = 0; i < nw; i++) { + int err = pthread_create(&wt[i], NULL, writer, &ws[i]); + + if (err) { + fprintf(stderr, "pthread_create writer %d: %s\n", + i, strerror(err)); + return -1; + } + (*created)++; + } + return 0; +} + +static int open_bench_pipe(void) +{ + if (pipe(g_pipe) < 0) { + perror("pipe"); + return -1; + } + if (fcntl(g_pipe[1], F_SETPIPE_SZ, g_pipe_size) < 0) + perror("F_SETPIPE_SZ (continuing)"); + return 0; +} + +/* + * Normal termination: g_stop tells writers to leave the loop after the + * current write() returns. Closing the shared write-end fd means once + * the in-flight writes drain, readers see EOF and exit. Writers are not + * unblocked by EPIPE here -- g_pipe[0] stays open so readers can keep + * draining. + * + * Error path: some threads may have been created and others skipped, so + * writers could be blocked in write() with no reader making progress. + * Close both ends -- closing the read end is what delivers EPIPE to a + * blocked writer. + */ +static void stop_and_join(pthread_t *wt, int nw_created, + pthread_t *rt, int nr_created, int rc) +{ + atomic_store(&g_stop, 1); + close(g_pipe[1]); + if (rc < 0) + close(g_pipe[0]); + for (int i = 0; i < nw_created; i++) + pthread_join(wt[i], NULL); + for (int i = 0; i < nr_created; i++) + pthread_join(rt[i], NULL); + if (rc == 0) + close(g_pipe[0]); +} + +static int run_one(int nw, int nr) +{ + pthread_t *wt = NULL, *rt = NULL; + struct wstats *ws = NULL; + struct rstats *rs = NULL; + int nw_created = 0, nr_created = 0; + int rc = 0; + + atomic_store(&g_stop, 0); + + if (open_bench_pipe() < 0) + return -1; + + wt = calloc((size_t)nw, sizeof(*wt)); + rt = calloc((size_t)nr, sizeof(*rt)); + ws = calloc((size_t)nw, sizeof(*ws)); + rs = calloc((size_t)nr, sizeof(*rs)); + if (!wt || !rt || !ws || !rs) { + fprintf(stderr, "alloc failed\n"); + rc = -1; + goto teardown; + } + + if (alloc_thread_bufs(ws, nw, rs, nr) < 0) { + rc = -1; + goto teardown; + } + + if (start_readers(rt, rs, nr, &nr_created) < 0 || + start_writers(wt, ws, nw, &nw_created) < 0) { + rc = -1; + goto teardown; + } + + sleep((unsigned int)g_duration); + +teardown: + stop_and_join(wt, nw_created, rt, nr_created, rc); + + if (rc == 0) { + summarize(ws, nw, nr); + fflush(stdout); + } + + free_thread_bufs(ws, nw, rs, nr); + free(wt); + free(rt); + free(ws); + free(rs); + return rc; +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "usage: %s [-w writers] [-r readers] [-s msgsize] [-d secs] [-p pipe_size] [--memory-pressure]\n" + " default: sweep writers={1,2,5} x readers={1,5,10}\n" + " --memory-pressure: spawn stress-ng (--vm 4 --vm-bytes 80%% --vm-method all) for the run\n", + prog); +} + +static int parse_args(int argc, char **argv, + int *writers_override, int *readers_override) +{ + static const struct option long_opts[] = { + {"memory-pressure", no_argument, NULL, 'M'}, + {0, 0, 0, 0}, + }; + int opt; + + while ((opt = getopt_long(argc, argv, "w:r:s:d:p:", + long_opts, NULL)) != -1) { + switch (opt) { + case 'w': + *writers_override = atoi(optarg); + break; + case 'r': + *readers_override = atoi(optarg); + break; + case 's': + g_msgsize = (size_t)atol(optarg); + break; + case 'd': + g_duration = atoi(optarg); + break; + case 'p': + g_pipe_size = atoi(optarg); + break; + case 'M': + g_memory_pressure = 1; + break; + default: + usage(argv[0]); + return -1; + } + } + return 0; +} + +/* + * aligned_alloc(4096, size) requires size to be a multiple of the + * alignment (C11); glibc returns NULL otherwise, which would make + * writer/reader threads silently exit and the run report zero writes. + * Validate up front instead. + */ +static int validate_args(void) +{ + if (g_msgsize == 0 || g_msgsize % 4096 != 0) { + fprintf(stderr, + "msgsize must be a positive multiple of 4096 (got %zu)\n", + g_msgsize); + return -1; + } + if (g_duration <= 0) { + fprintf(stderr, "duration must be > 0 seconds (got %d)\n", + g_duration); + return -1; + } + if (g_pipe_size <= 0) { + fprintf(stderr, "pipe_size must be > 0 bytes (got %d)\n", + g_pipe_size); + return -1; + } + return 0; +} + +static int run_sweep(void) +{ + static const int writers_sweep[] = {1, 2, 5}; + static const int readers_sweep[] = {1, 5, 10}; + + for (size_t i = 0; i < ARRAY_SIZE(writers_sweep); i++) { + for (size_t j = 0; j < ARRAY_SIZE(readers_sweep); j++) { + printf("---\n"); + if (run_one(writers_sweep[i], readers_sweep[j]) < 0) + return -1; + } + } + return 0; +} + +int main(int argc, char **argv) +{ + int writers_override = 0, readers_override = 0; + pid_t stress_pid = -1; + int rc = 0; + + if (parse_args(argc, argv, &writers_override, &readers_override) < 0) + return 1; + if (validate_args() < 0) + return 1; + + signal(SIGPIPE, SIG_IGN); + setvbuf(stdout, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOLBF, 0); + + fprintf(stderr, "pid=%d\n", getpid()); + fflush(stderr); + + if (g_memory_pressure) { + stress_pid = spawn_stress_ng(); + if (stress_pid < 0) { + fprintf(stderr, + "memory_pressure requested but stress-ng could not be spawned\n"); + return 1; + } + } + + if (writers_override > 0 || readers_override > 0) { + int nw = writers_override > 0 ? writers_override : 1; + int nr = readers_override > 0 ? readers_override : 1; + + rc = run_one(nw, nr) < 0 ? 1 : 0; + } else { + rc = run_sweep() < 0 ? 1 : 0; + } + + kill_stress_ng(stress_pid); + return rc; +} diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c index 69c9d6d546b2..80f201035d77 100644 --- a/virt/kvm/guest_memfd.c +++ b/virt/kvm/guest_memfd.c @@ -973,8 +973,6 @@ static int kvm_gmem_init_fs_context(struct fs_context *fc) if (!init_pseudo(fc, GUEST_MEMFD_MAGIC)) return -ENOMEM; - fc->s_iflags |= SB_I_NOEXEC; - fc->s_iflags |= SB_I_NODEV; ctx = fc->fs_private; ctx->ops = &kvm_gmem_super_operations;