From 682e87dbc0a18a703fd4a746b5ef7ee22fb559cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:27:38 +0000 Subject: [PATCH 001/105] build(deps): bump github/codeql-action from 3.27.9 to 3.28.0 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.9 to 3.28.0. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/df409f7d9260372bd5f19e5b04e83cb3c43714ae...48ab28a6f5dbc2a99bf1e0131198dd8f1df78169) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7f36ea064..1cccb5f13 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -49,7 +49,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -74,7 +74,7 @@ jobs: meson compile -C build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: category: "/language:${{matrix.language}}" From efa630fa491566938c08508f64f41b590cfedc07 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 11:15:30 +0100 Subject: [PATCH 002/105] Restore compat ABI in 'struct fuse_file_info' This fixes commit a5eb7f2 "Enable parallel direct writes on the same file" and restores the ABI by moving the parallel_direct_writes bit. Given that there were already several releases with this commit we will still need an so version bump. Signed-off-by: Bernd Schubert --- include/fuse.h | 31 ++++++++++++++++--------------- include/fuse_common.h | 8 ++++---- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/include/fuse.h b/include/fuse.h index 396358d8d..49ba7d91f 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -280,21 +280,6 @@ struct fuse_config { */ int nullpath_ok; - /** - * Allow parallel direct-io writes to operate on the same file. - * - * FUSE implementations which do not handle parallel writes on - * same file/region should NOT enable this option at all as it - * might lead to data inconsistencies. - * - * For the FUSE implementations which have their own mechanism - * of cache/data integrity are beneficiaries of this setting as - * it now open doors to parallel writes on the same file (without - * enabling this setting, all direct writes on the same file are - * serialized, resulting in huge data bandwidth loss). - */ - int parallel_direct_writes; - /** * These 3 options are used by libfuse internally and * should not be touched. @@ -310,6 +295,22 @@ struct fuse_config { */ unsigned int fmask; unsigned int dmask; + + /** + * Allow parallel direct-io writes to operate on the same file. + * + * FUSE implementations which do not handle parallel writes on + * same file/region should NOT enable this option at all as it + * might lead to data inconsistencies. + * + * For the FUSE implementations which have their own mechanism + * of cache/data integrity are beneficiaries of this setting as + * it now open doors to parallel writes on the same file (without + * enabling this setting, all direct writes on the same file are + * serialized, resulting in huge data bandwidth loss). + */ + int parallel_direct_writes; + }; diff --git a/include/fuse_common.h b/include/fuse_common.h index e6c3b5d44..c46aacba6 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -67,10 +67,6 @@ struct fuse_file_info { the file/directory is closed. */ unsigned int keep_cache : 1; - /** Can be filled by open/create, to allow parallel direct writes on this - file */ - unsigned int parallel_direct_writes : 1; - /** Indicates a flush operation. Set in flush operation, also maybe set in highlevel lock operation and lowlevel release operation. */ @@ -95,6 +91,10 @@ struct fuse_file_info { on close. */ unsigned int noflush : 1; + /** Can be filled by open/create, to allow parallel direct writes on this + file */ + unsigned int parallel_direct_writes : 1; + /** Padding. Reserved for future use*/ unsigned int padding : 23; unsigned int padding2 : 32; From 9f7132314727447c92b8a6e23452386af26c37ea Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 11:22:14 +0100 Subject: [PATCH 003/105] Restore compat ABI in 'struct fuse_config' This fixes commit dad15aee26835 "Add no_rofd_flush mount option" and restores the ABI by moving the no_rofd_flush field. Given that there were already several releases with this commit we will still need an so version bump. Signed-off-by: Bernd Schubert --- include/fuse.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/fuse.h b/include/fuse.h index 49ba7d91f..78d856b56 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -252,14 +252,6 @@ struct fuse_config { */ int auto_cache; - /** - * By default, fuse waits for all pending writes to complete - * and calls the FLUSH operation on close(2) of every fuse fd. - * With this option, wait and FLUSH are not done for read-only - * fuse fd, similar to the behavior of NFS/SMB clients. - */ - int no_rofd_flush; - /** * The timeout in seconds for which file attributes are cached * for the purpose of checking if auto_cache should flush the @@ -296,6 +288,14 @@ struct fuse_config { unsigned int fmask; unsigned int dmask; + /** + * By default, fuse waits for all pending writes to complete + * and calls the FLUSH operation on close(2) of every fuse fd. + * With this option, wait and FLUSH are not done for read-only + * fuse fd, similar to the behavior of NFS/SMB clients. + */ + int no_rofd_flush; + /** * Allow parallel direct-io writes to operate on the same file. * From 68b84d92ada8e934abdb9fe4fa7bd094582365f5 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 13:14:43 +0100 Subject: [PATCH 004/105] Use (u)int32_t for struct fuse_config and extend the struct Enforce 32-bit integers for struct fuse_config and add future ABI fields to that struct. Signed-off-by: Bernd Schubert --- include/fuse.h | 58 +++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/include/fuse.h b/include/fuse.h index 78d856b56..2fc1b6fa9 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -103,22 +103,22 @@ struct fuse_config { * If `set_gid` is non-zero, the st_gid attribute of each file * is overwritten with the value of `gid`. */ - int set_gid; - unsigned int gid; + int32_t set_gid; + uint32_t gid; /** * If `set_uid` is non-zero, the st_uid attribute of each file * is overwritten with the value of `uid`. */ - int set_uid; - unsigned int uid; + int32_t set_uid; + uint32_t uid; /** * If `set_mode` is non-zero, the any permissions bits set in * `umask` are unset in the st_mode attribute of each file. */ - int set_mode; - unsigned int umask; + int32_t set_mode; + uint32_t umask; /** * The timeout in seconds for which name lookups will be @@ -145,14 +145,14 @@ struct fuse_config { /** * Allow requests to be interrupted */ - int intr; + int32_t intr; /** * Specify which signal number to send to the filesystem when * a request is interrupted. The default is hardcoded to * USR1. */ - int intr_signal; + int32_t intr_signal; /** * Normally, FUSE assigns inodes to paths only for as long as @@ -164,7 +164,7 @@ struct fuse_config { * A number of -1 means that inodes will be remembered for the * entire life-time of the file-system process. */ - int remember; + int32_t remember; /** * The default behavior is that if an open file is deleted, @@ -182,7 +182,7 @@ struct fuse_config { * ENOENT): read(2), write(2), fsync(2), close(2), f*xattr(2), * ftruncate(2), fstat(2), fchmod(2), fchown(2) */ - int hard_remove; + int32_t hard_remove; /** * Honor the st_ino field in the functions getattr() and @@ -195,7 +195,7 @@ struct fuse_config { * Note that this does *not* affect the inode that libfuse * and the kernel use internally (also called the "nodeid"). */ - int use_ino; + int32_t use_ino; /** * If use_ino option is not given, still try to fill in the @@ -204,7 +204,7 @@ struct fuse_config { * found there will be used. Otherwise it will be set to -1. * If use_ino option is given, this option is ignored. */ - int readdir_ino; + int32_t readdir_ino; /** * This option disables the use of page cache (file content cache) @@ -223,7 +223,7 @@ struct fuse_config { * `direct_io` field of `struct fuse_file_info` - overwriting * any value that was put there by the file system. */ - int direct_io; + int32_t direct_io; /** * This option disables flushing the cache of the file @@ -242,7 +242,7 @@ struct fuse_config { * `keep_cache` field of `struct fuse_file_info` - overwriting * any value that was put there by the file system. */ - int kernel_cache; + int32_t kernel_cache; /** * This option is an alternative to `kernel_cache`. Instead of @@ -250,14 +250,14 @@ struct fuse_config { * invalidated on open(2) if if the modification time or the * size of the file has changed since it was last opened. */ - int auto_cache; + int32_t auto_cache; - /** + /* * The timeout in seconds for which file attributes are cached * for the purpose of checking if auto_cache should flush the * file data on open. */ - int ac_attr_timeout_set; + int32_t ac_attr_timeout_set; double ac_attr_timeout; /** @@ -270,23 +270,23 @@ struct fuse_config { * operations the path will be provided only if the struct * fuse_file_info argument is NULL. */ - int nullpath_ok; + int32_t nullpath_ok; /** * These 3 options are used by libfuse internally and * should not be touched. */ - int show_help; + int32_t show_help; char *modules; - int debug; + int32_t debug; /** * `fmask` and `dmask` function the same way as `umask`, but apply * to files and directories separately. If non-zero, `fmask` and * `dmask` take precedence over the `umask` setting. */ - unsigned int fmask; - unsigned int dmask; + uint32_t fmask; + uint32_t dmask; /** * By default, fuse waits for all pending writes to complete @@ -294,7 +294,7 @@ struct fuse_config { * With this option, wait and FLUSH are not done for read-only * fuse fd, similar to the behavior of NFS/SMB clients. */ - int no_rofd_flush; + int32_t no_rofd_flush; /** * Allow parallel direct-io writes to operate on the same file. @@ -309,8 +309,18 @@ struct fuse_config { * enabling this setting, all direct writes on the same file are * serialized, resulting in huge data bandwidth loss). */ - int parallel_direct_writes; + int32_t parallel_direct_writes; + + /** + * Reserved for future use. + */ + uint32_t flags; + + /** + * Reserved for future use. + */ + uint64_t reserved[48]; }; From 2eb7767e2c2a4e6f9f21f7b10f79d4860ebe2831 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 13:19:41 +0100 Subject: [PATCH 005/105] Use (u)int32_t for struct fuse_file_info Enforce 32 bit integers for #struct fuse_file_info'. Signed-off-by: Bernd Schubert --- include/fuse_common.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index c46aacba6..bb66206b1 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -48,7 +48,7 @@ extern "C" { */ struct fuse_file_info { /** Open flags. Available in open(), release() and create() */ - int flags; + int32_t flags; /** In case of a write operation indicates if this was caused by a delayed write from the page cache. If so, then the @@ -56,44 +56,44 @@ struct fuse_file_info { the *fh* value may not match the *fh* value that would have been sent with the corresponding individual write requests if write caching had been disabled. */ - unsigned int writepage : 1; + uint32_t writepage : 1; /** Can be filled in by open/create, to use direct I/O on this file. */ - unsigned int direct_io : 1; + uint32_t direct_io : 1; /** Can be filled in by open and opendir. It signals the kernel that any currently cached data (ie., data that the filesystem provided the last time the file/directory was open) need not be invalidated when the file/directory is closed. */ - unsigned int keep_cache : 1; + uint32_t keep_cache : 1; /** Indicates a flush operation. Set in flush operation, also maybe set in highlevel lock operation and lowlevel release operation. */ - unsigned int flush : 1; + uint32_t flush : 1; /** Can be filled in by open, to indicate that the file is not seekable. */ - unsigned int nonseekable : 1; + uint32_t nonseekable : 1; /* Indicates that flock locks for this file should be released. If set, lock_owner shall contain a valid value. May only be set in ->release(). */ - unsigned int flock_release : 1; + uint32_t flock_release : 1; /** Can be filled in by opendir. It signals the kernel to enable caching of entries returned by readdir(). Has no effect when set in other contexts (in particular it does nothing when set by open()). */ - unsigned int cache_readdir : 1; + uint32_t cache_readdir : 1; /** Can be filled in by open, to indicate that flush is not needed on close. */ - unsigned int noflush : 1; + uint32_t noflush : 1; /** Can be filled by open/create, to allow parallel direct writes on this file */ - unsigned int parallel_direct_writes : 1; + uint32_t parallel_direct_writes : 1; /** Padding. Reserved for future use*/ unsigned int padding : 23; From ef2f0c2c08d0a70cbb2d3decf4cadaeab050cc8b Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 13:21:58 +0100 Subject: [PATCH 006/105] Add padding3 in struct fuse_file_info pahole was showing a 4 byte hole - add another padding to fill that hole. Signed-off-by: Bernd Schubert --- include/fuse_common.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/fuse_common.h b/include/fuse_common.h index bb66206b1..0b0ba9be1 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -98,6 +98,7 @@ struct fuse_file_info { /** Padding. Reserved for future use*/ unsigned int padding : 23; unsigned int padding2 : 32; + unsigned int padding3 : 32; /** File handle id. May be filled in by filesystem in create, * open, and opendir(). Available in most other file operations on the From e7482fbeaf132988eb024742adb6c2f6d4c5b42a Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 13:39:53 +0100 Subject: [PATCH 007/105] Use (u)int32_t for struct fuse_conn_info Signed-off-by: Bernd Schubert --- include/fuse_common.h | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index 0b0ba9be1..d9b7944a6 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -96,9 +96,9 @@ struct fuse_file_info { uint32_t parallel_direct_writes : 1; /** Padding. Reserved for future use*/ - unsigned int padding : 23; - unsigned int padding2 : 32; - unsigned int padding3 : 32; + uint32_t padding : 23; + uint32_t padding2 : 32; + uint32_t padding3 : 32; /** File handle id. May be filled in by filesystem in create, * open, and opendir(). Available in most other file operations on the @@ -531,17 +531,17 @@ struct fuse_conn_info { /** * Major version of the protocol (read-only) */ - unsigned proto_major; + uint32_t proto_major; /** * Minor version of the protocol (read-only) */ - unsigned proto_minor; + uint32_t proto_minor; /** * Maximum size of the write buffer */ - unsigned max_write; + uint32_t max_write; /** * Maximum size of read requests. A value of zero indicates no @@ -555,17 +555,17 @@ struct fuse_conn_info { * in the future, specifying the mount option will no longer * be necessary. */ - unsigned max_read; + uint32_t max_read; /** * Maximum readahead */ - unsigned max_readahead; + uint32_t max_readahead; /** * Capability flags that the kernel supports (read-only) */ - unsigned capable; + uint32_t capable; /** * Capability flags that the filesystem wants to enable. @@ -573,7 +573,7 @@ struct fuse_conn_info { * libfuse attempts to initialize this field with * reasonable default values before calling the init() handler. */ - unsigned want; + uint32_t want; /** * Maximum number of pending "background" requests. A @@ -603,7 +603,7 @@ struct fuse_conn_info { * call actually blocks, so these are also limited to one per * thread). */ - unsigned max_background; + uint32_t max_background; /** * Kernel congestion threshold parameter. If the number of pending @@ -613,7 +613,7 @@ struct fuse_conn_info { * adjust its algorithms accordingly (e.g. by putting a waiting thread * to sleep instead of using a busy-loop). */ - unsigned congestion_threshold; + uint32_t congestion_threshold; /** * When FUSE_CAP_WRITEBACK_CACHE is enabled, the kernel is responsible @@ -630,7 +630,7 @@ struct fuse_conn_info { * nano-second resolution. Filesystems supporting only second resolution * should set this to 1000000000. */ - unsigned time_gran; + uint32_t time_gran; /** * When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed @@ -650,7 +650,7 @@ struct fuse_conn_info { */ #define FUSE_BACKING_STACKED_UNDER (0) #define FUSE_BACKING_STACKED_OVER (1) - unsigned max_backing_stack_depth; + uint32_t max_backing_stack_depth; /** * Disable FUSE_INTERRUPT requests. @@ -660,12 +660,12 @@ struct fuse_conn_info { * 2) Return ENOSYS for the reply of FUSE_INTERRUPT request to * inform the kernel not to send the FUSE_INTERRUPT request. */ - unsigned no_interrupt; + uint32_t no_interrupt; /** * For future use. */ - unsigned reserved[20]; + uint32_t reserved[20]; }; struct fuse_session; From 15d924c450eb8346607e5fba24d3b60e842e365b Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 13:43:58 +0100 Subject: [PATCH 008/105] Use single bit for struct fuse_file_info::no_interrupt Now that we know the exact size of the integer we can use a single bit and reserve the other bits. Signed-off-by: Bernd Schubert --- include/fuse_common.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index d9b7944a6..ee3c4e60a 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -660,7 +660,10 @@ struct fuse_conn_info { * 2) Return ENOSYS for the reply of FUSE_INTERRUPT request to * inform the kernel not to send the FUSE_INTERRUPT request. */ - uint32_t no_interrupt; + uint32_t no_interrupt : 1; + + /* reserved bits for future use */ + uint32_t padding : 31; /** * For future use. From b646fa9195ea15efa9303dbe0b39cb542a2cc796 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 14:01:04 +0100 Subject: [PATCH 009/105] struct fuse_file_info extension Add a flags and reserved fields to struct fuse_file_info and add a static assert on the size. Also add another static assert for 'struct fuse_conn_info' Signed-off-by: Bernd Schubert --- include/fuse_common.h | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index ee3c4e60a..1999eaa4f 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -25,10 +25,19 @@ #include "fuse_log.h" #include #include +#include #define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min)) #define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION) +#if (defined(__cplusplus) && __cplusplus >= 201103L) || \ + (!defined(__cplusplus) && defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 201112L) +#define fuse_static_assert(condition, message) static_assert(condition, message) +#else +#define fuse_static_assert(condition, message) +#endif + #ifdef __cplusplus extern "C" { #endif @@ -116,9 +125,14 @@ struct fuse_file_info { * create and open. It is used to create a passthrough connection * between FUSE file and backing file. */ int32_t backing_id; -}; + /** struct fuse_file_info api and abi flags */ + uint64_t compat_flags; + uint64_t reserved[2]; +}; +fuse_static_assert(sizeof(struct fuse_file_info) == 64, + "fuse_file_info size mismatch"); /** * Configuration parameters passed to fuse_session_loop_mt() and @@ -670,6 +684,8 @@ struct fuse_conn_info { */ uint32_t reserved[20]; }; +fuse_static_assert(sizeof(struct fuse_conn_info) == 128, + "Size of struct fuse_conn_info must be 128 bytes"); struct fuse_session; struct fuse_pollhandle; From 20b2edf61db70cff874e41c1ba750a560737331b Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 13:48:51 +0100 Subject: [PATCH 010/105] Use uint32_t for libfuse_version Should be used internally only, but is still in a common file - we better use arch independent values. Signed-off-by: Bernd Schubert --- include/fuse_common.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index 1999eaa4f..2c866c44f 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -934,10 +934,10 @@ struct fuse_bufvec { */ struct libfuse_version { - int major; - int minor; - int hotfix; - int padding; + uint32_t major; + uint32_t minor; + uint32_t hotfix; + uint32_t padding; }; /* Initialize bufvec with a single buffer of given size */ From 3d90402f9084edb90ddb00d6ccd8c2183e6df140 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 14:41:15 +0100 Subject: [PATCH 011/105] fuse_lowlevel.c: define FUSE_MAXOP as CUSE_INIT We don't want FUSE_MAXOP calculated at compilation time, as the ABI limit is CUSE_INIT - better use that value directly. Signed-off-by: Bernd Schubert --- lib/fuse_lowlevel.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index e48efa527..a0d8647df 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2712,7 +2712,12 @@ static struct { [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, }; -#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) +/* + * For ABI compatibility we cannot allow higher values than CUSE_INIT. + * Without ABI compatibility we could use the size of the array. + * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) + */ +#define FUSE_MAXOP (CUSE_INIT + 1) static const char *opname(enum fuse_opcode opcode) { From 24f5b129c4e1b03ebbd05ac0c7673f306facea1a Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 15:10:03 +0100 Subject: [PATCH 012/105] Add 64-bit conn::{capable,want}_ext fields The previous fields are left for ABI compatibility, although it is not beautiful to add that complexity when we have to increase the so-version as we had ABI breakage anyway. example/printcap is simplified to use an array, as every line would have needed to be modified anyway. Missing 'FUSE_CAP_PASSTHROUGH' was added. Signed-off-by: Bernd Schubert --- example/passthrough_ll.c | 22 ++--- example/printcap.c | 113 ++++++++++++------------- include/fuse_common.h | 40 ++++++++- lib/cuse_lowlevel.c | 6 +- lib/fuse.c | 3 +- lib/fuse_lowlevel.c | 172 ++++++++++++++++++++++++--------------- test/test_write_cache.c | 2 +- 7 files changed, 216 insertions(+), 142 deletions(-) diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index 309d8dd56..5f1fde9a7 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -168,18 +168,20 @@ static bool lo_debug(fuse_req_t req) static void lo_init(void *userdata, struct fuse_conn_info *conn) { - struct lo_data *lo = (struct lo_data*) userdata; - - if (lo->writeback && - conn->capable & FUSE_CAP_WRITEBACK_CACHE) { - if (lo->debug) - fuse_log(FUSE_LOG_DEBUG, "lo_init: activating writeback\n"); - conn->want |= FUSE_CAP_WRITEBACK_CACHE; + struct lo_data *lo = (struct lo_data *)userdata; + bool has_flag; + + if (lo->writeback) { + has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); + if (lo->debug && has_flag) + fuse_log(FUSE_LOG_DEBUG, + "lo_init: activating writeback\n"); } if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) { - if (lo->debug) - fuse_log(FUSE_LOG_DEBUG, "lo_init: activating flock locks\n"); - conn->want |= FUSE_CAP_FLOCK_LOCKS; + has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + if (lo->debug && has_flag) + fuse_log(FUSE_LOG_DEBUG, + "lo_init: activating flock locks\n"); } /* Disable the receiving and processing of FUSE_INTERRUPT requests */ diff --git a/example/printcap.c b/example/printcap.c index 01b4d3f83..82a759884 100644 --- a/example/printcap.c +++ b/example/printcap.c @@ -29,68 +29,63 @@ struct fuse_session *se; -static void pc_init(void *userdata, - struct fuse_conn_info *conn) +// Define a structure to hold capability information +struct cap_info { + uint64_t flag; + const char *name; +}; + +// Define an array of all capabilities +static const struct cap_info capabilities[] = { + {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"}, + {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"}, + {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"}, + {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"}, + {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"}, + {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"}, + {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"}, + {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"}, + {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"}, + {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"}, + {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"}, + {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"}, + {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"}, + {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"}, + {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"}, + {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"}, + {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"}, + {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"}, + {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"}, + {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"}, + {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"}, + {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"}, + {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"}, + {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"}, + {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"}, + {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"}, + {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"}, + {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"}, + // Add any new capabilities here + {0, NULL} // Sentinel to mark the end of the array +}; + +static void print_capabilities(struct fuse_conn_info *conn) +{ + printf("Capabilities:\n"); + for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) { + if (fuse_get_feature_flag(conn, cap->flag)) { + printf("\t%s\n", cap->name); + } + } +} + +static void pc_init(void *userdata, struct fuse_conn_info *conn) { (void) userdata; - + printf("Protocol version: %d.%d\n", conn->proto_major, conn->proto_minor); - printf("Capabilities:\n"); - if(conn->capable & FUSE_CAP_ASYNC_READ) - printf("\tFUSE_CAP_ASYNC_READ\n"); - if(conn->capable & FUSE_CAP_POSIX_LOCKS) - printf("\tFUSE_CAP_POSIX_LOCKS\n"); - if(conn->capable & FUSE_CAP_ATOMIC_O_TRUNC) - printf("\tFUSE_CAP_ATOMIC_O_TRUNC\n"); - if(conn->capable & FUSE_CAP_EXPORT_SUPPORT) - printf("\tFUSE_CAP_EXPORT_SUPPORT\n"); - if(conn->capable & FUSE_CAP_DONT_MASK) - printf("\tFUSE_CAP_DONT_MASK\n"); - if(conn->capable & FUSE_CAP_SPLICE_MOVE) - printf("\tFUSE_CAP_SPLICE_MOVE\n"); - if(conn->capable & FUSE_CAP_SPLICE_READ) - printf("\tFUSE_CAP_SPLICE_READ\n"); - if(conn->capable & FUSE_CAP_SPLICE_WRITE) - printf("\tFUSE_CAP_SPLICE_WRITE\n"); - if(conn->capable & FUSE_CAP_FLOCK_LOCKS) - printf("\tFUSE_CAP_FLOCK_LOCKS\n"); - if(conn->capable & FUSE_CAP_IOCTL_DIR) - printf("\tFUSE_CAP_IOCTL_DIR\n"); - if(conn->capable & FUSE_CAP_AUTO_INVAL_DATA) - printf("\tFUSE_CAP_AUTO_INVAL_DATA\n"); - if(conn->capable & FUSE_CAP_READDIRPLUS) - printf("\tFUSE_CAP_READDIRPLUS\n"); - if(conn->capable & FUSE_CAP_READDIRPLUS_AUTO) - printf("\tFUSE_CAP_READDIRPLUS_AUTO\n"); - if(conn->capable & FUSE_CAP_ASYNC_DIO) - printf("\tFUSE_CAP_ASYNC_DIO\n"); - if(conn->capable & FUSE_CAP_WRITEBACK_CACHE) - printf("\tFUSE_CAP_WRITEBACK_CACHE\n"); - if(conn->capable & FUSE_CAP_NO_OPEN_SUPPORT) - printf("\tFUSE_CAP_NO_OPEN_SUPPORT\n"); - if(conn->capable & FUSE_CAP_PARALLEL_DIROPS) - printf("\tFUSE_CAP_PARALLEL_DIROPS\n"); - if(conn->capable & FUSE_CAP_POSIX_ACL) - printf("\tFUSE_CAP_POSIX_ACL\n"); - if(conn->capable & FUSE_CAP_CACHE_SYMLINKS) - printf("\tFUSE_CAP_CACHE_SYMLINKS\n"); - if(conn->capable & FUSE_CAP_NO_OPENDIR_SUPPORT) - printf("\tFUSE_CAP_NO_OPENDIR_SUPPORT\n"); - if(conn->capable & FUSE_CAP_EXPLICIT_INVAL_DATA) - printf("\tFUSE_CAP_EXPLICIT_INVAL_DATA\n"); - if(conn->capable & FUSE_CAP_EXPIRE_ONLY) - printf("\tFUSE_CAP_EXPIRE_ONLY\n"); - if(conn->capable & FUSE_CAP_SETXATTR_EXT) - printf("\tFUSE_CAP_SETXATTR_EXT\n"); - if(conn->capable & FUSE_CAP_HANDLE_KILLPRIV) - printf("\tFUSE_CAP_HANDLE_KILLPRIV\n"); - if(conn->capable & FUSE_CAP_HANDLE_KILLPRIV_V2) - printf("\tFUSE_CAP_HANDLE_KILLPRIV_V2\n"); - if(conn->capable & FUSE_CAP_DIRECT_IO_ALLOW_MMAP) - printf("\tFUSE_CAP_DIRECT_IO_ALLOW_MMAP\n"); - if (conn->capable & FUSE_CAP_NO_EXPORT_SUPPORT) - printf("\tFUSE_CAP_NO_EXPORT_SUPPORT\n"); + print_capabilities(conn); fuse_session_exit(se); } @@ -110,7 +105,7 @@ int main(int argc, char **argv) perror("mkdtemp"); return 1; } - + printf("FUSE library version %s\n", fuse_pkgversion()); fuse_lowlevel_version(); diff --git a/include/fuse_common.h b/include/fuse_common.h index 2c866c44f..fbf16ba6e 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -540,6 +540,10 @@ struct fuse_loop_config_v1 { * Some of the elements are read-write, these can be changed to * indicate the value requested by the filesystem. The requested * value must usually be smaller than the indicated value. + * + * Note: The `capable` and `want` fields are limited to 32 bits for + * ABI compatibility. For full 64-bit capability support, use the + * `capable_ext` and `want_ext` fields instead. */ struct fuse_conn_info { /** @@ -578,6 +582,8 @@ struct fuse_conn_info { /** * Capability flags that the kernel supports (read-only) + * + * Deprecated left over for ABI compatibility, use capable_ext */ uint32_t capable; @@ -586,6 +592,10 @@ struct fuse_conn_info { * * libfuse attempts to initialize this field with * reasonable default values before calling the init() handler. + * + * Deprecated left over for ABI compatibility. + * Use want_ext with the helper functions + * fuse_set_feature_flag() / fuse_unset_feature_flag() */ uint32_t want; @@ -679,10 +689,26 @@ struct fuse_conn_info { /* reserved bits for future use */ uint32_t padding : 31; + /** + * Extended capability flags that the kernel supports (read-only) + * This field provides full 64-bit capability support. + */ + uint64_t capable_ext; + + /** + * Extended capability flags that the filesystem wants to enable. + * This field provides full 64-bit capability support. + * + * Don't set this field directly, but use the helper functions + * fuse_set_feature_flag() / fuse_unset_feature_flag() + * + */ + uint64_t want_ext; + /** * For future use. */ - uint32_t reserved[20]; + uint32_t reserved[16]; }; fuse_static_assert(sizeof(struct fuse_conn_info) == 128, "Size of struct fuse_conn_info must be 128 bytes"); @@ -1076,8 +1102,8 @@ void fuse_loop_cfg_convert(struct fuse_loop_config *config, static inline bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag) { - if (conn->capable & flag) { - conn->want |= flag; + if (conn->capable_ext & flag) { + conn->want_ext |= flag; return true; } return false; @@ -1086,7 +1112,13 @@ static inline bool fuse_set_feature_flag(struct fuse_conn_info *conn, static inline void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag) { - conn->want &= ~flag; + conn->want_ext &= ~flag; +} + +static inline bool fuse_get_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + return conn->capable_ext & flag ? true : false; } /* ----------------------------------------------------------- * diff --git a/lib/cuse_lowlevel.c b/lib/cuse_lowlevel.c index cd687870b..5387f8430 100644 --- a/lib/cuse_lowlevel.c +++ b/lib/cuse_lowlevel.c @@ -208,8 +208,10 @@ void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) } se->conn.proto_major = arg->major; se->conn.proto_minor = arg->minor; - se->conn.capable = 0; - se->conn.want = 0; + + /* XXX This is not right.*/ + se->conn.capable_ext = 0; + se->conn.want_ext = 0; if (arg->major < 7) { fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n", diff --git a/lib/fuse.c b/lib/fuse.c index b327bab49..a1537afd1 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -2619,8 +2619,7 @@ static void fuse_lib_init(void *data, struct fuse_conn_info *conn) struct fuse *f = (struct fuse *) data; fuse_create_context(f); - if(conn->capable & FUSE_CAP_EXPORT_SUPPORT) - conn->want |= FUSE_CAP_EXPORT_SUPPORT; + fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT); fuse_fs_init(f->fs, conn, &f->conf); if (f->conf.intr) { diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index a0d8647df..d84c6781e 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -728,7 +728,7 @@ static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, goto fallback; if (se->conn.proto_minor < 14 || - !(se->conn.want & FUSE_CAP_SPLICE_WRITE)) + !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE)) goto fallback; llp = fuse_ll_get_pipe(se); @@ -869,7 +869,7 @@ static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, splice_flags = 0; if ((flags & FUSE_BUF_SPLICE_MOVE) && - (se->conn.want & FUSE_CAP_SPLICE_MOVE)) + (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE)) splice_flags |= SPLICE_F_MOVE; if (se->io != NULL && se->io->splice_send != NULL) { @@ -1410,7 +1410,7 @@ static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) if (req->se->op.open) req->se->op.open(req, nodeid, &fi); - else if (req->se->conn.want & FUSE_CAP_NO_OPEN_SUPPORT) + else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT) fuse_reply_err(req, ENOSYS); else fuse_reply_open(req, &fi); @@ -1568,7 +1568,7 @@ static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) if (req->se->op.opendir) req->se->op.opendir(req, nodeid, &fi); - else if (req->se->conn.want & FUSE_CAP_NO_OPENDIR_SUPPORT) + else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT) fuse_reply_err(req, ENOSYS); else fuse_reply_open(req, &fi); @@ -1651,7 +1651,7 @@ static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_session *se = req->se; - unsigned int xattr_ext = !!(se->conn.want & FUSE_CAP_SETXATTR_EXT); + unsigned int xattr_ext = !!(se->conn.want_ext & FUSE_CAP_SETXATTR_EXT); struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *) inarg; char *name = xattr_ext ? PARAM(arg) : (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE; @@ -1882,7 +1882,7 @@ static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) struct fuse_file_info fi; if (flags & FUSE_IOCTL_DIR && - !(req->se->conn.want & FUSE_CAP_IOCTL_DIR)) { + !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) { fuse_reply_err(req, ENOTTY); return; } @@ -1997,6 +1997,27 @@ static bool want_flags_valid(uint64_t capable, uint64_t want) return true; } +/** + * Get the wanted capability flags, converting from old format if necessary + * Also applies the first 32 bits of capable_ext to capable + * + */ +static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn, + uint64_t want_ext_default) +{ + /* Convert want to want_ext if necessary */ + if (conn->want != 0) { + if (conn->want_ext != want_ext_default) { + fuse_log(FUSE_LOG_ERR, + "fuse: both 'want' and 'want_ext' are set\n"); + return -EINVAL; + } + conn->want_ext |= conn->want; + } + + return 0; +} + /* Prevent bogus data races (bogus since "init" is called before * multi-threading becomes relevant */ static __attribute__((no_sanitize("thread"))) @@ -2021,8 +2042,8 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) } se->conn.proto_major = arg->major; se->conn.proto_minor = arg->minor; - se->conn.capable = 0; - se->conn.want = 0; + se->conn.capable_ext = 0; + se->conn.want_ext = 0; memset(&outarg, 0, sizeof(outarg)); outarg.major = FUSE_KERNEL_VERSION; @@ -2048,45 +2069,45 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) if (inargflags & FUSE_INIT_EXT) inargflags = inargflags | (uint64_t) arg->flags2 << 32; if (inargflags & FUSE_ASYNC_READ) - se->conn.capable |= FUSE_CAP_ASYNC_READ; + se->conn.capable_ext |= FUSE_CAP_ASYNC_READ; if (inargflags & FUSE_POSIX_LOCKS) - se->conn.capable |= FUSE_CAP_POSIX_LOCKS; + se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS; if (inargflags & FUSE_ATOMIC_O_TRUNC) - se->conn.capable |= FUSE_CAP_ATOMIC_O_TRUNC; + se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC; if (inargflags & FUSE_EXPORT_SUPPORT) - se->conn.capable |= FUSE_CAP_EXPORT_SUPPORT; + se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT; if (inargflags & FUSE_DONT_MASK) - se->conn.capable |= FUSE_CAP_DONT_MASK; + se->conn.capable_ext |= FUSE_CAP_DONT_MASK; if (inargflags & FUSE_FLOCK_LOCKS) - se->conn.capable |= FUSE_CAP_FLOCK_LOCKS; + se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS; if (inargflags & FUSE_AUTO_INVAL_DATA) - se->conn.capable |= FUSE_CAP_AUTO_INVAL_DATA; + se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA; if (inargflags & FUSE_DO_READDIRPLUS) - se->conn.capable |= FUSE_CAP_READDIRPLUS; + se->conn.capable_ext |= FUSE_CAP_READDIRPLUS; if (inargflags & FUSE_READDIRPLUS_AUTO) - se->conn.capable |= FUSE_CAP_READDIRPLUS_AUTO; + se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO; if (inargflags & FUSE_ASYNC_DIO) - se->conn.capable |= FUSE_CAP_ASYNC_DIO; + se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO; if (inargflags & FUSE_WRITEBACK_CACHE) - se->conn.capable |= FUSE_CAP_WRITEBACK_CACHE; + se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE; if (inargflags & FUSE_NO_OPEN_SUPPORT) - se->conn.capable |= FUSE_CAP_NO_OPEN_SUPPORT; + se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT; if (inargflags & FUSE_PARALLEL_DIROPS) - se->conn.capable |= FUSE_CAP_PARALLEL_DIROPS; + se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS; if (inargflags & FUSE_POSIX_ACL) - se->conn.capable |= FUSE_CAP_POSIX_ACL; + se->conn.capable_ext |= FUSE_CAP_POSIX_ACL; if (inargflags & FUSE_HANDLE_KILLPRIV) - se->conn.capable |= FUSE_CAP_HANDLE_KILLPRIV; + se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV; if (inargflags & FUSE_HANDLE_KILLPRIV_V2) - se->conn.capable |= FUSE_CAP_HANDLE_KILLPRIV_V2; + se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2; if (inargflags & FUSE_CACHE_SYMLINKS) - se->conn.capable |= FUSE_CAP_CACHE_SYMLINKS; + se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS; if (inargflags & FUSE_NO_OPENDIR_SUPPORT) - se->conn.capable |= FUSE_CAP_NO_OPENDIR_SUPPORT; + se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT; if (inargflags & FUSE_EXPLICIT_INVAL_DATA) - se->conn.capable |= FUSE_CAP_EXPLICIT_INVAL_DATA; + se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA; if (inargflags & FUSE_SETXATTR_EXT) - se->conn.capable |= FUSE_CAP_SETXATTR_EXT; + se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT; if (!(inargflags & FUSE_MAX_PAGES)) { size_t max_bufsize = FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize() @@ -2097,13 +2118,13 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) buf_reallocable = false; } if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP) - se->conn.capable |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP; + se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP; if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY)) - se->conn.capable |= FUSE_CAP_EXPIRE_ONLY; + se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY; if (inargflags & FUSE_PASSTHROUGH) - se->conn.capable |= FUSE_CAP_PASSTHROUGH; + se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH; if (inargflags & FUSE_NO_EXPORT_SUPPORT) - se->conn.capable |= FUSE_CAP_NO_EXPORT_SUPPORT; + se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT; } else { se->conn.max_readahead = 0; } @@ -2112,16 +2133,17 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) #ifdef HAVE_SPLICE #ifdef HAVE_VMSPLICE if ((se->io == NULL) || (se->io->splice_send != NULL)) { - se->conn.capable |= FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE; + se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE; } #endif if ((se->io == NULL) || (se->io->splice_receive != NULL)) { - se->conn.capable |= FUSE_CAP_SPLICE_READ; + se->conn.capable_ext |= FUSE_CAP_SPLICE_READ; } #endif } if (se->conn.proto_minor >= 18) - se->conn.capable |= FUSE_CAP_IOCTL_DIR; + se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR; /* Default settings for modern filesystems. * @@ -2130,9 +2152,10 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) * we can finally enable them by default (as long as they're * supported by the kernel). */ -#define LL_SET_DEFAULT(cond, cap) \ - if ((cond) && (se->conn.capable & (cap))) \ - se->conn.want |= (cap) +#define LL_SET_DEFAULT(cond, cap) \ + if ((cond)) \ + fuse_set_feature_flag(&se->conn, cap) + LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ); LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA); LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO); @@ -2154,10 +2177,31 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) se->conn.time_gran = 1; se->got_init = 1; - if (se->op.init) + if (se->op.init) { + uint32_t want_ext_default = se->conn.want_ext; + int rc; + + // Apply the first 32 bits of capable_ext to capable + se->conn.capable = + (uint32_t)(se->conn.capable_ext & 0xFFFFFFFF); + se->op.init(se->userdata, &se->conn); - if (!want_flags_valid(se->conn.capable, se->conn.want)) { + /* + * se->conn.want is 32-bit value and deprecated in favour of + * se->conn.want_ext + * Userspace might still use conn.want - we need to convert it + */ + rc = convert_to_conn_want_ext(&se->conn, want_ext_default); + if (rc != 0) { + fuse_reply_err(req, EPROTO); + se->error = -EPROTO; + fuse_session_exit(se); + return; + } + } + + if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) { fuse_reply_err(req, EPROTO); se->error = -EPROTO; fuse_session_exit(se); @@ -2196,45 +2240,45 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) by the max_write option */ outargflags |= FUSE_BIG_WRITES; - if (se->conn.want & FUSE_CAP_ASYNC_READ) + if (se->conn.want_ext & FUSE_CAP_ASYNC_READ) outargflags |= FUSE_ASYNC_READ; - if (se->conn.want & FUSE_CAP_POSIX_LOCKS) + if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS) outargflags |= FUSE_POSIX_LOCKS; - if (se->conn.want & FUSE_CAP_ATOMIC_O_TRUNC) + if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC) outargflags |= FUSE_ATOMIC_O_TRUNC; - if (se->conn.want & FUSE_CAP_EXPORT_SUPPORT) + if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT) outargflags |= FUSE_EXPORT_SUPPORT; - if (se->conn.want & FUSE_CAP_DONT_MASK) + if (se->conn.want_ext & FUSE_CAP_DONT_MASK) outargflags |= FUSE_DONT_MASK; - if (se->conn.want & FUSE_CAP_FLOCK_LOCKS) + if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS) outargflags |= FUSE_FLOCK_LOCKS; - if (se->conn.want & FUSE_CAP_AUTO_INVAL_DATA) + if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA) outargflags |= FUSE_AUTO_INVAL_DATA; - if (se->conn.want & FUSE_CAP_READDIRPLUS) + if (se->conn.want_ext & FUSE_CAP_READDIRPLUS) outargflags |= FUSE_DO_READDIRPLUS; - if (se->conn.want & FUSE_CAP_READDIRPLUS_AUTO) + if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO) outargflags |= FUSE_READDIRPLUS_AUTO; - if (se->conn.want & FUSE_CAP_ASYNC_DIO) + if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO) outargflags |= FUSE_ASYNC_DIO; - if (se->conn.want & FUSE_CAP_WRITEBACK_CACHE) + if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE) outargflags |= FUSE_WRITEBACK_CACHE; - if (se->conn.want & FUSE_CAP_PARALLEL_DIROPS) + if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS) outargflags |= FUSE_PARALLEL_DIROPS; - if (se->conn.want & FUSE_CAP_POSIX_ACL) + if (se->conn.want_ext & FUSE_CAP_POSIX_ACL) outargflags |= FUSE_POSIX_ACL; - if (se->conn.want & FUSE_CAP_HANDLE_KILLPRIV) + if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV) outargflags |= FUSE_HANDLE_KILLPRIV; - if (se->conn.want & FUSE_CAP_HANDLE_KILLPRIV_V2) + if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2) outargflags |= FUSE_HANDLE_KILLPRIV_V2; - if (se->conn.want & FUSE_CAP_CACHE_SYMLINKS) + if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS) outargflags |= FUSE_CACHE_SYMLINKS; - if (se->conn.want & FUSE_CAP_EXPLICIT_INVAL_DATA) + if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA) outargflags |= FUSE_EXPLICIT_INVAL_DATA; - if (se->conn.want & FUSE_CAP_SETXATTR_EXT) + if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT) outargflags |= FUSE_SETXATTR_EXT; - if (se->conn.want & FUSE_CAP_DIRECT_IO_ALLOW_MMAP) + if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP) outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP; - if (se->conn.want & FUSE_CAP_PASSTHROUGH) { + if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) { outargflags |= FUSE_PASSTHROUGH; /* * outarg.max_stack_depth includes the fuse stack layer, @@ -2242,7 +2286,7 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) */ outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1; } - if (se->conn.want & FUSE_CAP_NO_EXPORT_SUPPORT) + if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT) outargflags |= FUSE_NO_EXPORT_SUPPORT; if (inargflags & FUSE_INIT_EXT) { @@ -2282,7 +2326,7 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) outarg.congestion_threshold); fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n", outarg.time_gran); - if (se->conn.want & FUSE_CAP_PASSTHROUGH) + if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n", outarg.max_stack_depth); } @@ -2467,7 +2511,7 @@ int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent if (!se) return -EINVAL; - if (!(se->conn.capable & FUSE_CAP_EXPIRE_ONLY)) + if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY)) return -ENOSYS; return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY); @@ -3006,7 +3050,7 @@ static int _fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf tmpbuf; if (se->conn.proto_minor < 14 || - !(se->conn.want & FUSE_CAP_SPLICE_READ)) + !(se->conn.want_ext & FUSE_CAP_SPLICE_READ)) goto fallback; llp = fuse_ll_get_pipe(se); diff --git a/test/test_write_cache.c b/test/test_write_cache.c index cc827c7f3..d3c7af086 100644 --- a/test/test_write_cache.c +++ b/test/test_write_cache.c @@ -65,7 +65,7 @@ static void tfs_init (void *userdata, struct fuse_conn_info *conn) (void) userdata; if(options.writeback) { - assert(conn->capable & FUSE_CAP_WRITEBACK_CACHE); + assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE)); conn->want |= FUSE_CAP_WRITEBACK_CACHE; } } From df15498c9a6f0faf661664849822796ab746f1e3 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Sat, 28 Dec 2024 18:32:34 +0100 Subject: [PATCH 013/105] Convert FUSE_CAP defines to enum fuse_capability Signed-off-by: Bernd Schubert --- include/fuse_common.h | 637 +++++++++++++++++++++--------------------- 1 file changed, 322 insertions(+), 315 deletions(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index fbf16ba6e..aba1c15c2 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -174,348 +174,355 @@ struct fuse_loop_config_v1 { * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' * **************************************************************************/ -/** - * Indicates that the filesystem supports asynchronous read requests. - * - * If this capability is not requested/available, the kernel will - * ensure that there is at most one pending read request per - * file-handle at any time, and will attempt to order read requests by - * increasing offset. - * - * This feature is enabled by default when supported by the kernel. - */ -#define FUSE_CAP_ASYNC_READ (1 << 0) +enum fuse_capability { + /** + * Indicates that the filesystem supports asynchronous read requests. + * + * If this capability is not requested/available, the kernel will + * ensure that there is at most one pending read request per + * file-handle at any time, and will attempt to order read requests by + * increasing offset. + * + * This feature is enabled by default when supported by the kernel. + */ + FUSE_CAP_ASYNC_READ = (1 << 0), -/** - * Indicates that the filesystem supports "remote" locking. - * - * This feature is enabled by default when supported by the kernel, - * and if getlk() and setlk() handlers are implemented. - */ -#define FUSE_CAP_POSIX_LOCKS (1 << 1) + /** + * Indicates that the filesystem supports "remote" locking. + * + * This feature is enabled by default when supported by the kernel, + * and if getlk() and setlk() handlers are implemented. + */ + FUSE_CAP_POSIX_LOCKS = (1 << 1), -/** - * Indicates that the filesystem supports the O_TRUNC open flag. If - * disabled, and an application specifies O_TRUNC, fuse first calls - * truncate() and then open() with O_TRUNC filtered out. - * - * This feature is enabled by default when supported by the kernel. - */ -#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3) + /** + * Indicates that the filesystem supports the O_TRUNC open flag. If + * disabled, and an application specifies O_TRUNC, fuse first calls + * truncate() and then open() with O_TRUNC filtered out. + * + * This feature is enabled by default when supported by the kernel. + */ + FUSE_CAP_ATOMIC_O_TRUNC = (1 << 3), -/** - * Indicates that the filesystem supports lookups of "." and "..". - * - * When this flag is set, the filesystem must be prepared to receive requests - * for invalid inodes (i.e., for which a FORGET request was received or - * which have been used in a previous instance of the filesystem daemon) and - * must not reuse node-ids (even when setting generation numbers). - * - * This feature is disabled by default. - */ -#define FUSE_CAP_EXPORT_SUPPORT (1 << 4) + /** + * Indicates that the filesystem supports lookups of "." and "..". + * + * When this flag is set, the filesystem must be prepared to receive requests + * for invalid inodes (i.e., for which a FORGET request was received or + * which have been used in a previous instance of the filesystem daemon) and + * must not reuse node-ids (even when setting generation numbers). + * + * This feature is disabled by default. + */ + FUSE_CAP_EXPORT_SUPPORT = (1 << 4), -/** - * Indicates that the kernel should not apply the umask to the - * file mode on create operations. - * - * This feature is disabled by default. - */ -#define FUSE_CAP_DONT_MASK (1 << 6) + /** + * Indicates that the kernel should not apply the umask to the + * file mode on create operations. + * + * This feature is disabled by default. + */ + FUSE_CAP_DONT_MASK = (1 << 6), -/** - * Indicates that libfuse should try to use splice() when writing to - * the fuse device. This may improve performance. - * - * This feature is disabled by default. - */ -#define FUSE_CAP_SPLICE_WRITE (1 << 7) + /** + * Indicates that libfuse should try to use splice() when writing to + * the fuse device. This may improve performance. + * + * This feature is disabled by default. + */ + FUSE_CAP_SPLICE_WRITE = (1 << 7), -/** - * Indicates that libfuse should try to move pages instead of copying when - * writing to / reading from the fuse device. This may improve performance. - * - * This feature is disabled by default. - */ -#define FUSE_CAP_SPLICE_MOVE (1 << 8) + /** + * Indicates that libfuse should try to move pages instead of copying when + * writing to / reading from the fuse device. This may improve performance. + * + * This feature is disabled by default. + */ + FUSE_CAP_SPLICE_MOVE = (1 << 8), -/** - * Indicates that libfuse should try to use splice() when reading from - * the fuse device. This may improve performance. - * - * This feature is enabled by default when supported by the kernel and - * if the filesystem implements a write_buf() handler. - */ -#define FUSE_CAP_SPLICE_READ (1 << 9) + /** + * Indicates that libfuse should try to use splice() when reading from + * the fuse device. This may improve performance. + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements a write_buf() handler. + */ + FUSE_CAP_SPLICE_READ = (1 << 9), -/** - * If set, the calls to flock(2) will be emulated using POSIX locks and must - * then be handled by the filesystem's setlock() handler. - * - * If not set, flock(2) calls will be handled by the FUSE kernel module - * internally (so any access that does not go through the kernel cannot be taken - * into account). - * - * This feature is enabled by default when supported by the kernel and - * if the filesystem implements a flock() handler. - */ -#define FUSE_CAP_FLOCK_LOCKS (1 << 10) + /** + * If set, the calls to flock(2) will be emulated using POSIX locks and must + * then be handled by the filesystem's setlock() handler. + * + * If not set, flock(2) calls will be handled by the FUSE kernel module + * internally (so any access that does not go through the kernel cannot be taken + * into account). + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements a flock() handler. + */ + FUSE_CAP_FLOCK_LOCKS = (1 << 10), -/** - * Indicates that the filesystem supports ioctl's on directories. - * - * This feature is enabled by default when supported by the kernel. - */ -#define FUSE_CAP_IOCTL_DIR (1 << 11) + /** + * Indicates that the filesystem supports ioctl's on directories. + * + * This feature is enabled by default when supported by the kernel. + */ + FUSE_CAP_IOCTL_DIR = (1 << 11), -/** - * Traditionally, while a file is open the FUSE kernel module only - * asks the filesystem for an update of the file's attributes when a - * client attempts to read beyond EOF. This is unsuitable for - * e.g. network filesystems, where the file contents may change - * without the kernel knowing about it. - * - * If this flag is set, FUSE will check the validity of the attributes - * on every read. If the attributes are no longer valid (i.e., if the - * *attr_timeout* passed to fuse_reply_attr() or set in `struct - * fuse_entry_param` has passed), it will first issue a `getattr` - * request. If the new mtime differs from the previous value, any - * cached file *contents* will be invalidated as well. - * - * This flag should always be set when available. If all file changes - * go through the kernel, *attr_timeout* should be set to a very large - * number to avoid unnecessary getattr() calls. - * - * This feature is enabled by default when supported by the kernel. - */ -#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12) + /** + * Traditionally, while a file is open the FUSE kernel module only + * asks the filesystem for an update of the file's attributes when a + * client attempts to read beyond EOF. This is unsuitable for + * e.g. network filesystems, where the file contents may change + * without the kernel knowing about it. + * + * If this flag is set, FUSE will check the validity of the attributes + * on every read. If the attributes are no longer valid (i.e., if the + * *attr_timeout* passed to fuse_reply_attr() or set in `struct + * fuse_entry_param` has passed), it will first issue a `getattr` + * request. If the new mtime differs from the previous value, any + * cached file *contents* will be invalidated as well. + * + * This flag should always be set when available. If all file changes + * go through the kernel, *attr_timeout* should be set to a very large + * number to avoid unnecessary getattr() calls. + * + * This feature is enabled by default when supported by the kernel. + */ + FUSE_CAP_AUTO_INVAL_DATA = (1 << 12), -/** - * Indicates that the filesystem supports readdirplus. - * - * This feature is enabled by default when supported by the kernel and if the - * filesystem implements a readdirplus() handler. - */ -#define FUSE_CAP_READDIRPLUS (1 << 13) + /** + * Indicates that the filesystem supports readdirplus. + * + * This feature is enabled by default when supported by the kernel and if the + * filesystem implements a readdirplus() handler. + */ + FUSE_CAP_READDIRPLUS = (1 << 13), -/** - * Indicates that the filesystem supports adaptive readdirplus. - * - * If FUSE_CAP_READDIRPLUS is not set, this flag has no effect. - * - * If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel - * will always issue readdirplus() requests to retrieve directory - * contents. - * - * If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel - * will issue both readdir() and readdirplus() requests, depending on - * how much information is expected to be required. - * - * As of Linux 4.20, the algorithm is as follows: when userspace - * starts to read directory entries, issue a READDIRPLUS request to - * the filesystem. If any entry attributes have been looked up by the - * time userspace requests the next batch of entries continue with - * READDIRPLUS, otherwise switch to plain READDIR. This will reasult - * in eg plain "ls" triggering READDIRPLUS first then READDIR after - * that because it doesn't do lookups. "ls -l" should result in all - * READDIRPLUS, except if dentries are already cached. - * - * This feature is enabled by default when supported by the kernel and - * if the filesystem implements both a readdirplus() and a readdir() - * handler. - */ -#define FUSE_CAP_READDIRPLUS_AUTO (1 << 14) + /** + * Indicates that the filesystem supports adaptive readdirplus. + * + * If FUSE_CAP_READDIRPLUS is not set, this flag has no effect. + * + * If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel + * will always issue readdirplus() requests to retrieve directory + * contents. + * + * If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel + * will issue both readdir() and readdirplus() requests, depending on + * how much information is expected to be required. + * + * As of Linux 4.20, the algorithm is as follows: when userspace + * starts to read directory entries, issue a READDIRPLUS request to + * the filesystem. If any entry attributes have been looked up by the + * time userspace requests the next batch of entries continue with + * READDIRPLUS, otherwise switch to plain READDIR. This will reasult + * in eg plain "ls" triggering READDIRPLUS first then READDIR after + * that because it doesn't do lookups. "ls -l" should result in all + * READDIRPLUS, except if dentries are already cached. + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements both a readdirplus() and a readdir() + * handler. + */ + FUSE_CAP_READDIRPLUS_AUTO = (1 << 14), -/** - * Indicates that the filesystem supports asynchronous direct I/O submission. - * - * If this capability is not requested/available, the kernel will ensure that - * there is at most one pending read and one pending write request per direct - * I/O file-handle at any time. - * - * This feature is enabled by default when supported by the kernel. - */ -#define FUSE_CAP_ASYNC_DIO (1 << 15) + /** + * Indicates that the filesystem supports asynchronous direct I/O submission. + * + * If this capability is not requested/available, the kernel will ensure that + * there is at most one pending read and one pending write request per direct + * I/O file-handle at any time. + * + * This feature is enabled by default when supported by the kernel. + */ + FUSE_CAP_ASYNC_DIO = (1 << 15), -/** - * Indicates that writeback caching should be enabled. This means that - * individual write request may be buffered and merged in the kernel - * before they are send to the filesystem. - * - * This feature is disabled by default. - */ -#define FUSE_CAP_WRITEBACK_CACHE (1 << 16) + /** + * Indicates that writeback caching should be enabled. This means that + * individual write request may be buffered and merged in the kernel + * before they are send to the filesystem. + * + * This feature is disabled by default. + */ + FUSE_CAP_WRITEBACK_CACHE = (1 << 16), -/** - * Indicates support for zero-message opens. If this flag is set in - * the `capable` field of the `fuse_conn_info` structure, then the - * filesystem may return `ENOSYS` from the open() handler to indicate - * success. Further attempts to open files will be handled in the - * kernel. (If this flag is not set, returning ENOSYS will be treated - * as an error and signaled to the caller). - * - * Setting this flag in the `want` field enables this behavior automatically - * within libfuse for low level API users. If non-low level users wish to have - * this behavior you must return `ENOSYS` from the open() handler on supporting - * kernels. - */ -#define FUSE_CAP_NO_OPEN_SUPPORT (1 << 17) + /** + * Indicates support for zero-message opens. If this flag is set in + * the `capable` field of the `fuse_conn_info` structure, then the + * filesystem may return `ENOSYS` from the open() handler to indicate + * success. Further attempts to open files will be handled in the + * kernel. (If this flag is not set, returning ENOSYS will be treated + * as an error and signaled to the caller). + * + * Setting this flag in the `want` field enables this behavior automatically + * within libfuse for low level API users. If non-low level users wish to have + * this behavior you must return `ENOSYS` from the open() handler on supporting + * kernels. + */ + FUSE_CAP_NO_OPEN_SUPPORT = (1 << 17), -/** - * Indicates support for parallel directory operations. If this flag - * is unset, the FUSE kernel module will ensure that lookup() and - * readdir() requests are never issued concurrently for the same - * directory. - */ -#define FUSE_CAP_PARALLEL_DIROPS (1 << 18) + /** + * Indicates support for parallel directory operations. If this flag + * is unset, the FUSE kernel module will ensure that lookup() and + * readdir() requests are never issued concurrently for the same + * directory. + */ + FUSE_CAP_PARALLEL_DIROPS = (1 << 18), -/** - * Indicates support for POSIX ACLs. - * - * If this feature is enabled, the kernel will cache and have - * responsibility for enforcing ACLs. ACL will be stored as xattrs and - * passed to userspace, which is responsible for updating the ACLs in - * the filesystem, keeping the file mode in sync with the ACL, and - * ensuring inheritance of default ACLs when new filesystem nodes are - * created. Note that this requires that the file system is able to - * parse and interpret the xattr representation of ACLs. - * - * Enabling this feature implicitly turns on the - * ``default_permissions`` mount option (even if it was not passed to - * mount(2)). - * - * This feature is disabled by default. - */ -#define FUSE_CAP_POSIX_ACL (1 << 19) + /** + * Indicates support for POSIX ACLs. + * + * If this feature is enabled, the kernel will cache and have + * responsibility for enforcing ACLs. ACL will be stored as xattrs and + * passed to userspace, which is responsible for updating the ACLs in + * the filesystem, keeping the file mode in sync with the ACL, and + * ensuring inheritance of default ACLs when new filesystem nodes are + * created. Note that this requires that the file system is able to + * parse and interpret the xattr representation of ACLs. + * + * Enabling this feature implicitly turns on the + * ``default_permissions`` mount option (even if it was not passed to + * mount(2)). + * + * This feature is disabled by default. + */ + FUSE_CAP_POSIX_ACL = (1 << 19), -/** - * Indicates that the filesystem is responsible for unsetting - * setuid and setgid bits when a file is written, truncated, or - * its owner is changed. - * - * This feature is disabled by default. - */ -#define FUSE_CAP_HANDLE_KILLPRIV (1 << 20) + /** + * Indicates that the filesystem is responsible for unsetting + * setuid and setgid bits when a file is written, truncated, or + * its owner is changed. + * + * This feature is disabled by default. + */ + FUSE_CAP_HANDLE_KILLPRIV = (1 << 20), -/** - * Indicates that the filesystem is responsible for unsetting - * setuid and setgid bit and additionally cap (stored as xattr) when a - * file is written, truncated, or its owner is changed. - * Upon write/truncate suid/sgid is only killed if caller - * does not have CAP_FSETID. Additionally upon - * write/truncate sgid is killed only if file has group - * execute permission. (Same as Linux VFS behavior). - * KILLPRIV_V2 requires handling of - * - FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags) - * - FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid) - * - FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags) - * - * This feature is disabled by default. - */ -#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1 << 21) + /** + * Indicates that the filesystem is responsible for unsetting + * setuid and setgid bit and additionally cap (stored as xattr) when a + * file is written, truncated, or its owner is changed. + * Upon write/truncate suid/sgid is only killed if caller + * does not have CAP_FSETID. Additionally upon + * write/truncate sgid is killed only if file has group + * execute permission. (Same as Linux VFS behavior). + * KILLPRIV_V2 requires handling of + * - FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags) + * - FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid) + * - FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags) + * + * This feature is disabled by default. + */ + FUSE_CAP_HANDLE_KILLPRIV_V2 = (1 << 21), -/** - * Indicates that the kernel supports caching symlinks in its page cache. - * - * When this feature is enabled, symlink targets are saved in the page cache. - * You can invalidate a cached link by calling: - * `fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);` - * - * This feature is disabled by default. - * If the kernel supports it (>= 4.20), you can enable this feature by - * setting this flag in the `want` field of the `fuse_conn_info` structure. - */ -#define FUSE_CAP_CACHE_SYMLINKS (1 << 23) + /** + * Indicates that the kernel supports caching symlinks in its page cache. + * + * When this feature is enabled, symlink targets are saved in the page cache. + * You can invalidate a cached link by calling: + * `fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);` + * + * This feature is disabled by default. + * If the kernel supports it (>= 4.20), you can enable this feature by + * setting this flag in the `want` field of the `fuse_conn_info` structure. + */ + FUSE_CAP_CACHE_SYMLINKS = (1 << 23), -/** - * Indicates support for zero-message opendirs. If this flag is set in - * the `capable` field of the `fuse_conn_info` structure, then the filesystem - * may return `ENOSYS` from the opendir() handler to indicate success. Further - * opendir and releasedir messages will be handled in the kernel. (If this - * flag is not set, returning ENOSYS will be treated as an error and signalled - * to the caller.) - * - * Setting this flag in the `want` field enables this behavior automatically - * within libfuse for low level API users. If non-low level users with to have - * this behavior you must return `ENOSYS` from the opendir() handler on - * supporting kernels. - */ -#define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24) + /** + * Indicates support for zero-message opendirs. If this flag is set in + * the `capable` field of the `fuse_conn_info` structure, then the filesystem + * may return `ENOSYS` from the opendir() handler to indicate success. Further + * opendir and releasedir messages will be handled in the kernel. (If this + * flag is not set, returning ENOSYS will be treated as an error and signalled + * to the caller.) + * + * Setting this flag in the `want` field enables this behavior automatically + * within libfuse for low level API users. If non-low level users with to have + * this behavior you must return `ENOSYS` from the opendir() handler on + * supporting kernels. + */ + FUSE_CAP_NO_OPENDIR_SUPPORT = (1 << 24), -/** - * Indicates support for invalidating cached pages only on explicit request. - * - * If this flag is set in the `capable` field of the `fuse_conn_info` structure, - * then the FUSE kernel module supports invalidating cached pages only on - * explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() - * or fuse_invalidate_path(). - * - * By setting this flag in the `want` field of the `fuse_conn_info` structure, - * the filesystem is responsible for invalidating cached pages through explicit - * requests to the kernel. - * - * Note that setting this flag does not prevent the cached pages from being - * flushed by OS itself and/or through user actions. - * - * Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA - * are set in the `capable` field of the `fuse_conn_info` structure then - * FUSE_CAP_AUTO_INVAL_DATA takes precedence. - * - * This feature is disabled by default. - */ -#define FUSE_CAP_EXPLICIT_INVAL_DATA (1 << 25) + /** + * Indicates support for invalidating cached pages only on explicit request. + * + * If this flag is set in the `capable` field of the `fuse_conn_info` structure, + * then the FUSE kernel module supports invalidating cached pages only on + * explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() + * or fuse_invalidate_path(). + * + * By setting this flag in the `want` field of the `fuse_conn_info` structure, + * the filesystem is responsible for invalidating cached pages through explicit + * requests to the kernel. + * + * Note that setting this flag does not prevent the cached pages from being + * flushed by OS itself and/or through user actions. + * + * Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA + * are set in the `capable` field of the `fuse_conn_info` structure then + * FUSE_CAP_AUTO_INVAL_DATA takes precedence. + * + * This feature is disabled by default. + */ + FUSE_CAP_EXPLICIT_INVAL_DATA = (1 << 25), -/** - * Indicates support that dentries can be expired. - * - * Expiring dentries, instead of invalidating them, makes a difference for - * overmounted dentries, where plain invalidation would detach all submounts - * before dropping the dentry from the cache. If only expiry is set on the - * dentry, then any overmounts are left alone and until ->d_revalidate() - * is called. - * - * Note: ->d_revalidate() is not called for the case of following a submount, - * so invalidation will only be triggered for the non-overmounted case. - * The dentry could also be mounted in a different mount instance, in which case - * any submounts will still be detached. -*/ -#define FUSE_CAP_EXPIRE_ONLY (1 << 26) + /** + * Indicates support that dentries can be expired. + * + * Expiring dentries, instead of invalidating them, makes a difference for + * overmounted dentries, where plain invalidation would detach all submounts + * before dropping the dentry from the cache. If only expiry is set on the + * dentry, then any overmounts are left alone and until ->d_revalidate() + * is called. + * + * Note: ->d_revalidate() is not called for the case of following a submount, + * so invalidation will only be triggered for the non-overmounted case. + * The dentry could also be mounted in a different mount instance, in which case + * any submounts will still be detached. + */ + FUSE_CAP_EXPIRE_ONLY = (1 << 26), -/** - * Indicates that an extended 'struct fuse_setxattr' is used by the kernel - * side - extra_flags are passed, which are used (as of now by acl) processing. - * For example FUSE_SETXATTR_ACL_KILL_SGID might be set. - */ -#define FUSE_CAP_SETXATTR_EXT (1 << 27) + /** + * Indicates that an extended 'struct fuse_setxattr' is used by the kernel + * side - extra_flags are passed, which are used (as of now by acl) processing. + * For example FUSE_SETXATTR_ACL_KILL_SGID might be set. + */ + FUSE_CAP_SETXATTR_EXT = (1 << 27), -/** - * Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction - * is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). - * MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to - * ensure coherency between mount points (or network clients) and with kernel page - * cache as enforced by mmap that cannot be guaranteed anymore. - */ -#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1 << 28) + /** + * Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction + * is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). + * MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to + * ensure coherency between mount points (or network clients) and with kernel page + * cache as enforced by mmap that cannot be guaranteed anymore. + */ + FUSE_CAP_DIRECT_IO_ALLOW_MMAP = (1 << 28), -/** - * Indicates support for passthrough mode access for read/write operations. - * - * If this flag is set in the `capable` field of the `fuse_conn_info` - * structure, then the FUSE kernel module supports redirecting read/write - * operations to the backing file instead of letting them to be handled - * by the FUSE daemon. - * - * This feature is disabled by default. - */ -#define FUSE_CAP_PASSTHROUGH (1 << 29) + /** + * Indicates support for passthrough mode access for read/write operations. + * + * If this flag is set in the `capable` field of the `fuse_conn_info` + * structure, then the FUSE kernel module supports redirecting read/write + * operations to the backing file instead of letting them to be handled + * by the FUSE daemon. + * + * This feature is disabled by default. + */ + FUSE_CAP_PASSTHROUGH = (1 << 29), -/** - * Indicates that the file system cannot handle NFS export - * - * If this flag is set NFS export and name_to_handle_at - * is not going to work at all and will fail with EOPNOTSUPP. - */ -#define FUSE_CAP_NO_EXPORT_SUPPORT (1 << 30) + /** + * Indicates that the file system cannot handle NFS export + * + * If this flag is set NFS export and name_to_handle_at + * is not going to work at all and will fail with EOPNOTSUPP. + */ + FUSE_CAP_NO_EXPORT_SUPPORT = (1 << 30), + + /** + * Current maximum capability value. + */ + FUSE_CAP_CURRENT_MAX +}; /** * Ioctl flags From 3f9d369d083cfeb0a01b9781772efcf9920bdfcb Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Mon, 23 Sep 2024 18:28:29 +0200 Subject: [PATCH 014/105] Increase .so version to 4 We have multiple ABI breakages - increase the .so version. Signed-off-by: Bernd Schubert --- lib/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/meson.build b/lib/meson.build index 34dd60772..e201a5a98 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -34,7 +34,7 @@ endif fusermount_path = join_paths(get_option('prefix'), get_option('bindir')) libfuse = library('fuse3', libfuse_sources, version: meson.project_version(), - soversion: '3', include_directories: include_dirs, + soversion: '4', include_directories: include_dirs, dependencies: deps, install: true, link_depends: 'fuse_versionscript', c_args: [ '-DFUSE_USE_VERSION=317', From 666b2c3fa5159e3c72a0d08117507475117c9319 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 1 Jan 2025 20:47:41 +0100 Subject: [PATCH 015/105] passthrough_ll: set correct keep_cache flag in opendir (#1083) In cache=always mode, set keep_cache flag in opendir(), same as done in open() and same as passthrough_hp does in opendir(). In the default cache=auto mode, use readdir cache, but do not set keep_cache, same as regular files use page cache for an open file, but do not keep_cache for a new open. Note that passthrough_hp by default behaves the same as passthrough_ll cache=always mode and supports the cache=never mode with --nocache, but it does not support the equivalent of cache=auto mode. Signed-off-by: Amir Goldstein --- example/passthrough_ll.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index 5f1fde9a7..e1a36efa6 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -652,8 +652,10 @@ static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi d->entry = NULL; fi->fh = (uintptr_t) d; - if (lo->cache == CACHE_ALWAYS) + if (lo->cache != CACHE_NEVER) fi->cache_readdir = 1; + if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; fuse_reply_open(req, fi); return; From b34e97163221a15a3f6baac1c8ebf9f53f8cad5f Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Wed, 1 Jan 2025 23:02:52 +0100 Subject: [PATCH 016/105] Update ChangeLog.rst for 3.17 (#1085) Signed-off-by: Bernd Schubert --- ChangeLog.rst | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 8fd8cd34e..3bf401d99 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,12 +1,38 @@ -libfuse 3.17 (unreleased) -======================== +libfuse 3.17 (2024-01-01) +========================= +* 3.11 and 3.14.2 introduced ABI incompatibilities, the ABI is restored + to 3.10, .so version was increased since there were releases with + the incompatible ABI + +* The libfuse version a program was compiled against is now encoded into + that program, using inlined functions in fuse_lowlevel.h and fuse.h * Allows to handle fatal signals and to print a backtrace. - New public function: fuse_set_fail_signal_handlers() + New API function: fuse_set_fail_signal_handlers() + * Allows fuse_log() messages to be send to syslog instead of stderr - New public functions: fuse_log_enable_syslog() and fuse_log_close_syslog() + New API functions: fuse_log_enable_syslog() and fuse_log_close_syslog() + * Handle buffer misalignment for FUSE_WRITE +* Added support for filesystem passthrough read/write of files when + FUSE_PASSTHROUGH capability is enabled + New API functions: fuse_passthrough_open() and fuse_passthrough_close(), + also see example/passthrough_hp.cc + +* Added fmask and dmask options to high-level API + - dmask: umask applied to directories + - fmask: umask applied to non-directories + +* Added FUSE_FILL_DIR_DEFAULTS enum to support C++ programs using + fuse_fill_dir_t function + +* Added support for FUSE_CAP_HANDLE_KILLPRIV_V2 + +Fixes: +* Fixed compilation failure on FreeBSD (mount_bsd.c now points to correct + header) + libfuse 3.16.2 (2023-10-10) =========================== From 3ee8c046c27edb8056e3f3c95fc349cfca75d375 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Wed, 1 Jan 2025 21:41:17 +0100 Subject: [PATCH 017/105] Run github workflow actions on release branches Running on the 'master' is not enough, actions also need to run on release branches. Signed-off-by: Bernd Schubert --- .github/workflows/abicheck.yml | 3 ++- .github/workflows/codeql.yml | 8 ++++++-- .github/workflows/codespell.yml | 8 ++++++-- .github/workflows/pr-ci.yml | 2 ++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/abicheck.yml b/.github/workflows/abicheck.yml index 23859d0b9..c75d912cf 100644 --- a/.github/workflows/abicheck.yml +++ b/.github/workflows/abicheck.yml @@ -5,10 +5,11 @@ on: push: branches: - master + - '[0-9]+.[0-9]+' # This will match branches like 3.17, 3.18, 4.0, etc. pull_request: branches: - master - + - '[0-9]+.[0-9]+' permissions: contents: read diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1cccb5f13..b4c6fc175 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,9 +13,13 @@ name: "CodeQL Advanced" on: push: - branches: [ "master" ] + branches: + - master + - '[0-9]+.[0-9]+' # This will match branches like 3.17, 3.18, 4.0, etc. pull_request: - branches: [ "master" ] + branches: + - master + - '[0-9]+.[0-9]+' jobs: analyze: diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 472bf6a8c..045bb207f 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -3,9 +3,13 @@ name: Codespell on: push: - branches: [master] + branches: + - master + - '[0-9]+.[0-9]+' # This will match branches like 3.17, 3.18, 4.0, etc. pull_request: - branches: [master] + branches: + - master + - '[0-9]+.[0-9]+' permissions: contents: read diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 54d30ac59..bec616621 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -4,9 +4,11 @@ on: push: branches: - master + - '[0-9]+.[0-9]+' # This will match branches like 3.17, 3.18, 4.0, etc. pull_request: branches: - master + - '[0-9]+.[0-9]+' permissions: contents: read From 30ca5ba5dd95f82d0bce5290ae236820526c54e3 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Wed, 1 Jan 2025 22:53:38 +0100 Subject: [PATCH 018/105] Add a github action to check for "Signed-off-by" Signed-off-by: Bernd Schubert --- .github/workflows/dco.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/dco.yml diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml new file mode 100644 index 000000000..1a2121d63 --- /dev/null +++ b/.github/workflows/dco.yml @@ -0,0 +1,28 @@ +name: DCO Check (Developer Certificate of Origin) + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + check-dco: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Check DCO + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + run: | + echo "Checking DCO for PR #${{ github.event.pull_request.number }}" + commits=$(gh pr view ${{ github.event.pull_request.number }} --json commits --jq '.commits[].oid') + for commit in $commits + do + if ! git log --format='%B' -n 1 $commit | grep -q "Signed-off-by:"; then + echo "Commit $commit is missing Signed-off-by line" + exit 1 + fi + done + echo "All commits have Signed-off-by lines" From 8be3247b9db976170ec977a8cbc4c2deed3edf5a Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Wed, 1 Jan 2025 22:58:45 +0100 Subject: [PATCH 019/105] Add a checkpatch.pl github workflow Signed-off-by: Bernd Schubert --- .github/workflows/checkpatch.yml | 22 + checkpatch.pl | 7820 ++++++++++++++++++++++++++++++ 2 files changed, 7842 insertions(+) create mode 100644 .github/workflows/checkpatch.yml create mode 100755 checkpatch.pl diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml new file mode 100644 index 000000000..9a3cd1b7f --- /dev/null +++ b/.github/workflows/checkpatch.yml @@ -0,0 +1,22 @@ +name: Checkpatch + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + checkpatch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y perl + - name: Run checkpatch.pl + run: | + git fetch origin ${{ github.base_ref }} + base_commit=$(git merge-base FETCH_HEAD ${{ github.event.pull_request.head.sha }}) + ./checkpatch.pl --no-tree -g $base_commit diff --git a/checkpatch.pl b/checkpatch.pl new file mode 100755 index 000000000..9eed3683a --- /dev/null +++ b/checkpatch.pl @@ -0,0 +1,7820 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# (c) 2001, Dave Jones. (the file handling bit) +# (c) 2005, Joel Schopp (the ugly bit) +# (c) 2007,2008, Andy Whitcroft (new conditions, test suite) +# (c) 2008-2010 Andy Whitcroft +# (c) 2010-2018 Joe Perches + +use strict; +use warnings; +use POSIX; +use File::Basename; +use Cwd 'abs_path'; +use Term::ANSIColor qw(:constants); +use Encode qw(decode encode); + +my $P = $0; +my $D = dirname(abs_path($P)); + +my $V = '0.32'; + +use Getopt::Long qw(:config no_auto_abbrev); + +my $quiet = 0; +my $verbose = 0; +my %verbose_messages = (); +my %verbose_emitted = (); +my $tree = 1; +my $chk_signoff = 1; +my $chk_fixes_tag = 1; +my $chk_patch = 1; +my $tst_only; +my $emacs = 0; +my $terse = 0; +my $showfile = 0; +my $file = 0; +my $git = 0; +my %git_commits = (); +my $check = 0; +my $check_orig = 0; +my $summary = 1; +my $mailback = 0; +my $summary_file = 0; +my $show_types = 0; +my $list_types = 0; +my $fix = 0; +my $fix_inplace = 0; +my $root; +my $gitroot = $ENV{'GIT_DIR'}; +$gitroot = ".git" if !defined($gitroot); +my %debug; +my %camelcase = (); +my %use_type = (); +my @use = (); +my %ignore_type = (); +my @ignore = (); +my $help = 0; +my $configuration_file = ".checkpatch.conf"; +my $max_line_length = 100; +my $ignore_perl_version = 0; +my $minimum_perl_version = 5.10.0; +my $min_conf_desc_length = 4; +my $spelling_file = "$D/spelling.txt"; +my $codespell = 0; +my $codespellfile = "/usr/share/codespell/dictionary.txt"; +my $user_codespellfile = ""; +my $conststructsfile = "$D/const_structs.checkpatch"; +my $docsfile = "$D/../Documentation/dev-tools/checkpatch.rst"; +my $typedefsfile; +my $color = "auto"; +my $allow_c99_comments = 1; # Can be overridden by --ignore C99_COMMENT_TOLERANCE +# git output parsing needs US English output, so first set backtick child process LANGUAGE +my $git_command ='export LANGUAGE=en_US.UTF-8; git'; +my $tabsize = 8; +my ${CONFIG_} = "CONFIG_"; + +my %maybe_linker_symbol; # for externs in c exceptions, when seen in *vmlinux.lds.h + +sub help { + my ($exitcode) = @_; + + print << "EOM"; +Usage: $P [OPTION]... [FILE]... +Version: $V + +Options: + -q, --quiet quiet + -v, --verbose verbose mode + --no-tree run without a kernel tree + --no-signoff do not check for 'Signed-off-by' line + --no-fixes-tag do not check for 'Fixes:' tag + --patch treat FILE as patchfile (default) + --emacs emacs compile window format + --terse one line per report + --showfile emit diffed file position, not input file position + -g, --git treat FILE as a single commit or git revision range + single git commit with: + + ^ + ~n + multiple git commits with: + .. + ... + - + git merges are ignored + -f, --file treat FILE as regular source file + --subjective, --strict enable more subjective tests + --list-types list the possible message types + --types TYPE(,TYPE2...) show only these comma separated message types + --ignore TYPE(,TYPE2...) ignore various comma separated message types + --show-types show the specific message type in the output + --max-line-length=n set the maximum line length, (default $max_line_length) + if exceeded, warn on patches + requires --strict for use with --file + --min-conf-desc-length=n set the min description length, if shorter, warn + --tab-size=n set the number of spaces for tab (default $tabsize) + --root=PATH PATH to the kernel tree root + --no-summary suppress the per-file summary + --mailback only produce a report in case of warnings/errors + --summary-file include the filename in summary + --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of + 'values', 'possible', 'type', and 'attr' (default + is all off) + --test-only=WORD report only warnings/errors containing WORD + literally + --fix EXPERIMENTAL - may create horrible results + If correctable single-line errors exist, create + ".EXPERIMENTAL-checkpatch-fixes" + with potential errors corrected to the preferred + checkpatch style + --fix-inplace EXPERIMENTAL - may create horrible results + Is the same as --fix, but overwrites the input + file. It's your fault if there's no backup or git + --ignore-perl-version override checking of perl version. expect + runtime errors. + --codespell Use the codespell dictionary for spelling/typos + (default:$codespellfile) + --codespellfile Use this codespell dictionary + --typedefsfile Read additional types from this file + --color[=WHEN] Use colors 'always', 'never', or only when output + is a terminal ('auto'). Default is 'auto'. + --kconfig-prefix=WORD use WORD as a prefix for Kconfig symbols (default + ${CONFIG_}) + -h, --help, --version display this help and exit + +When FILE is - read standard input. +EOM + + exit($exitcode); +} + +sub uniq { + my %seen; + return grep { !$seen{$_}++ } @_; +} + +sub list_types { + my ($exitcode) = @_; + + my $count = 0; + + local $/ = undef; + + open(my $script, '<', abs_path($P)) or + die "$P: Can't read '$P' $!\n"; + + my $text = <$script>; + close($script); + + my %types = (); + # Also catch when type or level is passed through a variable + while ($text =~ /(?:(\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) { + if (defined($1)) { + if (exists($types{$2})) { + $types{$2} .= ",$1" if ($types{$2} ne $1); + } else { + $types{$2} = $1; + } + } else { + $types{$2} = "UNDETERMINED"; + } + } + + print("#\tMessage type\n\n"); + if ($color) { + print(" ( Color coding: "); + print(RED . "ERROR" . RESET); + print(" | "); + print(YELLOW . "WARNING" . RESET); + print(" | "); + print(GREEN . "CHECK" . RESET); + print(" | "); + print("Multiple levels / Undetermined"); + print(" )\n\n"); + } + + foreach my $type (sort keys %types) { + my $orig_type = $type; + if ($color) { + my $level = $types{$type}; + if ($level eq "ERROR") { + $type = RED . $type . RESET; + } elsif ($level eq "WARN") { + $type = YELLOW . $type . RESET; + } elsif ($level eq "CHK") { + $type = GREEN . $type . RESET; + } + } + print(++$count . "\t" . $type . "\n"); + if ($verbose && exists($verbose_messages{$orig_type})) { + my $message = $verbose_messages{$orig_type}; + $message =~ s/\n/\n\t/g; + print("\t" . $message . "\n\n"); + } + } + + exit($exitcode); +} + +my $conf = which_conf($configuration_file); +if (-f $conf) { + my @conf_args; + open(my $conffile, '<', "$conf") + or warn "$P: Can't find a readable $configuration_file file $!\n"; + + while (<$conffile>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + $line =~ s/\s+/ /g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my @words = split(" ", $line); + foreach my $word (@words) { + last if ($word =~ m/^#/); + push (@conf_args, $word); + } + } + close($conffile); + unshift(@ARGV, @conf_args) if @conf_args; +} + +sub load_docs { + open(my $docs, '<', "$docsfile") + or warn "$P: Can't read the documentation file $docsfile $!\n"; + + my $type = ''; + my $desc = ''; + my $in_desc = 0; + + while (<$docs>) { + chomp; + my $line = $_; + $line =~ s/\s+$//; + + if ($line =~ /^\s*\*\*(.+)\*\*$/) { + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + $type = $1; + $desc = ''; + $in_desc = 1; + } elsif ($in_desc) { + if ($line =~ /^(?:\s{4,}|$)/) { + $line =~ s/^\s{4}//; + $desc .= $line; + $desc .= "\n"; + } else { + $verbose_messages{$type} = trim($desc); + $type = ''; + $desc = ''; + $in_desc = 0; + } + } + } + + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + close($docs); +} + +# Perl's Getopt::Long allows options to take optional arguments after a space. +# Prevent --color by itself from consuming other arguments +foreach (@ARGV) { + if ($_ eq "--color" || $_ eq "-color") { + $_ = "--color=$color"; + } +} + +GetOptions( + 'q|quiet+' => \$quiet, + 'v|verbose!' => \$verbose, + 'tree!' => \$tree, + 'signoff!' => \$chk_signoff, + 'fixes-tag!' => \$chk_fixes_tag, + 'patch!' => \$chk_patch, + 'emacs!' => \$emacs, + 'terse!' => \$terse, + 'showfile!' => \$showfile, + 'f|file!' => \$file, + 'g|git!' => \$git, + 'subjective!' => \$check, + 'strict!' => \$check, + 'ignore=s' => \@ignore, + 'types=s' => \@use, + 'show-types!' => \$show_types, + 'list-types!' => \$list_types, + 'max-line-length=i' => \$max_line_length, + 'min-conf-desc-length=i' => \$min_conf_desc_length, + 'tab-size=i' => \$tabsize, + 'root=s' => \$root, + 'summary!' => \$summary, + 'mailback!' => \$mailback, + 'summary-file!' => \$summary_file, + 'fix!' => \$fix, + 'fix-inplace!' => \$fix_inplace, + 'ignore-perl-version!' => \$ignore_perl_version, + 'debug=s' => \%debug, + 'test-only=s' => \$tst_only, + 'codespell!' => \$codespell, + 'codespellfile=s' => \$user_codespellfile, + 'typedefsfile=s' => \$typedefsfile, + 'color=s' => \$color, + 'no-color' => \$color, #keep old behaviors of -nocolor + 'nocolor' => \$color, #keep old behaviors of -nocolor + 'kconfig-prefix=s' => \${CONFIG_}, + 'h|help' => \$help, + 'version' => \$help +) or $help = 2; + +if ($user_codespellfile) { + # Use the user provided codespell file unconditionally + $codespellfile = $user_codespellfile; +} elsif (!(-f $codespellfile)) { + # If /usr/share/codespell/dictionary.txt is not present, try to find it + # under codespell's install directory: /data/dictionary.txt + if (($codespell || $help) && which("python3") ne "") { + my $python_codespell_dict = << "EOF"; + +import os.path as op +import codespell_lib +codespell_dir = op.dirname(codespell_lib.__file__) +codespell_file = op.join(codespell_dir, 'data', 'dictionary.txt') +print(codespell_file, end='') +EOF + + my $codespell_dict = `python3 -c "$python_codespell_dict" 2> /dev/null`; + $codespellfile = $codespell_dict if (-f $codespell_dict); + } +} + +# $help is 1 if either -h, --help or --version is passed as option - exitcode: 0 +# $help is 2 if invalid option is passed - exitcode: 1 +help($help - 1) if ($help); + +die "$P: --git cannot be used with --file or --fix\n" if ($git && ($file || $fix)); +die "$P: --verbose cannot be used with --terse\n" if ($verbose && $terse); + +if ($color =~ /^[01]$/) { + $color = !$color; +} elsif ($color =~ /^always$/i) { + $color = 1; +} elsif ($color =~ /^never$/i) { + $color = 0; +} elsif ($color =~ /^auto$/i) { + $color = (-t STDOUT); +} else { + die "$P: Invalid color mode: $color\n"; +} + +load_docs() if ($verbose); +list_types(0) if ($list_types); + +$fix = 1 if ($fix_inplace); +$check_orig = $check; + +my $exit = 0; + +my $perl_version_ok = 1; +if ($^V && $^V lt $minimum_perl_version) { + $perl_version_ok = 0; + printf "$P: requires at least perl version %vd\n", $minimum_perl_version; + exit(1) if (!$ignore_perl_version); +} + +#if no filenames are given, push '-' to read patch from stdin +if ($#ARGV < 0) { + push(@ARGV, '-'); +} + +# skip TAB size 1 to avoid additional checks on $tabsize - 1 +die "$P: Invalid TAB size: $tabsize\n" if ($tabsize < 2); + +sub hash_save_array_words { + my ($hashRef, $arrayRef) = @_; + + my @array = split(/,/, join(',', @$arrayRef)); + foreach my $word (@array) { + $word =~ s/\s*\n?$//g; + $word =~ s/^\s*//g; + $word =~ s/\s+/ /g; + $word =~ tr/[a-z]/[A-Z]/; + + next if ($word =~ m/^\s*#/); + next if ($word =~ m/^\s*$/); + + $hashRef->{$word}++; + } +} + +sub hash_show_words { + my ($hashRef, $prefix) = @_; + + if (keys %$hashRef) { + print "\nNOTE: $prefix message types:"; + foreach my $word (sort keys %$hashRef) { + print " $word"; + } + print "\n"; + } +} + +hash_save_array_words(\%ignore_type, \@ignore); +hash_save_array_words(\%use_type, \@use); + +my $dbg_values = 0; +my $dbg_possible = 0; +my $dbg_type = 0; +my $dbg_attr = 0; +for my $key (keys %debug) { + ## no critic + eval "\${dbg_$key} = '$debug{$key}';"; + die "$@" if ($@); +} + +my $rpt_cleaners = 0; + +if ($terse) { + $emacs = 1; + $quiet++; +} + +if ($tree) { + if (defined $root) { + if (!top_of_kernel_tree($root)) { + die "$P: $root: --root does not point at a valid tree\n"; + } + } else { + if (top_of_kernel_tree('.')) { + $root = '.'; + } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ && + top_of_kernel_tree($1)) { + $root = $1; + } + } + + if (!defined $root) { + print "Must be run from the top-level dir. of a kernel tree\n"; + exit(2); + } +} + +my $emitted_corrupt = 0; + +our $Ident = qr{ + [A-Za-z_][A-Za-z\d_]* + (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)* + }x; +our $Storage = qr{extern|static|asmlinkage}; +our $Sparse = qr{ + __user| + __kernel| + __force| + __iomem| + __must_check| + __kprobes| + __ref| + __refconst| + __refdata| + __rcu| + __private + }x; +our $InitAttributePrefix = qr{__(?:mem|cpu|dev|net_|)}; +our $InitAttributeData = qr{$InitAttributePrefix(?:initdata\b)}; +our $InitAttributeConst = qr{$InitAttributePrefix(?:initconst\b)}; +our $InitAttributeInit = qr{$InitAttributePrefix(?:init\b)}; +our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeInit}; + +# Notes to $Attribute: +# We need \b after 'init' otherwise 'initconst' will cause a false positive in a check +our $Attribute = qr{ + const| + volatile| + __percpu| + __nocast| + __safe| + __bitwise| + __packed__| + __packed2__| + __naked| + __maybe_unused| + __always_unused| + __noreturn| + __used| + __cold| + __pure| + __noclone| + __deprecated| + __read_mostly| + __ro_after_init| + __kprobes| + $InitAttribute| + __aligned\s*\(.*\)| + ____cacheline_aligned| + ____cacheline_aligned_in_smp| + ____cacheline_internodealigned_in_smp| + __weak| + __alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\) + }x; +our $Modifier; +our $Inline = qr{inline|__always_inline|noinline|__inline|__inline__}; +our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]}; +our $Lval = qr{$Ident(?:$Member)*}; + +our $Int_type = qr{(?i)llu|ull|ll|lu|ul|l|u}; +our $Binary = qr{(?i)0b[01]+$Int_type?}; +our $Hex = qr{(?i)0x[0-9a-f]+$Int_type?}; +our $Int = qr{[0-9]+$Int_type?}; +our $Octal = qr{0[0-7]+$Int_type?}; +our $String = qr{(?:\b[Lu])?"[X\t]*"}; +our $Float_hex = qr{(?i)0x[0-9a-f]+p-?[0-9]+[fl]?}; +our $Float_dec = qr{(?i)(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:e-?[0-9]+)?[fl]?}; +our $Float_int = qr{(?i)[0-9]+e-?[0-9]+[fl]?}; +our $Float = qr{$Float_hex|$Float_dec|$Float_int}; +our $Constant = qr{$Float|$Binary|$Octal|$Hex|$Int}; +our $Assignment = qr{\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=}; +our $Compare = qr{<=|>=|==|!=|<|(?}; +our $Arithmetic = qr{\+|-|\*|\/|%}; +our $Operators = qr{ + <=|>=|==|!=| + =>|->|<<|>>|<|>|!|~| + &&|\|\||,|\^|\+\+|--|&|\||$Arithmetic + }x; + +our $c90_Keywords = qr{do|for|while|if|else|return|goto|continue|switch|default|case|break}x; + +our $BasicType; +our $NonptrType; +our $NonptrTypeMisordered; +our $NonptrTypeWithAttr; +our $Type; +our $TypeMisordered; +our $Declare; +our $DeclareMisordered; + +our $NON_ASCII_UTF8 = qr{ + [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 +}x; + +our $UTF8 = qr{ + [\x09\x0A\x0D\x20-\x7E] # ASCII + | $NON_ASCII_UTF8 +}x; + +our $typeC99Typedefs = qr{(?:__)?(?:[us]_?)?int_?(?:8|16|32|64)_t}; +our $typeOtherOSTypedefs = qr{(?x: + u_(?:char|short|int|long) | # bsd + u(?:nchar|short|int|long) # sysv +)}; +our $typeKernelTypedefs = qr{(?x: + (?:__)?(?:u|s|be|le)(?:8|16|32|64)| + atomic_t +)}; +our $typeStdioTypedefs = qr{(?x: + FILE +)}; +our $typeTypedefs = qr{(?x: + $typeC99Typedefs\b| + $typeOtherOSTypedefs\b| + $typeKernelTypedefs\b| + $typeStdioTypedefs\b +)}; + +our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b}; + +our $logFunctions = qr{(?x: + printk(?:_ratelimited|_once|_deferred_once|_deferred|)| + (?:[a-z0-9]+_){1,2}(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)| + TP_printk| + WARN(?:_RATELIMIT|_ONCE|)| + panic| + MODULE_[A-Z_]+| + seq_vprintf|seq_printf|seq_puts +)}; + +our $allocFunctions = qr{(?x: + (?:(?:devm_)? + (?:kv|k|v)[czm]alloc(?:_array)?(?:_node)? | + kstrdup(?:_const)? | + kmemdup(?:_nul)?) | + (?:\w+)?alloc_skb(?:_ip_align)? | + # dev_alloc_skb/netdev_alloc_skb, et al + dma_alloc_coherent +)}; + +our $signature_tags = qr{(?xi: + Signed-off-by:| + Co-developed-by:| + Acked-by:| + Tested-by:| + Reviewed-by:| + Reported-by:| + Suggested-by:| + To:| + Cc: +)}; + +our @link_tags = qw(Link Closes); + +#Create a search and print patterns for all these strings to be used directly below +our $link_tags_search = ""; +our $link_tags_print = ""; +foreach my $entry (@link_tags) { + if ($link_tags_search ne "") { + $link_tags_search .= '|'; + $link_tags_print .= ' or '; + } + $entry .= ':'; + $link_tags_search .= $entry; + $link_tags_print .= "'$entry'"; +} +$link_tags_search = "(?:${link_tags_search})"; + +our $tracing_logging_tags = qr{(?xi: + [=-]*> | + <[=-]* | + \[ | + \] | + start | + called | + entered | + entry | + enter | + in | + inside | + here | + begin | + exit | + end | + done | + leave | + completed | + out | + return | + [\.\!:\s]* +)}; + +sub edit_distance_min { + my (@arr) = @_; + my $len = scalar @arr; + if ((scalar @arr) < 1) { + # if underflow, return + return; + } + my $min = $arr[0]; + for my $i (0 .. ($len-1)) { + if ($arr[$i] < $min) { + $min = $arr[$i]; + } + } + return $min; +} + +sub get_edit_distance { + my ($str1, $str2) = @_; + $str1 = lc($str1); + $str2 = lc($str2); + $str1 =~ s/-//g; + $str2 =~ s/-//g; + my $len1 = length($str1); + my $len2 = length($str2); + # two dimensional array storing minimum edit distance + my @distance; + for my $i (0 .. $len1) { + for my $j (0 .. $len2) { + if ($i == 0) { + $distance[$i][$j] = $j; + } elsif ($j == 0) { + $distance[$i][$j] = $i; + } elsif (substr($str1, $i-1, 1) eq substr($str2, $j-1, 1)) { + $distance[$i][$j] = $distance[$i - 1][$j - 1]; + } else { + my $dist1 = $distance[$i][$j - 1]; #insert distance + my $dist2 = $distance[$i - 1][$j]; # remove + my $dist3 = $distance[$i - 1][$j - 1]; #replace + $distance[$i][$j] = 1 + edit_distance_min($dist1, $dist2, $dist3); + } + } + } + return $distance[$len1][$len2]; +} + +sub find_standard_signature { + my ($sign_off) = @_; + my @standard_signature_tags = ( + 'Signed-off-by:', 'Co-developed-by:', 'Acked-by:', 'Tested-by:', + 'Reviewed-by:', 'Reported-by:', 'Suggested-by:' + ); + foreach my $signature (@standard_signature_tags) { + return $signature if (get_edit_distance($sign_off, $signature) <= 2); + } + + return ""; +} + +our $obsolete_archives = qr{(?xi: + \Qfreedesktop.org/archives/dri-devel\E | + \Qlists.infradead.org\E | + \Qlkml.org\E | + \Qmail-archive.com\E | + \Qmailman.alsa-project.org/pipermail\E | + \Qmarc.info\E | + \Qozlabs.org/pipermail\E | + \Qspinics.net\E +)}; + +our @typeListMisordered = ( + qr{char\s+(?:un)?signed}, + qr{int\s+(?:(?:un)?signed\s+)?short\s}, + qr{int\s+short(?:\s+(?:un)?signed)}, + qr{short\s+int(?:\s+(?:un)?signed)}, + qr{(?:un)?signed\s+int\s+short}, + qr{short\s+(?:un)?signed}, + qr{long\s+int\s+(?:un)?signed}, + qr{int\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed\s+int}, + qr{int\s+(?:un)?signed\s+long}, + qr{int\s+(?:un)?signed}, + qr{int\s+long\s+long\s+(?:un)?signed}, + qr{long\s+long\s+int\s+(?:un)?signed}, + qr{long\s+long\s+(?:un)?signed\s+int}, + qr{long\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed}, +); + +our @typeList = ( + qr{void}, + qr{(?:(?:un)?signed\s+)?char}, + qr{(?:(?:un)?signed\s+)?short\s+int}, + qr{(?:(?:un)?signed\s+)?short}, + qr{(?:(?:un)?signed\s+)?int}, + qr{(?:(?:un)?signed\s+)?long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long}, + qr{(?:(?:un)?signed\s+)?long}, + qr{(?:un)?signed}, + qr{float}, + qr{double}, + qr{bool}, + qr{struct\s+$Ident}, + qr{union\s+$Ident}, + qr{enum\s+$Ident}, + qr{${Ident}_t}, + qr{${Ident}_handler}, + qr{${Ident}_handler_fn}, + @typeListMisordered, +); + +our $C90_int_types = qr{(?x: + long\s+long\s+int\s+(?:un)?signed| + long\s+long\s+(?:un)?signed\s+int| + long\s+long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+long\s+int| + (?:(?:un)?signed\s+)?long\s+long| + int\s+long\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long\s+long| + + long\s+int\s+(?:un)?signed| + long\s+(?:un)?signed\s+int| + long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+int| + (?:(?:un)?signed\s+)?long| + int\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long| + + int\s+(?:un)?signed| + (?:(?:un)?signed\s+)?int +)}; + +our @typeListFile = (); +our @typeListWithAttr = ( + @typeList, + qr{struct\s+$InitAttribute\s+$Ident}, + qr{union\s+$InitAttribute\s+$Ident}, +); + +our @modifierList = ( + qr{fastcall}, +); +our @modifierListFile = (); + +our @mode_permission_funcs = ( + ["module_param", 3], + ["module_param_(?:array|named|string)", 4], + ["module_param_array_named", 5], + ["debugfs_create_(?:file|u8|u16|u32|u64|x8|x16|x32|x64|size_t|atomic_t|bool|blob|regset32|u32_array)", 2], + ["proc_create(?:_data|)", 2], + ["(?:CLASS|DEVICE|SENSOR|SENSOR_DEVICE|IIO_DEVICE)_ATTR", 2], + ["IIO_DEV_ATTR_[A-Z_]+", 1], + ["SENSOR_(?:DEVICE_|)ATTR_2", 2], + ["SENSOR_TEMPLATE(?:_2|)", 3], + ["__ATTR", 2], +); + +my $word_pattern = '\b[A-Z]?[a-z]{2,}\b'; + +#Create a search pattern for all these functions to speed up a loop below +our $mode_perms_search = ""; +foreach my $entry (@mode_permission_funcs) { + $mode_perms_search .= '|' if ($mode_perms_search ne ""); + $mode_perms_search .= $entry->[0]; +} +$mode_perms_search = "(?:${mode_perms_search})"; + +our %deprecated_apis = ( + "synchronize_rcu_bh" => "synchronize_rcu", + "synchronize_rcu_bh_expedited" => "synchronize_rcu_expedited", + "call_rcu_bh" => "call_rcu", + "rcu_barrier_bh" => "rcu_barrier", + "synchronize_sched" => "synchronize_rcu", + "synchronize_sched_expedited" => "synchronize_rcu_expedited", + "call_rcu_sched" => "call_rcu", + "rcu_barrier_sched" => "rcu_barrier", + "get_state_synchronize_sched" => "get_state_synchronize_rcu", + "cond_synchronize_sched" => "cond_synchronize_rcu", + "kmap" => "kmap_local_page", + "kunmap" => "kunmap_local", + "kmap_atomic" => "kmap_local_page", + "kunmap_atomic" => "kunmap_local", +); + +#Create a search pattern for all these strings to speed up a loop below +our $deprecated_apis_search = ""; +foreach my $entry (keys %deprecated_apis) { + $deprecated_apis_search .= '|' if ($deprecated_apis_search ne ""); + $deprecated_apis_search .= $entry; +} +$deprecated_apis_search = "(?:${deprecated_apis_search})"; + +our $mode_perms_world_writable = qr{ + S_IWUGO | + S_IWOTH | + S_IRWXUGO | + S_IALLUGO | + 0[0-7][0-7][2367] +}x; + +our %mode_permission_string_types = ( + "S_IRWXU" => 0700, + "S_IRUSR" => 0400, + "S_IWUSR" => 0200, + "S_IXUSR" => 0100, + "S_IRWXG" => 0070, + "S_IRGRP" => 0040, + "S_IWGRP" => 0020, + "S_IXGRP" => 0010, + "S_IRWXO" => 0007, + "S_IROTH" => 0004, + "S_IWOTH" => 0002, + "S_IXOTH" => 0001, + "S_IRWXUGO" => 0777, + "S_IRUGO" => 0444, + "S_IWUGO" => 0222, + "S_IXUGO" => 0111, +); + +#Create a search pattern for all these strings to speed up a loop below +our $mode_perms_string_search = ""; +foreach my $entry (keys %mode_permission_string_types) { + $mode_perms_string_search .= '|' if ($mode_perms_string_search ne ""); + $mode_perms_string_search .= $entry; +} +our $single_mode_perms_string_search = "(?:${mode_perms_string_search})"; +our $multi_mode_perms_string_search = qr{ + ${single_mode_perms_string_search} + (?:\s*\|\s*${single_mode_perms_string_search})* +}x; + +sub perms_to_octal { + my ($string) = @_; + + return trim($string) if ($string =~ /^\s*0[0-7]{3,3}\s*$/); + + my $val = ""; + my $oval = ""; + my $to = 0; + my $curpos = 0; + my $lastpos = 0; + while ($string =~ /\b(($single_mode_perms_string_search)\b(?:\s*\|\s*)?\s*)/g) { + $curpos = pos($string); + my $match = $2; + my $omatch = $1; + last if ($lastpos > 0 && ($curpos - length($omatch) != $lastpos)); + $lastpos = $curpos; + $to |= $mode_permission_string_types{$match}; + $val .= '\s*\|\s*' if ($val ne ""); + $val .= $match; + $oval .= $omatch; + } + $oval =~ s/^\s*\|\s*//; + $oval =~ s/\s*\|\s*$//; + return sprintf("%04o", $to); +} + +our $allowed_asm_includes = qr{(?x: + irq| + memory| + time| + reboot +)}; +# memory.h: ARM has a custom one + +# Load common spelling mistakes and build regular expression list. +my $misspellings; +my %spelling_fix; + +if (open(my $spelling, '<', $spelling_file)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my ($suspect, $fix) = split(/\|\|/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); +} else { + warn "No typos will be found - file '$spelling_file': $!\n"; +} + +if ($codespell) { + if (open(my $spelling, '<', $codespellfile)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + next if ($line =~ m/, disabled/i); + + $line =~ s/,.*$//; + + my ($suspect, $fix) = split(/->/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); + } else { + warn "No codespell typos will be found - file '$codespellfile': $!\n"; + } +} + +$misspellings = join("|", sort keys %spelling_fix) if keys %spelling_fix; + +sub read_words { + my ($wordsRef, $file) = @_; + + if (open(my $words, '<', $file)) { + while (<$words>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + if ($line =~ /\s/) { + print("$file: '$line' invalid - ignored\n"); + next; + } + + $$wordsRef .= '|' if (defined $$wordsRef); + $$wordsRef .= $line; + } + close($file); + return 1; + } + + return 0; +} + +my $const_structs; +if (show_type("CONST_STRUCT")) { + read_words(\$const_structs, $conststructsfile) + or warn "No structs that should be const will be found - file '$conststructsfile': $!\n"; +} + +if (defined($typedefsfile)) { + my $typeOtherTypedefs; + read_words(\$typeOtherTypedefs, $typedefsfile) + or warn "No additional types will be considered - file '$typedefsfile': $!\n"; + $typeTypedefs .= '|' . $typeOtherTypedefs if (defined $typeOtherTypedefs); +} + +sub build_types { + my $mods = "(?x: \n" . join("|\n ", (@modifierList, @modifierListFile)) . "\n)"; + my $all = "(?x: \n" . join("|\n ", (@typeList, @typeListFile)) . "\n)"; + my $Misordered = "(?x: \n" . join("|\n ", @typeListMisordered) . "\n)"; + my $allWithAttr = "(?x: \n" . join("|\n ", @typeListWithAttr) . "\n)"; + $Modifier = qr{(?:$Attribute|$Sparse|$mods)}; + $BasicType = qr{ + (?:$typeTypedefs\b)| + (?:${all}\b) + }x; + $NonptrType = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${all}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeMisordered = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:${Misordered}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeWithAttr = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${allWithAttr}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $Type = qr{ + $NonptrType + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} + (?:\s+$Inline|\s+$Modifier)* + }x; + $TypeMisordered = qr{ + $NonptrTypeMisordered + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} + (?:\s+$Inline|\s+$Modifier)* + }x; + $Declare = qr{(?:$Storage\s+(?:$Inline\s+)?)?$Type}; + $DeclareMisordered = qr{(?:$Storage\s+(?:$Inline\s+)?)?$TypeMisordered}; +} +build_types(); + +our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*}; + +# Using $balanced_parens, $LvalOrFunc, or $FuncArg +# requires at least perl version v5.10.0 +# Any use must be runtime checked with $^V + +our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/; +our $LvalOrFunc = qr{((?:[\&\*]\s*)?$Lval)\s*($balanced_parens{0,1})\s*}; +our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)}; + +our $declaration_macros = qr{(?x: + (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(| + (?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(| + (?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_STACK\s*\(| + (?:$Storage\s+)?(?:XA_STATE|XA_STATE_ORDER)\s*\( +)}; + +our %allow_repeated_words = ( + add => '', + added => '', + bad => '', + be => '', +); + +sub deparenthesize { + my ($string) = @_; + return "" if (!defined($string)); + + while ($string =~ /^\s*\(.*\)\s*$/) { + $string =~ s@^\s*\(\s*@@; + $string =~ s@\s*\)\s*$@@; + } + + $string =~ s@\s+@ @g; + + return $string; +} + +sub seed_camelcase_file { + my ($file) = @_; + + return if (!(-f $file)); + + local $/; + + open(my $include_file, '<', "$file") + or warn "$P: Can't read '$file' $!\n"; + my $text = <$include_file>; + close($include_file); + + my @lines = split('\n', $text); + + foreach my $line (@lines) { + next if ($line !~ /(?:[A-Z][a-z]|[a-z][A-Z])/); + if ($line =~ /^[ \t]*(?:#[ \t]*define|typedef\s+$Type)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*$Declare\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[\(\[,;]/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*(?:union|struct|enum)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[;\{]/) { + $camelcase{$1} = 1; + } + } +} + +our %maintained_status = (); + +sub is_maintained_obsolete { + my ($filename) = @_; + + return 0 if (!$tree || !(-e "$root/scripts/get_maintainer.pl")); + + if (!exists($maintained_status{$filename})) { + $maintained_status{$filename} = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`; + } + + return $maintained_status{$filename} =~ /obsolete/i; +} + +sub is_SPDX_License_valid { + my ($license) = @_; + + return 1 if (!$tree || which("python3") eq "" || !(-x "$root/scripts/spdxcheck.py") || !(-e "$gitroot")); + + my $root_path = abs_path($root); + my $status = `cd "$root_path"; echo "$license" | scripts/spdxcheck.py -`; + return 0 if ($status ne ""); + return 1; +} + +my $camelcase_seeded = 0; +sub seed_camelcase_includes { + return if ($camelcase_seeded); + + my $files; + my $camelcase_cache = ""; + my @include_files = (); + + $camelcase_seeded = 1; + + if (-e "$gitroot") { + my $git_last_include_commit = `${git_command} log --no-merges --pretty=format:"%h%n" -1 -- include`; + chomp $git_last_include_commit; + $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit"; + } else { + my $last_mod_date = 0; + $files = `find $root/include -name "*.h"`; + @include_files = split('\n', $files); + foreach my $file (@include_files) { + my $date = POSIX::strftime("%Y%m%d%H%M", + localtime((stat $file)[9])); + $last_mod_date = $date if ($last_mod_date < $date); + } + $camelcase_cache = ".checkpatch-camelcase.date.$last_mod_date"; + } + + if ($camelcase_cache ne "" && -f $camelcase_cache) { + open(my $camelcase_file, '<', "$camelcase_cache") + or warn "$P: Can't read '$camelcase_cache' $!\n"; + while (<$camelcase_file>) { + chomp; + $camelcase{$_} = 1; + } + close($camelcase_file); + + return; + } + + if (-e "$gitroot") { + $files = `${git_command} ls-files "include/*.h"`; + @include_files = split('\n', $files); + } + + foreach my $file (@include_files) { + seed_camelcase_file($file); + } + + if ($camelcase_cache ne "") { + unlink glob ".checkpatch-camelcase.*"; + open(my $camelcase_file, '>', "$camelcase_cache") + or warn "$P: Can't write '$camelcase_cache' $!\n"; + foreach (sort { lc($a) cmp lc($b) } keys(%camelcase)) { + print $camelcase_file ("$_\n"); + } + close($camelcase_file); + } +} + +sub git_is_single_file { + my ($filename) = @_; + + return 0 if ((which("git") eq "") || !(-e "$gitroot")); + + my $output = `${git_command} ls-files -- $filename 2>/dev/null`; + my $count = $output =~ tr/\n//; + return $count eq 1 && $output =~ m{^${filename}$}; +} + +sub git_commit_info { + my ($commit, $id, $desc) = @_; + + return ($id, $desc) if ((which("git") eq "") || !(-e "$gitroot")); + + my $output = `${git_command} log --no-color --format='%H %s' -1 $commit 2>&1`; + $output =~ s/^\s*//gm; + my @lines = split("\n", $output); + + return ($id, $desc) if ($#lines < 0); + + if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous/) { +# Maybe one day convert this block of bash into something that returns +# all matching commit ids, but it's very slow... +# +# echo "checking commits $1..." +# git rev-list --remotes | grep -i "^$1" | +# while read line ; do +# git log --format='%H %s' -1 $line | +# echo "commit $(cut -c 1-12,41-)" +# done + } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./ || + $lines[0] =~ /^fatal: bad object $commit/) { + $id = undef; + } else { + $id = substr($lines[0], 0, 12); + $desc = substr($lines[0], 41); + } + + return ($id, $desc); +} + +$chk_signoff = 0 if ($file); +$chk_fixes_tag = 0 if ($file); + +my @rawlines = (); +my @lines = (); +my @fixed = (); +my @fixed_inserted = (); +my @fixed_deleted = (); +my $fixlinenr = -1; + +# If input is git commits, extract all commits from the commit expressions. +# For example, HEAD-3 means we need check 'HEAD, HEAD~1, HEAD~2'. +die "$P: No git repository found\n" if ($git && !-e "$gitroot"); + +if ($git) { + my @commits = (); + foreach my $commit_expr (@ARGV) { + my $git_range; + if ($commit_expr =~ m/^(.*)-(\d+)$/) { + $git_range = "-$2 $1"; + } elsif ($commit_expr =~ m/\.\./) { + $git_range = "$commit_expr"; + } else { + $git_range = "-1 $commit_expr"; + } + my $lines = `${git_command} log --no-color --no-merges --pretty=format:'%H %s' $git_range`; + foreach my $line (split(/\n/, $lines)) { + $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/; + next if (!defined($1) || !defined($2)); + my $sha1 = $1; + my $subject = $2; + unshift(@commits, $sha1); + $git_commits{$sha1} = $subject; + } + } + die "$P: no git commits after extraction!\n" if (@commits == 0); + @ARGV = @commits; +} + +my $vname; +$allow_c99_comments = !defined $ignore_type{"C99_COMMENT_TOLERANCE"}; +for my $filename (@ARGV) { + my $FILE; + my $is_git_file = git_is_single_file($filename); + my $oldfile = $file; + $file = 1 if ($is_git_file); + if ($git) { + open($FILE, '-|', "git format-patch -M --stdout -1 $filename") || + die "$P: $filename: git format-patch failed - $!\n"; + } elsif ($file) { + open($FILE, '-|', "diff -u /dev/null $filename") || + die "$P: $filename: diff failed - $!\n"; + } elsif ($filename eq '-') { + open($FILE, '<&STDIN'); + } else { + open($FILE, '<', "$filename") || + die "$P: $filename: open failed - $!\n"; + } + if ($filename eq '-') { + $vname = 'Your patch'; + } elsif ($git) { + $vname = "Commit " . substr($filename, 0, 12) . ' ("' . $git_commits{$filename} . '")'; + } else { + $vname = $filename; + } + while (<$FILE>) { + chomp; + push(@rawlines, $_); + $vname = qq("$1") if ($filename eq '-' && $_ =~ m/^Subject:\s+(.+)/i); + } + close($FILE); + + if ($#ARGV > 0 && $quiet == 0) { + print '-' x length($vname) . "\n"; + print "$vname\n"; + print '-' x length($vname) . "\n"; + } + + if (!process($filename)) { + $exit = 1; + } + @rawlines = (); + @lines = (); + @fixed = (); + @fixed_inserted = (); + @fixed_deleted = (); + $fixlinenr = -1; + @modifierListFile = (); + @typeListFile = (); + build_types(); + $file = $oldfile if ($is_git_file); +} + +if (!$quiet) { + hash_show_words(\%use_type, "Used"); + hash_show_words(\%ignore_type, "Ignored"); + + if (!$perl_version_ok) { + print << "EOM" + +NOTE: perl $^V is not modern enough to detect all possible issues. + An upgrade to at least perl $minimum_perl_version is suggested. +EOM + } + if ($exit) { + print << "EOM" + +NOTE: If any of the errors are false positives, please report + them to the maintainer, see CHECKPATCH in MAINTAINERS. +EOM + } +} + +exit($exit); + +sub top_of_kernel_tree { + my ($root) = @_; + + my @tree_check = ( + "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", + "README", "Documentation", "arch", "include", "drivers", + "fs", "init", "ipc", "kernel", "lib", "scripts", + ); + + foreach my $check (@tree_check) { + if (! -e $root . '/' . $check) { + return 0; + } + } + return 1; +} + +sub parse_email { + my ($formatted_email) = @_; + + my $name = ""; + my $quoted = ""; + my $name_comment = ""; + my $address = ""; + my $comment = ""; + + if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) { + $name = $1; + $address = $2; + $comment = $3 if defined $3; + } elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + } elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + $formatted_email =~ s/\Q$address\E.*$//; + $name = $formatted_email; + $name = trim($name); + $name =~ s/^\"|\"$//g; + # If there's a name left after stripping spaces and + # leading quotes, and the address doesn't have both + # leading and trailing angle brackets, the address + # is invalid. ie: + # "joe smith joe@smith.com" bad + # "joe smith ]+>$/) { + $name = ""; + $address = ""; + $comment = ""; + } + } + + # Extract comments from names excluding quoted parts + # "John D. (Doe)" - Do not extract + if ($name =~ s/\"(.+)\"//) { + $quoted = $1; + } + while ($name =~ s/\s*($balanced_parens)\s*/ /) { + $name_comment .= trim($1); + } + $name =~ s/^[ \"]+|[ \"]+$//g; + $name = trim("$quoted $name"); + + $address = trim($address); + $address =~ s/^\<|\>$//g; + $comment = trim($comment); + + if ($name =~ /[^\w \-]/i) { ##has "must quote" chars + $name =~ s/(?"; + } + $formatted_email .= "$comment"; + return $formatted_email; +} + +sub reformat_email { + my ($email) = @_; + + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + return format_email($email_name, $name_comment, $email_address, $comment); +} + +sub same_email_addresses { + my ($email1, $email2) = @_; + + my ($email1_name, $name1_comment, $email1_address, $comment1) = parse_email($email1); + my ($email2_name, $name2_comment, $email2_address, $comment2) = parse_email($email2); + + return $email1_name eq $email2_name && + $email1_address eq $email2_address && + $name1_comment eq $name2_comment && + $comment1 eq $comment2; +} + +sub which { + my ($bin) = @_; + + foreach my $path (split(/:/, $ENV{PATH})) { + if (-e "$path/$bin") { + return "$path/$bin"; + } + } + + return ""; +} + +sub which_conf { + my ($conf) = @_; + + foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) { + if (-e "$path/$conf") { + return "$path/$conf"; + } + } + + return ""; +} + +sub expand_tabs { + my ($str) = @_; + + my $res = ''; + my $n = 0; + for my $c (split(//, $str)) { + if ($c eq "\t") { + $res .= ' '; + $n++; + for (; ($n % $tabsize) != 0; $n++) { + $res .= ' '; + } + next; + } + $res .= $c; + $n++; + } + + return $res; +} +sub copy_spacing { + (my $res = shift) =~ tr/\t/ /c; + return $res; +} + +sub line_stats { + my ($line) = @_; + + # Drop the diff line leader and expand tabs + $line =~ s/^.//; + $line = expand_tabs($line); + + # Pick the indent from the front of the line. + my ($white) = ($line =~ /^(\s*)/); + + return (length($line), length($white)); +} + +my $sanitise_quote = ''; + +sub sanitise_line_reset { + my ($in_comment) = @_; + + if ($in_comment) { + $sanitise_quote = '*/'; + } else { + $sanitise_quote = ''; + } +} +sub sanitise_line { + my ($line) = @_; + + my $res = ''; + my $l = ''; + + my $qlen = 0; + my $off = 0; + my $c; + + # Always copy over the diff marker. + $res = substr($line, 0, 1); + + for ($off = 1; $off < length($line); $off++) { + $c = substr($line, $off, 1); + + # Comments we are whacking completely including the begin + # and end, all to $;. + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') { + $sanitise_quote = '*/'; + + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') { + $sanitise_quote = ''; + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') { + $sanitise_quote = '//'; + + substr($res, $off, 2, $sanitise_quote); + $off++; + next; + } + + # A \ in a string means ignore the next character. + if (($sanitise_quote eq "'" || $sanitise_quote eq '"') && + $c eq "\\") { + substr($res, $off, 2, 'XX'); + $off++; + next; + } + # Regular quotes. + if ($c eq "'" || $c eq '"') { + if ($sanitise_quote eq '') { + $sanitise_quote = $c; + + substr($res, $off, 1, $c); + next; + } elsif ($sanitise_quote eq $c) { + $sanitise_quote = ''; + } + } + + #print "c<$c> SQ<$sanitise_quote>\n"; + if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote && $c ne "\t") { + substr($res, $off, 1, 'X'); + } else { + substr($res, $off, 1, $c); + } + } + + if ($sanitise_quote eq '//') { + $sanitise_quote = ''; + } + + # The pathname on a #include may be surrounded by '<' and '>'. + if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) { + my $clean = 'X' x length($1); + $res =~ s@\<.*\>@<$clean>@; + + # The whole of a #error is a string. + } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) { + my $clean = 'X' x length($1); + $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@; + } + + if ($allow_c99_comments && $res =~ m@(//.*$)@) { + my $match = $1; + $res =~ s/\Q$match\E/"$;" x length($match)/e; + } + + return $res; +} + +sub get_quoted_string { + my ($line, $rawline) = @_; + + return "" if (!defined($line) || !defined($rawline)); + return "" if ($line !~ m/($String)/g); + return substr($rawline, $-[0], $+[0] - $-[0]); +} + +sub ctx_statement_block { + my ($linenr, $remain, $off) = @_; + my $line = $linenr - 1; + my $blk = ''; + my $soff = $off; + my $coff = $off - 1; + my $coff_set = 0; + + my $loff = 0; + + my $type = ''; + my $level = 0; + my @stack = (); + my $p; + my $c; + my $len = 0; + + my $remainder; + while (1) { + @stack = (['', 0]) if ($#stack == -1); + + #warn "CSB: blk<$blk> remain<$remain>\n"; + # If we are about to drop off the end, pull in more + # context. + if ($off >= $len) { + for (; $remain > 0; $line++) { + last if (!defined $lines[$line]); + next if ($lines[$line] =~ /^-/); + $remain--; + $loff = $len; + $blk .= $lines[$line] . "\n"; + $len = length($blk); + $line++; + last; + } + # Bail if there is no further context. + #warn "CSB: blk<$blk> off<$off> len<$len>\n"; + if ($off >= $len) { + last; + } + if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) { + $level++; + $type = '#'; + } + } + $p = $c; + $c = substr($blk, $off, 1); + $remainder = substr($blk, $off); + + #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n"; + + # Handle nested #if/#else. + if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, [ $type, $level ]); + } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) { + ($type, $level) = @{$stack[$#stack - 1]}; + } elsif ($remainder =~ /^#\s*endif\b/) { + ($type, $level) = @{pop(@stack)}; + } + + # Statement ends at the ';' or a close '}' at the + # outermost level. + if ($level == 0 && $c eq ';') { + last; + } + + # An else is really a conditional as long as its not else if + if ($level == 0 && $coff_set == 0 && + (!defined($p) || $p =~ /(?:\s|\}|\+)/) && + $remainder =~ /^(else)(?:\s|{)/ && + $remainder !~ /^else\s+if\b/) { + $coff = $off + length($1) - 1; + $coff_set = 1; + #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n"; + #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n"; + } + + if (($type eq '' || $type eq '(') && $c eq '(') { + $level++; + $type = '('; + } + if ($type eq '(' && $c eq ')') { + $level--; + $type = ($level != 0)? '(' : ''; + + if ($level == 0 && $coff < $soff) { + $coff = $off; + $coff_set = 1; + #warn "CSB: mark coff<$coff>\n"; + } + } + if (($type eq '' || $type eq '{') && $c eq '{') { + $level++; + $type = '{'; + } + if ($type eq '{' && $c eq '}') { + $level--; + $type = ($level != 0)? '{' : ''; + + if ($level == 0) { + if (substr($blk, $off + 1, 1) eq ';') { + $off++; + } + last; + } + } + # Preprocessor commands end at the newline unless escaped. + if ($type eq '#' && $c eq "\n" && $p ne "\\") { + $level--; + $type = ''; + $off++; + last; + } + $off++; + } + # We are truly at the end, so shuffle to the next line. + if ($off == $len) { + $loff = $len + 1; + $line++; + $remain--; + } + + my $statement = substr($blk, $soff, $off - $soff + 1); + my $condition = substr($blk, $soff, $coff - $soff + 1); + + #warn "STATEMENT<$statement>\n"; + #warn "CONDITION<$condition>\n"; + + #print "coff<$coff> soff<$off> loff<$loff>\n"; + + return ($statement, $condition, + $line, $remain + 1, $off - $loff + 1, $level); +} + +sub statement_lines { + my ($stmt) = @_; + + # Strip the diff line prefixes and rip blank lines at start and end. + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_rawlines { + my ($stmt) = @_; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_block_size { + my ($stmt) = @_; + + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*{//; + $stmt =~ s/}\s*$//; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + my @stmt_statements = ($stmt =~ /;/g); + + my $stmt_lines = $#stmt_lines + 2; + my $stmt_statements = $#stmt_statements + 1; + + if ($stmt_lines > $stmt_statements) { + return $stmt_lines; + } else { + return $stmt_statements; + } +} + +sub ctx_statement_full { + my ($linenr, $remain, $off) = @_; + my ($statement, $condition, $level); + + my (@chunks); + + # Grab the first conditional/block pair. + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "F: c<$condition> s<$statement> remain<$remain>\n"; + push(@chunks, [ $condition, $statement ]); + if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) { + return ($level, $linenr, @chunks); + } + + # Pull in the following conditional/block pairs and see if they + # could continue the statement. + for (;;) { + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "C: c<$condition> s<$statement> remain<$remain>\n"; + last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s)); + #print "C: push\n"; + push(@chunks, [ $condition, $statement ]); + } + + return ($level, $linenr, @chunks); +} + +sub ctx_block_get { + my ($linenr, $remain, $outer, $open, $close, $off) = @_; + my $line; + my $start = $linenr - 1; + my $blk = ''; + my @o; + my @c; + my @res = (); + + my $level = 0; + my @stack = ($level); + for ($line = $start; $remain > 0; $line++) { + next if ($rawlines[$line] =~ /^-/); + $remain--; + + $blk .= $rawlines[$line]; + + # Handle nested #if/#else. + if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, $level); + } elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) { + $level = $stack[$#stack - 1]; + } elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) { + $level = pop(@stack); + } + + foreach my $c (split(//, $lines[$line])) { + ##print "C<$c>L<$level><$open$close>O<$off>\n"; + if ($off > 0) { + $off--; + next; + } + + if ($c eq $close && $level > 0) { + $level--; + last if ($level == 0); + } elsif ($c eq $open) { + $level++; + } + } + + if (!$outer || $level <= 1) { + push(@res, $rawlines[$line]); + } + + last if ($level == 0); + } + + return ($level, @res); +} +sub ctx_block_outer { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0); + return @r; +} +sub ctx_block { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0); + return @r; +} +sub ctx_statement { + my ($linenr, $remain, $off) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off); + return @r; +} +sub ctx_block_level { + my ($linenr, $remain) = @_; + + return ctx_block_get($linenr, $remain, 0, '{', '}', 0); +} +sub ctx_statement_level { + my ($linenr, $remain, $off) = @_; + + return ctx_block_get($linenr, $remain, 0, '(', ')', $off); +} + +sub ctx_locate_comment { + my ($first_line, $end_line) = @_; + + # If c99 comment on the current line, or the line before or after + my ($current_comment) = ($rawlines[$end_line - 1] =~ m@^\+.*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line - 2] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + + # Catch a comment on the end of the line itself. + ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); + return $current_comment if (defined $current_comment); + + # Look through the context and try and figure out if there is a + # comment. + my $in_comment = 0; + $current_comment = ''; + for (my $linenr = $first_line; $linenr < $end_line; $linenr++) { + my $line = $rawlines[$linenr - 1]; + #warn " $line\n"; + if ($linenr == $first_line and $line =~ m@^.\s*\*@) { + $in_comment = 1; + } + if ($line =~ m@/\*@) { + $in_comment = 1; + } + if (!$in_comment && $current_comment ne '') { + $current_comment = ''; + } + $current_comment .= $line . "\n" if ($in_comment); + if ($line =~ m@\*/@) { + $in_comment = 0; + } + } + + chomp($current_comment); + return($current_comment); +} +sub ctx_has_comment { + my ($first_line, $end_line) = @_; + my $cmt = ctx_locate_comment($first_line, $end_line); + + ##print "LINE: $rawlines[$end_line - 1 ]\n"; + ##print "CMMT: $cmt\n"; + + return ($cmt ne ''); +} + +sub raw_line { + my ($linenr, $cnt) = @_; + + my $offset = $linenr - 1; + $cnt++; + + my $line; + while ($cnt) { + $line = $rawlines[$offset++]; + next if (defined($line) && $line =~ /^-/); + $cnt--; + } + + return $line; +} + +sub get_stat_real { + my ($linenr, $lc) = @_; + + my $stat_real = raw_line($linenr, 0); + for (my $count = $linenr + 1; $count <= $lc; $count++) { + $stat_real = $stat_real . "\n" . raw_line($count, 0); + } + + return $stat_real; +} + +sub get_stat_here { + my ($linenr, $cnt, $here) = @_; + + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + + return $herectx; +} + +sub cat_vet { + my ($vet) = @_; + my ($res, $coded); + + $res = ''; + while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) { + $res .= $1; + if ($2 ne '') { + $coded = sprintf("^%c", unpack('C', $2) + 64); + $res .= $coded; + } + } + $res =~ s/$/\$/; + + return $res; +} + +my $av_preprocessor = 0; +my $av_pending; +my @av_paren_type; +my $av_pend_colon; + +sub annotate_reset { + $av_preprocessor = 0; + $av_pending = '_'; + @av_paren_type = ('E'); + $av_pend_colon = 'O'; +} + +sub annotate_values { + my ($stream, $type) = @_; + + my $res; + my $var = '_' x length($stream); + my $cur = $stream; + + print "$stream\n" if ($dbg_values > 1); + + while (length($cur)) { + @av_paren_type = ('E') if ($#av_paren_type < 0); + print " <" . join('', @av_paren_type) . + "> <$type> <$av_pending>" if ($dbg_values > 1); + if ($cur =~ /^(\s+)/o) { + print "WS($1)\n" if ($dbg_values > 1); + if ($1 =~ /\n/ && $av_preprocessor) { + $type = pop(@av_paren_type); + $av_preprocessor = 0; + } + + } elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') { + print "CAST($1)\n" if ($dbg_values > 1); + push(@av_paren_type, $type); + $type = 'c'; + + } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) { + print "DECLARE($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^($Modifier)\s*/) { + print "MODIFIER($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) { + print "DEFINE($1,$2)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + if ($2 ne '') { + $av_pending = 'N'; + } + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) { + print "UNDEF($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + + } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) { + print "PRE_START($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) { + print "PRE_RESTART($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $av_paren_type[$#av_paren_type]); + + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:endif))/o) { + print "PRE_END($1)\n" if ($dbg_values > 1); + + $av_preprocessor = 1; + + # Assume all arms of the conditional end as this + # one does, and continue as if the #endif was not here. + pop(@av_paren_type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\\\n)/o) { + print "PRECONT($1)\n" if ($dbg_values > 1); + + } elsif ($cur =~ /^(__attribute__)\s*\(?/o) { + print "ATTR($1)\n" if ($dbg_values > 1); + $av_pending = $type; + $type = 'N'; + + } elsif ($cur =~ /^(sizeof)\s*(\()?/o) { + print "SIZEOF($1)\n" if ($dbg_values > 1); + if (defined $2) { + $av_pending = 'V'; + } + $type = 'N'; + + } elsif ($cur =~ /^(if|while|for)\b/o) { + print "COND($1)\n" if ($dbg_values > 1); + $av_pending = 'E'; + $type = 'N'; + + } elsif ($cur =~/^(case)/o) { + print "CASE($1)\n" if ($dbg_values > 1); + $av_pend_colon = 'C'; + $type = 'N'; + + } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) { + print "KEYWORD($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(\()/o) { + print "PAREN('$1')\n" if ($dbg_values > 1); + push(@av_paren_type, $av_pending); + $av_pending = '_'; + $type = 'N'; + + } elsif ($cur =~ /^(\))/o) { + my $new_type = pop(@av_paren_type); + if ($new_type ne '_') { + $type = $new_type; + print "PAREN('$1') -> $type\n" + if ($dbg_values > 1); + } else { + print "PAREN('$1')\n" if ($dbg_values > 1); + } + + } elsif ($cur =~ /^($Ident)\s*\(/o) { + print "FUNC($1)\n" if ($dbg_values > 1); + $type = 'V'; + $av_pending = 'V'; + + } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) { + if (defined $2 && $type eq 'C' || $type eq 'T') { + $av_pend_colon = 'B'; + } elsif ($type eq 'E') { + $av_pend_colon = 'L'; + } + print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Ident|$Constant)/o) { + print "IDENT($1)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Assignment)/o) { + print "ASSIGN($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~/^(;|{|})/) { + print "END($1)\n" if ($dbg_values > 1); + $type = 'E'; + $av_pend_colon = 'O'; + + } elsif ($cur =~/^(,)/) { + print "COMMA($1)\n" if ($dbg_values > 1); + $type = 'C'; + + } elsif ($cur =~ /^(\?)/o) { + print "QUESTION($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(:)/o) { + print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1); + + substr($var, length($res), 1, $av_pend_colon); + if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') { + $type = 'E'; + } else { + $type = 'N'; + } + $av_pend_colon = 'O'; + + } elsif ($cur =~ /^(\[)/o) { + print "CLOSE($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) { + my $variant; + + print "OPV($1)\n" if ($dbg_values > 1); + if ($type eq 'V') { + $variant = 'B'; + } else { + $variant = 'U'; + } + + substr($var, length($res), 1, $variant); + $type = 'N'; + + } elsif ($cur =~ /^($Operators)/o) { + print "OP($1)\n" if ($dbg_values > 1); + if ($1 ne '++' && $1 ne '--') { + $type = 'N'; + } + + } elsif ($cur =~ /(^.)/o) { + print "C($1)\n" if ($dbg_values > 1); + } + if (defined $1) { + $cur = substr($cur, length($1)); + $res .= $type x length($1); + } + } + + return ($res, $var); +} + +sub possible { + my ($possible, $line) = @_; + my $notPermitted = qr{(?: + ^(?: + $Modifier| + $Storage| + $Type| + DEFINE_\S+ + )$| + ^(?: + goto| + return| + case| + else| + asm|__asm__| + do| + \#| + \#\#| + )(?:\s|$)| + ^(?:typedef|struct|enum)\b + )}x; + warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2); + if ($possible !~ $notPermitted) { + # Check for modifiers. + $possible =~ s/\s*$Storage\s*//g; + $possible =~ s/\s*$Sparse\s*//g; + if ($possible =~ /^\s*$/) { + + } elsif ($possible =~ /\s/) { + $possible =~ s/\s*$Type\s*//g; + for my $modifier (split(' ', $possible)) { + if ($modifier !~ $notPermitted) { + warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible); + push(@modifierListFile, $modifier); + } + } + + } else { + warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible); + push(@typeListFile, $possible); + } + build_types(); + } else { + warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1); + } +} + +my $prefix = ''; + +sub show_type { + my ($type) = @_; + + $type =~ tr/[a-z]/[A-Z]/; + + return defined $use_type{$type} if (scalar keys %use_type > 0); + + return !defined $ignore_type{$type}; +} + +sub report { + my ($level, $type, $msg) = @_; + + if (!show_type($type) || + (defined $tst_only && $msg !~ /\Q$tst_only\E/)) { + return 0; + } + my $output = ''; + if ($color) { + if ($level eq 'ERROR') { + $output .= RED; + } elsif ($level eq 'WARNING') { + $output .= YELLOW; + } else { + $output .= GREEN; + } + } + $output .= $prefix . $level . ':'; + if ($show_types) { + $output .= BLUE if ($color); + $output .= "$type:"; + } + $output .= RESET if ($color); + $output .= ' ' . $msg . "\n"; + + if ($showfile) { + my @lines = split("\n", $output, -1); + splice(@lines, 1, 1); + $output = join("\n", @lines); + } + + if ($terse) { + $output = (split('\n', $output))[0] . "\n"; + } + + if ($verbose && exists($verbose_messages{$type}) && + !exists($verbose_emitted{$type})) { + $output .= $verbose_messages{$type} . "\n\n"; + $verbose_emitted{$type} = 1; + } + + push(our @report, $output); + + return 1; +} + +sub report_dump { + our @report; +} + +sub fixup_current_range { + my ($lineRef, $offset, $length) = @_; + + if ($$lineRef =~ /^\@\@ -\d+,\d+ \+(\d+),(\d+) \@\@/) { + my $o = $1; + my $l = $2; + my $no = $o + $offset; + my $nl = $l + $length; + $$lineRef =~ s/\+$o,$l \@\@/\+$no,$nl \@\@/; + } +} + +sub fix_inserted_deleted_lines { + my ($linesRef, $insertedRef, $deletedRef) = @_; + + my $range_last_linenr = 0; + my $delta_offset = 0; + + my $old_linenr = 0; + my $new_linenr = 0; + + my $next_insert = 0; + my $next_delete = 0; + + my @lines = (); + + my $inserted = @{$insertedRef}[$next_insert++]; + my $deleted = @{$deletedRef}[$next_delete++]; + + foreach my $old_line (@{$linesRef}) { + my $save_line = 1; + my $line = $old_line; #don't modify the array + if ($line =~ /^(?:\+\+\+|\-\-\-)\s+\S+/) { #new filename + $delta_offset = 0; + } elsif ($line =~ /^\@\@ -\d+,\d+ \+\d+,\d+ \@\@/) { #new hunk + $range_last_linenr = $new_linenr; + fixup_current_range(\$line, $delta_offset, 0); + } + + while (defined($deleted) && ${$deleted}{'LINENR'} == $old_linenr) { + $deleted = @{$deletedRef}[$next_delete++]; + $save_line = 0; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset--, -1); + } + + while (defined($inserted) && ${$inserted}{'LINENR'} == $old_linenr) { + push(@lines, ${$inserted}{'LINE'}); + $inserted = @{$insertedRef}[$next_insert++]; + $new_linenr++; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset++, 1); + } + + if ($save_line) { + push(@lines, $line); + $new_linenr++; + } + + $old_linenr++; + } + + return @lines; +} + +sub fix_insert_line { + my ($linenr, $line) = @_; + + my $inserted = { + LINENR => $linenr, + LINE => $line, + }; + push(@fixed_inserted, $inserted); +} + +sub fix_delete_line { + my ($linenr, $line) = @_; + + my $deleted = { + LINENR => $linenr, + LINE => $line, + }; + + push(@fixed_deleted, $deleted); +} + +sub ERROR { + my ($type, $msg) = @_; + + if (report("ERROR", $type, $msg)) { + our $clean = 0; + our $cnt_error++; + return 1; + } + return 0; +} +sub WARN { + my ($type, $msg) = @_; + + if (report("WARNING", $type, $msg)) { + our $clean = 0; + our $cnt_warn++; + return 1; + } + return 0; +} +sub CHK { + my ($type, $msg) = @_; + + if ($check && report("CHECK", $type, $msg)) { + our $clean = 0; + our $cnt_chk++; + return 1; + } + return 0; +} + +sub check_absolute_file { + my ($absolute, $herecurr) = @_; + my $file = $absolute; + + ##print "absolute<$absolute>\n"; + + # See if any suffix of this path is a path within the tree. + while ($file =~ s@^[^/]*/@@) { + if (-f "$root/$file") { + ##print "file<$file>\n"; + last; + } + } + if (! -f _) { + return 0; + } + + # It is, so see if the prefix is acceptable. + my $prefix = $absolute; + substr($prefix, -length($file)) = ''; + + ##print "prefix<$prefix>\n"; + if ($prefix ne ".../") { + WARN("USE_RELATIVE_PATH", + "use relative pathname instead of absolute in changelog text\n" . $herecurr); + } +} + +sub trim { + my ($string) = @_; + + $string =~ s/^\s+|\s+$//g; + + return $string; +} + +sub ltrim { + my ($string) = @_; + + $string =~ s/^\s+//; + + return $string; +} + +sub rtrim { + my ($string) = @_; + + $string =~ s/\s+$//; + + return $string; +} + +sub string_find_replace { + my ($string, $find, $replace) = @_; + + $string =~ s/$find/$replace/g; + + return $string; +} + +sub tabify { + my ($leading) = @_; + + my $source_indent = $tabsize; + my $max_spaces_before_tab = $source_indent - 1; + my $spaces_to_tab = " " x $source_indent; + + #convert leading spaces to tabs + 1 while $leading =~ s@^([\t]*)$spaces_to_tab@$1\t@g; + #Remove spaces before a tab + 1 while $leading =~ s@^([\t]*)( {1,$max_spaces_before_tab})\t@$1\t@g; + + return "$leading"; +} + +sub pos_last_openparen { + my ($line) = @_; + + my $pos = 0; + + my $opens = $line =~ tr/\(/\(/; + my $closes = $line =~ tr/\)/\)/; + + my $last_openparen = 0; + + if (($opens == 0) || ($closes >= $opens)) { + return -1; + } + + my $len = length($line); + + for ($pos = 0; $pos < $len; $pos++) { + my $string = substr($line, $pos); + if ($string =~ /^($FuncArg|$balanced_parens)/) { + $pos += length($1) - 1; + } elsif (substr($line, $pos, 1) eq '(') { + $last_openparen = $pos; + } elsif (index($string, '(') == -1) { + last; + } + } + + return length(expand_tabs(substr($line, 0, $last_openparen))) + 1; +} + +sub get_raw_comment { + my ($line, $rawline) = @_; + my $comment = ''; + + for my $i (0 .. (length($line) - 1)) { + if (substr($line, $i, 1) eq "$;") { + $comment .= substr($rawline, $i, 1); + } + } + + return $comment; +} + +sub exclude_global_initialisers { + my ($realfile) = @_; + + # Do not check for BPF programs (tools/testing/selftests/bpf/progs/*.c, samples/bpf/*_kern.c, *.bpf.c). + return $realfile =~ m@^tools/testing/selftests/bpf/progs/.*\.c$@ || + $realfile =~ m@^samples/bpf/.*_kern\.c$@ || + $realfile =~ m@/bpf/.*\.bpf\.c$@; +} + +sub process { + my $filename = shift; + + my $linenr=0; + my $prevline=""; + my $prevrawline=""; + my $stashline=""; + my $stashrawline=""; + + my $length; + my $indent; + my $previndent=0; + my $stashindent=0; + + our $clean = 1; + my $signoff = 0; + my $fixes_tag = 0; + my $is_revert = 0; + my $needs_fixes_tag = ""; + my $author = ''; + my $authorsignoff = 0; + my $author_sob = ''; + my $is_patch = 0; + my $is_binding_patch = -1; + my $in_header_lines = $file ? 0 : 1; + my $in_commit_log = 0; #Scanning lines before patch + my $has_patch_separator = 0; #Found a --- line + my $has_commit_log = 0; #Encountered lines before patch + my $commit_log_lines = 0; #Number of commit log lines + my $commit_log_possible_stack_dump = 0; + my $commit_log_long_line = 0; + my $commit_log_has_diff = 0; + my $reported_maintainer_file = 0; + my $non_utf8_charset = 0; + + my $last_git_commit_id_linenr = -1; + + my $last_blank_line = 0; + my $last_coalesced_string_linenr = -1; + + our @report = (); + our $cnt_lines = 0; + our $cnt_error = 0; + our $cnt_warn = 0; + our $cnt_chk = 0; + + # Trace the real file/line as we go. + my $realfile = ''; + my $realline = 0; + my $realcnt = 0; + my $here = ''; + my $context_function; #undef'd unless there's a known function + my $in_comment = 0; + my $comment_edge = 0; + my $first_line = 0; + my $p1_prefix = ''; + + my $prev_values = 'E'; + + # suppression flags + my %suppress_ifbraces; + my %suppress_whiletrailers; + my %suppress_export; + my $suppress_statement = 0; + + my %signatures = (); + + # Pre-scan the patch sanitizing the lines. + # Pre-scan the patch looking for any __setup documentation. + # + my @setup_docs = (); + my $setup_docs = 0; + + my $camelcase_file_seeded = 0; + + my $checklicenseline = 1; + + sanitise_line_reset(); + my $line; + foreach my $rawline (@rawlines) { + $linenr++; + $line = $rawline; + + push(@fixed, $rawline) if ($fix); + + if ($rawline=~/^\+\+\+\s+(\S+)/) { + $setup_docs = 0; + if ($1 =~ m@Documentation/admin-guide/kernel-parameters.txt$@) { + $setup_docs = 1; + } + #next; + } + if ($rawline =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + $in_comment = 0; + + # Guestimate if this is a continuing comment. Run + # the context looking for a comment "edge". If this + # edge is a close comment then we must be in a comment + # at context start. + my $edge; + my $cnt = $realcnt; + for (my $ln = $linenr + 1; $cnt > 0; $ln++) { + next if (defined $rawlines[$ln - 1] && + $rawlines[$ln - 1] =~ /^-/); + $cnt--; + #print "RAW<$rawlines[$ln - 1]>\n"; + last if (!defined $rawlines[$ln - 1]); + if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ && + $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) { + ($edge) = $1; + last; + } + } + if (defined $edge && $edge eq '*/') { + $in_comment = 1; + } + + # Guestimate if this is a continuing comment. If this + # is the start of a diff block and this line starts + # ' *' then it is very likely a comment. + if (!defined $edge && + $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@) + { + $in_comment = 1; + } + + ##print "COMMENT:$in_comment edge<$edge> $rawline\n"; + sanitise_line_reset($in_comment); + + } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) { + # Standardise the strings and chars within the input to + # simplify matching -- only bother with positive lines. + $line = sanitise_line($rawline); + } + push(@lines, $line); + + if ($realcnt > 1) { + $realcnt-- if ($line =~ /^(?:\+| |$)/); + } else { + $realcnt = 0; + } + + #print "==>$rawline\n"; + #print "-->$line\n"; + + if ($setup_docs && $line =~ /^\+/) { + push(@setup_docs, $line); + } + } + + $prefix = ''; + + $realcnt = 0; + $linenr = 0; + $fixlinenr = -1; + foreach my $line (@lines) { + $linenr++; + $fixlinenr++; + my $sline = $line; #copy of $line + $sline =~ s/$;/ /g; #with comments as spaces + + my $rawline = $rawlines[$linenr - 1]; + my $raw_comment = get_raw_comment($line, $rawline); + +# check if it's a mode change, rename or start of a patch + if (!$in_commit_log && + ($line =~ /^ mode change [0-7]+ => [0-7]+ \S+\s*$/ || + ($line =~ /^rename (?:from|to) \S+\s*$/ || + $line =~ /^diff --git a\/[\w\/\.\_\-]+ b\/\S+\s*$/))) { + $is_patch = 1; + } + +#extract the line range in the file after the patch is applied + if (!$in_commit_log && + $line =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@(.*)/) { + my $context = $4; + $is_patch = 1; + $first_line = $linenr + 1; + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + annotate_reset(); + $prev_values = 'E'; + + %suppress_ifbraces = (); + %suppress_whiletrailers = (); + %suppress_export = (); + $suppress_statement = 0; + if ($context =~ /\b(\w+)\s*\(/) { + $context_function = $1; + } else { + undef $context_function; + } + next; + +# track the line number as we move through the hunk, note that +# new versions of GNU diff omit the leading space on completely +# blank context lines so we need to count that too. + } elsif ($line =~ /^( |\+|$)/) { + $realline++; + $realcnt-- if ($realcnt != 0); + + # Measure the line length and indent. + ($length, $indent) = line_stats($rawline); + + # Track the previous line. + ($prevline, $stashline) = ($stashline, $line); + ($previndent, $stashindent) = ($stashindent, $indent); + ($prevrawline, $stashrawline) = ($stashrawline, $rawline); + + #warn "line<$line>\n"; + + } elsif ($realcnt == 1) { + $realcnt--; + } + + my $hunk_line = ($realcnt != 0); + + $here = "#$linenr: " if (!$file); + $here = "#$realline: " if ($file); + + my $found_file = 0; + # extract the filename as it passes + if ($line =~ /^diff --git.*?(\S+)$/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + $found_file = 1; + } elsif ($line =~ /^\+\+\+\s+(\S+)/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + + $p1_prefix = $1; + if (!$file && $tree && $p1_prefix ne '' && + -e "$root/$p1_prefix") { + WARN("PATCH_PREFIX", + "patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n"); + } + + if ($realfile =~ m@^include/asm/@) { + ERROR("MODIFIED_INCLUDE_ASM", + "do not modify files in include/asm, change architecture specific files in include/asm-\n" . "$here$rawline\n"); + } + $found_file = 1; + } + +#make up the handle for any error we report on this line + if ($showfile) { + $prefix = "$realfile:$realline: " + } elsif ($emacs) { + if ($file) { + $prefix = "$filename:$realline: "; + } else { + $prefix = "$filename:$linenr: "; + } + } + + if ($found_file) { + if (is_maintained_obsolete($realfile)) { + WARN("OBSOLETE", + "$realfile is marked as 'obsolete' in the MAINTAINERS hierarchy. No unnecessary modifications please.\n"); + } + if ($realfile =~ m@^(?:drivers/net/|net/|drivers/staging/)@) { + $check = 1; + } else { + $check = $check_orig; + } + $checklicenseline = 1; + + if ($realfile !~ /^MAINTAINERS/) { + my $last_binding_patch = $is_binding_patch; + + $is_binding_patch = () = $realfile =~ m@^(?:Documentation/devicetree/|include/dt-bindings/)@; + + if (($last_binding_patch != -1) && + ($last_binding_patch ^ $is_binding_patch)) { + WARN("DT_SPLIT_BINDING_PATCH", + "DT binding docs and includes should be a separate patch. See: Documentation/devicetree/bindings/submitting-patches.rst\n"); + } + } + + next; + } + + $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); + + my $hereline = "$here\n$rawline\n"; + my $herecurr = "$here\n$rawline\n"; + my $hereprev = "$here\n$prevrawline\n$rawline\n"; + + $cnt_lines++ if ($realcnt != 0); + +# Verify the existence of a commit log if appropriate +# 2 is used because a $signature is counted in $commit_log_lines + if ($in_commit_log) { + if ($line !~ /^\s*$/) { + $commit_log_lines++; #could be a $signature + } + } elsif ($has_commit_log && $commit_log_lines < 2) { + WARN("COMMIT_MESSAGE", + "Missing commit description - Add an appropriate one\n"); + $commit_log_lines = 2; #warn only once + } + +# Check if the commit log has what seems like a diff which can confuse patch + if ($in_commit_log && !$commit_log_has_diff && + (($line =~ m@^\s+diff\b.*a/([\w/]+)@ && + $line =~ m@^\s+diff\b.*a/[\w/]+\s+b/$1\b@) || + $line =~ m@^\s*(?:\-\-\-\s+a/|\+\+\+\s+b/)@ || + $line =~ m/^\s*\@\@ \-\d+,\d+ \+\d+,\d+ \@\@/)) { + ERROR("DIFF_IN_COMMIT_MSG", + "Avoid using diff content in the commit message - patch(1) might not work\n" . $herecurr); + $commit_log_has_diff = 1; + } + +# Check for incorrect file permissions + if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) { + my $permhere = $here . "FILE: $realfile\n"; + if ($realfile !~ m@scripts/@ && + $realfile !~ /\.(py|pl|awk|sh)$/) { + ERROR("EXECUTE_PERMISSIONS", + "do not set execute permissions for source files\n" . $permhere); + } + } + +# Check the patch for a From: + if (decode("MIME-Header", $line) =~ /^From:\s*(.*)/) { + $author = $1; + my $curline = $linenr; + while(defined($rawlines[$curline]) && ($rawlines[$curline++] =~ /^[ \t]\s*(.*)/)) { + $author .= $1; + } + $author = encode("utf8", $author) if ($line =~ /=\?utf-8\?/i); + $author =~ s/"//g; + $author = reformat_email($author); + } + +# Check the patch for a signoff: + if ($line =~ /^\s*signed-off-by:\s*(.*)/i) { + $signoff++; + $in_commit_log = 0; + if ($author ne '' && $authorsignoff != 1) { + if (same_email_addresses($1, $author)) { + $authorsignoff = 1; + } else { + my $ctx = $1; + my ($email_name, $email_comment, $email_address, $comment1) = parse_email($ctx); + my ($author_name, $author_comment, $author_address, $comment2) = parse_email($author); + + if (lc $email_address eq lc $author_address && $email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 2; + } elsif (lc $email_address eq lc $author_address) { + $author_sob = $ctx; + $authorsignoff = 3; + } elsif ($email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 4; + + my $address1 = $email_address; + my $address2 = $author_address; + + if ($address1 =~ /(\S+)\+\S+(\@.*)/) { + $address1 = "$1$2"; + } + if ($address2 =~ /(\S+)\+\S+(\@.*)/) { + $address2 = "$1$2"; + } + if ($address1 eq $address2) { + $authorsignoff = 5; + } + } + } + } + } + +# Check for patch separator + if ($line =~ /^---$/) { + $has_patch_separator = 1; + $in_commit_log = 0; + } + +# Check if MAINTAINERS is being updated. If so, there's probably no need to +# emit the "does MAINTAINERS need updating?" message on file add/move/delete + if ($line =~ /^\s*MAINTAINERS\s*\|/) { + $reported_maintainer_file = 1; + } + +# Check signature styles + if (!$in_header_lines && + $line =~ /^(\s*)([a-z0-9_-]+by:|$signature_tags)(\s*)(.*)/i) { + my $space_before = $1; + my $sign_off = $2; + my $space_after = $3; + my $email = $4; + my $ucfirst_sign_off = ucfirst(lc($sign_off)); + + if ($sign_off !~ /$signature_tags/) { + my $suggested_signature = find_standard_signature($sign_off); + if ($suggested_signature eq "") { + WARN("BAD_SIGN_OFF", + "Non-standard signature: $sign_off\n" . $herecurr); + } else { + if (WARN("BAD_SIGN_OFF", + "Non-standard signature: '$sign_off' - perhaps '$suggested_signature'?\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/$sign_off/$suggested_signature/; + } + } + } + if (defined $space_before && $space_before ne "") { + if (WARN("BAD_SIGN_OFF", + "Do not use whitespace before $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) { + if (WARN("BAD_SIGN_OFF", + "'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + + } + if (!defined $space_after || $space_after ne " ") { + if (WARN("BAD_SIGN_OFF", + "Use a single space after $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + my $suggested_email = format_email(($email_name, $name_comment, $email_address, $comment)); + if ($suggested_email eq "") { + ERROR("BAD_SIGN_OFF", + "Unrecognized email address: '$email'\n" . $herecurr); + } else { + my $dequoted = $suggested_email; + $dequoted =~ s/^"//; + $dequoted =~ s/" 1) { + WARN("BAD_SIGN_OFF", + "Use a single name comment in email: '$email'\n" . $herecurr); + } + + + # stable@vger.kernel.org or stable@kernel.org shouldn't + # have an email name. In addition comments should strictly + # begin with a # + if ($email =~ /^.*stable\@(?:vger\.)?kernel\.org/i) { + if (($comment ne "" && $comment !~ /^#.+/) || + ($email_name ne "")) { + my $cur_name = $email_name; + my $new_comment = $comment; + $cur_name =~ s/[a-zA-Z\s\-\"]+//g; + + # Remove brackets enclosing comment text + # and # from start of comments to get comment text + $new_comment =~ s/^\((.*)\)$/$1/; + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^[\s\#]+|\s+$//g; + + $new_comment = trim("$new_comment $cur_name") if ($cur_name ne $new_comment); + $new_comment = " # $new_comment" if ($new_comment ne ""); + my $new_email = "$email_address$new_comment"; + + if (WARN("BAD_STABLE_ADDRESS_STYLE", + "Invalid email format for stable: '$email', prefer '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } + } + } elsif ($comment ne "" && $comment !~ /^(?:#.+|\(.+\))$/) { + my $new_comment = $comment; + + # Extract comment text from within brackets or + # c89 style /*...*/ comments + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^\/\*(.*)\*\/$/$1/; + + $new_comment = trim($new_comment); + $new_comment =~ s/^[^\w]$//; # Single lettered comment with non word character is usually a typo + $new_comment = "($new_comment)" if ($new_comment ne ""); + my $new_email = format_email($email_name, $name_comment, $email_address, $new_comment); + + if (WARN("BAD_SIGN_OFF", + "Unexpected content after email: '$email', should be: '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } + } + } + +# Check for duplicate signatures + my $sig_nospace = $line; + $sig_nospace =~ s/\s//g; + $sig_nospace = lc($sig_nospace); + if (defined $signatures{$sig_nospace}) { + WARN("BAD_SIGN_OFF", + "Duplicate signature\n" . $herecurr); + } else { + $signatures{$sig_nospace} = 1; + } + +# Check Co-developed-by: immediately followed by Signed-off-by: with same name and email + if ($sign_off =~ /^co-developed-by:$/i) { + if ($email eq $author) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: should not be used to attribute nominal patch author '$author'\n" . $herecurr); + } + if (!defined $lines[$linenr]) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr); + } elsif ($rawlines[$linenr] !~ /^signed-off-by:\s*(.*)/i) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr . $rawlines[$linenr] . "\n"); + } elsif ($1 ne $email) { + WARN("BAD_SIGN_OFF", + "Co-developed-by and Signed-off-by: name/email do not match\n" . $herecurr . $rawlines[$linenr] . "\n"); + } + } + +# check if Reported-by: is followed by a Closes: tag + if ($sign_off =~ /^reported(?:|-and-tested)-by:$/i) { + if (!defined $lines[$linenr]) { + WARN("BAD_REPORTED_BY_LINK", + "Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . "\n"); + } elsif ($rawlines[$linenr] !~ /^closes:\s*/i) { + WARN("BAD_REPORTED_BY_LINK", + "Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . $rawlines[$linenr] . "\n"); + } + } + } + +# These indicate a bug fix + if (!$in_header_lines && !$is_patch && + $line =~ /^This reverts commit/) { + $is_revert = 1; + } + + if (!$in_header_lines && !$is_patch && + $line =~ /((?:(?:BUG: K.|UB)SAN: |Call Trace:|stable\@|syzkaller))/) { + $needs_fixes_tag = $1; + } + +# Check Fixes: styles is correct + if (!$in_header_lines && + $line =~ /^\s*(fixes:?)\s*(?:commit\s*)?([0-9a-f]{5,40})(?:\s*($balanced_parens))?/i) { + my $tag = $1; + my $orig_commit = $2; + my $title; + my $title_has_quotes = 0; + $fixes_tag = 1; + if (defined $3) { + # Always strip leading/trailing parens then double quotes if existing + $title = substr($3, 1, -1); + if ($title =~ /^".*"$/) { + $title = substr($title, 1, -1); + $title_has_quotes = 1; + } + } else { + $title = "commit title" + } + + + my $tag_case = not ($tag eq "Fixes:"); + my $tag_space = not ($line =~ /^fixes:? [0-9a-f]{5,40} ($balanced_parens)/i); + + my $id_length = not ($orig_commit =~ /^[0-9a-f]{12}$/i); + my $id_case = not ($orig_commit !~ /[A-F]/); + + my $id = "0123456789ab"; + my ($cid, $ctitle) = git_commit_info($orig_commit, $id, + $title); + + if ($ctitle ne $title || $tag_case || $tag_space || + $id_length || $id_case || !$title_has_quotes) { + if (WARN("BAD_FIXES_TAG", + "Please use correct Fixes: style 'Fixes: <12 chars of sha1> (\"\")' - ie: 'Fixes: $cid (\"$ctitle\")'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = "Fixes: $cid (\"$ctitle\")"; + } + } + } + +# Check email subject for common tools that don't need to be mentioned + if ($in_header_lines && + $line =~ /^Subject:.*\b(?:checkpatch|sparse|smatch)\b[^:]/i) { + WARN("EMAIL_SUBJECT", + "A patch subject line should describe the change not the tool that found it\n" . $herecurr); + } + +# Check for Gerrit Change-Ids not in any patch context + if ($realfile eq '' && !$has_patch_separator && $line =~ /^\s*change-id:/i) { + if (ERROR("GERRIT_CHANGE_ID", + "Remove Gerrit Change-Id's before submitting upstream\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# Check if the commit log is in a possible stack dump + if ($in_commit_log && !$commit_log_possible_stack_dump && + ($line =~ /^\s*(?:WARNING:|BUG:)/ || + $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ || + # timestamp + $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/) || + $line =~ /^(?:\s+\w+:\s+[0-9a-fA-F]+){3,3}/ || + $line =~ /^\s*\#\d+\s*\[[0-9a-fA-F]+\]\s*\w+ at [0-9a-fA-F]+/) { + # stack dump address styles + $commit_log_possible_stack_dump = 1; + } + +# Check for line lengths > 75 in commit log, warn once + if ($in_commit_log && !$commit_log_long_line && + length($line) > 75 && + !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ || + # file delta changes + $line =~ /^\s*(?:[\w\.\-\+]*\/)++[\w\.\-\+]+:/ || + # filename then : + $line =~ /^\s*(?:Fixes:|$link_tags_search|$signature_tags)/i || + # A Fixes:, link or signature tag line + $commit_log_possible_stack_dump)) { + WARN("COMMIT_LOG_LONG_LINE", + "Prefer a maximum 75 chars per line (possible unwrapped commit description?)\n" . $herecurr); + $commit_log_long_line = 1; + } + +# Reset possible stack dump if a blank line is found + if ($in_commit_log && $commit_log_possible_stack_dump && + $line =~ /^\s*$/) { + $commit_log_possible_stack_dump = 0; + } + +# Check for odd tags before a URI/URL + if ($in_commit_log && + $line =~ /^\s*(\w+:)\s*http/ && $1 !~ /^$link_tags_search$/) { + if ($1 =~ /^v(?:ersion)?\d+/i) { + WARN("COMMIT_LOG_VERSIONING", + "Patch version information should be after the --- line\n" . $herecurr); + } else { + WARN("COMMIT_LOG_USE_LINK", + "Unknown link reference '$1', use $link_tags_print instead\n" . $herecurr); + } + } + +# Check for misuse of the link tags + if ($in_commit_log && + $line =~ /^\s*(\w+:)\s*(\S+)/) { + my $tag = $1; + my $value = $2; + if ($tag =~ /^$link_tags_search$/ && $value !~ m{^https?://}) { + WARN("COMMIT_LOG_WRONG_LINK", + "'$tag' should be followed by a public http(s) link\n" . $herecurr); + } + } + +# Check for lines starting with a # + if ($in_commit_log && $line =~ /^#/) { + if (WARN("COMMIT_COMMENT_SYMBOL", + "Commit log lines starting with '#' are dropped by git as comments\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^/ /; + } + } + +# Check for git id commit length and improperly formed commit descriptions +# A correctly formed commit description is: +# commit <SHA-1 hash length 12+ chars> ("Complete commit subject") +# with the commit subject '("' prefix and '")' suffix +# This is a fairly compilicated block as it tests for what appears to be +# bare SHA-1 hash with minimum length of 5. It also avoids several types of +# possible SHA-1 matches. +# A commit match can span multiple lines so this block attempts to find a +# complete typical commit on a maximum of 3 lines + if ($perl_version_ok && + $in_commit_log && !$commit_log_possible_stack_dump && + $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink|base-commit):/i && + $line !~ /^This reverts commit [0-9a-f]{7,40}/ && + (($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i || + ($line =~ /\bcommit\s*$/i && defined($rawlines[$linenr]) && $rawlines[$linenr] =~ /^\s*[0-9a-f]{5,}\b/i)) || + ($line =~ /(?:\s|^)[0-9a-f]{12,40}(?:[\s"'\(\[]|$)/i && + $line !~ /[\<\[][0-9a-f]{12,40}[\>\]]/i && + $line !~ /\bfixes:\s*[0-9a-f]{12,40}/i))) { + my $init_char = "c"; + my $orig_commit = ""; + my $short = 1; + my $long = 0; + my $case = 1; + my $space = 1; + my $id = '0123456789ab'; + my $orig_desc = "commit description"; + my $description = ""; + my $herectx = $herecurr; + my $has_parens = 0; + my $has_quotes = 0; + + my $input = $line; + if ($line =~ /(?:\bcommit\s+[0-9a-f]{5,}|\bcommit\s*$)/i) { + for (my $n = 0; $n < 2; $n++) { + if ($input =~ /\bcommit\s+[0-9a-f]{5,}\s*($balanced_parens)/i) { + $orig_desc = $1; + $has_parens = 1; + # Always strip leading/trailing parens then double quotes if existing + $orig_desc = substr($orig_desc, 1, -1); + if ($orig_desc =~ /^".*"$/) { + $orig_desc = substr($orig_desc, 1, -1); + $has_quotes = 1; + } + last; + } + last if ($#lines < $linenr + $n); + $input .= " " . trim($rawlines[$linenr + $n]); + $herectx .= "$rawlines[$linenr + $n]\n"; + } + $herectx = $herecurr if (!$has_parens); + } + + if ($input =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) { + $init_char = $1; + $orig_commit = lc($2); + $short = 0 if ($input =~ /\bcommit\s+[0-9a-f]{12,40}/i); + $long = 1 if ($input =~ /\bcommit\s+[0-9a-f]{41,}/i); + $space = 0 if ($input =~ /\bcommit [0-9a-f]/i); + $case = 0 if ($input =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/); + } elsif ($input =~ /\b([0-9a-f]{12,40})\b/i) { + $orig_commit = lc($1); + } + + ($id, $description) = git_commit_info($orig_commit, + $id, $orig_desc); + + if (defined($id) && + ($short || $long || $space || $case || ($orig_desc ne $description) || !$has_quotes) && + $last_git_commit_id_linenr != $linenr - 1) { + ERROR("GIT_COMMIT_ID", + "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herectx); + } + #don't report the next line if this line ends in commit and the sha1 hash is the next line + $last_git_commit_id_linenr = $linenr if ($line =~ /\bcommit\s*$/i); + } + +# Check for mailing list archives other than lore.kernel.org + if ($rawline =~ m{http.*\b$obsolete_archives}) { + WARN("PREFER_LORE_ARCHIVE", + "Use lore.kernel.org archive links when possible - see https://lore.kernel.org/lists.html\n" . $herecurr); + } + +# Check for added, moved or deleted files + if (!$reported_maintainer_file && !$in_commit_log && + ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ || + $line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ || + ($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ && + (defined($1) || defined($2))))) { + $is_patch = 1; + $reported_maintainer_file = 1; + WARN("FILE_PATH_CHANGES", + "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr); + } + +# Check for adding new DT bindings not in schema format + if (!$in_commit_log && + ($line =~ /^new file mode\s*\d+\s*$/) && + ($realfile =~ m@^Documentation/devicetree/bindings/.*\.txt$@)) { + WARN("DT_SCHEMA_BINDING_PATCH", + "DT bindings should be in DT schema format. See: Documentation/devicetree/bindings/writing-schema.rst\n"); + } + +# Check for wrappage within a valid hunk of the file + if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { + ERROR("CORRUPTED_PATCH", + "patch seems to be corrupt (line wrapped?)\n" . + $herecurr) if (!$emitted_corrupt++); + } + +# UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php + if (($realfile =~ /^$/ || $line =~ /^\+/) && + $rawline !~ m/^$UTF8*$/) { + my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/); + + my $blank = copy_spacing($rawline); + my $ptr = substr($blank, 0, length($utf8_prefix)) . "^"; + my $hereptr = "$hereline$ptr\n"; + + CHK("INVALID_UTF8", + "Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr); + } + +# Check if it's the start of a commit log +# (not a header line and we haven't seen the patch filename) + if ($in_header_lines && $realfile =~ /^$/ && + !($rawline =~ /^\s+(?:\S|$)/ || + $rawline =~ /^(?:commit\b|from\b|[\w-]+:)/i)) { + $in_header_lines = 0; + $in_commit_log = 1; + $has_commit_log = 1; + } + +# Check if there is UTF-8 in a commit log when a mail header has explicitly +# declined it, i.e defined some charset where it is missing. + if ($in_header_lines && + $rawline =~ /^Content-Type:.+charset="(.+)".*$/ && + $1 !~ /utf-8/i) { + $non_utf8_charset = 1; + } + + if ($in_commit_log && $non_utf8_charset && $realfile =~ /^$/ && + $rawline =~ /$NON_ASCII_UTF8/) { + WARN("UTF8_BEFORE_PATCH", + "8-bit UTF-8 used in possible commit log\n" . $herecurr); + } + +# Check for absolute kernel paths in commit message + if ($tree && $in_commit_log) { + while ($line =~ m{(?:^|\s)(/\S*)}g) { + my $file = $1; + + if ($file =~ m{^(.*?)(?::\d+)+:?$} && + check_absolute_file($1, $herecurr)) { + # + } else { + check_absolute_file($file, $herecurr); + } + } + } + +# Check for various typo / spelling mistakes + if (defined($misspellings) && + ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) { + while ($rawline =~ /(?:^|[^\w\-'`])($misspellings)(?:[^\w\-'`]|$)/gi) { + my $typo = $1; + my $blank = copy_spacing($rawline); + my $ptr = substr($blank, 0, $-[1]) . "^" x length($typo); + my $hereptr = "$hereline$ptr\n"; + my $typo_fix = $spelling_fix{lc($typo)}; + $typo_fix = ucfirst($typo_fix) if ($typo =~ /^[A-Z]/); + $typo_fix = uc($typo_fix) if ($typo =~ /^[A-Z]+$/); + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("TYPO_SPELLING", + "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $hereptr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/; + } + } + } + +# check for invalid commit id + if ($in_commit_log && $line =~ /(^fixes:|\bcommit)\s+([0-9a-f]{6,40})\b/i) { + my $id; + my $description; + ($id, $description) = git_commit_info($2, undef, undef); + if (!defined($id)) { + WARN("UNKNOWN_COMMIT_ID", + "Unknown commit id '$2', maybe rebased or not pulled?\n" . $herecurr); + } + } + +# check for repeated words separated by a single space +# avoid false positive from list command eg, '-rw-r--r-- 1 root root' + if (($rawline =~ /^\+/ || $in_commit_log) && + $rawline !~ /[bcCdDlMnpPs\?-][rwxsStT-]{9}/) { + pos($rawline) = 1 if (!$in_commit_log); + while ($rawline =~ /\b($word_pattern) (?=($word_pattern))/g) { + + my $first = $1; + my $second = $2; + my $start_pos = $-[1]; + my $end_pos = $+[2]; + if ($first =~ /(?:struct|union|enum)/) { + pos($rawline) += length($first) + length($second) + 1; + next; + } + + next if (lc($first) ne lc($second)); + next if ($first eq 'long'); + + # check for character before and after the word matches + my $start_char = ''; + my $end_char = ''; + $start_char = substr($rawline, $start_pos - 1, 1) if ($start_pos > ($in_commit_log ? 0 : 1)); + $end_char = substr($rawline, $end_pos, 1) if ($end_pos < length($rawline)); + + next if ($start_char =~ /^\S$/); + next if (index(" \t.,;?!", $end_char) == -1); + + # avoid repeating hex occurrences like 'ff ff fe 09 ...' + if ($first =~ /\b[0-9a-f]{2,}\b/i) { + next if (!exists($allow_repeated_words{lc($first)})); + } + + if (WARN("REPEATED_WORD", + "Possible repeated word: '$first'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$first $second\b/$first/; + } + } + + # if it's a repeated word on consecutive lines in a comment block + if ($prevline =~ /$;+\s*$/ && + $prevrawline =~ /($word_pattern)\s*$/) { + my $last_word = $1; + if ($rawline =~ /^\+\s*\*\s*$last_word /) { + if (WARN("REPEATED_WORD", + "Possible repeated word: '$last_word'\n" . $hereprev) && + $fix) { + $fixed[$fixlinenr] =~ s/(\+\s*\*\s*)$last_word /$1/; + } + } + } + } + +# ignore non-hunk lines and lines being removed + next if (!$hunk_line || $line =~ /^-/); + +#trailing whitespace + if ($line =~ /^\+.*\015/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("DOS_LINE_ENDINGS", + "DOS line endings\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/[\s\015]+$//; + } + } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("TRAILING_WHITESPACE", + "trailing whitespace\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + + $rpt_cleaners = 1; + } + +# Check for FSF mailing addresses. + if ($rawline =~ /\bwrite to the Free/i || + $rawline =~ /\b675\s+Mass\s+Ave/i || + $rawline =~ /\b59\s+Temple\s+Pl/i || + $rawline =~ /\b51\s+Franklin\s+St/i) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + my $msg_level = \&ERROR; + $msg_level = \&CHK if ($file); + &{$msg_level}("FSF_MAILING_ADDRESS", + "Do not include the paragraph about writing to the Free Software Foundation's mailing address from the sample GPL notice. The FSF has changed addresses in the past, and may do so again. Linux already includes a copy of the GPL.\n" . $herevet) + } + +# check for Kconfig help text having a real description +# Only applies when adding the entry originally, after that we do not have +# sufficient context to determine whether it is indeed long enough. + if ($realfile =~ /Kconfig/ && + # 'choice' is usually the last thing on the line (though + # Kconfig supports named choices), so use a word boundary + # (\b) rather than a whitespace character (\s) + $line =~ /^\+\s*(?:config|menuconfig|choice)\b/) { + my $ln = $linenr; + my $needs_help = 0; + my $has_help = 0; + my $help_length = 0; + while (defined $lines[$ln]) { + my $f = $lines[$ln++]; + + next if ($f =~ /^-/); + last if ($f !~ /^[\+ ]/); # !patch context + + if ($f =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) { + $needs_help = 1; + next; + } + if ($f =~ /^\+\s*help\s*$/) { + $has_help = 1; + next; + } + + $f =~ s/^.//; # strip patch context [+ ] + $f =~ s/#.*//; # strip # directives + $f =~ s/^\s+//; # strip leading blanks + next if ($f =~ /^$/); # skip blank lines + + # At the end of this Kconfig block: + # This only checks context lines in the patch + # and so hopefully shouldn't trigger false + # positives, even though some of these are + # common words in help texts + if ($f =~ /^(?:config|menuconfig|choice|endchoice| + if|endif|menu|endmenu|source)\b/x) { + last; + } + $help_length++ if ($has_help); + } + if ($needs_help && + $help_length < $min_conf_desc_length) { + my $stat_real = get_stat_real($linenr, $ln - 1); + WARN("CONFIG_DESCRIPTION", + "please write a help paragraph that fully describes the config symbol\n" . "$here\n$stat_real\n"); + } + } + +# check MAINTAINERS entries + if ($realfile =~ /^MAINTAINERS$/) { +# check MAINTAINERS entries for the right form + if ($rawline =~ /^\+[A-Z]:/ && + $rawline !~ /^\+[A-Z]:\t\S/) { + if (WARN("MAINTAINERS_STYLE", + "MAINTAINERS entries use one tab after TYPE:\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+[A-Z]):\s*/$1:\t/; + } + } +# check MAINTAINERS entries for the right ordering too + my $preferred_order = 'MRLSWQBCPTFXNK'; + if ($rawline =~ /^\+[A-Z]:/ && + $prevrawline =~ /^[\+ ][A-Z]:/) { + $rawline =~ /^\+([A-Z]):\s*(.*)/; + my $cur = $1; + my $curval = $2; + $prevrawline =~ /^[\+ ]([A-Z]):\s*(.*)/; + my $prev = $1; + my $prevval = $2; + my $curindex = index($preferred_order, $cur); + my $previndex = index($preferred_order, $prev); + if ($curindex < 0) { + WARN("MAINTAINERS_STYLE", + "Unknown MAINTAINERS entry type: '$cur'\n" . $herecurr); + } else { + if ($previndex >= 0 && $curindex < $previndex) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list '$cur:' before '$prev:'\n" . $hereprev); + } elsif ((($prev eq 'F' && $cur eq 'F') || + ($prev eq 'X' && $cur eq 'X')) && + ($prevval cmp $curval) > 0) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list file patterns in alphabetic order\n" . $hereprev); + } + } + } + } + + if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) && + ($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) { + my $flag = $1; + my $replacement = { + 'EXTRA_AFLAGS' => 'asflags-y', + 'EXTRA_CFLAGS' => 'ccflags-y', + 'EXTRA_CPPFLAGS' => 'cppflags-y', + 'EXTRA_LDFLAGS' => 'ldflags-y', + }; + + WARN("DEPRECATED_VARIABLE", + "Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag}); + } + +# check for DT compatible documentation + if (defined $root && + (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) || + ($realfile =~ /\.[ch]$/ && $line =~ /^\+.*\.compatible\s*=\s*\"/))) { + + my @compats = $rawline =~ /\"([a-zA-Z0-9\-\,\.\+_]+)\"/g; + + my $dt_path = $root . "/Documentation/devicetree/bindings/"; + my $vp_file = $dt_path . "vendor-prefixes.yaml"; + + foreach my $compat (@compats) { + my $compat2 = $compat; + $compat2 =~ s/\,[a-zA-Z0-9]*\-/\,<\.\*>\-/; + my $compat3 = $compat; + $compat3 =~ s/\,([a-z]*)[0-9]*\-/\,$1<\.\*>\-/; + `grep -Erq "$compat|$compat2|$compat3" $dt_path`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string \"$compat\" appears un-documented -- check $dt_path\n" . $herecurr); + } + + next if $compat !~ /^([a-zA-Z0-9\-]+)\,/; + my $vendor = $1; + `grep -Eq "\\"\\^\Q$vendor\E,\\.\\*\\":" $vp_file`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_file\n" . $herecurr); + } + } + } + +# check for using SPDX license tag at beginning of files + if ($realline == $checklicenseline) { + if ($rawline =~ /^[ \+]\s*\#\!\s*\//) { + $checklicenseline = 2; + } elsif ($rawline =~ /^\+/) { + my $comment = ""; + if ($realfile =~ /\.(h|s|S)$/) { + $comment = '/*'; + } elsif ($realfile =~ /\.(c|rs|dts|dtsi)$/) { + $comment = '//'; + } elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc|yaml)$/) { + $comment = '#'; + } elsif ($realfile =~ /\.rst$/) { + $comment = '..'; + } + +# check SPDX comment style for .[chsS] files + if ($realfile =~ /\.[chsS]$/ && + $rawline =~ /SPDX-License-Identifier:/ && + $rawline !~ m@^\+\s*\Q$comment\E\s*@) { + WARN("SPDX_LICENSE_TAG", + "Improper SPDX comment style for '$realfile', please use '$comment' instead\n" . $herecurr); + } + + if ($comment !~ /^$/ && + $rawline !~ m@^\+\Q$comment\E SPDX-License-Identifier: @) { + WARN("SPDX_LICENSE_TAG", + "Missing or malformed SPDX-License-Identifier tag in line $checklicenseline\n" . $herecurr); + } elsif ($rawline =~ /(SPDX-License-Identifier: .*)/) { + my $spdx_license = $1; + if (!is_SPDX_License_valid($spdx_license)) { + WARN("SPDX_LICENSE_TAG", + "'$spdx_license' is not supported in LICENSES/...\n" . $herecurr); + } + if ($realfile =~ m@^Documentation/devicetree/bindings/@ && + $spdx_license !~ /GPL-2\.0(?:-only)? OR BSD-2-Clause/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("SPDX_LICENSE_TAG", + + "DT binding documents should be licensed (GPL-2.0-only OR BSD-2-Clause)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/SPDX-License-Identifier: .*/SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)/; + } + } + if ($realfile =~ m@^include/dt-bindings/@ && + $spdx_license !~ /GPL-2\.0(?:-only)? OR \S+/) { + WARN("SPDX_LICENSE_TAG", + "DT binding headers should be licensed (GPL-2.0-only OR .*)\n" . $herecurr); + } + } + } + } + +# check for embedded filenames + if ($rawline =~ /^\+.*\b\Q$realfile\E\b/) { + WARN("EMBEDDED_FILENAME", + "It's generally not useful to have the filename in the file\n" . $herecurr); + } + +# check we are in a valid source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c|rs|s|S|sh|dtsi|dts)$/); + +# check for using SPDX-License-Identifier on the wrong line number + if ($realline != $checklicenseline && + $rawline =~ /\bSPDX-License-Identifier:/ && + substr($line, @-, @+ - @-) eq "$;" x (@+ - @-)) { + WARN("SPDX_LICENSE_TAG", + "Misplaced SPDX-License-Identifier tag - use line $checklicenseline instead\n" . $herecurr); + } + +# line length limit (with some exclusions) +# +# There are a few types of lines that may extend beyond $max_line_length: +# logging functions like pr_info that end in a string +# lines with a single string +# #defines that are a single string +# lines with an RFC3986 like URL +# +# There are 3 different line length message types: +# LONG_LINE_COMMENT a comment starts before but extends beyond $max_line_length +# LONG_LINE_STRING a string starts before but extends beyond $max_line_length +# LONG_LINE all other lines longer than $max_line_length +# +# if LONG_LINE is ignored, the other 2 types are also ignored +# + + if ($line =~ /^\+/ && $length > $max_line_length) { + my $msg_type = "LONG_LINE"; + + # Check the allowed long line types first + + # logging functions that end in a string that starts + # before $max_line_length + if ($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(?:KERN_\S+\s*|[^"]*))?($String\s*(?:|,|\)\s*;)\s*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = ""; + + # lines with only strings (w/ possible termination) + # #defines with only strings + } elsif ($line =~ /^\+\s*$String\s*(?:\s*|,|\)\s*;)\s*$/ || + $line =~ /^\+\s*#\s*define\s+\w+\s+$String$/) { + $msg_type = ""; + + # More special cases + } elsif ($line =~ /^\+.*\bEFI_GUID\s*\(/ || + $line =~ /^\+\s*(?:\w+)?\s*DEFINE_PER_CPU/) { + $msg_type = ""; + + # URL ($rawline is used in case the URL is in a comment) + } elsif ($rawline =~ /^\+.*\b[a-z][\w\.\+\-]*:\/\/\S+/i) { + $msg_type = ""; + + # Otherwise set the alternate message types + + # a comment starts before $max_line_length + } elsif ($line =~ /($;[\s$;]*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_COMMENT" + + # a quoted string starts before $max_line_length + } elsif ($sline =~ /\s*($String(?:\s*(?:\\|,\s*|\)\s*;\s*))?)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_STRING" + } + + if ($msg_type ne "" && + show_type("LONG_LINE") && show_type($msg_type)) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}($msg_type, + "line length of $length exceeds $max_line_length columns\n" . $herecurr); + } + } + +# check for adding lines without a newline. + if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { + if (WARN("MISSING_EOF_NEWLINE", + "adding a line without newline at end of file\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr+1, "No newline at end of file"); + } + } + +# check for .L prefix local symbols in .S files + if ($realfile =~ /\.S$/ && + $line =~ /^\+\s*(?:[A-Z]+_)?SYM_[A-Z]+_(?:START|END)(?:_[A-Z_]+)?\s*\(\s*\.L/) { + WARN("AVOID_L_PREFIX", + "Avoid using '.L' prefixed local symbol names for denoting a range of code via 'SYM_*_START/END' annotations; see Documentation/core-api/asm-annotations.rst\n" . $herecurr); + } + +# check we are in a valid source file C or perl if not then ignore this hunk + next if ($realfile !~ /\.(h|c|pl|dtsi|dts)$/); + +# at the beginning of a line any tabs must come first and anything +# more than $tabsize must use tabs. + if ($rawline =~ /^\+\s* \t\s*\S/ || + $rawline =~ /^\+\s* \s*/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + $rpt_cleaners = 1; + if (ERROR("CODE_INDENT", + "code indent should use tabs where possible\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check for space before tabs. + if ($rawline =~ /^\+/ && $rawline =~ / \t/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("SPACE_BEFORE_TAB", + "please, no space before tabs\n" . $herevet) && + $fix) { + while ($fixed[$fixlinenr] =~ + s/(^\+.*) {$tabsize,$tabsize}\t/$1\t\t/) {} + while ($fixed[$fixlinenr] =~ + s/(^\+.*) +\t/$1\t/) {} + } + } + +# check for assignments on the start of a line + if ($sline =~ /^\+\s+($Assignment)[^=]/) { + my $operator = $1; + if (CHK("ASSIGNMENT_CONTINUATIONS", + "Assignment operator '$1' should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # add assignment operator to the previous line, remove from current line + $fixed[$fixlinenr - 1] .= " $operator"; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } + } + +# check for && or || at the start of a line + if ($rawline =~ /^\+\s*(&&|\|\|)/) { + my $operator = $1; + if (CHK("LOGICAL_CONTINUATIONS", + "Logical continuations should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # insert logical operator at last non-comment, non-whitepsace char on previous line + $prevline =~ /[\s$;]*$/; + my $line_end = substr($prevrawline, $-[0]); + $fixed[$fixlinenr - 1] =~ s/\Q$line_end\E$/ $operator$line_end/; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } + } + +# check indentation starts on a tab stop + if ($perl_version_ok && + $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$)|$Declare\s*$Ident\s*[;=])/) { + my $indent = length($1); + if ($indent % $tabsize) { + if (WARN("TABSTOP", + "Statements should start on a tabstop\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/$tabsize)@e; + } + } + } + +# check multi-line statement indentation matches previous line + if ($perl_version_ok && + $prevline =~ /^\+([ \t]*)((?:$c90_Keywords(?:\s+if)\s*)|(?:$Declare\s*)?(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*|(?:\*\s*)*$Lval\s*=\s*$Ident\s*)\(.*(\&\&|\|\||,)\s*$/) { + $prevline =~ /^\+(\t*)(.*)$/; + my $oldindent = $1; + my $rest = $2; + + my $pos = pos_last_openparen($rest); + if ($pos >= 0) { + $line =~ /^(\+| )([ \t]*)/; + my $newindent = $2; + + my $goodtabindent = $oldindent . + "\t" x ($pos / $tabsize) . + " " x ($pos % $tabsize); + my $goodspaceindent = $oldindent . " " x $pos; + + if ($newindent ne $goodtabindent && + $newindent ne $goodspaceindent) { + + if (CHK("PARENTHESIS_ALIGNMENT", + "Alignment should match open parenthesis\n" . $hereprev) && + $fix && $line =~ /^\+/) { + $fixed[$fixlinenr] =~ + s/^\+[ \t]*/\+$goodtabindent/; + } + } + } + } + +# check for space after cast like "(int) foo" or "(struct foo) bar" +# avoid checking a few false positives: +# "sizeof(<type>)" or "__alignof__(<type>)" +# function pointer declarations like "(*foo)(int) = bar;" +# structure definitions like "(struct foo) { 0 };" +# multiline macros that define functions +# known attributes or the __attribute__ keyword + if ($line =~ /^\+(.*)\(\s*$Type\s*\)([ \t]++)((?![={]|\\$|$Attribute|__attribute__))/ && + (!defined($1) || $1 !~ /\b(?:sizeof|__alignof__)\s*$/)) { + if (CHK("SPACING", + "No space is necessary after a cast\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/(\(\s*$Type\s*\))[ \t]+/$1/; + } + } + +# Block comments use * on subsequent lines + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $prevrawline =~ /^\+.*?\/\*/ && #starting /* + $prevrawline !~ /\*\/[ \t]*$/ && #no trailing */ + $rawline =~ /^\+/ && #line is new + $rawline !~ /^\+[ \t]*\*/) { #no leading * + WARN("BLOCK_COMMENT_STYLE", + "Block comments use * on subsequent lines\n" . $hereprev); + } + +# Block comments use */ on trailing lines + if ($rawline !~ m@^\+[ \t]*\*/[ \t]*$@ && #trailing */ + $rawline !~ m@^\+.*/\*.*\*/[ \t]*$@ && #inline /*...*/ + $rawline !~ m@^\+.*\*{2,}/[ \t]*$@ && #trailing **/ + $rawline =~ m@^\+[ \t]*.+\*\/[ \t]*$@) { #non blank */ + WARN("BLOCK_COMMENT_STYLE", + "Block comments use a trailing */ on a separate line\n" . $herecurr); + } + +# Block comment * alignment + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $line =~ /^\+[ \t]*$;/ && #leading comment + $rawline =~ /^\+[ \t]*\*/ && #leading * + (($prevrawline =~ /^\+.*?\/\*/ && #leading /* + $prevrawline !~ /\*\/[ \t]*$/) || #no trailing */ + $prevrawline =~ /^\+[ \t]*\*/)) { #leading * + my $oldindent; + $prevrawline =~ m@^\+([ \t]*/?)\*@; + if (defined($1)) { + $oldindent = expand_tabs($1); + } else { + $prevrawline =~ m@^\+(.*/?)\*@; + $oldindent = expand_tabs($1); + } + $rawline =~ m@^\+([ \t]*)\*@; + my $newindent = $1; + $newindent = expand_tabs($newindent); + if (length($oldindent) ne length($newindent)) { + WARN("BLOCK_COMMENT_STYLE", + "Block comments should align the * on each line\n" . $hereprev); + } + } + +# check for missing blank lines after struct/union declarations +# with exceptions for various attributes and macros + if ($prevline =~ /^[\+ ]};?\s*$/ && + $line =~ /^\+/ && + !($line =~ /^\+\s*$/ || + $line =~ /^\+\s*(?:EXPORT_SYMBOL|early_param|ALLOW_ERROR_INJECTION)/ || + $line =~ /^\+\s*MODULE_/i || + $line =~ /^\+\s*\#\s*(?:end|elif|else)/ || + $line =~ /^\+[a-z_]*init/ || + $line =~ /^\+\s*(?:static\s+)?[A-Z_]*ATTR/ || + $line =~ /^\+\s*DECLARE/ || + $line =~ /^\+\s*builtin_[\w_]*driver/ || + $line =~ /^\+\s*__setup/)) { + if (CHK("LINE_SPACING", + "Please use a blank line after function/struct/union/enum declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + +# check for multiple consecutive blank lines + if ($prevline =~ /^[\+ ]\s*$/ && + $line =~ /^\+\s*$/ && + $last_blank_line != ($linenr - 1)) { + if (CHK("LINE_SPACING", + "Please don't use multiple blank lines\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + + $last_blank_line = $linenr; + } + +# check for missing blank lines after declarations +# (declarations must have the same indentation and not be at the start of line) + if (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/) { + # use temporaries + my $sl = $sline; + my $pl = $prevline; + # remove $Attribute/$Sparse uses to simplify comparisons + $sl =~ s/\b(?:$Attribute|$Sparse)\b//g; + $pl =~ s/\b(?:$Attribute|$Sparse)\b//g; + if (($pl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $pl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $pl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $pl =~ /^\+\s+$declaration_macros/) && + # for "else if" which can look like "$Ident $Ident" + !($pl =~ /^\+\s+$c90_Keywords\b/ || + # other possible extensions of declaration lines + $pl =~ /(?:$Compare|$Assignment|$Operators)\s*$/ || + # not starting a section or a macro "\" extended line + $pl =~ /(?:\{\s*|\\)$/) && + # looks like a declaration + !($sl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $sl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $sl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $sl =~ /^\+\s+$declaration_macros/ || + # start of struct or union or enum + $sl =~ /^\+\s+(?:static\s+)?(?:const\s+)?(?:union|struct|enum|typedef)\b/ || + # start or end of block or continuation of declaration + $sl =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ || + # bitfield continuation + $sl =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ || + # other possible extensions of declaration lines + $sl =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/)) { + if (WARN("LINE_SPACING", + "Missing a blank line after declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + } + +# check for spaces at the beginning of a line. +# Exceptions: +# 1) within comments +# 2) indented preprocessor commands +# 3) hanging labels + if ($rawline =~ /^\+ / && $line !~ /^\+ *(?:$;|#|$Ident:)/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("LEADING_SPACE", + "please, no spaces at the start of a line\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check we are in a valid C source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c)$/); + +# check for unusual line ending [ or ( + if ($line =~ /^\+.*([\[\(])\s*$/) { + CHK("OPEN_ENDED_LINE", + "Lines should not end with a '$1'\n" . $herecurr); + } + +# check if this appears to be the start function declaration, save the name + if ($sline =~ /^\+\{\s*$/ && + $prevline =~ /^\+(?:(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*)?($Ident)\(/) { + $context_function = $1; + } + +# check if this appears to be the end of function declaration + if ($sline =~ /^\+\}\s*$/) { + undef $context_function; + } + +# check indentation of any line with a bare else +# (but not if it is a multiple line "if (foo) return bar; else return baz;") +# if the previous line is a break or return and is indented 1 tab more... + if ($sline =~ /^\+([\t]+)(?:}[ \t]*)?else(?:[ \t]*{)?\s*$/) { + my $tabs = length($1) + 1; + if ($prevline =~ /^\+\t{$tabs,$tabs}break\b/ || + ($prevline =~ /^\+\t{$tabs,$tabs}return\b/ && + defined $lines[$linenr] && + $lines[$linenr] !~ /^[ \+]\t{$tabs,$tabs}return/)) { + WARN("UNNECESSARY_ELSE", + "else is not generally useful after a break or return\n" . $hereprev); + } + } + +# check indentation of a line with a break; +# if the previous line is a goto, return or break +# and is indented the same # of tabs + if ($sline =~ /^\+([\t]+)break\s*;\s*$/) { + my $tabs = $1; + if ($prevline =~ /^\+$tabs(goto|return|break)\b/) { + if (WARN("UNNECESSARY_BREAK", + "break is not useful after a $1\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + } + +# check for RCS/CVS revision markers + if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) { + WARN("CVS_KEYWORD", + "CVS style keyword markers, these will _not_ be updated\n". $herecurr); + } + +# check for old HOTPLUG __dev<foo> section markings + if ($line =~ /\b(__dev(init|exit)(data|const|))\b/) { + WARN("HOTPLUG_SECTION", + "Using $1 is unnecessary\n" . $herecurr); + } + +# Check for potential 'bare' types + my ($stat, $cond, $line_nr_next, $remain_next, $off_next, + $realline_next); +#print "LINE<$line>\n"; + if ($linenr > $suppress_statement && + $realcnt && $sline =~ /.\s*\S/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0); + $stat =~ s/\n./\n /g; + $cond =~ s/\n./\n /g; + +#print "linenr<$linenr> <$stat>\n"; + # If this statement has no statement boundaries within + # it there is no point in retrying a statement scan + # until we hit end of it. + my $frag = $stat; $frag =~ s/;+\s*$//; + if ($frag !~ /(?:{|;)/) { +#print "skip<$line_nr_next>\n"; + $suppress_statement = $line_nr_next; + } + + # Find the real next line. + $realline_next = $line_nr_next; + if (defined $realline_next && + (!defined $lines[$realline_next - 1] || + substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) { + $realline_next++; + } + + my $s = $stat; + $s =~ s/{.*$//s; + + # Ignore goto labels. + if ($s =~ /$Ident:\*$/s) { + + # Ignore functions being called + } elsif ($s =~ /^.\s*$Ident\s*\(/s) { + + } elsif ($s =~ /^.\s*else\b/s) { + + # declarations always start with types + } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) { + my $type = $1; + $type =~ s/\s+/ /g; + possible($type, "A:" . $s); + + # definitions in global scope can only start with types + } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) { + possible($1, "B:" . $s); + } + + # any (foo ... *) is a pointer cast, and foo is a type + while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) { + possible($1, "C:" . $s); + } + + # Check for any sort of function declaration. + # int foo(something bar, other baz); + # void (*store_gdt)(x86_descr_ptr *); + if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) { + my ($name_len) = length($1); + + my $ctx = $s; + substr($ctx, 0, $name_len + 1, ''); + $ctx =~ s/\)[^\)]*$//; + + for my $arg (split(/\s*,\s*/, $ctx)) { + if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) { + + possible($1, "D:" . $s); + } + } + } + + } + +# +# Checks which may be anchored in the context. +# + +# Check for switch () and associated case and default +# statements should be at the same indent. + if ($line=~/\bswitch\s*\(.*\)/) { + my $err = ''; + my $sep = ''; + my @ctx = ctx_block_outer($linenr, $realcnt); + shift(@ctx); + for my $ctx (@ctx) { + my ($clen, $cindent) = line_stats($ctx); + if ($ctx =~ /^\+\s*(case\s+|default:)/ && + $indent != $cindent) { + $err .= "$sep$ctx\n"; + $sep = ''; + } else { + $sep = "[...]\n"; + } + } + if ($err ne '') { + ERROR("SWITCH_CASE_INDENT_LEVEL", + "switch and case should be at the same indent\n$hereline$err"); + } + } + +# if/while/etc brace do not go on next line, unless defining a do while loop, +# or if that brace on the next line is for something else + if ($line =~ /(.*)\b((?:if|while|for|switch|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) { + my $pre_ctx = "$1$2"; + + my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0); + + if ($line =~ /^\+\t{6,}/) { + WARN("DEEP_INDENTATION", + "Too many leading tabs - consider code refactoring\n" . $herecurr); + } + + my $ctx_cnt = $realcnt - $#ctx - 1; + my $ctx = join("\n", @ctx); + + my $ctx_ln = $linenr; + my $ctx_skip = $realcnt; + + while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt && + defined $lines[$ctx_ln - 1] && + $lines[$ctx_ln - 1] =~ /^-/)) { + ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n"; + $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/); + $ctx_ln++; + } + + #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n"; + #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n"; + + if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln - 1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) { + ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ && + $ctx =~ /\)\s*\;\s*$/ && + defined $lines[$ctx_ln - 1]) + { + my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]); + if ($nindent > $indent) { + WARN("TRAILING_SEMICOLON", + "trailing semicolon indicates no statements, indent implies otherwise\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + } + } + +# Check relative indent for conditionals and blocks. + if ($line =~ /\b(?:(?:if|while|for|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|(?:do|else)\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($s, $c) = ($stat, $cond); + + substr($s, 0, length($c), ''); + + # remove inline comments + $s =~ s/$;/ /g; + $c =~ s/$;/ /g; + + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + + # Make sure we remove the line prefixes as we have + # none on the first line, and are going to readd them + # where necessary. + $s =~ s/\n./\n/gs; + while ($s =~ /\n\s+\\\n/) { + $cond_lines += $s =~ s/\n\s+\\\n/\n/g; + } + + # We want to check the first line inside the block + # starting at the end of the conditional, so remove: + # 1) any blank line termination + # 2) any opening brace { on end of the line + # 3) any do (...) { + my $continuation = 0; + my $check = 0; + $s =~ s/^.*\bdo\b//; + $s =~ s/^\s*{//; + if ($s =~ s/^\s*\\//) { + $continuation = 1; + } + if ($s =~ s/^\s*?\n//) { + $check = 1; + $cond_lines++; + } + + # Also ignore a loop construct at the end of a + # preprocessor statement. + if (($prevline =~ /^.\s*#\s*define\s/ || + $prevline =~ /\\\s*$/) && $continuation == 0) { + $check = 0; + } + + my $cond_ptr = -1; + $continuation = 0; + while ($cond_ptr != $cond_lines) { + $cond_ptr = $cond_lines; + + # If we see an #else/#elif then the code + # is not linear. + if ($s =~ /^\s*\#\s*(?:else|elif)/) { + $check = 0; + } + + # Ignore: + # 1) blank lines, they should be at 0, + # 2) preprocessor lines, and + # 3) labels. + if ($continuation || + $s =~ /^\s*?\n/ || + $s =~ /^\s*#\s*?/ || + $s =~ /^\s*$Ident\s*:/) { + $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0; + if ($s =~ s/^.*?\n//) { + $cond_lines++; + } + } + } + + my (undef, $sindent) = line_stats("+" . $s); + my $stat_real = raw_line($linenr, $cond_lines); + + # Check if either of these lines are modified, else + # this is not this patch's fault. + if (!defined($stat_real) || + $stat !~ /^\+/ && $stat_real !~ /^\+/) { + $check = 0; + } + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; + + if ($check && $s ne '' && + (($sindent % $tabsize) != 0 || + ($sindent < $indent) || + ($sindent == $indent && + ($s !~ /^\s*(?:\}|\{|else\b)/)) || + ($sindent > $indent + $tabsize))) { + WARN("SUSPECT_CODE_INDENT", + "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); + } + } + + # Track the 'values' across context and added lines. + my $opline = $line; $opline =~ s/^./ /; + my ($curr_values, $curr_vars) = + annotate_values($opline . "\n", $prev_values); + $curr_values = $prev_values . $curr_values; + if ($dbg_values) { + my $outline = $opline; $outline =~ s/\t/ /g; + print "$linenr > .$outline\n"; + print "$linenr > $curr_values\n"; + print "$linenr > $curr_vars\n"; + } + $prev_values = substr($curr_values, -1); + +#ignore lines not being added + next if ($line =~ /^[^\+]/); + +# check for self assignments used to avoid compiler warnings +# e.g.: int foo = foo, *bar = NULL; +# struct foo bar = *(&(bar)); + if ($line =~ /^\+\s*(?:$Declare)?([A-Za-z_][A-Za-z\d_]*)\s*=/) { + my $var = $1; + if ($line =~ /^\+\s*(?:$Declare)?$var\s*=\s*(?:$var|\*\s*\(?\s*&\s*\(?\s*$var\s*\)?\s*\)?)\s*[;,]/) { + WARN("SELF_ASSIGNMENT", + "Do not use self-assignments to avoid compiler warnings\n" . $herecurr); + } + } + +# check for dereferences that span multiple lines + if ($prevline =~ /^\+.*$Lval\s*(?:\.|->)\s*$/ && + $line =~ /^\+\s*(?!\#\s*(?!define\s+|if))\s*$Lval/) { + $prevline =~ /($Lval\s*(?:\.|->))\s*$/; + my $ref = $1; + $line =~ /^.\s*($Lval)/; + $ref .= $1; + $ref =~ s/\s//g; + WARN("MULTILINE_DEREFERENCE", + "Avoid multiple line dereference - prefer '$ref'\n" . $hereprev); + } + +# check for declarations of signed or unsigned without int + while ($line =~ m{\b($Declare)\s*(?!char\b|short\b|int\b|long\b)\s*($Ident)?\s*[=,;\[\)\(]}g) { + my $type = $1; + my $var = $2; + $var = "" if (!defined $var); + if ($type =~ /^(?:(?:$Storage|$Inline|$Attribute)\s+)*((?:un)?signed)((?:\s*\*)*)\s*$/) { + my $sign = $1; + my $pointer = $2; + + $pointer = "" if (!defined $pointer); + + if (WARN("UNSPECIFIED_INT", + "Prefer '" . trim($sign) . " int" . rtrim($pointer) . "' to bare use of '$sign" . rtrim($pointer) . "'\n" . $herecurr) && + $fix) { + my $decl = trim($sign) . " int "; + my $comp_pointer = $pointer; + $comp_pointer =~ s/\s//g; + $decl .= $comp_pointer; + $decl = rtrim($decl) if ($var eq ""); + $fixed[$fixlinenr] =~ s@\b$sign\s*\Q$pointer\E\s*$var\b@$decl$var@; + } + } + } + +# TEST: allow direct testing of the type matcher. + if ($dbg_type) { + if ($line =~ /^.\s*$Declare\s*$/) { + ERROR("TEST_TYPE", + "TEST: is type\n" . $herecurr); + } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) { + ERROR("TEST_NOT_TYPE", + "TEST: is not type ($1 is)\n". $herecurr); + } + next; + } +# TEST: allow direct testing of the attribute matcher. + if ($dbg_attr) { + if ($line =~ /^.\s*$Modifier\s*$/) { + ERROR("TEST_ATTR", + "TEST: is attr\n" . $herecurr); + } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) { + ERROR("TEST_NOT_ATTR", + "TEST: is not attr ($1 is)\n". $herecurr); + } + next; + } + +# check for initialisation to aggregates open brace on the next line + if ($line =~ /^.\s*{/ && + $prevline =~ /(?:^|[^=])=\s*$/) { + if (ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/\s*=\s*$/ = {/; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $line; + $fixedline =~ s/^(.\s*)\{\s*/$1/; + fix_insert_line($fixlinenr, $fixedline); + } + } + +# +# Checks which are anchored on the added line. +# + +# check for malformed paths in #include statements (uses RAW line) + if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) { + my $path = $1; + if ($path =~ m{//}) { + ERROR("MALFORMED_INCLUDE", + "malformed #include filename\n" . $herecurr); + } + if ($path =~ "^uapi/" && $realfile =~ m@\binclude/uapi/@) { + ERROR("UAPI_INCLUDE", + "No #include in ...include/uapi/... should use a uapi/ path prefix\n" . $herecurr); + } + } + +# no C99 // comments + if ($line =~ m{//}) { + if (ERROR("C99_COMMENTS", + "do not use C99 // comments\n" . $herecurr) && + $fix) { + my $line = $fixed[$fixlinenr]; + if ($line =~ /\/\/(.*)$/) { + my $comment = trim($1); + $fixed[$fixlinenr] =~ s@\/\/(.*)$@/\* $comment \*/@; + } + } + } + # Remove C99 comments. + $line =~ s@//.*@@; + $opline =~ s@//.*@@; + +# EXPORT_SYMBOL should immediately follow the thing it is exporting, consider +# the whole statement. +#print "APW <$lines[$realline_next - 1]>\n"; + if (defined $realline_next && + exists $lines[$realline_next - 1] && + !defined $suppress_export{$realline_next} && + ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/)) { + # Handle definitions which produce identifiers with + # a prefix: + # XXX(foo); + # EXPORT_SYMBOL(something_foo); + my $name = $1; + $name =~ s/^\s*($Ident).*/$1/; + if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ && + $name =~ /^${Ident}_$2/) { +#print "FOO C name<$name>\n"; + $suppress_export{$realline_next} = 1; + + } elsif ($stat !~ /(?: + \n.}\s*$| + ^.DEFINE_$Ident\(\Q$name\E\)| + ^.DECLARE_$Ident\(\Q$name\E\)| + ^.LIST_HEAD\(\Q$name\E\)| + ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(| + \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\() + )/x) { +#print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n"; + $suppress_export{$realline_next} = 2; + } else { + $suppress_export{$realline_next} = 1; + } + } + if (!defined $suppress_export{$linenr} && + $prevline =~ /^.\s*$/ && + ($line =~ /EXPORT_SYMBOL.*\((.*)\)/)) { +#print "FOO B <$lines[$linenr - 1]>\n"; + $suppress_export{$linenr} = 2; + } + if (defined $suppress_export{$linenr} && + $suppress_export{$linenr} == 2) { + WARN("EXPORT_SYMBOL", + "EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr); + } + +# check for global initialisers. + if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/ && + !exclude_global_initialisers($realfile)) { + if (ERROR("GLOBAL_INITIALISERS", + "do not initialise globals to $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.$Type\s*$Ident(?:\s+$Modifier)*)\s*=\s*$zero_initializer\s*;/$1;/; + } + } +# check for static initialisers. + if ($line =~ /^\+.*\bstatic\s.*=\s*($zero_initializer)\s*;/) { + if (ERROR("INITIALISED_STATIC", + "do not initialise statics to $1\n" . + $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s.*?)\s*=\s*$zero_initializer\s*;/$1;/; + } + } + +# check for misordered declarations of char/short/int/long with signed/unsigned + while ($sline =~ m{(\b$TypeMisordered\b)}g) { + my $tmp = trim($1); + WARN("MISORDERED_TYPE", + "type '$tmp' should be specified in [[un]signed] [short|int|long|long long] order\n" . $herecurr); + } + +# check for unnecessary <signed> int declarations of short/long/long long + while ($sline =~ m{\b($TypeMisordered(\s*\*)*|$C90_int_types)\b}g) { + my $type = trim($1); + next if ($type !~ /\bint\b/); + next if ($type !~ /\b(?:short|long\s+long|long)\b/); + my $new_type = $type; + $new_type =~ s/\b\s*int\s*\b/ /; + $new_type =~ s/\b\s*(?:un)?signed\b\s*/ /; + $new_type =~ s/^const\s+//; + $new_type = "unsigned $new_type" if ($type =~ /\bunsigned\b/); + $new_type = "const $new_type" if ($type =~ /^const\b/); + $new_type =~ s/\s+/ /g; + $new_type = trim($new_type); + if (WARN("UNNECESSARY_INT", + "Prefer '$new_type' over '$type' as the int is unnecessary\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$type\E\b/$new_type/; + } + } + +# check for static const char * arrays. + if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static const char * array should probably be static const char * const\n" . + $herecurr); + } + +# check for initialized const char arrays that should be static const + if ($line =~ /^\+\s*const\s+(char|unsigned\s+char|_*u8|(?:[us]_)?int8_t)\s+\w+\s*\[\s*(?:\w+\s*)?\]\s*=\s*"/) { + if (WARN("STATIC_CONST_CHAR_ARRAY", + "const array should probably be static const\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.\s*)const\b/${1}static const/; + } + } + +# check for static char foo[] = "bar" declarations. + if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static char array declaration should probably be static const char\n" . + $herecurr); + } + +# check for const <foo> const where <foo> is not a pointer or array type + if ($sline =~ /\bconst\s+($BasicType)\s+const\b/) { + my $found = $1; + if ($sline =~ /\bconst\s+\Q$found\E\s+const\b\s*\*/) { + WARN("CONST_CONST", + "'const $found const *' should probably be 'const $found * const'\n" . $herecurr); + } elsif ($sline !~ /\bconst\s+\Q$found\E\s+const\s+\w+\s*\[/) { + WARN("CONST_CONST", + "'const $found const' should probably be 'const $found'\n" . $herecurr); + } + } + +# check for const static or static <non ptr type> const declarations +# prefer 'static const <foo>' over 'const static <foo>' and 'static <foo> const' + if ($sline =~ /^\+\s*const\s+static\s+($Type)\b/ || + $sline =~ /^\+\s*static\s+($BasicType)\s+const\b/) { + if (WARN("STATIC_CONST", + "Move const after static - use 'static const $1'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bconst\s+static\b/static const/; + $fixed[$fixlinenr] =~ s/\bstatic\s+($BasicType)\s+const\b/static const $1/; + } + } + +# check for non-global char *foo[] = {"bar", ...} declarations. + if ($line =~ /^.\s+(?:static\s+|const\s+)?char\s+\*\s*\w+\s*\[\s*\]\s*=\s*\{/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "char * array declaration might be better as static const\n" . + $herecurr); + } + +# check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo) + if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) { + my $array = $1; + if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) { + my $array_div = $1; + if (WARN("ARRAY_SIZE", + "Prefer ARRAY_SIZE($array)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/; + } + } + } + +# check for function declarations without arguments like "int foo()" + if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) { + if (ERROR("FUNCTION_WITHOUT_ARGS", + "Bad function definition - $1() should probably be $1(void)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\b($Type)\s+($Ident))\s*\(\s*\)/$2 $3(void)/; + } + } + +# check for new typedefs, only function parameters and sparse annotations +# make sense. + if ($line =~ /\btypedef\s/ && + $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ && + $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ && + $line !~ /\b$typeTypedefs\b/ && + $line !~ /\b__bitwise\b/) { + WARN("NEW_TYPEDEFS", + "do not add new typedefs\n" . $herecurr); + } + +# * goes on variable not on type + # (char*[ const]) + while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) { + #print "AA<$1>\n"; + my ($ident, $from, $to) = ($1, $2, $2); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + +## print "1: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to) { + if (ERROR("POINTER_LOCATION", + "\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr) && + $fix) { + my $sub_from = $ident; + my $sub_to = $ident; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) { + #print "BB<$1>\n"; + my ($match, $from, $to, $ident) = ($1, $2, $2, $3); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + # Modifiers should have spaces. + $to =~ s/(\b$Modifier$)/$1 /; + +## print "2: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to && $ident !~ /^$Modifier$/) { + if (ERROR("POINTER_LOCATION", + "\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr) && + $fix) { + + my $sub_from = $match; + my $sub_to = $match; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + +# do not use BUG() or variants + if ($line =~ /\b(?!AA_|BUILD_|DCCP_|IDA_|KVM_|RWLOCK_|snd_|SPIN_)(?:[a-zA-Z_]*_)?BUG(?:_ON)?(?:_[A-Z_]+)?\s*\(/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("AVOID_BUG", + "Do not crash the kernel unless it is absolutely unavoidable--use WARN_ON_ONCE() plus recovery code (if feasible) instead of BUG() or variants\n" . $herecurr); + } + +# avoid LINUX_VERSION_CODE + if ($line =~ /\bLINUX_VERSION_CODE\b/) { + WARN("LINUX_VERSION_CODE", + "LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr); + } + +# check for uses of printk_ratelimit + if ($line =~ /\bprintk_ratelimit\s*\(/) { + WARN("PRINTK_RATELIMITED", + "Prefer printk_ratelimited or pr_<level>_ratelimited to printk_ratelimit\n" . $herecurr); + } + +# printk should use KERN_* levels + if ($line =~ /\bprintk\s*\(\s*(?!KERN_[A-Z]+\b)/) { + WARN("PRINTK_WITHOUT_KERN_LEVEL", + "printk() should include KERN_<LEVEL> facility level\n" . $herecurr); + } + +# prefer variants of (subsystem|netdev|dev|pr)_<level> to printk(KERN_<LEVEL> + if ($line =~ /\b(printk(_once|_ratelimited)?)\s*\(\s*KERN_([A-Z]+)/) { + my $printk = $1; + my $modifier = $2; + my $orig = $3; + $modifier = "" if (!defined($modifier)); + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + my $level2 = $level; + $level2 = "dbg" if ($level eq "debug"); + $level .= $modifier; + $level2 .= $modifier; + WARN("PREFER_PR_LEVEL", + "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to $printk(KERN_$orig ...\n" . $herecurr); + } + +# prefer dev_<level> to dev_printk(KERN_<LEVEL> + if ($line =~ /\bdev_printk\s*\(\s*KERN_([A-Z]+)/) { + my $orig = $1; + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + $level = "dbg" if ($level eq "debug"); + WARN("PREFER_DEV_LEVEL", + "Prefer dev_$level(... to dev_printk(KERN_$orig, ...\n" . $herecurr); + } + +# trace_printk should not be used in production code. + if ($line =~ /\b(trace_printk|trace_puts|ftrace_vprintk)\s*\(/) { + WARN("TRACE_PRINTK", + "Do not use $1() in production code (this can be ignored if built only with a debug config option)\n" . $herecurr); + } + +# ENOSYS means "bad syscall nr" and nothing else. This will have a small +# number of false positives, but assembly files are not checked, so at +# least the arch entry code will not trigger this warning. + if ($line =~ /\bENOSYS\b/) { + WARN("ENOSYS", + "ENOSYS means 'invalid syscall nr' and nothing else\n" . $herecurr); + } + +# ENOTSUPP is not a standard error code and should be avoided in new patches. +# Folks usually mean EOPNOTSUPP (also called ENOTSUP), when they type ENOTSUPP. +# Similarly to ENOSYS warning a small number of false positives is expected. + if (!$file && $line =~ /\bENOTSUPP\b/) { + if (WARN("ENOTSUPP", + "ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bENOTSUPP\b/EOPNOTSUPP/; + } + } + +# function brace can't be on same line, except for #defines of do while, +# or if closed on same line + if ($perl_version_ok && + $sline =~ /$Type\s*$Ident\s*$balanced_parens\s*\{/ && + $sline !~ /\#\s*define\b.*do\s*\{/ && + $sline !~ /}/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + my $fixed_line = $rawline; + $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*)\{(.*)$/; + my $line1 = $1; + my $line2 = $2; + fix_insert_line($fixlinenr, ltrim($line1)); + fix_insert_line($fixlinenr, "\+{"); + if ($line2 !~ /^\s*$/) { + fix_insert_line($fixlinenr, "\+\t" . trim($line2)); + } + } + } + +# open braces for enum, union and struct go on the same line. + if ($line =~ /^.\s*{/ && + $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following $1 go on the same line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = rtrim($prevrawline) . " {"; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)\{\s*/$1\t/; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +# missing space after union, struct or enum definition + if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident){1,2}[=\{]/) { + if (WARN("SPACING", + "missing space after $1 definition\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*(?:typedef\s+)?(?:enum|union|struct)(?:\s+$Ident){1,2})([=\{])/$1 $2/; + } + } + +# Function pointer declarations +# check spacing between type, funcptr, and args +# canonical declaration is "type (*funcptr)(args...)" + if ($line =~ /^.\s*($Declare)\((\s*)\*(\s*)($Ident)(\s*)\)(\s*)\(/) { + my $declare = $1; + my $pre_pointer_space = $2; + my $post_pointer_space = $3; + my $funcname = $4; + my $post_funcname_space = $5; + my $pre_args_space = $6; + +# the $Declare variable will capture all spaces after the type +# so check it for a missing trailing missing space but pointer return types +# don't need a space so don't warn for those. + my $post_declare_space = ""; + if ($declare =~ /(\s+)$/) { + $post_declare_space = $1; + $declare = rtrim($declare); + } + if ($declare !~ /\*$/ && $post_declare_space =~ /^$/) { + WARN("SPACING", + "missing space after return type\n" . $herecurr); + $post_declare_space = " "; + } + +# unnecessary space "type (*funcptr)(args...)" +# This test is not currently implemented because these declarations are +# equivalent to +# int foo(int bar, ...) +# and this is form shouldn't/doesn't generate a checkpatch warning. +# +# elsif ($declare =~ /\s{2,}$/) { +# WARN("SPACING", +# "Multiple spaces after return type\n" . $herecurr); +# } + +# unnecessary space "type ( *funcptr)(args...)" + if (defined $pre_pointer_space && + $pre_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer open parenthesis\n" . $herecurr); + } + +# unnecessary space "type (* funcptr)(args...)" + if (defined $post_pointer_space && + $post_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr )(args...)" + if (defined $post_funcname_space && + $post_funcname_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr) (args...)" + if (defined $pre_args_space && + $pre_args_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer arguments\n" . $herecurr); + } + + if (show_type("SPACING") && $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*)$Declare\s*\(\s*\*\s*$Ident\s*\)\s*\(/$1 . $declare . $post_declare_space . '(*' . $funcname . ')('/ex; + } + } + +# check for spacing round square brackets; allowed: +# 1. with a type on the left -- int [] a; +# 2. at the beginning of a line for slice initialisers -- [0...10] = 5, +# 3. inside a curly brace -- = { [0...10] = 5 } + while ($line =~ /(.*?\s)\[/g) { + my ($where, $prefix) = ($-[1], $1); + if ($prefix !~ /$Type\s+$/ && + ($where != 0 || $prefix !~ /^.\s+$/) && + $prefix !~ /[{,:]\s+$/) { + if (ERROR("BRACKET_SPACE", + "space prohibited before open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(\+.*?)\s+\[/$1\[/; + } + } + } + +# check for spaces between functions and their parentheses. + while ($line =~ /($Ident)\s+\(/g) { + my $name = $1; + my $ctx_before = substr($line, 0, $-[1]); + my $ctx = "$ctx_before$name"; + + # Ignore those directives where spaces _are_ permitted. + if ($name =~ /^(?: + if|for|while|switch|return|case| + volatile|__volatile__| + __attribute__|format|__extension__| + asm|__asm__|scoped_guard)$/x) + { + # cpp #define statements have non-optional spaces, ie + # if there is a space between the name and the open + # parenthesis it is simply not a parameter group. + } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) { + + # cpp #elif statement condition may start with a ( + } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) { + + # If this whole things ends with a type its most + # likely a typedef for a function. + } elsif ($ctx =~ /$Type$/) { + + } else { + if (WARN("SPACING", + "space prohibited between function name and open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b$name\s+\(/$name\(/; + } + } + } + +# Check operator spacing. + if (!($line=~/\#\s*include/)) { + my $fixed_line = ""; + my $line_fixed = 0; + + my $ops = qr{ + <<=|>>=|<=|>=|==|!=| + \+=|-=|\*=|\/=|%=|\^=|\|=|&=| + =>|->|<<|>>|<|>|=|!|~| + &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%| + \?:|\?|: + }x; + my @elements = split(/($ops|;)/, $opline); + +## print("element count: <" . $#elements . ">\n"); +## foreach my $el (@elements) { +## print("el: <$el>\n"); +## } + + my @fix_elements = (); + my $off = 0; + + foreach my $el (@elements) { + push(@fix_elements, substr($rawline, $off, length($el))); + $off += length($el); + } + + $off = 0; + + my $blank = copy_spacing($opline); + my $last_after = -1; + + for (my $n = 0; $n < $#elements; $n += 2) { + + my $good = $fix_elements[$n] . $fix_elements[$n + 1]; + +## print("n: <$n> good: <$good>\n"); + + $off += length($elements[$n]); + + # Pick up the preceding and succeeding characters. + my $ca = substr($opline, 0, $off); + my $cc = ''; + if (length($opline) >= ($off + length($elements[$n + 1]))) { + $cc = substr($opline, $off + length($elements[$n + 1])); + } + my $cb = "$ca$;$cc"; + + my $a = ''; + $a = 'V' if ($elements[$n] ne ''); + $a = 'W' if ($elements[$n] =~ /\s$/); + $a = 'C' if ($elements[$n] =~ /$;$/); + $a = 'B' if ($elements[$n] =~ /(\[|\()$/); + $a = 'O' if ($elements[$n] eq ''); + $a = 'E' if ($ca =~ /^\s*$/); + + my $op = $elements[$n + 1]; + + my $c = ''; + if (defined $elements[$n + 2]) { + $c = 'V' if ($elements[$n + 2] ne ''); + $c = 'W' if ($elements[$n + 2] =~ /^\s/); + $c = 'C' if ($elements[$n + 2] =~ /^$;/); + $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/); + $c = 'O' if ($elements[$n + 2] eq ''); + $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/); + } else { + $c = 'E'; + } + + my $ctx = "${a}x${c}"; + + my $at = "(ctx:$ctx)"; + + my $ptr = substr($blank, 0, $off) . "^"; + my $hereptr = "$hereline$ptr\n"; + + # Pull out the value of this operator. + my $op_type = substr($curr_values, $off + 1, 1); + + # Get the full operator variant. + my $opv = $op . substr($curr_vars, $off, 1); + + # Ignore operators passed as parameters. + if ($op_type ne 'V' && + $ca =~ /\s$/ && $cc =~ /^\s*[,\)]/) { + +# # Ignore comments +# } elsif ($op =~ /^$;+$/) { + + # ; should have either the end of line or a space or \ after it + } elsif ($op eq ';') { + if ($ctx !~ /.x[WEBC]/ && + $cc !~ /^\\/ && $cc !~ /^;/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + + # // is a comment + } elsif ($op eq '//') { + + # : when part of a bitfield + } elsif ($opv eq ':B') { + # skip the bitfield test for now + + # No spaces for: + # -> + } elsif ($op eq '->') { + if ($ctx =~ /Wx.|.xW/) { + if (ERROR("SPACING", + "spaces prohibited around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # , must not have a space before and must have a space on the right. + } elsif ($op eq ',') { + my $rtrim_before = 0; + my $space_after = 0; + if ($ctx =~ /Wx./) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $rtrim_before = 1; + } + } + if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $last_after = $n; + $space_after = 1; + } + } + if ($rtrim_before || $space_after) { + if ($rtrim_before) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + } else { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + } + if ($space_after) { + $good .= " "; + } + } + + # '*' as part of a type definition -- reported already. + } elsif ($opv eq '*_') { + #warn "'*' is part of type\n"; + + # unary operators should have a space before and + # none after. May be left adjacent to another + # unary operator, or a cast + } elsif ($op eq '!' || $op eq '~' || + $opv eq '*U' || $opv eq '-U' || + $opv eq '&U' || $opv eq '&&U') { + if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) { + if (ERROR("SPACING", + "space required before that '$op' $at\n" . $hereptr)) { + if ($n != $last_after + 2) { + $good = $fix_elements[$n] . " " . ltrim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } + if ($op eq '*' && $cc =~/\s*$Modifier\b/) { + # A unary '*' may be const + + } elsif ($ctx =~ /.xW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . rtrim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # unary ++ and unary -- are allowed no space on one side. + } elsif ($op eq '++' or $op eq '--') { + if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) { + if (ERROR("SPACING", + "space required one side of that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + if ($ctx =~ /Wx[BE]/ || + ($ctx =~ /Wx./ && $cc =~ /^;/)) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + if ($ctx =~ /ExW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # << and >> may either have or not have spaces both sides + } elsif ($op eq '<<' or $op eq '>>' or + $op eq '&' or $op eq '^' or $op eq '|' or + $op eq '+' or $op eq '-' or + $op eq '*' or $op eq '/' or + $op eq '%') + { + if ($check) { + if (defined $fix_elements[$n + 2] && $ctx !~ /[EW]x[EW]/) { + if (CHK("SPACING", + "spaces preferred around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + $fix_elements[$n + 2] =~ s/^\s+//; + $line_fixed = 1; + } + } elsif (!defined $fix_elements[$n + 2] && $ctx !~ /Wx[OE]/) { + if (CHK("SPACING", + "space preferred before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } elsif ($ctx =~ /Wx[^WCE]|[^WCE]xW/) { + if (ERROR("SPACING", + "need consistent spacing around '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # A colon needs no spaces before when it is + # terminating a case value or a label. + } elsif ($opv eq ':C' || $opv eq ':L') { + if ($ctx =~ /Wx./ and $realfile !~ m@.*\.lds\.h$@) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + + # All the others need spaces both sides. + } elsif ($ctx !~ /[EWC]x[CWE]/) { + my $ok = 0; + + # Ignore email addresses <foo@bar> + if (($op eq '<' && + $cc =~ /^\S+\@\S+>/) || + ($op eq '>' && + $ca =~ /<\S+\@\S+$/)) + { + $ok = 1; + } + + # for asm volatile statements + # ignore a colon with another + # colon immediately before or after + if (($op eq ':') && + ($ca =~ /:$/ || $cc =~ /^:/)) { + $ok = 1; + } + + # messages are ERROR, but ?: are CHK + if ($ok == 0) { + my $msg_level = \&ERROR; + $msg_level = \&CHK if (($op eq '?:' || $op eq '?' || $op eq ':') && $ctx =~ /VxV/); + + if (&{$msg_level}("SPACING", + "spaces required around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + } + $off += length($elements[$n + 1]); + +## print("n: <$n> GOOD: <$good>\n"); + + $fixed_line = $fixed_line . $good; + } + + if (($#elements % 2) == 0) { + $fixed_line = $fixed_line . $fix_elements[$#elements]; + } + + if ($fix && $line_fixed && $fixed_line ne $fixed[$fixlinenr]) { + $fixed[$fixlinenr] = $fixed_line; + } + + + } + +# check for whitespace before a non-naked semicolon + if ($line =~ /^\+.*\S\s+;\s*$/) { + if (WARN("SPACING", + "space prohibited before semicolon\n" . $herecurr) && + $fix) { + 1 while $fixed[$fixlinenr] =~ + s/^(\+.*\S)\s+;/$1;/; + } + } + +# check for multiple assignments + if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) { + CHK("MULTIPLE_ASSIGNMENTS", + "multiple assignments should be avoided\n" . $herecurr); + } + +## # check for multiple declarations, allowing for a function declaration +## # continuation. +## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ && +## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { +## +## # Remove any bracketed sections to ensure we do not +## # falsely report the parameters of functions. +## my $ln = $line; +## while ($ln =~ s/\([^\(\)]*\)//g) { +## } +## if ($ln =~ /,/) { +## WARN("MULTIPLE_DECLARATION", +## "declaring multiple variables together should be avoided\n" . $herecurr); +## } +## } + +#need space before brace following if, while, etc + if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\)\{/) || + $line =~ /\b(?:else|do)\{/) { + if (ERROR("SPACING", + "space required before the open brace '{'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*(?:do|else|\)))\{/$1 {/; + } + } + +## # check for blank lines before declarations +## if ($line =~ /^.\t+$Type\s+$Ident(?:\s*=.*)?;/ && +## $prevrawline =~ /^.\s*$/) { +## WARN("SPACING", +## "No blank lines before declarations\n" . $hereprev); +## } +## + +# closing brace should have a space following it when it has anything +# on the line + if ($line =~ /}(?!(?:,|;|\)|\}))\S/) { + if (ERROR("SPACING", + "space required after that close brace '}'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/}((?!(?:,|;|\)))\S)/} $1/; + } + } + +# check spacing on square brackets + if ($line =~ /\[\s/ && $line !~ /\[\s*$/) { + if (ERROR("SPACING", + "space prohibited after that open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\[\s+/\[/; + } + } + if ($line =~ /\s\]/) { + if (ERROR("SPACING", + "space prohibited before that close square bracket ']'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\]/\]/; + } + } + +# check spacing on parentheses + if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && + $line !~ /for\s*\(\s+;/) { + if (ERROR("SPACING", + "space prohibited after that open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\(\s+/\(/; + } + } + if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && + $line !~ /for\s*\(.*;\s+\)/ && + $line !~ /:\s+\)/) { + if (ERROR("SPACING", + "space prohibited before that close parenthesis ')'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\)/\)/; + } + } + +# check unnecessary parentheses around addressof/dereference single $Lvals +# ie: &(foo->bar) should be &foo->bar and *(foo->bar) should be *foo->bar + + while ($line =~ /(?:[^&]&\s*|\*)\(\s*($Ident\s*(?:$Member\s*)+)\s*\)/g) { + my $var = $1; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around $var\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(\s*\Q$var\E\s*\)/$var/; + } + } + +# check for unnecessary parentheses around function pointer uses +# ie: (foo->bar)(); should be foo->bar(); +# but not "if (foo->bar) (" to avoid some false positives + if ($line =~ /(\bif\s*|)(\(\s*$Ident\s*(?:$Member\s*)+\))[ \t]*\(/ && $1 !~ /^if/) { + my $var = $2; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around function pointer $var\n" . $herecurr) && + $fix) { + my $var2 = deparenthesize($var); + $var2 =~ s/\s//g; + $fixed[$fixlinenr] =~ s/\Q$var\E/$var2/; + } + } + +# check for unnecessary parentheses around comparisons in if uses +# when !drivers/staging or command-line uses --strict + if (($realfile !~ m@^(?:drivers/staging/)@ || $check_orig) && + $perl_version_ok && defined($stat) && + $stat =~ /(^.\s*if\s*($balanced_parens))/) { + my $if_stat = $1; + my $test = substr($2, 1, -1); + my $herectx; + while ($test =~ /(?:^|[^\w\&\!\~])+\s*\(\s*([\&\!\~]?\s*$Lval\s*(?:$Compare\s*$FuncArg)?)\s*\)/g) { + my $match = $1; + # avoid parentheses around potential macro args + next if ($match =~ /^\s*\w+\s*$/); + if (!defined($herectx)) { + $herectx = $here . "\n"; + my $cnt = statement_rawlines($if_stat); + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + last if $rl =~ /^[ \+].*\{/; + } + } + CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around '$match'\n" . $herectx); + } + } + +# check that goto labels aren't indented (allow a single space indentation) +# and ignore bitfield definitions like foo:1 +# Strictly, labels can have whitespace after the identifier and before the : +# but this is not allowed here as many ?: uses would appear to be labels + if ($sline =~ /^.\s+[A-Za-z_][A-Za-z\d_]*:(?!\s*\d+)/ && + $sline !~ /^. [A-Za-z\d_][A-Za-z\d_]*:/ && + $sline !~ /^.\s+default:/) { + if (WARN("INDENTED_LABEL", + "labels should not be indented\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.)\s+/$1/; + } + } + +# check if a statement with a comma should be two statements like: +# foo = bar(), /* comma should be semicolon */ +# bar = baz(); + if (defined($stat) && + $stat =~ /^\+\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*,\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*;\s*$/) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + WARN("SUSPECT_COMMA_SEMICOLON", + "Possible comma where semicolon could be used\n" . $herectx); + } + +# return is not a function + if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) { + my $spacing = $1; + if ($perl_version_ok && + $stat =~ /^.\s*return\s*($balanced_parens)\s*;\s*$/) { + my $value = $1; + $value = deparenthesize($value); + if ($value =~ m/^\s*$FuncArg\s*(?:\?|$)/) { + ERROR("RETURN_PARENTHESES", + "return is not a function, parentheses are not required\n" . $herecurr); + } + } elsif ($spacing !~ /\s+/) { + ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr); + } + } + +# unnecessary return in a void function +# at end-of-function, with the previous line a single leading tab, then return; +# and the line before that not a goto label target like "out:" + if ($sline =~ /^[ \+]}\s*$/ && + $prevline =~ /^\+\treturn\s*;\s*$/ && + $linenr >= 3 && + $lines[$linenr - 3] =~ /^[ +]/ && + $lines[$linenr - 3] !~ /^[ +]\s*$Ident\s*:/) { + WARN("RETURN_VOID", + "void function return statements are not generally useful\n" . $hereprev); + } + +# if statements using unnecessary parentheses - ie: if ((foo == bar)) + if ($perl_version_ok && + $line =~ /\bif\s*((?:\(\s*){2,})/) { + my $openparens = $1; + my $count = $openparens =~ tr@\(@\(@; + my $msg = ""; + if ($line =~ /\bif\s*(?:\(\s*){$count,$count}$LvalOrFunc\s*($Compare)\s*$LvalOrFunc(?:\s*\)){$count,$count}/) { + my $comp = $4; #Not $1 because of $LvalOrFunc + $msg = " - maybe == should be = ?" if ($comp eq "=="); + WARN("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses$msg\n" . $herecurr); + } + } + +# comparisons with a constant or upper case identifier on the left +# avoid cases like "foo + BAR < baz" +# only fix matches surrounded by parentheses to avoid incorrect +# conversions like "FOO < baz() + 5" being "misfixed" to "baz() > FOO + 5" + if ($perl_version_ok && + $line =~ /^\+(.*)\b($Constant|[A-Z_][A-Z0-9_]*)\s*($Compare)\s*($LvalOrFunc)/) { + my $lead = $1; + my $const = $2; + my $comp = $3; + my $to = $4; + my $newcomp = $comp; + if ($lead !~ /(?:$Operators|\.)\s*$/ && + $to !~ /^(?:Constant|[A-Z_][A-Z0-9_]*)$/ && + WARN("CONSTANT_COMPARISON", + "Comparisons should place the constant on the right side of the test\n" . $herecurr) && + $fix) { + if ($comp eq "<") { + $newcomp = ">"; + } elsif ($comp eq "<=") { + $newcomp = ">="; + } elsif ($comp eq ">") { + $newcomp = "<"; + } elsif ($comp eq ">=") { + $newcomp = "<="; + } + $fixed[$fixlinenr] =~ s/\(\s*\Q$const\E\s*$Compare\s*\Q$to\E\s*\)/($to $newcomp $const)/; + } + } + +# Return of what appears to be an errno should normally be negative + if ($sline =~ /\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\s*|\s*)[;:,]/) { + my $name = $1; + if ($name ne 'EOF' && $name ne 'ERROR' && $name !~ /^EPOLL/) { + WARN("USE_NEGATIVE_ERRNO", + "return of an errno should typically be negative (ie: return -$1)\n" . $herecurr); + } + } + +# Need a space before open parenthesis after if, while etc + if ($line =~ /\b(if|while|for|switch)\(/) { + if (ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b(if|while|for|switch)\(/$1 \(/; + } + } + +# Check for illegal assignment in if conditional -- and check for trailing +# statements after the conditional. + if ($line =~ /do\s*(?!{)/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($stat_next) = ctx_statement_block($line_nr_next, + $remain_next, $off_next); + $stat_next =~ s/\n./\n /g; + ##print "stat<$stat> stat_next<$stat_next>\n"; + + if ($stat_next =~ /^\s*while\b/) { + # If the statement carries leading newlines, + # then count those as offsets. + my ($whitespace) = + ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = + statement_rawlines($whitespace) - 1; + + $suppress_whiletrailers{$line_nr_next + + $offset} = 1; + } + } + if (!defined $suppress_whiletrailers{$linenr} && + defined($stat) && defined($cond) && + $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { + my ($s, $c) = ($stat, $cond); + my $fixed_assign_in_if = 0; + + if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { + if (ERROR("ASSIGN_IN_IF", + "do not use assignment in if condition\n" . $herecurr) && + $fix && $perl_version_ok) { + if ($rawline =~ /^\+(\s+)if\s*\(\s*(\!)?\s*\(\s*(($Lval)\s*=\s*$LvalOrFunc)\s*\)\s*(?:($Compare)\s*($FuncArg))?\s*\)\s*(\{)?\s*$/) { + my $space = $1; + my $not = $2; + my $statement = $3; + my $assigned = $4; + my $test = $8; + my $against = $9; + my $brace = $15; + fix_delete_line($fixlinenr, $rawline); + fix_insert_line($fixlinenr, "$space$statement;"); + my $newline = "${space}if ("; + $newline .= '!' if defined($not); + $newline .= '(' if (defined $not && defined($test) && defined($against)); + $newline .= "$assigned"; + $newline .= " $test $against" if (defined($test) && defined($against)); + $newline .= ')' if (defined $not && defined($test) && defined($against)); + $newline .= ')'; + $newline .= " {" if (defined($brace)); + fix_insert_line($fixlinenr + 1, $newline); + $fixed_assign_in_if = 1; + } + } + } + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + $s =~ s/$;//g; # Remove any comments + if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && + $c !~ /}\s*while\s*/) + { + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + my $stat_real = ''; + + $stat_real = raw_line($linenr, $cond_lines) + . "\n" if ($cond_lines); + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + if (ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr . $stat_real) && + !$fixed_assign_in_if && + $cond_lines == 0 && + $fix && $perl_version_ok && + $fixed[$fixlinenr] =~ /^\+(\s*)((?:if|while|for)\s*$balanced_parens)\s*(.*)$/) { + my $indent = $1; + my $test = $2; + my $rest = rtrim($4); + if ($rest =~ /;$/) { + $fixed[$fixlinenr] = "\+$indent$test"; + fix_insert_line($fixlinenr + 1, "$indent\t$rest"); + } + } + } + } + +# Check for bitwise tests written as boolean + if ($line =~ / + (?: + (?:\[|\(|\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\|) + | + (?:\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\||\)|\]) + )/x) + { + WARN("HEXADECIMAL_BOOLEAN_TEST", + "boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr); + } + +# if and else should not have general statements after it + if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { + my $s = $1; + $s =~ s/$;//g; # Remove any comments + if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + } +# if should not continue a brace + if ($line =~ /}\s*if\b/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line (or did you mean 'else if'?)\n" . + $herecurr); + } +# case and default should not have general statements after them + if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g && + $line !~ /\G(?: + (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$| + \s*return\s+ + )/xg) + { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + + # Check for }<nl>else {, these must be at the same + # indent level to be relevant to each other. + if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ && + $previndent == $indent) { + if (ERROR("ELSE_AFTER_BRACE", + "else should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/}\s*$//; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)else/$1} else/; + fix_insert_line($fixlinenr, $fixedline); + } + } + + if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ && + $previndent == $indent) { + my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0); + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + + if ($s =~ /^\s*;/) { + if (ERROR("WHILE_AFTER_BRACE", + "while should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + my $trailing = $rawline; + $trailing =~ s/^\+//; + $trailing = trim($trailing); + $fixedline =~ s/}\s*$/} $trailing/; + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +#Specific variable tests + while ($line =~ m{($Constant|$Lval)}g) { + my $var = $1; + +#CamelCase + if ($var !~ /^$Constant$/ && + $var =~ /[A-Z][a-z]|[a-z][A-Z]/ && +#Ignore some autogenerated defines and enum values + $var !~ /^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]/ && +#Ignore Page<foo> variants + $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && +#Ignore ETHTOOL_LINK_MODE_<foo> variants + $var !~ /^ETHTOOL_LINK_MODE_/ && +#Ignore SI style variants like nS, mV and dB +#(ie: max_uV, regulator_min_uA_show, RANGE_mA_VALUE) + $var !~ /^(?:[a-z0-9_]*|[A-Z0-9_]*)?_?[a-z][A-Z](?:_[a-z0-9_]+|_[A-Z0-9_]+)?$/ && +#Ignore some three character SI units explicitly, like MiB and KHz + $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { + while ($var =~ m{\b($Ident)}g) { + my $word = $1; + next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/); + if ($check) { + seed_camelcase_includes(); + if (!$file && !$camelcase_file_seeded) { + seed_camelcase_file($realfile); + $camelcase_file_seeded = 1; + } + } + if (!defined $camelcase{$word}) { + $camelcase{$word} = 1; + CHK("CAMELCASE", + "Avoid CamelCase: <$word>\n" . $herecurr); + } + } + } + } + +#no spaces allowed after \ in define + if ($line =~ /\#\s*define.*\\\s+$/) { + if (WARN("WHITESPACE_AFTER_LINE_CONTINUATION", + "Whitespace after \\ makes next lines useless\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + } + +# warn if <asm/foo.h> is #included and <linux/foo.h> is available and includes +# itself <asm/foo.h> (uses RAW line) + if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\<asm\/(.*)\.h\>}) { + my $file = "$1.h"; + my $checkfile = "include/linux/$file"; + if (-f "$root/$checkfile" && + $realfile ne $checkfile && + $1 !~ /$allowed_asm_includes/) + { + my $asminclude = `grep -Ec "#include\\s+<asm/$file>" $root/$checkfile`; + if ($asminclude > 0) { + if ($realfile =~ m{^arch/}) { + CHK("ARCH_INCLUDE_LINUX", + "Consider using #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } else { + WARN("INCLUDE_LINUX", + "Use #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } + } + } + } + +# multi-statement macros should be enclosed in a do while loop, grab the +# first statement and ensure its the whole macro if its not enclosed +# in a known good container + if ($realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + my $has_flow_statement = 0; + my $has_arg_concat = 0; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n"; + #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n"; + + $has_flow_statement = 1 if ($ctx =~ /\b(goto|return)\b/); + $has_arg_concat = 1 if ($ctx =~ /\#\#/ && $ctx !~ /\#\#\s*(?:__VA_ARGS__|args)\b/); + + $dstat =~ s/^.\s*\#\s*define\s+$Ident(\([^\)]*\))?\s*//; + my $define_args = $1; + my $define_stmt = $dstat; + my @def_args = (); + + if (defined $define_args && $define_args ne "") { + $define_args = substr($define_args, 1, length($define_args) - 2); + $define_args =~ s/\s*//g; + $define_args =~ s/\\\+?//g; + @def_args = split(",", $define_args); + } + + $dstat =~ s/$;//g; + $dstat =~ s/\\\n.//g; + $dstat =~ s/^\s*//s; + $dstat =~ s/\s*$//s; + + # Flatten any parentheses and braces + while ($dstat =~ s/\([^\(\)]*\)/1u/ || + $dstat =~ s/\{[^\{\}]*\}/1u/ || + $dstat =~ s/.\[[^\[\]]*\]/1u/) + { + } + + # Flatten any obvious string concatenation. + while ($dstat =~ s/($String)\s*$Ident/$1/ || + $dstat =~ s/$Ident\s*($String)/$1/) + { + } + + # Make asm volatile uses seem like a generic function + $dstat =~ s/\b_*asm_*\s+_*volatile_*\b/asm_volatile/g; + + my $exceptions = qr{ + $Declare| + module_param_named| + MODULE_PARM_DESC| + DECLARE_PER_CPU| + DEFINE_PER_CPU| + __typeof__\(| + union| + struct| + \.$Ident\s*=\s*| + ^\"|\"$| + ^\[ + }x; + #print "REST<$rest> dstat<$dstat> ctx<$ctx>\n"; + + $ctx =~ s/\n*$//; + my $stmt_cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $stmt_cnt, $here); + + if ($dstat ne '' && + $dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(), + $dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo(); + $dstat !~ /^[!~-]?(?:$Lval|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo // foo->bar // foo.bar->baz + $dstat !~ /^'X'$/ && $dstat !~ /^'XX'$/ && # character constants + $dstat !~ /$exceptions/ && + $dstat !~ /^\.$Ident\s*=/ && # .foo = + $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo + $dstat !~ /^case\b/ && # case ... + $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) + $dstat !~ /^while\s*$Constant\s*$Constant\s*$/ && # while (...) {...} + $dstat !~ /^for\s*$Constant$/ && # for (...) + $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar() + $dstat !~ /^do\s*{/ && # do {... + $dstat !~ /^\(\{/ && # ({... + $ctx !~ /^.\s*#\s*define\s+TRACE_(?:SYSTEM|INCLUDE_FILE|INCLUDE_PATH)\b/) + { + if ($dstat =~ /^\s*if\b/) { + ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Macros starting with if should be enclosed by a do - while loop to avoid possible if/else logic defects\n" . "$herectx"); + } elsif ($dstat =~ /;/) { + ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx"); + } else { + ERROR("COMPLEX_MACRO", + "Macros with complex values should be enclosed in parentheses\n" . "$herectx"); + } + + } + + # Make $define_stmt single line, comment-free, etc + my @stmt_array = split('\n', $define_stmt); + my $first = 1; + $define_stmt = ""; + foreach my $l (@stmt_array) { + $l =~ s/\\$//; + if ($first) { + $define_stmt = $l; + $first = 0; + } elsif ($l =~ /^[\+ ]/) { + $define_stmt .= substr($l, 1); + } + } + $define_stmt =~ s/$;//g; + $define_stmt =~ s/\s+/ /g; + $define_stmt = trim($define_stmt); + +# check if any macro arguments are reused (ignore '...' and 'type') + foreach my $arg (@def_args) { + next if ($arg =~ /\.\.\./); + next if ($arg =~ /^type$/i); + my $tmp_stmt = $define_stmt; + $tmp_stmt =~ s/\b(__must_be_array|offsetof|sizeof|sizeof_field|__stringify|typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g; + $tmp_stmt =~ s/\#+\s*$arg\b//g; + $tmp_stmt =~ s/\b$arg\s*\#\#//g; + my $use_cnt = () = $tmp_stmt =~ /\b$arg\b/g; + if ($use_cnt > 1) { + CHK("MACRO_ARG_REUSE", + "Macro argument reuse '$arg' - possible side-effects?\n" . "$herectx"); + } +# check if any macro arguments may have other precedence issues + if ($tmp_stmt =~ m/($Operators)?\s*\b$arg\b\s*($Operators)?/m && + ((defined($1) && $1 ne ',') || + (defined($2) && $2 ne ','))) { + CHK("MACRO_ARG_PRECEDENCE", + "Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx"); + } + +# check if this is an unused argument + if ($define_stmt !~ /\b$arg\b/) { + WARN("MACRO_ARG_UNUSED", + "Argument '$arg' is not used in function-like macro\n" . "$herectx"); + } + } + +# check for macros with flow control, but without ## concatenation +# ## concatenation is commonly a macro that defines a function so ignore those + if ($has_flow_statement && !$has_arg_concat) { + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("MACRO_WITH_FLOW_CONTROL", + "Macros with flow control statements should be avoided\n" . "$herectx"); + } + +# check for line continuations outside of #defines, preprocessor #, and asm + + } elsif ($realfile =~ m@/vmlinux.lds.h$@) { + $line =~ s/(\w+)/$maybe_linker_symbol{$1}++/ge; + #print "REAL: $realfile\nln: $line\nkeys:", sort keys %maybe_linker_symbol; + } else { + if ($prevline !~ /^..*\\$/ && + $line !~ /^\+\s*\#.*\\$/ && # preprocessor + $line !~ /^\+.*\b(__asm__|asm)\b.*\\$/ && # asm + $line =~ /^\+.*\\$/) { + WARN("LINE_CONTINUATIONS", + "Avoid unnecessary line continuations\n" . $herecurr); + } + } + +# do {} while (0) macro tests: +# single-statement macros do not need to be enclosed in do while (0) loop, +# macro should not end with a semicolon + if ($perl_version_ok && + $realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s+$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + + $dstat =~ s/\\\n.//g; + $dstat =~ s/$;/ /g; + + if ($dstat =~ /^\+\s*#\s*define\s+$Ident\s*${balanced_parens}\s*do\s*{(.*)\s*}\s*while\s*\(\s*0\s*\)\s*([;\s]*)\s*$/) { + my $stmts = $2; + my $semis = $3; + + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (($stmts =~ tr/;/;/) == 1 && + $stmts !~ /^\s*(if|while|for|switch)\b/) { + WARN("SINGLE_STATEMENT_DO_WHILE_MACRO", + "Single statement macros should not use a do {} while (0) loop\n" . "$herectx"); + } + if (defined $semis && $semis ne "") { + WARN("DO_WHILE_MACRO_WITH_TRAILING_SEMICOLON", + "do {} while (0) macros should not be semicolon terminated\n" . "$herectx"); + } + } elsif ($dstat =~ /^\+\s*#\s*define\s+$Ident.*;\s*$/) { + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("TRAILING_SEMICOLON", + "macros should not use a trailing semicolon\n" . "$herectx"); + } + } + +# check for redundant bracing round if etc + if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, 1); + #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n"; + #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n"; + if ($#chunks > 0 && $level == 0) { + my @allowed = (); + my $allow = 0; + my $seen = 0; + my $herectx = $here . "\n"; + my $ln = $linenr - 1; + for my $chunk (@chunks) { + my ($cond, $block) = @{$chunk}; + + # If the condition carries leading newlines, then count those as offsets. + my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = statement_rawlines($whitespace) - 1; + + $allowed[$allow] = 0; + #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n"; + + # We have looked at and allowed this specific line. + $suppress_ifbraces{$ln + $offset} = 1; + + $herectx .= "$rawlines[$ln + $offset]\n[...]\n"; + $ln += statement_rawlines($block) - 1; + + substr($block, 0, length($cond), ''); + + $seen++ if ($block =~ /^\s*{/); + + #print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n"; + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed[$allow] = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed[$allow] = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed[$allow] = 1; + } + $allow++; + } + if ($seen) { + my $sum_allowed = 0; + foreach (@allowed) { + $sum_allowed += $_; + } + if ($sum_allowed == 0) { + WARN("BRACES", + "braces {} are not necessary for any arm of this statement\n" . $herectx); + } elsif ($sum_allowed != $allow && + $seen != $allow) { + CHK("BRACES", + "braces {} should be used on all arms of this statement\n" . $herectx); + } + } + } + } + if (!defined $suppress_ifbraces{$linenr - 1} && + $line =~ /\b(if|while|for|else)\b/) { + my $allowed = 0; + + # Check the pre-context. + if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) { + #print "APW: ALLOWED: pre<$1>\n"; + $allowed = 1; + } + + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, $-[0]); + + # Check the condition. + my ($cond, $block) = @{$chunks[0]}; + #print "CHECKING<$linenr> cond<$cond> block<$block>\n"; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed = 1; + } + # Check the post-context. + if (defined $chunks[1]) { + my ($cond, $block) = @{$chunks[1]}; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if ($block =~ /^\s*\{/) { + #print "APW: ALLOWED: chunk-1 block<$block>\n"; + $allowed = 1; + } + } + if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) { + my $cnt = statement_rawlines($block); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("BRACES", + "braces {} are not necessary for single statement blocks\n" . $herectx); + } + } + +# check for single line unbalanced braces + if ($sline =~ /^.\s*\}\s*else\s*$/ || + $sline =~ /^.\s*else\s*\{\s*$/) { + CHK("BRACES", "Unbalanced braces around else statement\n" . $herecurr); + } + +# check for unnecessary blank lines around braces + if (($line =~ /^.\s*}\s*$/ && $prevrawline =~ /^.\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary before a close brace '}'\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + } + } + if (($rawline =~ /^.\s*$/ && $prevline =~ /^..*{\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary after an open brace '{'\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# no volatiles please + my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b}; + if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) { + WARN("VOLATILE", + "Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst\n" . $herecurr); + } + +# Check for user-visible strings broken across lines, which breaks the ability +# to grep for the string. Make exceptions when the previous string ends in a +# newline (multiple lines in one string constant) or '\t', '\r', ';', or '{' +# (common in inline assembly) or is a octal \123 or hexadecimal \xaf value + if ($line =~ /^\+\s*$String/ && + $prevline =~ /"\s*$/ && + $prevrawline !~ /(?:\\(?:[ntr]|[0-7]{1,3}|x[0-9a-fA-F]{1,2})|;\s*|\{\s*)"\s*$/) { + if (WARN("SPLIT_STRING", + "quoted string split across lines\n" . $hereprev) && + $fix && + $prevrawline =~ /^\+.*"\s*$/ && + $last_coalesced_string_linenr != $linenr - 1) { + my $extracted_string = get_quoted_string($line, $rawline); + my $comma_close = ""; + if ($rawline =~ /\Q$extracted_string\E(\s*\)\s*;\s*$|\s*,\s*)/) { + $comma_close = $1; + } + + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/"\s*$//; + $fixedline .= substr($extracted_string, 1) . trim($comma_close); + fix_insert_line($fixlinenr - 1, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/\Q$extracted_string\E\Q$comma_close\E//; + if ($fixedline !~ /\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $last_coalesced_string_linenr = $linenr; + } + } + +# check for missing a space in a string concatenation + if ($prevrawline =~ /[^\\]\w"$/ && $rawline =~ /^\+[\t ]+"\w/) { + WARN('MISSING_SPACE', + "break quoted strings at a space character\n" . $hereprev); + } + +# check for an embedded function name in a string when the function is known +# This does not work very well for -f --file checking as it depends on patch +# context providing the function name or a single line form for in-file +# function declarations + if ($line =~ /^\+.*$String/ && + defined($context_function) && + get_quoted_string($line, $rawline) =~ /\b$context_function\b/ && + length(get_quoted_string($line, $rawline)) != (length($context_function) + 2)) { + WARN("EMBEDDED_FUNCTION_NAME", + "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr); + } + +# check for unnecessary function tracing like uses +# This does not use $logFunctions because there are many instances like +# 'dprintk(FOO, "%s()\n", __func__);' which do not match $logFunctions + if ($rawline =~ /^\+.*\([^"]*"$tracing_logging_tags{0,3}%s(?:\s*\(\s*\)\s*)?$tracing_logging_tags{0,3}(?:\\n)?"\s*,\s*__func__\s*\)\s*;/) { + if (WARN("TRACING_LOGGING", + "Unnecessary ftrace-like logging - prefer using ftrace\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# check for spaces before a quoted newline + if ($rawline =~ /^.*\".*\s\\n/) { + if (WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE", + "unnecessary whitespace before a quoted newline\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*\".*)\s+\\n/$1\\n/; + } + + } + +# concatenated string without spaces between elements + if ($line =~ /$String[A-Z_]/ || + ($line =~ /([A-Za-z0-9_]+)$String/ && $1 !~ /^[Lu]$/)) { + if (CHK("CONCATENATED_STRING", + "Concatenated strings should use spaces between elements\n" . $herecurr) && + $fix) { + while ($line =~ /($String)/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E([A-Za-z0-9_])/$extracted_string $1/; + $fixed[$fixlinenr] =~ s/([A-Za-z0-9_])\Q$extracted_string\E/$1 $extracted_string/; + } + } + } + +# uncoalesced string fragments + if ($line =~ /$String\s*[Lu]?"/) { + if (WARN("STRING_FRAGMENTS", + "Consecutive strings are generally better as a single string\n" . $herecurr) && + $fix) { + while ($line =~ /($String)(?=\s*")/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E\s*"/substr($extracted_string, 0, -1)/e; + } + } + } + +# check for non-standard and hex prefixed decimal printf formats + my $show_L = 1; #don't show the same defect twice + my $show_Z = 1; + while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { + my $string = substr($rawline, $-[1], $+[1] - $-[1]); + $string =~ s/%%/__/g; + # check for %L + if ($show_L && $string =~ /%[\*\d\.\$]*L([diouxX])/) { + WARN("PRINTF_L", + "\%L$1 is non-standard C, use %ll$1\n" . $herecurr); + $show_L = 0; + } + # check for %Z + if ($show_Z && $string =~ /%[\*\d\.\$]*Z([diouxX])/) { + WARN("PRINTF_Z", + "%Z$1 is non-standard C, use %z$1\n" . $herecurr); + $show_Z = 0; + } + # check for 0x<decimal> + if ($string =~ /0x%[\*\d\.\$\Llzth]*[diou]/) { + ERROR("PRINTF_0XDECIMAL", + "Prefixing 0x with decimal output is defective\n" . $herecurr); + } + } + +# check for line continuations in quoted strings with odd counts of " + if ($rawline =~ /\\$/ && $sline =~ tr/"/"/ % 2) { + WARN("LINE_CONTINUATIONS", + "Avoid line continuations in quoted strings\n" . $herecurr); + } + +# warn about #if 0 + if ($line =~ /^.\s*\#\s*if\s+0\b/) { + WARN("IF_0", + "Consider removing the code enclosed by this #if 0 and its #endif\n" . $herecurr); + } + +# warn about #if 1 + if ($line =~ /^.\s*\#\s*if\s+1\b/) { + WARN("IF_1", + "Consider removing the #if 1 and its #endif\n" . $herecurr); + } + +# check for needless "if (<foo>) fn(<foo>)" uses + if ($prevline =~ /\bif\s*\(\s*($Lval)\s*\)/) { + my $tested = quotemeta($1); + my $expr = '\s*\(\s*' . $tested . '\s*\)\s*;'; + if ($line =~ /\b(kfree|usb_free_urb|debugfs_remove(?:_recursive)?|(?:kmem_cache|mempool|dma_pool)_destroy)$expr/) { + my $func = $1; + if (WARN('NEEDLESS_IF', + "$func(NULL) is safe and this check is probably not required\n" . $hereprev) && + $fix) { + my $do_fix = 1; + my $leading_tabs = ""; + my $new_leading_tabs = ""; + if ($lines[$linenr - 2] =~ /^\+(\t*)if\s*\(\s*$tested\s*\)\s*$/) { + $leading_tabs = $1; + } else { + $do_fix = 0; + } + if ($lines[$linenr - 1] =~ /^\+(\t+)$func\s*\(\s*$tested\s*\)\s*;\s*$/) { + $new_leading_tabs = $1; + if (length($leading_tabs) + 1 ne length($new_leading_tabs)) { + $do_fix = 0; + } + } else { + $do_fix = 0; + } + if ($do_fix) { + fix_delete_line($fixlinenr - 1, $prevrawline); + $fixed[$fixlinenr] =~ s/^\+$new_leading_tabs/\+$leading_tabs/; + } + } + } + } + +# check for unnecessary "Out of Memory" messages + if ($line =~ /^\+.*\b$logFunctions\s*\(/ && + $prevline =~ /^[ \+]\s*if\s*\(\s*(\!\s*|NULL\s*==\s*)?($Lval)(\s*==\s*NULL\s*)?\s*\)/ && + (defined $1 || defined $3) && + $linenr > 3) { + my $testval = $2; + my $testline = $lines[$linenr - 3]; + + my ($s, $c) = ctx_statement_block($linenr - 3, $realcnt, 0); +# print("line: <$line>\nprevline: <$prevline>\ns: <$s>\nc: <$c>\n\n\n"); + + if ($s =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*$allocFunctions\s*\(/ && + $s !~ /\b__GFP_NOWARN\b/ ) { + WARN("OOM_MESSAGE", + "Possible unnecessary 'out of memory' message\n" . $hereprev); + } + } + +# check for logging functions with KERN_<LEVEL> + if ($line !~ /printk(?:_ratelimited|_once)?\s*\(/ && + $line =~ /\b$logFunctions\s*\(.*\b(KERN_[A-Z]+)\b/) { + my $level = $1; + if (WARN("UNNECESSARY_KERN_LEVEL", + "Possible unnecessary $level\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s*$level\s*//; + } + } + +# check for logging continuations + if ($line =~ /\bprintk\s*\(\s*KERN_CONT\b|\bpr_cont\s*\(/) { + WARN("LOGGING_CONTINUATION", + "Avoid logging continuation uses where feasible\n" . $herecurr); + } + +# check for unnecessary use of %h[xudi] and %hh[xudi] in logging functions + if (defined $stat && + $line =~ /\b$logFunctions\s*\(/ && + index($stat, '"') >= 0) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + pos($stat_real) = index($stat_real, '"'); + while ($stat_real =~ /[^\"%]*(%[\#\d\.\*\-]*(h+)[idux])/g) { + my $pspec = $1; + my $h = $2; + my $lineoff = substr($stat_real, 0, $-[1]) =~ tr@\n@@; + if (WARN("UNNECESSARY_MODIFIER", + "Integer promotion: Using '$h' in '$pspec' is unnecessary\n" . "$here\n$stat_real\n") && + $fix && $fixed[$fixlinenr + $lineoff] =~ /^\+/) { + my $nspec = $pspec; + $nspec =~ s/h//g; + $fixed[$fixlinenr + $lineoff] =~ s/\Q$pspec\E/$nspec/; + } + } + } + +# check for mask then right shift without a parentheses + if ($perl_version_ok && + $line =~ /$LvalOrFunc\s*\&\s*($LvalOrFunc)\s*>>/ && + $4 !~ /^\&/) { # $LvalOrFunc may be &foo, ignore if so + WARN("MASK_THEN_SHIFT", + "Possible precedence defect with mask then right shift - may need parentheses\n" . $herecurr); + } + +# check for pointer comparisons to NULL + if ($perl_version_ok) { + while ($line =~ /\b$LvalOrFunc\s*(==|\!=)\s*NULL\b/g) { + my $val = $1; + my $equal = "!"; + $equal = "" if ($4 eq "!="); + if (CHK("COMPARISON_TO_NULL", + "Comparison to NULL could be written \"${equal}${val}\"\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$val\E\s*(?:==|\!=)\s*NULL\b/$equal$val/; + } + } + } + +# check for bad placement of section $InitAttribute (e.g.: __initdata) + if ($line =~ /(\b$InitAttribute\b)/) { + my $attr = $1; + if ($line =~ /^\+\s*static\s+(?:const\s+)?(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*[=;]/) { + my $ptr = $1; + my $var = $2; + if ((($ptr =~ /\b(union|struct)\s+$attr\b/ && + ERROR("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr)) || + ($ptr !~ /\b(union|struct)\s+$attr\b/ && + WARN("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr))) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s+(?:const\s+)?)(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*([=;])\s*/"$1" . trim(string_find_replace($2, "\\s*$attr\\s*", " ")) . " " . trim(string_find_replace($3, "\\s*$attr\\s*", "")) . " $attr" . ("$4" eq ";" ? ";" : " = ")/e; + } + } + } + +# check for $InitAttributeData (ie: __initdata) with const + if ($line =~ /\bconst\b/ && $line =~ /($InitAttributeData)/) { + my $attr = $1; + $attr =~ /($InitAttributePrefix)(.*)/; + my $attr_prefix = $1; + my $attr_type = $2; + if (ERROR("INIT_ATTRIBUTE", + "Use of const init definition must use ${attr_prefix}initconst\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/$InitAttributeData/${attr_prefix}initconst/; + } + } + +# check for $InitAttributeConst (ie: __initconst) without const + if ($line !~ /\bconst\b/ && $line =~ /($InitAttributeConst)/) { + my $attr = $1; + if (ERROR("INIT_ATTRIBUTE", + "Use of $attr requires a separate use of const\n" . $herecurr) && + $fix) { + my $lead = $fixed[$fixlinenr] =~ + /(^\+\s*(?:static\s+))/; + $lead = rtrim($1); + $lead = "$lead " if ($lead !~ /^\+$/); + $lead = "${lead}const "; + $fixed[$fixlinenr] =~ s/(^\+\s*(?:static\s+))/$lead/; + } + } + +# check for __read_mostly with const non-pointer (should just be const) + if ($line =~ /\b__read_mostly\b/ && + $line =~ /($Type)\s*$Ident/ && $1 !~ /\*\s*$/ && $1 =~ /\bconst\b/) { + if (ERROR("CONST_READ_MOSTLY", + "Invalid use of __read_mostly with const type\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+__read_mostly\b//; + } + } + +# don't use __constant_<foo> functions outside of include/uapi/ + if ($realfile !~ m@^include/uapi/@ && + $line =~ /(__constant_(?:htons|ntohs|[bl]e(?:16|32|64)_to_cpu|cpu_to_[bl]e(?:16|32|64)))\s*\(/) { + my $constant_func = $1; + my $func = $constant_func; + $func =~ s/^__constant_//; + if (WARN("CONSTANT_CONVERSION", + "$constant_func should be $func\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$constant_func\b/$func/g; + } + } + +# prefer usleep_range over udelay + if ($line =~ /\budelay\s*\(\s*(\d+)\s*\)/) { + my $delay = $1; + # ignore udelay's < 10, however + if (! ($delay < 10) ) { + CHK("USLEEP_RANGE", + "usleep_range is preferred over udelay; see function description of usleep_range() and udelay().\n" . $herecurr); + } + if ($delay > 2000) { + WARN("LONG_UDELAY", + "long udelay - prefer mdelay; see function description of mdelay().\n" . $herecurr); + } + } + +# warn about unexpectedly long msleep's + if ($line =~ /\bmsleep\s*\((\d+)\);/) { + if ($1 < 20) { + WARN("MSLEEP", + "msleep < 20ms can sleep for up to 20ms; see function description of msleep().\n" . $herecurr); + } + } + +# check for comparisons of jiffies + if ($line =~ /\bjiffies\s*$Compare|$Compare\s*jiffies\b/) { + WARN("JIFFIES_COMPARISON", + "Comparing jiffies is almost always wrong; prefer time_after, time_before and friends\n" . $herecurr); + } + +# check for comparisons of get_jiffies_64() + if ($line =~ /\bget_jiffies_64\s*\(\s*\)\s*$Compare|$Compare\s*get_jiffies_64\s*\(\s*\)/) { + WARN("JIFFIES_COMPARISON", + "Comparing get_jiffies_64() is almost always wrong; prefer time_after64, time_before64 and friends\n" . $herecurr); + } + +# warn about #ifdefs in C files +# if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { +# print "#ifdef in C files should be avoided\n"; +# print "$herecurr"; +# $clean = 0; +# } + +# warn about spacing in #ifdefs + if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) { + if (ERROR("SPACING", + "exactly one space required after that #$1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*\#\s*(ifdef|ifndef|elif))\s{2,}/$1 /; + } + + } + +# check for spinlock_t definitions without a comment. + if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ || + $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) { + my $which = $1; + if (!ctx_has_comment($first_line, $linenr)) { + CHK("UNCOMMENTED_DEFINITION", + "$1 definition without comment\n" . $herecurr); + } + } +# check for memory barriers without a comment. + + my $barriers = qr{ + mb| + rmb| + wmb + }x; + my $barrier_stems = qr{ + mb__before_atomic| + mb__after_atomic| + store_release| + load_acquire| + store_mb| + (?:$barriers) + }x; + my $all_barriers = qr{ + (?:$barriers)| + smp_(?:$barrier_stems)| + virt_(?:$barrier_stems) + }x; + + if ($line =~ /\b(?:$all_barriers)\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("MEMORY_BARRIER", + "memory barrier without comment\n" . $herecurr); + } + } + + my $underscore_smp_barriers = qr{__smp_(?:$barrier_stems)}x; + + if ($realfile !~ m@^include/asm-generic/@ && + $realfile !~ m@/barrier\.h$@ && + $line =~ m/\b(?:$underscore_smp_barriers)\s*\(/ && + $line !~ m/^.\s*\#\s*define\s+(?:$underscore_smp_barriers)\s*\(/) { + WARN("MEMORY_BARRIER", + "__smp memory barriers shouldn't be used outside barrier.h and asm-generic\n" . $herecurr); + } + +# check for waitqueue_active without a comment. + if ($line =~ /\bwaitqueue_active\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("WAITQUEUE_ACTIVE", + "waitqueue_active without comment\n" . $herecurr); + } + } + +# check for data_race without a comment. + if ($line =~ /\bdata_race\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("DATA_RACE", + "data_race without comment\n" . $herecurr); + } + } + +# check of hardware specific defines + if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) { + CHK("ARCH_DEFINES", + "architecture specific defines should be avoided\n" . $herecurr); + } + +# check that the storage class is not after a type + if ($line =~ /\b($Type)\s+($Storage)\b/) { + WARN("STORAGE_CLASS", + "storage class '$2' should be located before type '$1'\n" . $herecurr); + } +# Check that the storage class is at the beginning of a declaration + if ($line =~ /\b$Storage\b/ && + $line !~ /^.\s*$Storage/ && + $line =~ /^.\s*(.+?)\$Storage\s/ && + $1 !~ /[\,\)]\s*$/) { + WARN("STORAGE_CLASS", + "storage class should be at the beginning of the declaration\n" . $herecurr); + } + +# check the location of the inline attribute, that it is between +# storage class and type. + if ($line =~ /\b$Type\s+$Inline\b/ || + $line =~ /\b$Inline\s+$Storage\b/) { + ERROR("INLINE_LOCATION", + "inline keyword should sit between storage class and type\n" . $herecurr); + } + +# Check for __inline__ and __inline, prefer inline + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b(__inline__|__inline)\b/) { + if (WARN("INLINE", + "plain inline is preferred over $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b(__inline__|__inline)\b/inline/; + + } + } + +# Check for compiler attributes + if ($realfile !~ m@\binclude/uapi/@ && + $rawline =~ /\b__attribute__\s*\(\s*($balanced_parens)\s*\)/) { + my $attr = $1; + $attr =~ s/\s*\(\s*(.*)\)\s*/$1/; + + my %attr_list = ( + "alias" => "__alias", + "aligned" => "__aligned", + "always_inline" => "__always_inline", + "assume_aligned" => "__assume_aligned", + "cold" => "__cold", + "const" => "__attribute_const__", + "copy" => "__copy", + "designated_init" => "__designated_init", + "externally_visible" => "__visible", + "format" => "printf|scanf", + "gnu_inline" => "__gnu_inline", + "malloc" => "__malloc", + "mode" => "__mode", + "no_caller_saved_registers" => "__no_caller_saved_registers", + "noclone" => "__noclone", + "noinline" => "noinline", + "nonstring" => "__nonstring", + "noreturn" => "__noreturn", + "packed" => "__packed", + "pure" => "__pure", + "section" => "__section", + "used" => "__used", + "weak" => "__weak" + ); + + while ($attr =~ /\s*(\w+)\s*(${balanced_parens})?/g) { + my $orig_attr = $1; + my $params = ''; + $params = $2 if defined($2); + my $curr_attr = $orig_attr; + $curr_attr =~ s/^[\s_]+|[\s_]+$//g; + if (exists($attr_list{$curr_attr})) { + my $new = $attr_list{$curr_attr}; + if ($curr_attr eq "format" && $params) { + $params =~ /^\s*\(\s*(\w+)\s*,\s*(.*)/; + $new = "__$1\($2"; + } else { + $new = "$new$params"; + } + if (WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "Prefer $new over __attribute__(($orig_attr$params))\n" . $herecurr) && + $fix) { + my $remove = "\Q$orig_attr\E" . '\s*' . "\Q$params\E" . '(?:\s*,\s*)?'; + $fixed[$fixlinenr] =~ s/$remove//; + $fixed[$fixlinenr] =~ s/\b__attribute__/$new __attribute__/; + $fixed[$fixlinenr] =~ s/\}\Q$new\E/} $new/; + $fixed[$fixlinenr] =~ s/ __attribute__\s*\(\s*\(\s*\)\s*\)//; + } + } + } + + # Check for __attribute__ unused, prefer __always_unused or __maybe_unused + if ($attr =~ /^_*unused/) { + WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "__always_unused or __maybe_unused is preferred over __attribute__((__unused__))\n" . $herecurr); + } + } + +# Check for __attribute__ weak, or __weak declarations (may have link issues) + if ($perl_version_ok && + $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ && + ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ || + $line =~ /\b__weak\b/)) { + ERROR("WEAK_DECLARATION", + "Using weak declarations can have unintended link defects\n" . $herecurr); + } + +# check for c99 types like uint8_t used outside of uapi/ and tools/ + if ($realfile !~ m@\binclude/uapi/@ && + $realfile !~ m@\btools/@ && + $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) { + my $type = $1; + if ($type =~ /\b($typeC99Typedefs)\b/) { + $type = $1; + my $kernel_type = 'u'; + $kernel_type = 's' if ($type =~ /^_*[si]/); + $type =~ /(\d+)/; + $kernel_type .= $1; + if (CHK("PREFER_KERNEL_TYPES", + "Prefer kernel type '$kernel_type' over '$type'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$type\b/$kernel_type/; + } + } + } + +# check for cast of C90 native int or longer types constants + if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) { + my $cast = $1; + my $const = $2; + my $suffix = ""; + my $newconst = $const; + $newconst =~ s/${Int_type}$//; + $suffix .= 'U' if ($cast =~ /\bunsigned\b/); + if ($cast =~ /\blong\s+long\b/) { + $suffix .= 'LL'; + } elsif ($cast =~ /\blong\b/) { + $suffix .= 'L'; + } + if (WARN("TYPECAST_INT_CONSTANT", + "Unnecessary typecast of c90 int constant - '$cast$const' could be '$const$suffix'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$cast\E$const\b/$newconst$suffix/; + } + } + +# check for sizeof(&) + if ($line =~ /\bsizeof\s*\(\s*\&/) { + WARN("SIZEOF_ADDRESS", + "sizeof(& should be avoided\n" . $herecurr); + } + +# check for sizeof without parenthesis + if ($line =~ /\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/) { + if (WARN("SIZEOF_PARENTHESIS", + "sizeof $1 should be sizeof($1)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/"sizeof(" . trim($1) . ")"/ex; + } + } + +# check for struct spinlock declarations + if ($line =~ /^.\s*\bstruct\s+spinlock\s+\w+\s*;/) { + WARN("USE_SPINLOCK_T", + "struct spinlock should be spinlock_t\n" . $herecurr); + } + +# check for seq_printf uses that could be seq_puts + if ($sline =~ /\bseq_printf\s*\(.*"\s*\)\s*;\s*$/) { + my $fmt = get_quoted_string($line, $rawline); + $fmt =~ s/%%//g; + if ($fmt !~ /%/) { + if (WARN("PREFER_SEQ_PUTS", + "Prefer seq_puts to seq_printf\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bseq_printf\b/seq_puts/; + } + } + } + +# check for vsprintf extension %p<foo> misuses + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?![^\{]*\{\s*).*\b(\w+)\s*\(.*$String\s*,/s && + $1 !~ /^_*volatile_*$/) { + my $stat_real; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + for (my $count = $linenr; $count <= $lc; $count++) { + my $specifier; + my $extension; + my $qualifier; + my $bad_specifier = ""; + my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0)); + $fmt =~ s/%%//g; + + while ($fmt =~ /(\%[\*\d\.]*p(\w)(\w*))/g) { + $specifier = $1; + $extension = $2; + $qualifier = $3; + if ($extension !~ /[4SsBKRraEehMmIiUDdgVCbGNOxtf]/ || + ($extension eq "f" && + defined $qualifier && $qualifier !~ /^w/) || + ($extension eq "4" && + defined $qualifier && $qualifier !~ /^cc/)) { + $bad_specifier = $specifier; + last; + } + if ($extension eq "x" && !defined($stat_real)) { + if (!defined($stat_real)) { + $stat_real = get_stat_real($linenr, $lc); + } + WARN("VSPRINTF_SPECIFIER_PX", + "Using vsprintf specifier '\%px' potentially exposes the kernel memory layout, if you don't really need the address please consider using '\%p'.\n" . "$here\n$stat_real\n"); + } + } + if ($bad_specifier ne "") { + my $stat_real = get_stat_real($linenr, $lc); + my $msg_level = \&WARN; + my $ext_type = "Invalid"; + my $use = ""; + if ($bad_specifier =~ /p[Ff]/) { + $use = " - use %pS instead"; + $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/); + } elsif ($bad_specifier =~ /pA/) { + $use = " - '%pA' is only intended to be used from Rust code"; + $msg_level = \&ERROR; + } + + &{$msg_level}("VSPRINTF_POINTER_EXTENSION", + "$ext_type vsprintf pointer extension '$bad_specifier'$use\n" . "$here\n$stat_real\n"); + } + } + } + +# Check for misused memsets + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/) { + + my $ms_addr = $2; + my $ms_val = $7; + my $ms_size = $12; + + if ($ms_size =~ /^(0x|)0$/i) { + ERROR("MEMSET", + "memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n"); + } elsif ($ms_size =~ /^(0x|)1$/i) { + WARN("MEMSET", + "single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n"); + } + } + +# Check for memcpy(foo, bar, ETH_ALEN) that could be ether_addr_copy(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# if (WARN("PREFER_ETHER_ADDR_COPY", +# "Prefer ether_addr_copy() over memcpy() if the Ethernet addresses are __aligned(2)\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/ether_addr_copy($2, $7)/; +# } +# } + +# Check for memcmp(foo, bar, ETH_ALEN) that could be ether_addr_equal*(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcmp\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# WARN("PREFER_ETHER_ADDR_EQUAL", +# "Prefer ether_addr_equal() or ether_addr_equal_unaligned() over memcmp()\n" . "$here\n$stat\n") +# } + +# check for memset(foo, 0x0, ETH_ALEN) that could be eth_zero_addr +# check for memset(foo, 0xFF, ETH_ALEN) that could be eth_broadcast_addr +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# +# my $ms_val = $7; +# +# if ($ms_val =~ /^(?:0x|)0+$/i) { +# if (WARN("PREFER_ETH_ZERO_ADDR", +# "Prefer eth_zero_addr over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_zero_addr($2)/; +# } +# } elsif ($ms_val =~ /^(?:0xff|255)$/i) { +# if (WARN("PREFER_ETH_BROADCAST_ADDR", +# "Prefer eth_broadcast_addr() over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_broadcast_addr($2)/; +# } +# } +# } + +# strcpy uses that should likely be strscpy + if ($line =~ /\bstrcpy\s*\(/) { + WARN("STRCPY", + "Prefer strscpy over strcpy - see: https://github.com/KSPP/linux/issues/88\n" . $herecurr); + } + +# strlcpy uses that should likely be strscpy + if ($line =~ /\bstrlcpy\s*\(/) { + WARN("STRLCPY", + "Prefer strscpy over strlcpy - see: https://github.com/KSPP/linux/issues/89\n" . $herecurr); + } + +# strncpy uses that should likely be strscpy or strscpy_pad + if ($line =~ /\bstrncpy\s*\(/) { + WARN("STRNCPY", + "Prefer strscpy, strscpy_pad, or __nonstring over strncpy - see: https://github.com/KSPP/linux/issues/90\n" . $herecurr); + } + +# ethtool_sprintf uses that should likely be ethtool_puts + if ($line =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (WARN("PREFER_ETHTOOL_PUTS", + "Prefer ethtool_puts over ethtool_sprintf with only two arguments\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; + } + } + + # use $rawline because $line loses %s via sanitization and thus we can't match against it. + if ($rawline =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*\"\%s\"\s*,\s*$FuncArg\s*\)/) { + if (WARN("PREFER_ETHTOOL_PUTS", + "Prefer ethtool_puts over ethtool_sprintf with standalone \"%s\" specifier\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*"\%s"\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; + } + } + + +# typecasts on min/max could be min_t/max_t + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (defined $2 || defined $7) { + my $call = $1; + my $cast1 = deparenthesize($2); + my $arg1 = $3; + my $cast2 = deparenthesize($7); + my $arg2 = $8; + my $cast; + + if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) { + $cast = "$cast1 or $cast2"; + } elsif ($cast1 ne "") { + $cast = $cast1; + } else { + $cast = $cast2; + } + WARN("MINMAX", + "$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n"); + } + } + +# check usleep_range arguments + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) { + my $min = $1; + my $max = $7; + if ($min eq $max) { + WARN("USLEEP_RANGE", + "usleep_range should not use min == max args; see function description of usleep_range().\n" . "$here\n$stat\n"); + } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && + $min > $max) { + WARN("USLEEP_RANGE", + "usleep_range args reversed, use min then max; see function description of usleep_range().\n" . "$here\n$stat\n"); + } + } + +# check for naked sscanf + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/ && + ($stat !~ /$Ident\s*=\s*sscanf\s*$balanced_parens/ && + $stat !~ /\bsscanf\s*$balanced_parens\s*(?:$Compare)/ && + $stat !~ /(?:$Compare)\s*\bsscanf\s*$balanced_parens/)) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + WARN("NAKED_SSCANF", + "unchecked sscanf return value\n" . "$here\n$stat_real\n"); + } + +# check for simple sscanf that should be kstrto<foo> + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + if ($stat_real =~ /\bsscanf\b\s*\(\s*$FuncArg\s*,\s*("[^"]+")/) { + my $format = $6; + my $count = $format =~ tr@%@%@; + if ($count == 1 && + $format =~ /^"\%(?i:ll[udxi]|[udxi]ll|ll|[hl]h?[udxi]|[udxi][hl]h?|[hl]h?|[udxi])"$/) { + WARN("SSCANF_TO_KSTRTO", + "Prefer kstrto<type> to single variable sscanf\n" . "$here\n$stat_real\n"); + } + } + } + +# check for new externs in .h files. + if ($realfile =~ /\.h$/ && + $line =~ /^\+\s*(extern\s+)$Type\s*$Ident\s*\(/s) { + if (CHK("AVOID_EXTERNS", + "extern prototypes should be avoided in .h files\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(.*)\bextern\b\s*(.*)/$1$2/; + } + } + +# check for new externs in .c files. + if ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s) + { + my $function_name = $1; + my $paren_space = $2; + + my $s = $stat; + if (defined $cond) { + substr($s, 0, length($cond), ''); + } + if ($s =~ /^\s*;/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + + if ($paren_space =~ /\n/) { + WARN("FUNCTION_ARGUMENTS", + "arguments for function declarations should follow identifier\n" . $herecurr); + } + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^\+extern struct\s+(\w+)\s+(\w+)\[\];/) + { + my ($st_type, $st_name) = ($1, $2); + + for my $s (keys %maybe_linker_symbol) { + #print "Linker symbol? $st_name : $s\n"; + goto LIKELY_LINKER_SYMBOL + if $st_name =~ /$s/; + } + WARN("AVOID_EXTERNS", + "found a file-scoped extern type:$st_type name:$st_name in .c file\n" + . "is this a linker symbol ?\n" . $herecurr); + LIKELY_LINKER_SYMBOL: + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*extern\s+/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + +# check for function declarations that have arguments without identifier names + if (defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s*(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*\(\s*([^{]+)\s*\)\s*;/s && + $1 ne "void") { + my $args = trim($1); + while ($args =~ m/\s*($Type\s*(?:$Ident|\(\s*\*\s*$Ident?\s*\)\s*$balanced_parens)?)/g) { + my $arg = trim($1); + if ($arg =~ /^$Type$/ && $arg !~ /enum\s+$Ident$/) { + WARN("FUNCTION_ARGUMENTS", + "function definition argument '$arg' should also have an identifier name\n" . $herecurr); + } + } + } + +# check for function definitions + if ($perl_version_ok && + defined $stat && + $stat =~ /^.\s*(?:$Storage\s+)?$Type\s*($Ident)\s*$balanced_parens\s*{/s) { + $context_function = $1; + +# check for multiline function definition with misplaced open brace + my $ok = 0; + my $cnt = statement_rawlines($stat); + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + $ok = 1 if ($rl =~ /^[ \+]\{/); + $ok = 1 if ($rl =~ /\{/ && $n == 0); + last if $rl =~ /^[ \+].*\{/; + } + if (!$ok) { + ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herectx); + } + } + +# checks for new __setup's + if ($rawline =~ /\b__setup\("([^"]*)"/) { + my $name = $1; + + if (!grep(/$name/, @setup_docs)) { + CHK("UNDOCUMENTED_SETUP", + "__setup appears un-documented -- check Documentation/admin-guide/kernel-parameters.txt\n" . $herecurr); + } + } + +# check for pointless casting of alloc functions + if ($line =~ /\*\s*\)\s*$allocFunctions\b/) { + WARN("UNNECESSARY_CASTS", + "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); + } + +# alloc style +# p = alloc(sizeof(struct foo), ...) should be p = alloc(sizeof(*p), ...) + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k|v)[mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) { + CHK("ALLOC_SIZEOF_STRUCT", + "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr); + } + +# check for (kv|k)[mz]alloc with multiplies that could be kmalloc_array/kvmalloc_array/kvcalloc/kcalloc + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { + my $oldfunc = $3; + my $a1 = $4; + my $a2 = $10; + my $newfunc = "kmalloc_array"; + $newfunc = "kvmalloc_array" if ($oldfunc eq "kvmalloc"); + $newfunc = "kvcalloc" if ($oldfunc eq "kvzalloc"); + $newfunc = "kcalloc" if ($oldfunc eq "kzalloc"); + my $r1 = $a1; + my $r2 = $a2; + if ($a1 =~ /^sizeof\s*\S/) { + $r1 = $a2; + $r2 = $a1; + } + if ($r1 !~ /^sizeof\b/ && $r2 =~ /^sizeof\s*\S/ && + !($r1 =~ /^$Constant$/ || $r1 =~ /^[A-Z_][A-Z0-9_]*$/)) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (WARN("ALLOC_WITH_MULTIPLY", + "Prefer $newfunc over $oldfunc with multiply\n" . $herectx) && + $cnt == 1 && + $fix) { + $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . trim($r1) . ', ' . trim($r2)/e; + } + } + } + +# check for krealloc arg reuse + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*($Lval)\s*,/ && + $1 eq $3) { + WARN("KREALLOC_ARG_REUSE", + "Reusing the krealloc arg is almost always a bug\n" . $herecurr); + } + +# check for alloc argument mismatch + if ($line =~ /\b((?:devm_)?((?:k|kv)?(calloc|malloc_array)(?:_node)?))\s*\(\s*sizeof\b/) { + WARN("ALLOC_ARRAY_ARGS", + "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr); + } + +# check for multiple semicolons + if ($line =~ /;\s*;\s*$/) { + if (WARN("ONE_SEMICOLON", + "Statements terminations use 1 semicolon\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\s*;\s*){2,}$/;/g; + } + } + +# check for #defines like: 1 << <digit> that could be BIT(digit), it is not exported to uapi + if ($realfile !~ m@^include/uapi/@ && + $line =~ /#\s*define\s+\w+\s+\(?\s*1\s*([ulUL]*)\s*\<\<\s*(?:\d+|$Ident)\s*\)?/) { + my $ull = ""; + $ull = "_ULL" if (defined($1) && $1 =~ /ll/i); + if (CHK("BIT_MACRO", + "Prefer using the BIT$ull macro\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(?\s*1\s*[ulUL]*\s*<<\s*(\d+|$Ident)\s*\)?/BIT${ull}($1)/; + } + } + +# check for IS_ENABLED() without CONFIG_<FOO> ($rawline for comments too) + if ($rawline =~ /\bIS_ENABLED\s*\(\s*(\w+)\s*\)/ && $1 !~ /^${CONFIG_}/) { + WARN("IS_ENABLED_CONFIG", + "IS_ENABLED($1) is normally used as IS_ENABLED(${CONFIG_}$1)\n" . $herecurr); + } + +# check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE + if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(${CONFIG_}[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) { + my $config = $1; + if (WARN("PREFER_IS_ENABLED", + "Prefer IS_ENABLED(<FOO>) to ${CONFIG_}<FOO> || ${CONFIG_}<FOO>_MODULE\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = "\+#if IS_ENABLED($config)"; + } + } + +# check for /* fallthrough */ like comment, prefer fallthrough; + my @fallthroughs = ( + 'fallthrough', + '@fallthrough@', + 'lint -fallthrough[ \t]*', + 'intentional(?:ly)?[ \t]*fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)', + '(?:else,?\s*)?FALL(?:S | |-)?THR(?:OUGH|U|EW)[ \t.!]*(?:-[^\n\r]*)?', + 'Fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + 'fall(?:s | |-)?thr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + ); + if ($raw_comment ne '') { + foreach my $ft (@fallthroughs) { + if ($raw_comment =~ /$ft/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("PREFER_FALLTHROUGH", + "Prefer 'fallthrough;' over fallthrough comment\n" . $herecurr); + last; + } + } + } + +# check for switch/default statements without a break; + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("DEFAULT_NO_BREAK", + "switch default: should use break\n" . $herectx); + } + +# check for gcc specific __FUNCTION__ + if ($line =~ /\b__FUNCTION__\b/) { + if (WARN("USE_FUNC", + "__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b__FUNCTION__\b/__func__/g; + } + } + +# check for uses of __DATE__, __TIME__, __TIMESTAMP__ + while ($line =~ /\b(__(?:DATE|TIME|TIMESTAMP)__)\b/g) { + ERROR("DATE_TIME", + "Use of the '$1' macro makes the build non-deterministic\n" . $herecurr); + } + +# check for use of yield() + if ($line =~ /\byield\s*\(\s*\)/) { + WARN("YIELD", + "Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr); + } + +# check for comparisons against true and false + if ($line =~ /\+\s*(.*?)\b(true|false|$Lval)\s*(==|\!=)\s*(true|false|$Lval)\b(.*)$/i) { + my $lead = $1; + my $arg = $2; + my $test = $3; + my $otype = $4; + my $trail = $5; + my $op = "!"; + + ($arg, $otype) = ($otype, $arg) if ($arg =~ /^(?:true|false)$/i); + + my $type = lc($otype); + if ($type =~ /^(?:true|false)$/) { + if (("$test" eq "==" && "$type" eq "true") || + ("$test" eq "!=" && "$type" eq "false")) { + $op = ""; + } + + CHK("BOOL_COMPARISON", + "Using comparison to $otype is error prone\n" . $herecurr); + +## maybe suggesting a correct construct would better +## "Using comparison to $otype is error prone. Perhaps use '${lead}${op}${arg}${trail}'\n" . $herecurr); + + } + } + +# check for semaphores initialized locked + if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) { + WARN("CONSIDER_COMPLETION", + "consider using a completion\n" . $herecurr); + } + +# recommend kstrto* over simple_strto* and strict_strto* + if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) { + WARN("CONSIDER_KSTRTO", + "$1 is obsolete, use k$3 instead\n" . $herecurr); + } + +# check for __initcall(), use device_initcall() explicitly or more appropriate function please + if ($line =~ /^.\s*__initcall\s*\(/) { + WARN("USE_DEVICE_INITCALL", + "please use device_initcall() or more appropriate function instead of __initcall() (see include/linux/init.h)\n" . $herecurr); + } + +# check for spin_is_locked(), suggest lockdep instead + if ($line =~ /\bspin_is_locked\(/) { + WARN("USE_LOCKDEP", + "Where possible, use lockdep_assert_held instead of assertions based on spin_is_locked\n" . $herecurr); + } + +# check for deprecated apis + if ($line =~ /\b($deprecated_apis_search)\b\s*\(/) { + my $deprecated_api = $1; + my $new_api = $deprecated_apis{$deprecated_api}; + WARN("DEPRECATED_API", + "Deprecated use of '$deprecated_api', prefer '$new_api' instead\n" . $herecurr); + } + +# check for various structs that are normally const (ops, kgdb, device_tree) +# and avoid what seem like struct definitions 'struct foo {' + if (defined($const_structs) && + $line !~ /\bconst\b/ && + $line =~ /\bstruct\s+($const_structs)\b(?!\s*\{)/) { + WARN("CONST_STRUCT", + "struct $1 should normally be const\n" . $herecurr); + } + +# use of NR_CPUS is usually wrong +# ignore definitions of NR_CPUS and usage to define arrays as likely right +# ignore designated initializers using NR_CPUS + if ($line =~ /\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/ && + $line !~ /^.\s*\.\w+\s*=\s*.*\bNR_CPUS\b/) + { + WARN("NR_CPUS", + "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); + } + +# Use of __ARCH_HAS_<FOO> or ARCH_HAVE_<BAR> is wrong. + if ($line =~ /\+\s*#\s*define\s+((?:__)?ARCH_(?:HAS|HAVE)\w*)\b/) { + ERROR("DEFINE_ARCH_HAS", + "#define of '$1' is wrong - use Kconfig variables or standard guards instead\n" . $herecurr); + } + +# likely/unlikely comparisons similar to "(likely(foo) > 0)" + if ($perl_version_ok && + $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) { + WARN("LIKELY_MISUSE", + "Using $1 should generally have parentheses around the comparison\n" . $herecurr); + } + +# return sysfs_emit(foo, fmt, ...) fmt without newline + if ($line =~ /\breturn\s+sysfs_emit\s*\(\s*$FuncArg\s*,\s*($String)/ && + substr($rawline, $-[6], $+[6] - $-[6]) !~ /\\n"$/) { + my $offset = $+[6] - 1; + if (WARN("SYSFS_EMIT", + "return sysfs_emit(...) formats should include a terminating newline\n" . $herecurr) && + $fix) { + substr($fixed[$fixlinenr], $offset, 0) = '\\n'; + } + } + +# check for array definition/declarations that should use flexible arrays instead + if ($sline =~ /^[\+ ]\s*\}(?:\s*__packed)?\s*;\s*$/ && + $prevline =~ /^\+\s*(?:\}(?:\s*__packed\s*)?|$Type)\s*$Ident\s*\[\s*(0|1)\s*\]\s*;\s*$/) { + if (ERROR("FLEXIBLE_ARRAY", + "Use C99 flexible arrays - see https://docs.kernel.org/process/deprecated.html#zero-length-and-one-element-arrays\n" . $hereprev) && + $1 == '0' && $fix) { + $fixed[$fixlinenr - 1] =~ s/\[\s*0\s*\]/[]/; + } + } + +# nested likely/unlikely calls + if ($line =~ /\b(?:(?:un)?likely)\s*\(\s*!?\s*(IS_ERR(?:_OR_NULL|_VALUE)?|WARN)/) { + WARN("LIKELY_MISUSE", + "nested (un)?likely() calls, $1 already uses unlikely() internally\n" . $herecurr); + } + +# whine mightly about in_atomic + if ($line =~ /\bin_atomic\s*\(/) { + if ($realfile =~ m@^drivers/@) { + ERROR("IN_ATOMIC", + "do not use in_atomic in drivers\n" . $herecurr); + } elsif ($realfile !~ m@^kernel/@) { + WARN("IN_ATOMIC", + "use of in_atomic() is incorrect outside core kernel code\n" . $herecurr); + } + } + +# Complain about RCU Tasks Trace used outside of BPF (and of course, RCU). + our $rcu_trace_funcs = qr{(?x: + rcu_read_lock_trace | + rcu_read_lock_trace_held | + rcu_read_unlock_trace | + call_rcu_tasks_trace | + synchronize_rcu_tasks_trace | + rcu_barrier_tasks_trace | + rcu_request_urgent_qs_task + )}; + our $rcu_trace_paths = qr{(?x: + kernel/bpf/ | + include/linux/bpf | + net/bpf/ | + kernel/rcu/ | + include/linux/rcu + )}; + if ($line =~ /\b($rcu_trace_funcs)\s*\(/) { + if ($realfile !~ m{^$rcu_trace_paths}) { + WARN("RCU_TASKS_TRACE", + "use of RCU tasks trace is incorrect outside BPF or core RCU code\n" . $herecurr); + } + } + +# check for lockdep_set_novalidate_class + if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ || + $line =~ /__lockdep_no_validate__\s*\)/ ) { + if ($realfile !~ m@^kernel/lockdep@ && + $realfile !~ m@^include/linux/lockdep@ && + $realfile !~ m@^drivers/base/core@) { + ERROR("LOCKDEP", + "lockdep_no_validate class is reserved for device->mutex.\n" . $herecurr); + } + } + + if ($line =~ /debugfs_create_\w+.*\b$mode_perms_world_writable\b/ || + $line =~ /DEVICE_ATTR.*\b$mode_perms_world_writable\b/) { + WARN("EXPORTED_WORLD_WRITABLE", + "Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr); + } + +# check for DEVICE_ATTR uses that could be DEVICE_ATTR_<FOO> +# and whether or not function naming is typical and if +# DEVICE_ATTR permissions uses are unusual too + if ($perl_version_ok && + defined $stat && + $stat =~ /\bDEVICE_ATTR\s*\(\s*(\w+)\s*,\s*\(?\s*(\s*(?:${multi_mode_perms_string_search}|0[0-7]{3,3})\s*)\s*\)?\s*,\s*(\w+)\s*,\s*(\w+)\s*\)/) { + my $var = $1; + my $perms = $2; + my $show = $3; + my $store = $4; + my $octal_perms = perms_to_octal($perms); + if ($show =~ /^${var}_show$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0644") { + if (WARN("DEVICE_ATTR_RW", + "Use DEVICE_ATTR_RW\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*$store\s*\)/DEVICE_ATTR_RW(${var})/; + } + } elsif ($show =~ /^${var}_show$/ && + $store =~ /^NULL$/ && + $octal_perms eq "0444") { + if (WARN("DEVICE_ATTR_RO", + "Use DEVICE_ATTR_RO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*NULL\s*\)/DEVICE_ATTR_RO(${var})/; + } + } elsif ($show =~ /^NULL$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0200") { + if (WARN("DEVICE_ATTR_WO", + "Use DEVICE_ATTR_WO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*NULL\s*,\s*$store\s*\)/DEVICE_ATTR_WO(${var})/; + } + } elsif ($octal_perms eq "0644" || + $octal_perms eq "0444" || + $octal_perms eq "0200") { + my $newshow = "$show"; + $newshow = "${var}_show" if ($show ne "NULL" && $show ne "${var}_show"); + my $newstore = $store; + $newstore = "${var}_store" if ($store ne "NULL" && $store ne "${var}_store"); + my $rename = ""; + if ($show ne $newshow) { + $rename .= " '$show' to '$newshow'"; + } + if ($store ne $newstore) { + $rename .= " '$store' to '$newstore'"; + } + WARN("DEVICE_ATTR_FUNCTIONS", + "Consider renaming function(s)$rename\n" . $herecurr); + } else { + WARN("DEVICE_ATTR_PERMS", + "DEVICE_ATTR unusual permissions '$perms' used\n" . $herecurr); + } + } + +# Mode permission misuses where it seems decimal should be octal +# This uses a shortcut match to avoid unnecessary uses of a slow foreach loop +# o Ignore module_param*(...) uses with a decimal 0 permission as that has a +# specific definition of not visible in sysfs. +# o Ignore proc_create*(...) uses with a decimal 0 permission as that means +# use the default permissions + if ($perl_version_ok && + defined $stat && + $line =~ /$mode_perms_search/) { + foreach my $entry (@mode_permission_funcs) { + my $func = $entry->[0]; + my $arg_pos = $entry->[1]; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + + my $skip_args = ""; + if ($arg_pos > 1) { + $arg_pos--; + $skip_args = "(?:\\s*$FuncArg\\s*,\\s*){$arg_pos,$arg_pos}"; + } + my $test = "\\b$func\\s*\\(${skip_args}($FuncArg(?:\\|\\s*$FuncArg)*)\\s*[,\\)]"; + if ($stat =~ /$test/) { + my $val = $1; + $val = $6 if ($skip_args ne ""); + if (!($func =~ /^(?:module_param|proc_create)/ && $val eq "0") && + (($val =~ /^$Int$/ && $val !~ /^$Octal$/) || + ($val =~ /^$Octal$/ && length($val) ne 4))) { + ERROR("NON_OCTAL_PERMISSIONS", + "Use 4 digit octal (0777) not decimal permissions\n" . "$here\n" . $stat_real); + } + if ($val =~ /^$Octal$/ && (oct($val) & 02)) { + ERROR("EXPORTED_WORLD_WRITABLE", + "Exporting writable files is usually an error. Consider more restrictive permissions.\n" . "$here\n" . $stat_real); + } + } + } + } + +# check for uses of S_<PERMS> that could be octal for readability + while ($line =~ m{\b($multi_mode_perms_string_search)\b}g) { + my $oval = $1; + my $octal = perms_to_octal($oval); + if (WARN("SYMBOLIC_PERMS", + "Symbolic permissions '$oval' are not preferred. Consider using octal permissions '$octal'.\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$oval\E/$octal/; + } + } + +# validate content of MODULE_LICENSE against list from include/linux/module.h + if ($line =~ /\bMODULE_LICENSE\s*\(\s*($String)\s*\)/) { + my $extracted_string = get_quoted_string($line, $rawline); + my $valid_licenses = qr{ + GPL| + GPL\ v2| + GPL\ and\ additional\ rights| + Dual\ BSD/GPL| + Dual\ MIT/GPL| + Dual\ MPL/GPL| + Proprietary + }x; + if ($extracted_string !~ /^"(?:$valid_licenses)"$/x) { + WARN("MODULE_LICENSE", + "unknown module license " . $extracted_string . "\n" . $herecurr); + } + if (!$file && $extracted_string eq '"GPL v2"') { + if (WARN("MODULE_LICENSE", + "Prefer \"GPL\" over \"GPL v2\" - see commit bf7fbeeae6db (\"module: Cure the MODULE_LICENSE \"GPL\" vs. \"GPL v2\" bogosity\")\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bMODULE_LICENSE\s*\(\s*"GPL v2"\s*\)/MODULE_LICENSE("GPL")/; + } + } + } + +# check for sysctl duplicate constants + if ($line =~ /\.extra[12]\s*=\s*&(zero|one|int_max)\b/) { + WARN("DUPLICATED_SYSCTL_CONST", + "duplicated sysctl range checking value '$1', consider using the shared one in include/linux/sysctl.h\n" . $herecurr); + } + } + + # If we have no input at all, then there is nothing to report on + # so just keep quiet. + if ($#rawlines == -1) { + exit(0); + } + + # In mailback mode only produce a report in the negative, for + # things that appear to be patches. + if ($mailback && ($clean == 1 || !$is_patch)) { + exit(0); + } + + # This is not a patch, and we are in 'no-patch' mode so + # just keep quiet. + if (!$chk_patch && !$is_patch) { + exit(0); + } + + if (!$is_patch && $filename !~ /cover-letter\.patch$/) { + ERROR("NOT_UNIFIED_DIFF", + "Does not appear to be a unified-diff format patch\n"); + } + if ($is_patch && $has_commit_log && $chk_fixes_tag) { + if ($needs_fixes_tag ne "" && !$is_revert && !$fixes_tag) { + WARN("MISSING_FIXES_TAG", + "The commit message has '$needs_fixes_tag', perhaps it also needs a 'Fixes:' tag?\n"); + } + } + if ($is_patch && $has_commit_log && $chk_signoff) { + if ($signoff == 0) { + ERROR("MISSING_SIGN_OFF", + "Missing Signed-off-by: line(s)\n"); + } elsif ($authorsignoff != 1) { + # authorsignoff values: + # 0 -> missing sign off + # 1 -> sign off identical + # 2 -> names and addresses match, comments mismatch + # 3 -> addresses match, names different + # 4 -> names match, addresses different + # 5 -> names match, addresses excluding subaddress details (refer RFC 5233) match + + my $sob_msg = "'From: $author' != 'Signed-off-by: $author_sob'"; + + if ($authorsignoff == 0) { + ERROR("NO_AUTHOR_SIGN_OFF", + "Missing Signed-off-by: line by nominal patch author '$author'\n"); + } elsif ($authorsignoff == 2) { + CHK("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email comments mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 3) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email name mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 4) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email address mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 5) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email subaddress mismatch: $sob_msg\n"); + } + } + } + + print report_dump(); + if ($summary && !($clean == 1 && $quiet == 1)) { + print "$filename " if ($summary_file); + print "total: $cnt_error errors, $cnt_warn warnings, " . + (($check)? "$cnt_chk checks, " : "") . + "$cnt_lines lines checked\n"; + } + + if ($quiet == 0) { + # If there were any defects found and not already fixing them + if (!$clean and !$fix) { + print << "EOM" + +NOTE: For some of the reported defects, checkpatch may be able to + mechanically convert to the typical style using --fix or --fix-inplace. +EOM + } + # If there were whitespace errors which cleanpatch can fix + # then suggest that. + if ($rpt_cleaners) { + $rpt_cleaners = 0; + print << "EOM" + +NOTE: Whitespace errors detected. + You may wish to use scripts/cleanpatch or scripts/cleanfile +EOM + } + } + + if ($clean == 0 && $fix && + ("@rawlines" ne "@fixed" || + $#fixed_inserted >= 0 || $#fixed_deleted >= 0)) { + my $newfile = $filename; + $newfile .= ".EXPERIMENTAL-checkpatch-fixes" if (!$fix_inplace); + my $linecount = 0; + my $f; + + @fixed = fix_inserted_deleted_lines(\@fixed, \@fixed_inserted, \@fixed_deleted); + + open($f, '>', $newfile) + or die "$P: Can't open $newfile for write\n"; + foreach my $fixed_line (@fixed) { + $linecount++; + if ($file) { + if ($linecount > 3) { + $fixed_line =~ s/^\+//; + print $f $fixed_line . "\n"; + } + } else { + print $f $fixed_line . "\n"; + } + } + close($f); + + if (!$quiet) { + print << "EOM"; + +Wrote EXPERIMENTAL --fix correction(s) to '$newfile' + +Do _NOT_ trust the results written to this file. +Do _NOT_ submit these changes without inspecting them for correctness. + +This EXPERIMENTAL file is simply a convenience to help rewrite patches. +No warranties, expressed or implied... +EOM + } + } + + if ($quiet == 0) { + print "\n"; + if ($clean == 1) { + print "$vname has no obvious style problems and is ready for submission.\n"; + } else { + print "$vname has style problems, please review.\n"; + } + } + return $clean; +} From f5ede51158639b0bcdacd1c934c958b8c5ada3ef Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 2 Jan 2025 21:10:11 +0100 Subject: [PATCH 020/105] Update AUTHORS for 3.17 and add script for that Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- AUTHORS | 28 ++++++++++++++++++++++++++++ dev-docs/extend-authors.sh | 35 +++++++++++++++++++++++++++++++++++ dev-docs/release-process.md | 2 +- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100755 dev-docs/extend-authors.sh diff --git a/AUTHORS b/AUTHORS index 146dae6a4..9fd3b49ac 100644 --- a/AUTHORS +++ b/AUTHORS @@ -231,3 +231,31 @@ Yuri Per <yuri@acronis.com> Zhansong Gao <zhsgao@hotmail.com> Zhiqiang Liu <liuzhiqiang26@huawei.com> zsugabubus <zsugabubus@users.noreply.github.com> + +# New authors since fuse-3.16.2 +farlongsignal <141166749+farlongsignal@users.noreply.github.com> +yangyun50 <149988609+yangyun50@users.noreply.github.com> +bigbrotherwei <1965867461@qq.com> +Caian Benedicto <2220062+Caian@users.noreply.github.com> +desertwitch <24509509+desertwitch@users.noreply.github.com> +SteveYang <40466358+SteveY4ng@users.noreply.github.com> +FredyVia <942513309@qq.com> +legezywzh <94814730+legezywzh@users.noreply.github.com> +CismonX <admin@cismon.net> +amitgeron <amit.geron@gmail.com> +Bernd Schubert <bernd@bsbernd.com> +Daniel Rosenberg <drosen@google.com> +Horst Birthelmer <hbirthelmer@ddn.com> +Joanne Koong <joannelkoong@gmail.com> +Josef Bacik <josef@toxicpanda.com> +Matthew <matthew@matthew-cash.com> +gandalfs_cat <meow@kittcat.dev> +MJ Harvey <mharvey@jumptrading.com> +Nils <nils@nilsand.re> +Norman Wilson <norman@teach.cs.toronto.edu> +leipeng <peng@topling.cn> +Vladimir Serbinenko <phcoder@gmail.com> +George Hilliard <thirtythreeforty@gmail.com> +Tyler Hall <tylerwhall@gmail.com> +yangyun <yangyun50@huawei.com> +Abhishek <abhi_45645@yahoo.com> diff --git a/dev-docs/extend-authors.sh b/dev-docs/extend-authors.sh new file mode 100755 index 000000000..30e7dff4b --- /dev/null +++ b/dev-docs/extend-authors.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Check if a starting tag is provided +if [ $# -eq 0 ]; then + echo "Usage: $0 <starting_tag>" + echo "Example: $0 fuse-3.16.2" + exit 1 +fi + +START_TAG=$1 + +# Extract email addresses from git log +git_emails=$(git log ${START_TAG}..HEAD --format='<%aE>' | sort -u | sed 's/^<//;s/>$//') + +# Extract email addresses from AUTHORS file +authors_emails=$(grep -oP '(?<=<)[^>]+' AUTHORS | sort -u) + +# Find new email addresses (in git_emails but not in authors_emails) +# -1 suppresses lines unique to AUTHORS, -3 suppresses lines common to both +# Result: only lines unique to git_emails (i.e., new authors) +new_emails=$(comm -1 -3 <(echo "$authors_emails") <(echo "$git_emails")) + +# If there are new email addresses, add corresponding authors to the AUTHORS file +if [ -n "$new_emails" ]; then + echo -e "\nNew authors to be added:" + echo -e "\n# New authors since ${START_TAG}" >> AUTHORS + for email in $new_emails; do + author=$(git log -1 --format='%aN <%aE>' --author="$email") + echo "$author" + echo "$author" >> AUTHORS + done + echo "AUTHORS file has been updated." +else + echo "No new authors found since ${START_TAG}." +fi diff --git a/dev-docs/release-process.md b/dev-docs/release-process.md index c466d947a..fe4c225d1 100644 --- a/dev-docs/release-process.md +++ b/dev-docs/release-process.md @@ -10,7 +10,7 @@ Release Process * Create signing key for the next release: `P=fuse-<A.B+1> signify-openbsd -G -n -p signify/$P.pub -s signify/$P.sec; git add signify/$P.pub` * Expire old release signing keys (keep one around just in case) -* Update authors: `git log --all --pretty="format:%an <%aE>" | sort -u >> AUTHORS` +* To update authors run : dev-docs/extend-authors.sh * `git commit --all -m "Released $TAG"` * `git tag $TAG` * Build tarball, `./make_release_tarball.sh` From 4d70636c8821d9d0a69c43d3a13b707e174bf823 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 2 Jan 2025 21:36:13 +0100 Subject: [PATCH 021/105] .github/workflows/codespell.yml: checkpatch.pl needs to be skipped checkpatch.pl has a list of mispelled words and the codespell test fails on that. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .github/workflows/codespell.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 045bb207f..3b771c1ea 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -24,3 +24,5 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Codespell uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630 # v2.1 + with: + skip: checkpatch.pl From 82cb5b7181cee04b354cabedda7094976bbd7e7e Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 2 Jan 2025 21:38:30 +0100 Subject: [PATCH 022/105] Fix the checkpatch.pl workflow - The MAINTAINERS test is not valid for libfuse. - Correct the base commit Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .github/workflows/checkpatch.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml index 9a3cd1b7f..3f0eb9f07 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -18,5 +18,14 @@ jobs: - name: Run checkpatch.pl run: | git fetch origin ${{ github.base_ref }} - base_commit=$(git merge-base FETCH_HEAD ${{ github.event.pull_request.head.sha }}) - ./checkpatch.pl --no-tree -g $base_commit + base_commit=$(git merge-base origin/${{ github.base_ref }} HEAD) + echo "Base commit: $base_commit" + echo "Running checkpatch.pl on all commits in the PR:" + git rev-list --reverse $base_commit..HEAD | while read commit; do + subject=$(git log -1 --format=%s $commit) + echo "Checking commit: $commit - $subject" + if ! ./checkpatch.pl --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT -g $commit; then + echo "checkpatch.pl found issues in commit $commit - $subject" + exit 1 + fi + done From 8899806d836e45d3fa4b97c1cb30dc2bfee90e91 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 2 Jan 2025 22:45:43 +0100 Subject: [PATCH 023/105] Remove the DCO check Actually checkpatch.pl already checks for "Signed-off-by" and also handles merge commits - no need for duplicate logic. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .github/workflows/dco.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/dco.yml diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml deleted file mode 100644 index 1a2121d63..000000000 --- a/.github/workflows/dco.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: DCO Check (Developer Certificate of Origin) - -on: - pull_request: - types: [opened, synchronize, reopened] - -jobs: - check-dco: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 - - name: Check DCO - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - run: | - echo "Checking DCO for PR #${{ github.event.pull_request.number }}" - commits=$(gh pr view ${{ github.event.pull_request.number }} --json commits --jq '.commits[].oid') - for commit in $commits - do - if ! git log --format='%B' -n 1 $commit | grep -q "Signed-off-by:"; then - echo "Commit $commit is missing Signed-off-by line" - exit 1 - fi - done - echo "All commits have Signed-off-by lines" From ada5f158064505f8c1870b0ab046e6f8e45c6475 Mon Sep 17 00:00:00 2001 From: Amir Goldstein <amir73il@gmail.com> Date: Wed, 1 Jan 2025 18:16:43 +0100 Subject: [PATCH 024/105] Fix libfuse build with FUSE_USE_VERSION 30 Signed-off-by: Amir Goldstein <amir73il@gmail.com> --- include/fuse.h | 4 +++- test/test_setattr.c | 3 +++ test/test_write_cache.c | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/fuse.h b/include/fuse.h index 2fc1b6fa9..dbfa439d9 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -1013,7 +1013,9 @@ struct fuse *_fuse_new(struct fuse_args *args, #if FUSE_USE_VERSION == 30 struct fuse *_fuse_new_30(struct fuse_args *args, const struct fuse_operations *op, - size_t op_size, void *user_data); + size_t op_size, + struct libfuse_version *version, + void *user_data); static inline struct fuse * fuse_new(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, diff --git a/test/test_setattr.c b/test/test_setattr.c index 8575203d1..ac552644c 100644 --- a/test/test_setattr.c +++ b/test/test_setattr.c @@ -9,6 +9,9 @@ #define FUSE_USE_VERSION 30 +/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */ +#include <fuse.h> + #include <fuse_config.h> #include <fuse_lowlevel.h> #include <stdio.h> diff --git a/test/test_write_cache.c b/test/test_write_cache.c index d3c7af086..0c8fa7e8b 100644 --- a/test/test_write_cache.c +++ b/test/test_write_cache.c @@ -9,6 +9,9 @@ #define FUSE_USE_VERSION 30 +/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */ +#include <fuse.h> + #include <fuse_config.h> #include <fuse_lowlevel.h> #include <stdio.h> From 92cfa4088a58815ecb99b40c396c253aa7257990 Mon Sep 17 00:00:00 2001 From: Amir Goldstein <amir73il@gmail.com> Date: Fri, 3 Jan 2025 12:57:15 +0100 Subject: [PATCH 025/105] Fix build of memfs_ll without manual meson reconfigure After pulling latest code, memfs_ll build would fail because it builds with C++11. Changing the default of cpp_std in meson.build is not enough to fix this problem even if user runs 'meson setup --reconfigure'. I had to run 'meson setup -Dcpp_std= --reconfigure' to fix the build as mentioned in this meson issue: https://github.com/mesonbuild/meson/issues/8062#issuecomment-1568249672 Signed-off-by: Amir Goldstein <amir73il@gmail.com> --- example/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/example/meson.build b/example/meson.build index 5c02cc1f3..b2e896cc4 100644 --- a/example/meson.build +++ b/example/meson.build @@ -37,6 +37,7 @@ if not platform.endswith('bsd') and platform != 'dragonfly' and add_languages('c install: false) executable('memfs_ll', 'memfs_ll.cc', dependencies: [ thread_dep, libfuse_dep ], + cpp_args : '-std=c++17', install: false) endif From c105b8fbfad6398b064f547e3ad193af24e53048 Mon Sep 17 00:00:00 2001 From: Amir Goldstein <amir73il@gmail.com> Date: Thu, 2 Jan 2025 20:25:13 +0100 Subject: [PATCH 026/105] example/passthrough: Enable testing of readdirplus without fill offsets passthrough example supports the --plus command line argument to reply to readdirplus with fill_dir_plus and unspecified (0) fill offsets. As explained in this comment: https://github.com/libfuse/libfuse/pull/896#issuecomment-1978917041 passthrough example needs a few more changes to be able to test commit dd95d13a ("fix readdirplus when filler is called with zero offset (#896)) With the changes in this commit, readdirplus without fill offsets can be tested to verify the readdirplus fix above with command line: passthrough --plus -o auto_cache,modules=subdir,subdir=/src /mnt Signed-off-by: Amir Goldstein <amir73il@gmail.com> --- example/passthrough.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/example/passthrough.c b/example/passthrough.c index 30a8ad51c..cc93f53ee 100644 --- a/example/passthrough.c +++ b/example/passthrough.c @@ -73,9 +73,11 @@ static void *xmp_init(struct fuse_conn_info *conn, the cache of the associated inode - resulting in an incorrect st_nlink value being reported for any remaining hardlinks to this inode. */ - cfg->entry_timeout = 0; - cfg->attr_timeout = 0; - cfg->negative_timeout = 0; + if (!cfg->auto_cache) { + cfg->entry_timeout = 0; + cfg->attr_timeout = 0; + cfg->negative_timeout = 0; + } return NULL; } @@ -134,9 +136,14 @@ static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, while ((de = readdir(dp)) != NULL) { struct stat st; - memset(&st, 0, sizeof(st)); - st.st_ino = de->d_ino; - st.st_mode = de->d_type << 12; + if (fill_dir_plus) { + fstatat(dirfd(dp), de->d_name, &st, + AT_SYMLINK_NOFOLLOW); + } else { + memset(&st, 0, sizeof(st)); + st.st_ino = de->d_ino; + st.st_mode = de->d_type << 12; + } if (filler(buf, de->d_name, &st, 0, fill_dir_plus)) break; } From e18535657101f79ab742d81c567bb03f108acae0 Mon Sep 17 00:00:00 2001 From: Amir Goldstein <amir73il@gmail.com> Date: Thu, 2 Jan 2025 21:14:01 +0100 Subject: [PATCH 027/105] Fix junk readdirplus results when filesystem not filling stat info Commit dd95d13a ("fix readdirplus when filler is called with zero offset (#896)) broke readdirplus with passthrough example command: passthrough -o auto_cache,modules=subdir,subdir=/src /mnt The /src directory looks like this: ~# ls -l /src total 0 drwx------ 3 root root 60 Jan 2 17:51 testdir And the fuse directory looks like this: ~# ls -l /mnt total 0 d--------- 0 root root 0 Jan 1 1970 testdir Because readdir_fill_from_list() ignores the fact that filesystem did not pass the FUSE_FILL_DIR_PLUS flag with valid stat info. Signed-off-by: Amir Goldstein <amir73il@gmail.com> --- lib/fuse.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/fuse.c b/lib/fuse.c index a1537afd1..aa5c61104 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -163,6 +163,7 @@ struct node_lru { struct fuse_direntry { struct stat stat; + enum fuse_fill_dir_flags flags; char *name; struct fuse_direntry *next; }; @@ -3437,7 +3438,7 @@ static int extend_contents(struct fuse_dh *dh, unsigned minsize) } static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name, - struct stat *st) + struct stat *st, enum fuse_fill_dir_flags flags) { struct fuse_direntry *de; @@ -3452,6 +3453,7 @@ static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name, free(de); return -1; } + de->flags = flags; de->stat = *st; de->next = NULL; @@ -3529,7 +3531,7 @@ static int fill_dir(void *dh_, const char *name, const struct stat *statp, } else { dh->filled = 1; - if (fuse_add_direntry_to_dh(dh, name, &stbuf) == -1) + if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1) return 1; } return 0; @@ -3607,7 +3609,7 @@ static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp, } else { dh->filled = 1; - if (fuse_add_direntry_to_dh(dh, name, &e.attr) == -1) + if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1) return 1; } @@ -3695,7 +3697,8 @@ static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh, .attr = de->stat, }; - if (!is_dot_or_dotdot(de->name)) { + if (de->flags & FUSE_FILL_DIR_PLUS && + !is_dot_or_dotdot(de->name)) { res = do_lookup(dh->fuse, dh->nodeid, de->name, &e); if (res) { From 818e29b467e73a68dc01d11fef747cbcd7b59378 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 4 Jan 2025 16:08:30 +0100 Subject: [PATCH 028/105] Fix fuse_main_real symbols Commit 58f85bfa9b7d ("Add in the libfuse version a program...") forgot to add a fuse_main_real function for libfuse compilations that are not symboled. That is now added in compat.c. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- lib/compat.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/compat.c b/lib/compat.c index bab3f88c6..ce8a93399 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -17,12 +17,15 @@ #include "libfuse_config.h" +#include <stddef.h> + struct fuse_args; struct fuse_cmdline_opts; struct fuse_cmdline_opts; struct fuse_session; struct fuse_custom_io; - +struct fuse_operations; +struct fuse_lowlevel_ops; /** * Compatibility ABI symbol for systems that do not support version symboling @@ -36,7 +39,7 @@ struct fuse_custom_io; #undef fuse_parse_cmdline #endif int fuse_parse_cmdline_30(struct fuse_args *args, - struct fuse_cmdline_opts *opts); + struct fuse_cmdline_opts *opts); int fuse_parse_cmdline(struct fuse_args *args, struct fuse_cmdline_opts *opts); int fuse_parse_cmdline(struct fuse_args *args, @@ -55,6 +58,30 @@ int fuse_session_custom_io(struct fuse_session *se, { return fuse_session_custom_io_30(se, io, fd); } + +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + return fuse_main_real_30(argc, argv, op, op_size, user_data); +} + +struct fuse_session *fuse_session_new_30(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); +struct fuse_session *fuse_session_new(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); +struct fuse_session *fuse_session_new(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata) +{ + return fuse_session_new_30(args, op, op_size, userdata); +} + #endif From 0dd19282594430fe2e2b3aa7f2def466675dcadf Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 4 Jan 2025 20:29:18 +0100 Subject: [PATCH 029/105] Avoid global declarion of internal functions that are new in 3.17 _fuse_new() is not supposed to be called by external users outside of internal functions or static inlined functions. This also removes several functions from lib/fuse_versionscript which where added and exported by commit 58f85bfa9b7d ("Add in the libfuse version a program...) as these are libfuse internal only. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- include/fuse.h | 25 ++++++++++++++++--------- include/fuse_lowlevel.h | 26 +++++++------------------- lib/fuse.c | 6 ++++++ lib/fuse_lowlevel.c | 13 +++++++++---- lib/fuse_versionscript | 4 ++-- lib/helper.c | 4 ++++ 6 files changed, 44 insertions(+), 34 deletions(-) diff --git a/include/fuse.h b/include/fuse.h index dbfa439d9..c3add843a 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -76,7 +76,7 @@ enum fuse_fill_dir_flags { * stream. It does not need to be the actual physical position. A * value of zero is reserved to indicate that seeking in directories * is not supported. - * + * * @param buf the buffer passed to the readdir() operation * @param name the file name of the directory entry * @param stbuf file attributes, can be NULL @@ -192,7 +192,7 @@ struct fuse_config { * have to guarantee uniqueness, however some applications * rely on this value being unique for the whole filesystem. * - * Note that this does *not* affect the inode that libfuse + * Note that this does *not* affect the inode that libfuse * and the kernel use internally (also called the "nodeid"). */ int32_t use_ino; @@ -522,9 +522,9 @@ struct fuse_operations { * * Flush is called on each close() of a file descriptor, as opposed to * release which is called on the close of the last file descriptor for - * a file. Under Linux, errors returned by flush() will be passed to + * a file. Under Linux, errors returned by flush() will be passed to * userspace as errors from close(), so flush() is a good place to write - * back any cached dirty data. However, many applications ignore errors + * back any cached dirty data. However, many applications ignore errors * on close(), and on non-Linux systems, close() may succeed even if flush() * returns an error. For these reasons, filesystems should not assume * that errors returned by flush will ever be noticed or even @@ -978,11 +978,6 @@ fuse_main(int argc, char *argv[], const struct fuse_operations *op, */ void fuse_lib_help(struct fuse_args *args); -struct fuse *_fuse_new(struct fuse_args *args, - const struct fuse_operations *op, - size_t op_size, struct libfuse_version *version, - void *user_data); - /** * Create a new FUSE filesystem. * @@ -1021,6 +1016,12 @@ fuse_new(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, void *user_data) { + /* not declared globally, to restrict usage of this function */ + struct fuse *_fuse_new(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, + void *user_data); + struct libfuse_version version = { .major = FUSE_MAJOR_VERSION, .minor = FUSE_MINOR_VERSION, @@ -1044,6 +1045,12 @@ fuse_new(struct fuse_args *args, .padding = 0 }; + /* not declared globally, to restrict usage of this function */ + struct fuse *_fuse_new(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, + void *user_data); + return _fuse_new(args, op, op_size, &version, user_data); } #else /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 3d398dec5..0fa20390d 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -2045,26 +2045,8 @@ int fuse_parse_cmdline_312(struct fuse_args *args, #endif #endif -/* - * This should mostly not be called directly, but instead the fuse_session_new() - * macro should be used, which fills in the libfuse version compilation - * is done against automatically. - */ -struct fuse_session *_fuse_session_new_317(struct fuse_args *args, - const struct fuse_lowlevel_ops *op, - size_t op_size, - struct libfuse_version *version, - void *userdata); - /* Do not call this directly, but only through fuse_session_new() */ -#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) -struct fuse_session * -_fuse_session_new(struct fuse_args *args, - const struct fuse_lowlevel_ops *op, - size_t op_size, - struct libfuse_version *version, - void *userdata); -#else +#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) struct fuse_session * _fuse_session_new_317(struct fuse_args *args, const struct fuse_lowlevel_ops *op, @@ -2116,6 +2098,12 @@ fuse_session_new(struct fuse_args *args, .padding = 0 }; + /* not declared globally, to restrict usage of this function */ + struct fuse_session *_fuse_session_new( + struct fuse_args *args, const struct fuse_lowlevel_ops *op, + size_t op_size, struct libfuse_version *version, + void *userdata); + return _fuse_session_new(args, op, op_size, &version, userdata); } diff --git a/lib/fuse.c b/lib/fuse.c index aa5c61104..8fbc035ba 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -5009,6 +5009,12 @@ struct fuse *_fuse_new_317(struct fuse_args *args, f->conf.readdir_ino = 1; #endif + /* not declared globally, to restrict usage of this function */ + struct fuse_session *_fuse_session_new( + struct fuse_args *args, const struct fuse_lowlevel_ops *op, + size_t op_size, struct libfuse_version *version, + void *userdata); + f->se = _fuse_session_new(args, &llop, sizeof(llop), version, f); if (f->se == NULL) goto out_free_fs; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index d84c6781e..c993860cd 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3248,10 +3248,15 @@ int fuse_session_receive_buf_internal(struct fuse_session *se, FUSE_SYMVER("_fuse_session_new_317", "_fuse_session_new@@FUSE_3.17") struct fuse_session *_fuse_session_new_317(struct fuse_args *args, - const struct fuse_lowlevel_ops *op, - size_t op_size, - struct libfuse_version *version, - void *userdata) + const struct fuse_lowlevel_ops *op, + size_t op_size, + struct libfuse_version *version, + void *userdata); +struct fuse_session *_fuse_session_new_317(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, + struct libfuse_version *version, + void *userdata) { int err; struct fuse_session *se; diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index 14cbca142..f20e2b43e 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -189,11 +189,11 @@ FUSE_3.12 { FUSE_3.17 { global: +#if !defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS) _fuse_session_new_317; - _fuse_new; - _fuse_new_30; _fuse_new_317; fuse_main_real_317; +#endif fuse_passthrough_open; fuse_passthrough_close; fuse_session_custom_io_30; diff --git a/lib/helper.c b/lib/helper.c index e84c857ce..2794e66bb 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -344,6 +344,10 @@ int fuse_main_real_317(int argc, char *argv[], const struct fuse_operations *op, goto out1; } + struct fuse *_fuse_new(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, + void *user_data); fuse = _fuse_new(&args, op, op_size, version, user_data); if (fuse == NULL) { res = 3; From 0cab87aeff36503a74222e181d5206b2c77c9bb9 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 4 Jan 2025 22:06:31 +0100 Subject: [PATCH 030/105] checkpatch: Disable warning for externs in C files We do actually need these, at least for compat.c. Also disable git commit id warnings, these are doing more harm than good (for example trigger long line warnings when fulfilled). Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .github/workflows/checkpatch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml index 3f0eb9f07..91db8f2e3 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -24,7 +24,7 @@ jobs: git rev-list --reverse $base_commit..HEAD | while read commit; do subject=$(git log -1 --format=%s $commit) echo "Checking commit: $commit - $subject" - if ! ./checkpatch.pl --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT -g $commit; then + if ! ./checkpatch.pl --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID -g $commit; then echo "checkpatch.pl found issues in commit $commit - $subject" exit 1 fi From 1d134a0ccf8fe95ceb1ea3cdd723cb30c5cfcdf1 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 4 Jan 2025 23:18:14 +0100 Subject: [PATCH 031/105] Make fuse_main_real() not symboled Addresses https://github.com/libfuse/libfuse/issues/1092 We actually don't need to make fuse_main_real() symboled, as it is not part of the official API. The inlined function now always calls into fuse_main_real_317 and the compat ABI function (which should also be available for dlopen/dlsym) is now always compiled, independent if the compiler/linker support versioned symbols. Additionally, fuse_main_real() is also declared as inlined function and a warning message is created when that function is called. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- include/fuse.h | 41 +++++++++++++++++++++++++++++------------ lib/compat.c | 23 +++++++++++------------ lib/fuse_versionscript | 2 +- lib/helper.c | 8 ++++---- 4 files changed, 45 insertions(+), 29 deletions(-) diff --git a/include/fuse.h b/include/fuse.h index c3add843a..969ed9785 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -877,21 +877,32 @@ struct fuse_context { mode_t umask; }; -#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) /** * The real main function * * Do not call this directly, use fuse_main() */ -int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, - size_t op_size, struct libfuse_version *version, - void *user_data); -#else -int fuse_main_real_317(int argc, char *argv[], const struct fuse_operations *op, - size_t op_size, struct libfuse_version *version, void *user_data); -#define fuse_main_real(argc, argv, op, op_size, version, user_data) \ - fuse_main_real_317(argc, argv, op, op_size, version, user_data); -#endif +static inline int fuse_main_real(int argc, char *argv[], + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 }; + + fuse_log(FUSE_LOG_ERR, + "%s is a libfuse internal function, please use fuse_main()\n", + __func__); + + /* not declared globally, to restrict usage of this function */ + int fuse_main_real_317(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, + void *user_data); + + return fuse_main_real_317(argc, argv, op, op_size, &version, user_data); +} /** * Main function of FUSE. @@ -957,8 +968,14 @@ fuse_main(int argc, char *argv[], const struct fuse_operations *op, .hotfix = FUSE_HOTFIX_VERSION, .padding = 0 }; - return fuse_main_real(argc, argv, op, sizeof(*(op)), &version, - user_data); + + /* not declared globally, to restrict usage of this function */ + int fuse_main_real_317(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, + void *user_data); + return fuse_main_real_317(argc, argv, op, sizeof(*(op)), &version, + user_data); } /* ----------------------------------------------------------- * diff --git a/lib/compat.c b/lib/compat.c index ce8a93399..685229920 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -18,6 +18,7 @@ #include "libfuse_config.h" #include <stddef.h> +#include <stdint.h> struct fuse_args; struct fuse_cmdline_opts; @@ -59,16 +60,6 @@ int fuse_session_custom_io(struct fuse_session *se, return fuse_session_custom_io_30(se, io, fd); } -int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, - size_t op_size, void *user_data); -int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, - size_t op_size, void *user_data); -int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, - size_t op_size, void *user_data) -{ - return fuse_main_real_30(argc, argv, op, op_size, user_data); -} - struct fuse_session *fuse_session_new_30(struct fuse_args *args, const struct fuse_lowlevel_ops *op, size_t op_size, void *userdata); @@ -82,6 +73,14 @@ struct fuse_session *fuse_session_new(struct fuse_args *args, return fuse_session_new_30(args, op, op_size, userdata); } -#endif - +#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + return fuse_main_real_30(argc, argv, op, op_size, user_data); +} diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index f20e2b43e..78df3f029 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -189,10 +189,10 @@ FUSE_3.12 { FUSE_3.17 { global: + fuse_main_real_317; #if !defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS) _fuse_session_new_317; _fuse_new_317; - fuse_main_real_317; #endif fuse_passthrough_open; fuse_passthrough_close; diff --git a/lib/helper.c b/lib/helper.c index 2794e66bb..660f029cd 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -304,9 +304,10 @@ int fuse_daemonize(int foreground) return 0; } +/* Not symboled, as not part of the official API */ int fuse_main_real_317(int argc, char *argv[], const struct fuse_operations *op, - size_t op_size, struct libfuse_version *version, void *user_data); -FUSE_SYMVER("fuse_main_real_317", "fuse_main_real@@FUSE_3.17") + size_t op_size, struct libfuse_version *version, + void *user_data); int fuse_main_real_317(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data) { @@ -400,14 +401,13 @@ int fuse_main_real_317(int argc, char *argv[], const struct fuse_operations *op, return res; } +/* Not symboled, as not part of the official API */ int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, void *user_data); -FUSE_SYMVER("fuse_main_real_30", "fuse_main_real@FUSE_3.0") int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, void *user_data) { struct libfuse_version version = { 0 }; - return fuse_main_real_317(argc, argv, op, op_size, &version, user_data); } From d7246542dbe1ece84b88013843dc7a9bb9c81b6f Mon Sep 17 00:00:00 2001 From: Zegang <zegang.luo@qq.com> Date: Wed, 8 Jan 2025 14:50:30 +0800 Subject: [PATCH 032/105] Identify the FD hold by parent process Issue: There is no directly way to get the FD hold by parent process which asked do fuse mount. Use Case: For auto_unmount case, identify the FD can easy to close the FD and make automatically unmount manually and explicitly. The FD[1] can be got via getenv(FUSE_COMMFD2_ENV). One potential use case is to satisfy FD-Leak checks. Solution: Add an extra env _FUSE_COMMFD2 to store the FD. This will provide a easy way to get the FD via FUSE_COMMFD2_ENV. Signed-off-by: Zegang Luo <zegang.luo@qq.com> --- lib/mount.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/mount.c b/lib/mount.c index aedd9b92e..7dd727ce2 100644 --- a/lib/mount.c +++ b/lib/mount.c @@ -48,6 +48,7 @@ #define FUSERMOUNT_PROG "fusermount3" #define FUSE_COMMFD_ENV "_FUSE_COMMFD" +#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2" #ifndef MS_DIRSYNC #define MS_DIRSYNC 128 @@ -367,6 +368,14 @@ static int setup_auto_unmount(const char *mountpoint, int quiet) char arg_fd_entry[30]; snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]); setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1); + /* + * This helps to identify the FD hold by parent process. + * In auto-unmount case, parent process can close this FD explicitly to do unmount. + * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV). + * One potential use case is to satisfy FD-Leak checks. + */ + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]); + setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1); char const *const argv[] = { FUSERMOUNT_PROG, @@ -431,6 +440,14 @@ static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, char arg_fd_entry[30]; snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]); setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1); + /* + * This helps to identify the FD hold by parent process. + * In auto-unmount case, parent process can close this FD explicitly to do unmount. + * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV). + * One potential use case is to satisfy FD-Leak checks. + */ + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]); + setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1); char const *const argv[] = { FUSERMOUNT_PROG, From e57c6fd52b6bf83588d814880548fa4e281ca15c Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 13 Jan 2025 15:50:19 +0100 Subject: [PATCH 033/105] Update doxygen/comments for fuse_reply_open/fuse_reply_create Comments for fuse_reply_open and fuse_reply_create and with that doxygen had not been updated for parallel_direct_writes and others. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- include/fuse_lowlevel.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 0fa20390d..f8213cae1 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -1381,7 +1381,8 @@ int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e); * Reply with a directory entry and open parameters * * currently the following members of 'fi' are used: - * fh, direct_io, keep_cache + * fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, + * parallel_direct_writes * * Possible requests: * create @@ -1440,7 +1441,8 @@ int fuse_passthrough_close(fuse_req_t req, int backing_id); * Reply with open parameters * * currently the following members of 'fi' are used: - * fh, direct_io, keep_cache + * fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, + * parallel_direct_writes, * * Possible requests: * open, opendir From efb2b9bd8795409b93388e0dcc496e1cf1357ed3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:40:13 +0000 Subject: [PATCH 034/105] build(deps): bump github/codeql-action from 3.28.0 to 3.28.1 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.0 to 3.28.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/48ab28a6f5dbc2a99bf1e0131198dd8f1df78169...b6a472f63d85b9c78a3ac5e89422239fc15e9b3c) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b4c6fc175..9c4c4d91e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -78,7 +78,7 @@ jobs: meson compile -C build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 with: category: "/language:${{matrix.language}}" From ec520e420082e3939d15458acd76708d05d79068 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Fri, 24 Jan 2025 11:15:41 +0100 Subject: [PATCH 035/105] codespell: Add 're-used' and 're-using' to the ignore list These seem to be fine and exist in the code and also seem to be common English (there are debates which spelling is right). Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .codespellrc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.codespellrc b/.codespellrc index 6bf9cccce..cbd941f1a 100644 --- a/.codespellrc +++ b/.codespellrc @@ -8,4 +8,6 @@ skip = .git,*.pdf,*.svg,AUTHORS # - alse: used in regex # - siz: wanted short # - fiter: variable -ignore-words-list = alse,siz,fiter +# - re-used: intentional hyphenation +# - re-using: intentional hyphenation +ignore-words-list = alse,siz,fiter,re-used,re-using From 33e53dc059944001bee77dc8861e2df70672d7d1 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Fri, 24 Jan 2025 11:28:52 +0100 Subject: [PATCH 036/105] checkpatch: Ignore ENOSYS_SYSCALL This ENOSYS is the fuse protocol return code to tell fuse client/kernel that an operation is not supported, warning on it is not right. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .github/workflows/checkpatch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml index 91db8f2e3..1adada4c5 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -24,7 +24,7 @@ jobs: git rev-list --reverse $base_commit..HEAD | while read commit; do subject=$(git log -1 --format=%s $commit) echo "Checking commit: $commit - $subject" - if ! ./checkpatch.pl --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID -g $commit; then + if ! ./checkpatch.pl --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL -g $commit; then echo "checkpatch.pl found issues in commit $commit - $subject" exit 1 fi From b64f63aba4b9af99120d670656f1c76e3d8cb855 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Fri, 24 Jan 2025 11:58:44 +0100 Subject: [PATCH 037/105] abidiff: Don't warn about added symbols So far we only want it to warn about changed symbols. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .github/workflows/abicheck.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/abicheck.yml b/.github/workflows/abicheck.yml index c75d912cf..526fb4296 100644 --- a/.github/workflows/abicheck.yml +++ b/.github/workflows/abicheck.yml @@ -57,7 +57,8 @@ jobs: meson compile -C build - name: Run abidiff - run: abidiff + run: abidiff + --no-added-syms --headers-dir1 previous/include/ --headers-dir2 current/include/ previous/build/lib/libfuse3.so From fa28806932ff118d2da5b874a53cf64a85899300 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 27 Jan 2025 21:33:43 +0100 Subject: [PATCH 038/105] lib: Set thread names Main worker threads: fuse_worker high level clean up threads: "fuse_prune_nodes" Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- lib/fuse.c | 4 ++++ lib/fuse_loop_mt.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/fuse.c b/lib/fuse.c index 8fbc035ba..5d57d2428 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -9,6 +9,8 @@ See the file COPYING.LIB */ +#define _GNU_SOURCE + #include "fuse_config.h" #include "fuse_i.h" #include "fuse_lowlevel.h" @@ -4890,6 +4892,8 @@ static void *fuse_prune_nodes(void *fuse) struct fuse *f = fuse; int sleep_time; + pthread_setname_np(pthread_self(), "fuse_prune_nodes"); + while(1) { sleep_time = fuse_clean_cache(f); sleep(sleep_time); diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index 0e79b499a..95316f7fd 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -8,6 +8,8 @@ See the file COPYING.LIB. */ +#define _GNU_SOURCE + #include "fuse_config.h" #include "fuse_lowlevel.h" #include "fuse_misc.h" @@ -130,6 +132,8 @@ static void *fuse_do_work(void *data) struct fuse_worker *w = (struct fuse_worker *) data; struct fuse_mt *mt = w->mt; + pthread_setname_np(pthread_self(), "fuse_worker"); + while (!fuse_session_exited(mt->se)) { int isforget = 0; int res; From d953462c77b569763dc68f6486a898b1a6658b9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:28:21 +0000 Subject: [PATCH 039/105] build(deps): bump github/codeql-action from 3.28.1 to 3.28.6 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.1 to 3.28.6. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/b6a472f63d85b9c78a3ac5e89422239fc15e9b3c...17a820bf2e43b47be2c72b39cc905417bc1ab6d0) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9c4c4d91e..ef3126c52 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 + uses: github/codeql-action/init@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 # v3.28.6 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -78,7 +78,7 @@ jobs: meson compile -C build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 + uses: github/codeql-action/analyze@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 # v3.28.6 with: category: "/language:${{matrix.language}}" From 1d373c61aef0bf514f7259710812410ccae2fe74 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Wed, 29 Jan 2025 10:46:18 +0100 Subject: [PATCH 040/105] github actions: Add an include-what-you-need check Probably lots of issues right now, so let's fix it step by step by only checking modified files - new PRs should fix their modified files. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .github/workflows/iwyi-check.yml | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/iwyi-check.yml diff --git a/.github/workflows/iwyi-check.yml b/.github/workflows/iwyi-check.yml new file mode 100644 index 000000000..3751f35de --- /dev/null +++ b/.github/workflows/iwyi-check.yml @@ -0,0 +1,51 @@ +# check for uneeded header includes of modified files +# False positives can be avoided with +# #include "some_include.h" // IWYU pragma: keep + +name: IWYU Check + +on: + pull_request: + branches: [ main ] + paths: + - '**.cpp' + - '**.hpp' + - '**.c' + - '**.h' + +jobs: + iwyu-check: + name: Include What You Use Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Install IWYU + run: | + sudo apt-get update + sudo apt-get install -y iwyu + + - name: Get changed files + id: changed-files + run: | + git fetch origin ${{ github.base_ref }} + base_commit=$(git merge-base FETCH_HEAD ${{ github.event.pull_request.head.sha }}) + changed_files=$(git diff --name-only $base_commit HEAD | grep -E '\.(cpp|hpp|c|h)$' || true) + echo "files=$changed_files" >> $GITHUB_OUTPUT + + - name: Run IWYU checks on changed files + if: steps.changed-files.outputs.files != '' + run: | + echo "${{ steps.changed-files.outputs.files }}" | while read -r file; do + if [ -f "$file" ]; then + echo "Checking $file..." + iwyu -Xiwyu --mapping_file=iwyu.imp "$file" 2>&1 || true + fi + done | tee iwyu_output.txt + if grep -q "should add these lines:" iwyu_output.txt || \ + grep -q "should remove these lines:" iwyu_output.txt; then + echo "IWYU checks failed. Please fix the includes in the affected files." + exit 1 + fi From dce6f59b106bccaba63bd1386138ab539864be89 Mon Sep 17 00:00:00 2001 From: Luis Henriques <luis@igalia.com> Date: Fri, 31 Jan 2025 15:26:20 +0000 Subject: [PATCH 041/105] fuse_lowlevel.c: fix possible 64 bits value truncation Because conn.want_ext is a uint64_t, copying it into a uint32_t may result in truncating it's value. This patch fixes a bug in do_init() where the 32 bits copy is again converted into a 64 bits value, because it will be used in convert_to_conn_want_ext(). Signed-off-by: Luis Henriques <luis@igalia.com> --- lib/fuse_lowlevel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index c993860cd..36baaa65b 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2178,7 +2178,7 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) se->got_init = 1; if (se->op.init) { - uint32_t want_ext_default = se->conn.want_ext; + uint64_t want_ext_default = se->conn.want_ext; int rc; // Apply the first 32 bits of capable_ext to capable From ad6d7d548fd33ef0e88607a15a054ea0a80af669 Mon Sep 17 00:00:00 2001 From: Luis Henriques <luis@igalia.com> Date: Fri, 31 Jan 2025 16:03:54 +0000 Subject: [PATCH 042/105] fuse_lowlevel.c: drop incorrect comment from convert_to_conn_want_ext() The comment is probably a leftover from older versions, as the application of the 32 bits of conn.capable_ext to conn.capable is done before function convert_to_conn_want_ext() is invoked. Signed-off-by: Luis Henriques <luis@igalia.com> --- lib/fuse_lowlevel.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 36baaa65b..840142b27 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -1999,8 +1999,6 @@ static bool want_flags_valid(uint64_t capable, uint64_t want) /** * Get the wanted capability flags, converting from old format if necessary - * Also applies the first 32 bits of capable_ext to capable - * */ static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn, uint64_t want_ext_default) From b289b8f8ffa22d7ee684f0fff712960435f67f78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:14:55 +0000 Subject: [PATCH 043/105] build(deps): bump github/codeql-action from 3.28.6 to 3.28.8 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.6 to 3.28.8. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/17a820bf2e43b47be2c72b39cc905417bc1ab6d0...dd746615b3b9d728a6a37ca2045b68ca76d4841a) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ef3126c52..89fbe2494 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 # v3.28.6 + uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -78,7 +78,7 @@ jobs: meson compile -C build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@17a820bf2e43b47be2c72b39cc905417bc1ab6d0 # v3.28.6 + uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: category: "/language:${{matrix.language}}" From efd45ba910aa987c9d11c28c3b4d1a73265487aa Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 8 Feb 2025 14:08:32 +0100 Subject: [PATCH 044/105] Rename fuse_main_real_317 to fuse_main_real_versioned As suggested by Bill in Issue #1092, rename to _versioned so that applications using dlopen/dlvsym better understand the meaning of this function. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- include/fuse.h | 27 +++++++++++++++------------ lib/helper.c | 14 ++++++++------ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/include/fuse.h b/include/fuse.h index 969ed9785..70d0d13ab 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -896,12 +896,14 @@ static inline int fuse_main_real(int argc, char *argv[], __func__); /* not declared globally, to restrict usage of this function */ - int fuse_main_real_317(int argc, char *argv[], - const struct fuse_operations *op, size_t op_size, - struct libfuse_version *version, - void *user_data); - - return fuse_main_real_317(argc, argv, op, op_size, &version, user_data); + int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, + size_t op_size, + struct libfuse_version *version, + void *user_data); + + return fuse_main_real_versioned(argc, argv, op, op_size, &version, + user_data); } /** @@ -970,12 +972,13 @@ fuse_main(int argc, char *argv[], const struct fuse_operations *op, }; /* not declared globally, to restrict usage of this function */ - int fuse_main_real_317(int argc, char *argv[], - const struct fuse_operations *op, size_t op_size, - struct libfuse_version *version, - void *user_data); - return fuse_main_real_317(argc, argv, op, sizeof(*(op)), &version, - user_data); + int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, + size_t op_size, + struct libfuse_version *version, + void *user_data); + return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version, + user_data); } /* ----------------------------------------------------------- * diff --git a/lib/helper.c b/lib/helper.c index 660f029cd..cab5ada70 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -305,11 +305,12 @@ int fuse_daemonize(int foreground) } /* Not symboled, as not part of the official API */ -int fuse_main_real_317(int argc, char *argv[], const struct fuse_operations *op, - size_t op_size, struct libfuse_version *version, - void *user_data); -int fuse_main_real_317(int argc, char *argv[], const struct fuse_operations *op, - size_t op_size, struct libfuse_version *version, void *user_data) +int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); +int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data) { struct fuse_args args = FUSE_ARGS_INIT(argc, argv); struct fuse *fuse; @@ -408,7 +409,8 @@ int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, void *user_data) { struct libfuse_version version = { 0 }; - return fuse_main_real_317(argc, argv, op, op_size, &version, user_data); + return fuse_main_real_versioned(argc, argv, op, op_size, &version, + user_data); } void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, From 19cb78a7e9854f6a68f0ed0a97e86df8208c4e3e Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 8 Feb 2025 14:14:43 +0100 Subject: [PATCH 045/105] Make fuse_main a macro again and wrap that to fuse_main_fn As suggested by Bill in Issue #1092 make fuse_main a macro again, just in case some applications expect it to be a macro. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- include/fuse.h | 8 +++++--- lib/fuse_versionscript | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/fuse.h b/include/fuse.h index 70d0d13ab..ae8d80ee4 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -960,9 +960,9 @@ static inline int fuse_main_real(int argc, char *argv[], * * Example usage, see hello.c */ -static inline int -fuse_main(int argc, char *argv[], const struct fuse_operations *op, - void *user_data) +static inline int fuse_main_fn(int argc, char *argv[], + const struct fuse_operations *op, + void *user_data) { struct libfuse_version version = { .major = FUSE_MAJOR_VERSION, @@ -980,6 +980,8 @@ fuse_main(int argc, char *argv[], const struct fuse_operations *op, return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version, user_data); } +#define fuse_main(argc, argv, op, user_data) \ + fuse_main_fn(argc, argv, op, user_data) /* ----------------------------------------------------------- * * More detailed API * diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index 78df3f029..b31453b28 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -189,7 +189,7 @@ FUSE_3.12 { FUSE_3.17 { global: - fuse_main_real_317; + fuse_main_real_versioned; #if !defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS) _fuse_session_new_317; _fuse_new_317; From c49c518f271068f4b562c6c617b6df0550a49dff Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 8 Feb 2025 14:35:46 +0100 Subject: [PATCH 046/105] Rename to fuse_session_new_versioned Similar previous renames to fuse_main_real_versioned, but here for the low level fuse_session_new. Also remove symbol versioned as part of "fuse_session_new" as that function is not part of the official API/ABI and to allow easier access with dlopen/dlsym. Also switch back to a macro fuse_session_new, just in case some code has some expectations on that. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- include/fuse_lowlevel.h | 25 +++++++------------------ lib/compat.c | 24 ++++++++++++------------ lib/fuse.c | 6 +++--- lib/fuse_lowlevel.c | 28 ++++++++++++---------------- lib/fuse_versionscript | 2 +- 5 files changed, 35 insertions(+), 50 deletions(-) diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index f8213cae1..d0617ba35 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -2047,18 +2047,6 @@ int fuse_parse_cmdline_312(struct fuse_args *args, #endif #endif -/* Do not call this directly, but only through fuse_session_new() */ -#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) -struct fuse_session * -_fuse_session_new_317(struct fuse_args *args, - const struct fuse_lowlevel_ops *op, - size_t op_size, - struct libfuse_version *version, - void *userdata); -#define _fuse_session_new(args, op, op_size, version, userdata) \ - _fuse_session_new_317(args, op, op_size, version, userdata) -#endif - /** * Create a low level session. * @@ -2088,10 +2076,8 @@ _fuse_session_new_317(struct fuse_args *args, * @return the fuse session on success, NULL on failure **/ static inline struct fuse_session * -fuse_session_new(struct fuse_args *args, - const struct fuse_lowlevel_ops *op, - size_t op_size, - void *userdata) +fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata) { struct libfuse_version version = { .major = FUSE_MAJOR_VERSION, @@ -2101,13 +2087,16 @@ fuse_session_new(struct fuse_args *args, }; /* not declared globally, to restrict usage of this function */ - struct fuse_session *_fuse_session_new( + struct fuse_session *fuse_session_new_versioned( struct fuse_args *args, const struct fuse_lowlevel_ops *op, size_t op_size, struct libfuse_version *version, void *userdata); - return _fuse_session_new(args, op, op_size, &version, userdata); + return fuse_session_new_versioned(args, op, op_size, &version, + userdata); } +#define fuse_session_new(args, op, op_size, userdata) \ + fuse_session_new_fn(args, op, op_size, userdata) /* * This should mostly not be called directly, but instead the diff --git a/lib/compat.c b/lib/compat.c index 685229920..b98ca4b04 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -60,6 +60,18 @@ int fuse_session_custom_io(struct fuse_session *se, return fuse_session_custom_io_30(se, io, fd); } +#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ + +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + return fuse_main_real_30(argc, argv, op, op_size, user_data); +} + struct fuse_session *fuse_session_new_30(struct fuse_args *args, const struct fuse_lowlevel_ops *op, size_t op_size, void *userdata); @@ -72,15 +84,3 @@ struct fuse_session *fuse_session_new(struct fuse_args *args, { return fuse_session_new_30(args, op, op_size, userdata); } - -#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ - -int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, - size_t op_size, void *user_data); -int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, - size_t op_size, void *user_data); -int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, - size_t op_size, void *user_data) -{ - return fuse_main_real_30(argc, argv, op, op_size, user_data); -} diff --git a/lib/fuse.c b/lib/fuse.c index 5d57d2428..0b04f47d3 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -5014,12 +5014,12 @@ struct fuse *_fuse_new_317(struct fuse_args *args, #endif /* not declared globally, to restrict usage of this function */ - struct fuse_session *_fuse_session_new( + struct fuse_session *fuse_session_new_versioned( struct fuse_args *args, const struct fuse_lowlevel_ops *op, size_t op_size, struct libfuse_version *version, void *userdata); - - f->se = _fuse_session_new(args, &llop, sizeof(llop), version, f); + f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version, + f); if (f->se == NULL) goto out_free_fs; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 840142b27..e3e79d528 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3244,17 +3244,14 @@ int fuse_session_receive_buf_internal(struct fuse_session *se, return _fuse_session_receive_buf(se, buf, ch, true); } -FUSE_SYMVER("_fuse_session_new_317", "_fuse_session_new@@FUSE_3.17") -struct fuse_session *_fuse_session_new_317(struct fuse_args *args, - const struct fuse_lowlevel_ops *op, - size_t op_size, - struct libfuse_version *version, - void *userdata); -struct fuse_session *_fuse_session_new_317(struct fuse_args *args, - const struct fuse_lowlevel_ops *op, - size_t op_size, - struct libfuse_version *version, - void *userdata) +struct fuse_session * +fuse_session_new_versioned(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, size_t op_size, + struct libfuse_version *version, void *userdata); +struct fuse_session * +fuse_session_new_versioned(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, size_t op_size, + struct libfuse_version *version, void *userdata) { int err; struct fuse_session *se; @@ -3355,10 +3352,8 @@ struct fuse_session *_fuse_session_new_317(struct fuse_args *args, } struct fuse_session *fuse_session_new_30(struct fuse_args *args, - const struct fuse_lowlevel_ops *op, - size_t op_size, - void *userdata); -FUSE_SYMVER("fuse_session_new_30", "fuse_session_new@FUSE_3.0") + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); struct fuse_session *fuse_session_new_30(struct fuse_args *args, const struct fuse_lowlevel_ops *op, size_t op_size, @@ -3367,7 +3362,8 @@ struct fuse_session *fuse_session_new_30(struct fuse_args *args, /* unknown version */ struct libfuse_version version = { 0 }; - return _fuse_session_new_317(args, op, op_size, &version, userdata); + return fuse_session_new_versioned(args, op, op_size, &version, + userdata); } FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17") diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index b31453b28..2d314c051 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -190,8 +190,8 @@ FUSE_3.12 { FUSE_3.17 { global: fuse_main_real_versioned; + fuse_session_new_versioned; #if !defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS) - _fuse_session_new_317; _fuse_new_317; #endif fuse_passthrough_open; From 4f025ae22e30e15f6f4bceef7c90dd13d1eac693 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 10 Feb 2025 01:52:08 +0100 Subject: [PATCH 047/105] fuse_new: Fix non symboled call to _fuse_new_317 Acidentally the wrong non-existing function was ccalled. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- include/fuse.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fuse.h b/include/fuse.h index ae8d80ee4..ab0c04a80 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -1094,7 +1094,7 @@ fuse_new(struct fuse_args *args, .padding = 0 }; - return _fuse_new(args, op, op_size, &version, user_data); + return _fuse_new_317(args, op, op_size, &version, user_data); } #endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ #endif From 5f748b316cae99d6f610b9b0c45d106219957ce1 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 10 Feb 2025 01:50:01 +0100 Subject: [PATCH 048/105] fuse_new version fixes: Change to fuse_new_versioned Another additon for https://github.com/libfuse/libfuse/issues/1092 Use _fuse_new_versioned() instead of _fuse_new_317 and actually also remove symbol versioning for it - we don't need it. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- include/fuse.h | 43 ++++++++++-------------------------------- lib/fuse.c | 9 ++++----- lib/fuse_versionscript | 5 ++--- lib/helper.c | 4 ++-- 4 files changed, 18 insertions(+), 43 deletions(-) diff --git a/include/fuse.h b/include/fuse.h index ab0c04a80..c0857fdbe 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -1034,12 +1034,12 @@ struct fuse *_fuse_new_30(struct fuse_args *args, struct libfuse_version *version, void *user_data); static inline struct fuse * -fuse_new(struct fuse_args *args, +fuse_new_fn(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, void *user_data) { /* not declared globally, to restrict usage of this function */ - struct fuse *_fuse_new(struct fuse_args *args, + struct fuse *_fuse_new_30(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data); @@ -1053,10 +1053,9 @@ fuse_new(struct fuse_args *args, return _fuse_new_30(args, op, op_size, &version, user_data); } -#else -#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) +#else /* FUSE_USE_VERSION */ static inline struct fuse * -fuse_new(struct fuse_args *args, +fuse_new_fn(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, void *user_data) { @@ -1068,36 +1067,14 @@ fuse_new(struct fuse_args *args, }; /* not declared globally, to restrict usage of this function */ - struct fuse *_fuse_new(struct fuse_args *args, - const struct fuse_operations *op, size_t op_size, - struct libfuse_version *version, - void *user_data); - - return _fuse_new(args, op, op_size, &version, user_data); + struct fuse *_fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, struct libfuse_version *version, + void *user_data); + return _fuse_new_31(args, op, op_size, &version, user_data); } -#else /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ -struct fuse *_fuse_new_317(struct fuse_args *args, - const struct fuse_operations *op, size_t op_size, - struct libfuse_version *version, - void *private_data); -#define _fuse_new(args, op, size, version, data) \ - _fuse_new_317(args, op, size, version, data) -static inline struct fuse * -fuse_new(struct fuse_args *args, - const struct fuse_operations *op, size_t op_size, - void *user_data) -{ - struct libfuse_version version = { - .major = FUSE_MAJOR_VERSION, - .minor = FUSE_MINOR_VERSION, - .hotfix = FUSE_HOTFIX_VERSION, - .padding = 0 - }; - - return _fuse_new_317(args, op, op_size, &version, user_data); -} -#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ #endif +#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data) /** * Mount a FUSE file system. diff --git a/lib/fuse.c b/lib/fuse.c index 0b04f47d3..6c69a6897 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -4923,12 +4923,11 @@ void fuse_stop_cleanup_thread(struct fuse *f) * Not supposed to be called directly, but supposed to be called * through the fuse_new macro */ -struct fuse *_fuse_new_317(struct fuse_args *args, +struct fuse *_fuse_new_31(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data); -FUSE_SYMVER("_fuse_new_317", "_fuse_new@@FUSE_3.17") -struct fuse *_fuse_new_317(struct fuse_args *args, +struct fuse *_fuse_new_31(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data) @@ -5103,7 +5102,7 @@ struct fuse *_fuse_new_30(struct fuse_args *args, fuse_lib_help(args); return NULL; } else - return _fuse_new_317(args, op, op_size, version, user_data); + return _fuse_new_31(args, op, op_size, version, user_data); } /* ABI compat version */ @@ -5117,7 +5116,7 @@ struct fuse *fuse_new_31(struct fuse_args *args, /* unknown version */ struct libfuse_version version = { 0 }; - return _fuse_new_317(args, op, op_size, &version, user_data); + return _fuse_new_31(args, op, op_size, &version, user_data); } /* diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index 2d314c051..6c5fc83eb 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -191,9 +191,8 @@ FUSE_3.17 { global: fuse_main_real_versioned; fuse_session_new_versioned; -#if !defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS) - _fuse_new_317; -#endif + _fuse_new_30; + _fuse_new_31; fuse_passthrough_open; fuse_passthrough_close; fuse_session_custom_io_30; diff --git a/lib/helper.c b/lib/helper.c index cab5ada70..a1cf98c3e 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -346,11 +346,11 @@ int fuse_main_real_versioned(int argc, char *argv[], goto out1; } - struct fuse *_fuse_new(struct fuse_args *args, + struct fuse *_fuse_new_31(struct fuse_args *args, const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data); - fuse = _fuse_new(&args, op, op_size, version, user_data); + fuse = _fuse_new_31(&args, op, op_size, version, user_data); if (fuse == NULL) { res = 3; goto out1; From dc4c0b2594f1bce28fc84025cb2957213b4ff7f7 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 10 Feb 2025 02:45:42 +0100 Subject: [PATCH 049/105] Build fixes for -Og MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleaning... 18 files. [35/69] Compiling C object example/passthrough_ll.p/passthrough_ll.c.o ../example/passthrough_ll.c: In function ‘lo_opendir’: ../example/passthrough_ll.c:666:20: warning: ‘fd’ may be used uninitialized [-Wmaybe-uninitialized] 666 | if (fd != -1) | ^ ../example/passthrough_ll.c:637:13: note: ‘fd’ was declared here 637 | int fd; | ^~ [38/69] Compiling C object test/test_syscalls.p/test_syscalls.c.o ../test/test_syscalls.c: In function ‘test_seekdir’: ../test/test_syscalls.c:804:16: warning: ‘de’ may be used uninitialized [-Wmaybe-uninitialized] 804 | while (de) | ^~ ../test/test_syscalls.c:776:24: note: ‘de’ was declared here 776 | struct dirent *de; | ^~ These are actually valid. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- example/passthrough_ll.c | 2 +- test/test_syscalls.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index e1a36efa6..fa8abb897 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -634,7 +634,7 @@ static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi int error = ENOMEM; struct lo_data *lo = lo_data(req); struct lo_dirp *d; - int fd; + int fd = -1; d = calloc(1, sizeof(struct lo_dirp)); if (d == NULL) diff --git a/test/test_syscalls.c b/test/test_syscalls.c index 7a2389df1..26b2ff2be 100644 --- a/test/test_syscalls.c +++ b/test/test_syscalls.c @@ -773,7 +773,7 @@ static int test_seekdir(void) int i; int res; DIR *dp; - struct dirent *de; + struct dirent *de = NULL; start_test("seekdir"); res = create_dir(testdir, testdir_files); From 0c2eaff63214b61b2398f542852b958b79786e9d Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 10 Feb 2025 02:36:39 +0100 Subject: [PATCH 050/105] Change version to 3.17.1-rc0 Somehow really hard to set -rcX with meson. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- lib/meson.build | 10 +++++++--- meson.build | 25 ++++++++++++++++--------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/meson.build b/lib/meson.build index e201a5a98..6a52d06a9 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -33,9 +33,13 @@ else endif fusermount_path = join_paths(get_option('prefix'), get_option('bindir')) -libfuse = library('fuse3', libfuse_sources, version: meson.project_version(), - soversion: '4', include_directories: include_dirs, - dependencies: deps, install: true, +libfuse = library('fuse3', + libfuse_sources, + version: base_version, + soversion: '4', + include_directories: include_dirs, + dependencies: deps, + install: true, link_depends: 'fuse_versionscript', c_args: [ '-DFUSE_USE_VERSION=317', '-DFUSERMOUNT_DIR="@0@"'.format(fusermount_path) ], diff --git a/meson.build b/meson.build index f05b94fb0..e191fc62c 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,6 @@ -project('libfuse3', ['c'], version: '3.17.0', - meson_version: '>= 0.51', +project('libfuse3', ['c'], + version: '3.17.1-rc0', # Version with RC suffix + meson_version: '>= 0.51.0', default_options: [ 'buildtype=debugoptimized', 'c_std=gnu11', @@ -11,10 +12,16 @@ project('libfuse3', ['c'], version: '3.17.0', # from integers, i.e. concatenating strings instead # of splitting a string, but 'project' needs to be # the first meson.build keyword... -version_list = meson.project_version().split('.') + +# split version into base and rc +version_parts = meson.project_version().split('-') +base_version = version_parts[0] + +version_list = base_version.split('.') FUSE_MAJOR_VERSION = version_list[0] FUSE_MINOR_VERSION = version_list[1] FUSE_HOTFIX_VERSION = version_list[2] +FUSE_RC_VERSION = version_parts.length() > 1 ? version_parts[1] : '' platform = host_machine.system() if platform == 'darwin' @@ -28,12 +35,6 @@ endif cc = meson.get_compiler('c') -# -# Feature detection, only available at libfuse compilation time, -# but not for application linking to libfuse. -# -private_cfg = configuration_data() - # # Feature detection, the resulting config file is installed # with the package. @@ -46,6 +47,7 @@ public_cfg = configuration_data() public_cfg.set('FUSE_MAJOR_VERSION', FUSE_MAJOR_VERSION) public_cfg.set('FUSE_MINOR_VERSION', FUSE_MINOR_VERSION) public_cfg.set('FUSE_HOTFIX_VERSION', FUSE_HOTFIX_VERSION) +public_cfg.set('FUSE_RC_VERSION', FUSE_RC_VERSION) # Default includes when checking for presence of functions and # struct members @@ -60,6 +62,11 @@ include_default = ''' ''' args_default = [ '-D_GNU_SOURCE' ] +# +# Feature detection, only available at libfuse compilation time, +# but not for application linking to libfuse. +# +private_cfg = configuration_data() private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) # Test for presence of some functions From 74ec57c5b17c7b4de1a37a9f408a4e337388803f Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 10 Feb 2025 15:55:20 +0100 Subject: [PATCH 051/105] Prepare 3.17.1 release Update ChangeLog.rst and AUTHORS Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- AUTHORS | 4 ++++ ChangeLog.rst | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9fd3b49ac..837a62c84 100644 --- a/AUTHORS +++ b/AUTHORS @@ -259,3 +259,7 @@ George Hilliard <thirtythreeforty@gmail.com> Tyler Hall <tylerwhall@gmail.com> yangyun <yangyun50@huawei.com> Abhishek <abhi_45645@yahoo.com> + +# New authors since 666b2c3fa5159e3c72a0d08117507475117c9319 +Luis Henriques <luis@igalia.com> +Zegang <zegang.luo@qq.com> diff --git a/ChangeLog.rst b/ChangeLog.rst index 3bf401d99..658f898aa 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,5 +1,21 @@ -libfuse 3.17 (2024-01-01) -========================= +libfuse 3.17.1-rc0 (2024-02.10) +=============================== + +* Fix libfuse build with FUSE_USE_VERSION 30 +* Fix build of memfs_ll without manual meson reconfigure +* Fix junk readdirplus results when filesystem not filling stat info +* Fix conn.want_ext truncation to 32bit +* Fix some build warnings with -Og +* Fix fuse_main_real symbols +* Several changes related to functions/symbols that added in + the libfuse version in 3.17 +* Add thread names to libfuse threads +* With auto-umounts the FUSE_COMMFD2 (parent process fd is + exported to be able to silence leak checkers + + +libfuse 3.17 (2024-01-01, not officially releaesed) +================================================== * 3.11 and 3.14.2 introduced ABI incompatibilities, the ABI is restored to 3.10, .so version was increased since there were releases with From a5aa028097fb557231665292276813b85c849e86 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 13 Feb 2025 14:55:52 +0100 Subject: [PATCH 052/105] Add signify key for 3.18 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- signify/fuse-3.18.pub | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 signify/fuse-3.18.pub diff --git a/signify/fuse-3.18.pub b/signify/fuse-3.18.pub new file mode 100644 index 000000000..eeee6bf74 --- /dev/null +++ b/signify/fuse-3.18.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWS6gMnNrKp/zRSYWv13J+KwXE26vCUsbC/hVZmjQ8PA3xjixGLjodz3 From 6adee44e78b70f1708b5d129281e958a2dfca33a Mon Sep 17 00:00:00 2001 From: "Laszlo Boszormenyi (GCS)" <gcs@debian.org> Date: Sun, 16 Feb 2025 09:07:36 +0100 Subject: [PATCH 053/105] Fix build of example/memfs_ll.cc on 32 bit architectures The code uses std::min() which expects its arguments to be size_t. Two times it uses an offset declared as off_t. While both size_t and off_t are 32-bit integers, the latter is signed. On 64 bit architectures the conversation of off_t -> size_t performed automatically. On 32 bit architectures it needs a type cast. Signed-off-by: Laszlo Boszormenyi (GCS) <gcs@debian.org> --- example/memfs_ll.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index c7b966359..9898f2528 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -150,7 +150,7 @@ class Inode { void read_content(char *buf, size_t size, off_t offset) const { - size_t bytes_to_read = std::min(size, content.size() - offset); + size_t bytes_to_read = std::min(size, content.size() - (size_t)offset); std::copy(content.begin() + offset, content.begin() + offset + bytes_to_read, buf); } @@ -613,7 +613,7 @@ static void memfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, } std::vector<char> content( - std::min(size, inode->content_size() - offset)); + std::min(size, inode->content_size() - (size_t)offset)); inode->read_content(content.data(), content.size(), offset); inode->unlock(); From 88f7fb2b64a234eb14575d02db14ddd86c523c2d Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 16 Feb 2025 22:40:41 +0100 Subject: [PATCH 054/105] fusermount: Exclude UFSD from whitelist for 32-bit builds The UFSD super magic is larger than 32-bit - I don't know if truncating to 32-bit would work - we just exclude it for now. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- util/fusermount.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/fusermount.c b/util/fusermount.c index b87d8bb70..683c5496f 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -1141,7 +1141,9 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd) 0x73717368 /* SQUASHFS_MAGIC */, 0x01021994 /* TMPFS_MAGIC */, 0x24051905 /* UBIFS_SUPER_MAGIC */, +#if __SIZEOF_LONG__ > 4 0x736675005346544e /* UFSD */, +#endif 0x58465342 /* XFS_SB_MAGIC */, 0x2FC12FC1 /* ZFS_SUPER_MAGIC */, 0x858458f6 /* RAMFS_MAGIC */, From 2ae86e787dc4f6b84c0c2236cecc6d45d466b840 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 16 Feb 2025 22:56:46 +0100 Subject: [PATCH 055/105] example/hello_ll_uds: Switch to %zu and avoid 32bit build warning Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- example/hello_ll_uds.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/hello_ll_uds.c b/example/hello_ll_uds.c index a566155dc..e9cd17315 100644 --- a/example/hello_ll_uds.c +++ b/example/hello_ll_uds.c @@ -185,8 +185,8 @@ static int create_socket(const char *socket_path) { if (strnlen(socket_path, sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) { - printf("Socket path may not be longer than %lu characters\n", - sizeof(addr.sun_path) - 1); + printf("Socket path may not be longer than %zu characters\n", + sizeof(addr.sun_path) - 1); return -1; } From 65ce923a6440b2ea37e234a20e8f7d69388bcbd2 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 17 Feb 2025 19:23:44 +0100 Subject: [PATCH 056/105] tests: Skip the ioctl test for x86 cross compilation Probably some weird corner case in cross compilation, for now we ignore this. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- test/test_examples.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/test_examples.py b/test/test_examples.py index e0fb8c467..54a2f88f9 100755 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -284,6 +284,11 @@ def test_ioctl(tmpdir, output_checker): progname = pjoin(basename, 'example', 'ioctl') if not os.path.exists(progname): pytest.skip('%s not built' % os.path.basename(progname)) + + # Check if binary is 32-bit + file_output = subprocess.check_output(['file', progname]).decode() + if 'ELF 32-bit' in file_output and platform.machine() == 'x86_64': + pytest.skip('ioctl test not supported for 32-bit binary on 64-bit system') mnt_dir = str(tmpdir) testfile = pjoin(mnt_dir, 'fioc') @@ -427,6 +432,14 @@ def test_dev_auto_unmount(short_tmpdir, output_checker, intended_user): @pytest.mark.skipif(os.getuid() != 0, reason='needs to run as root') def test_cuse(output_checker): + progname = pjoin(basename, 'example', 'cuse') + if not os.path.exists(progname): + pytest.skip('%s not built' % os.path.basename(progname)) + + # Check if binary is 32-bit + file_output = subprocess.check_output(['file', progname]).decode() + if 'ELF 32-bit' in file_output and platform.machine() == 'x86_64': + pytest.skip('cuse test not supported for 32-bit binary on 64-bit system') # Valgrind warns about unknown ioctls, that's ok output_checker.register_output(r'^==([0-9]+).+unhandled ioctl.+\n' From 57880e812ea47bb088b9f60cd0f5aaff40a95a64 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sun, 16 Feb 2025 22:15:31 +0100 Subject: [PATCH 057/105] ci-build test: Add a 32-bit compilation test That was missing so far. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .github/workflows/pr-ci.yml | 7 ++++++- test/ci-build.sh | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index bec616621..ccaaf7231 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -26,7 +26,12 @@ jobs: steps: - name: Install dependencies (Ubuntu) if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install -y clang doxygen gcc gcc-10 gcc-9 valgrind + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install -y clang doxygen gcc gcc-10 gcc-9 valgrind \ + gcc-multilib g++-multilib libc6-dev-i386 \ + libpcap0.8-dev:i386 libudev-dev:i386 pkg-config:i386 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/setup-python@v5 with: diff --git a/test/ci-build.sh b/test/ci-build.sh index 4f7da4c8f..3f0ff8546 100755 --- a/test/ci-build.sh +++ b/test/ci-build.sh @@ -118,6 +118,23 @@ sanitized_build() sudo rm -fr ${PREFIX_DIR} ) +# 32-bit sanitized build +export CC=clang +export CXX=clang++ +export CFLAGS="-m32" +export CXXFLAGS="-m32" +export LDFLAGS="-m32" +export PKG_CONFIG_PATH="/usr/lib/i386-linux-gnu/pkgconfig" +TEST_WITH_VALGRIND=false +sanitized_build +unset CFLAGS +unset CXXFLAGS +unset LDFLAGS +unset PKG_CONFIG_PATH +unset TEST_WITH_VALGRIND +unset CC +unset CXX + # Sanitized build export CC=clang export CXX=clang++ From 026ebedddf6c7386723b7672929b9a3be6d731c0 Mon Sep 17 00:00:00 2001 From: Maksim Harbachou <maksim.harbachou@resilio.com> Date: Fri, 14 Feb 2025 14:20:19 +0100 Subject: [PATCH 058/105] Add comment for fuse_passthrough_open() See https://github.com/libfuse/libfuse/issues/1125 Signed-off-by: Maksim Harbachou <maksim.harbachou@resilio.com> --- include/fuse_lowlevel.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index d0617ba35..b03b37aae 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -1427,6 +1427,8 @@ int fuse_reply_readlink(fuse_req_t req, const char *link); /** * Setup passthrough backing file for open reply * + * Currently there should be only one backing id per node / backing file. + * * Possible requests: * open, opendir, create * From 1ff42ee6484de1b35e9bca8e28d36afe36b25c90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 19:28:30 +0000 Subject: [PATCH 059/105] build(deps): bump github/codeql-action from 3.28.8 to 3.28.9 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.8 to 3.28.9. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/dd746615b3b9d728a6a37ca2045b68ca76d4841a...9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 89fbe2494..cefd1fcbf 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 + uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -78,7 +78,7 @@ jobs: meson compile -C build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 + uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 with: category: "/language:${{matrix.language}}" From 2eccb37c3a0134720017390c6d7d8fc20f5a93fa Mon Sep 17 00:00:00 2001 From: Vassili Tchersky <vt+git@vbcy.org> Date: Mon, 17 Feb 2025 08:47:57 +0100 Subject: [PATCH 060/105] tests: Disable tests with TMP_FILE on FreeBSD Not supported yet on this platform. See https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=283179 Signed-off-by: Vassili Tchersky <vt+git@vbcy.org> --- test/test_syscalls.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_syscalls.c b/test/test_syscalls.c index 26b2ff2be..97906a3e6 100644 --- a/test/test_syscalls.c +++ b/test/test_syscalls.c @@ -1957,6 +1957,7 @@ static int do_test_create_ro_dir(int flags, const char *flags_str) return 0; } +#ifndef __FreeBSD__ /* this tests open with O_TMPFILE note that this will only work with the fuse low level api you will get ENOTSUP with the high level api */ @@ -2054,6 +2055,7 @@ static int test_create_and_link_tmpfile(void) success(); return 0; } +#endif int main(int argc, char *argv[]) { @@ -2183,8 +2185,10 @@ int main(int argc, char *argv[]) err += test_create_ro_dir(O_CREAT | O_WRONLY); err += test_create_ro_dir(O_CREAT | O_TRUNC); err += test_copy_file_range(); +#ifndef __FreeBSD__ err += test_create_tmpfile(); err += test_create_and_link_tmpfile(); +#endif unlink(testfile2); unlink(testsock); From 13cdf97c0315197b57feba073070ea6a4f834eef Mon Sep 17 00:00:00 2001 From: Vassili Tchersky <vt+git@vbcy.org> Date: Mon, 17 Feb 2025 08:48:42 +0100 Subject: [PATCH 061/105] tests: Re-enable mknod and mkfifo tests on FreeBSD Signed-off-by: Vassili Tchersky <vt+git@vbcy.org> --- test/test_syscalls.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/test_syscalls.c b/test/test_syscalls.c index 97906a3e6..4bbe97340 100644 --- a/test/test_syscalls.c +++ b/test/test_syscalls.c @@ -1065,7 +1065,6 @@ static int test_create_unlink(void) return 0; } -#ifndef __FreeBSD__ static int test_mknod(void) { int err = 0; @@ -1098,7 +1097,6 @@ static int test_mknod(void) success(); return 0; } -#endif #define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode) @@ -1792,7 +1790,6 @@ static int test_rename_dir_loop(void) #undef PATH } -#ifndef __FreeBSD__ static int test_mkfifo(void) { int res; @@ -1824,7 +1821,6 @@ static int test_mkfifo(void) success(); return 0; } -#endif static int test_mkdir(void) { @@ -2120,10 +2116,8 @@ int main(int argc, char *argv[]) err += test_symlink(); err += test_link(); err += test_link2(); -#ifndef __FreeBSD__ err += test_mknod(); err += test_mkfifo(); -#endif err += test_mkdir(); err += test_rename_file(); err += test_rename_dir(); From fa1c84d0e0a60db855aa42aab4363624c6796e70 Mon Sep 17 00:00:00 2001 From: Vassili Tchersky <vt+git@vbcy.org> Date: Mon, 17 Feb 2025 08:50:19 +0100 Subject: [PATCH 062/105] mount_bsd: Fix usage of libfuse_strtol The check on fd < 0 was recently removed. However, this check is important as the content of FUSE_DEV_FD is passed as-is to mount_fusefs, and a string beginning by '-' is treated as an option. Additionally, add a proper include and type. Signed-off-by: Vassili Tchersky <vt+git@vbcy.org> --- lib/mount_bsd.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c index 1863c49c6..85332d34c 100644 --- a/lib/mount_bsd.c +++ b/lib/mount_bsd.c @@ -12,6 +12,7 @@ #include "fuse_i.h" #include "fuse_misc.h" #include "fuse_opt.h" +#include "util.h" #include <sys/param.h> #include "fuse_mount_compat.h" @@ -151,7 +152,7 @@ static int init_backgrounded(void) static int fuse_mount_core(const char *mountpoint, const char *opts) { const char *mountprog = FUSERMOUNT_PROG; - int fd; + long fd; char *fdnam, *dev; pid_t pid, cpid; int status; @@ -161,7 +162,7 @@ static int fuse_mount_core(const char *mountpoint, const char *opts) if (fdnam) { err = libfuse_strtol(fdnam, &fd); - if (err) { + if (err || fd < 0) { fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n"); return -1; } @@ -216,7 +217,7 @@ static int fuse_mount_core(const char *mountpoint, const char *opts) if (! fdnam) { - ret = asprintf(&fdnam, "%d", fd); + ret = asprintf(&fdnam, "%ld", fd); if(ret == -1) { perror("fuse: failed to assemble mount arguments"); From 981c97aed4813fd3ed0f0ceb7241c1f26e32b1b6 Mon Sep 17 00:00:00 2001 From: Vassili Tchersky <vt+git@vbcy.org> Date: Mon, 17 Feb 2025 08:53:03 +0100 Subject: [PATCH 063/105] FreeBSD: Remove useless options These options never had any effect. See https://svnweb.freebsd.org/base?view=revision&revision=347544 Signed-off-by: Vassili Tchersky <vt+git@vbcy.org> --- lib/mount_bsd.c | 37 ++++++------------------------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c index 85332d34c..789f61b7a 100644 --- a/lib/mount_bsd.c +++ b/lib/mount_bsd.c @@ -19,7 +19,6 @@ #include <sys/stat.h> #include <sys/wait.h> -#include <sys/sysctl.h> #include <sys/user.h> #include <stdio.h> #include <stdlib.h> @@ -86,7 +85,6 @@ static const struct fuse_opt fuse_mount_opts[] = { FUSE_DUAL_OPT_KEY("private", KEY_KERN), FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN), FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN), - FUSE_OPT_KEY("nosync_unmount", KEY_KERN), #if __FreeBSD_version >= 1200519 FUSE_DUAL_OPT_KEY("intr", KEY_KERN), #endif @@ -134,21 +132,6 @@ void fuse_kern_unmount(const char *mountpoint, int fd) unmount(mountpoint, MNT_FORCE); } -/* Check if kernel is doing init in background */ -static int init_backgrounded(void) -{ - unsigned ibg; - size_t len; - - len = sizeof(ibg); - - if (sysctlbyname("vfs.fuse.init_backgrounded", &ibg, &len, NULL, 0)) - return 0; - - return ibg; -} - - static int fuse_mount_core(const char *mountpoint, const char *opts) { const char *mountprog = FUSERMOUNT_PROG; @@ -194,20 +177,12 @@ static int fuse_mount_core(const char *mountpoint, const char *opts) } if (pid == 0) { - if (! init_backgrounded()) { - /* - * If init is not backgrounded, we have to - * call the mount util backgrounded, to avoid - * deadlock. - */ - - pid = fork(); - - if (pid == -1) { - perror("fuse: fork() failed"); - close(fd); - exit(1); - } + pid = fork(); + + if (pid == -1) { + perror("fuse: fork() failed"); + close(fd); + _exit(EXIT_FAILURE); } if (pid == 0) { From 8af70b496cac0f69a4b2835bc5388a8e327725a8 Mon Sep 17 00:00:00 2001 From: Vassili Tchersky <vt+git@vbcy.org> Date: Mon, 17 Feb 2025 08:54:10 +0100 Subject: [PATCH 064/105] mount_bsd: Proper exit calls Use _exit() instead of exit() inside children Signed-off-by: Vassili Tchersky <vt+git@vbcy.org> --- lib/mount_bsd.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c index 789f61b7a..ba17240ec 100644 --- a/lib/mount_bsd.c +++ b/lib/mount_bsd.c @@ -189,7 +189,7 @@ static int fuse_mount_core(const char *mountpoint, const char *opts) const char *argv[32]; int a = 0; int ret = -1; - + if (! fdnam) { ret = asprintf(&fdnam, "%ld", fd); @@ -197,7 +197,7 @@ static int fuse_mount_core(const char *mountpoint, const char *opts) { perror("fuse: failed to assemble mount arguments"); close(fd); - exit(1); + _exit(EXIT_FAILURE); } } @@ -212,10 +212,10 @@ static int fuse_mount_core(const char *mountpoint, const char *opts) execvp(mountprog, (char **) argv); perror("fuse: failed to exec mount program"); free(fdnam); - exit(1); + _exit(EXIT_FAILURE); } - exit(0); + _exit(EXIT_SUCCESS); } if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) { From 4c562bf25d4aa01d364a8c30f1315705f932ec72 Mon Sep 17 00:00:00 2001 From: Vassili Tchersky <vt+git@vbcy.org> Date: Mon, 17 Feb 2025 08:54:45 +0100 Subject: [PATCH 065/105] mount_bsd: Show errors when syscalls failed Log on unmount() and close() failure Signed-off-by: Vassili Tchersky <vt+git@vbcy.org> --- lib/mount_bsd.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c index ba17240ec..0e841df5f 100644 --- a/lib/mount_bsd.c +++ b/lib/mount_bsd.c @@ -128,8 +128,11 @@ static int fuse_mount_opt_proc(void *data, const char *arg, int key, void fuse_kern_unmount(const char *mountpoint, int fd) { - close(fd); - unmount(mountpoint, MNT_FORCE); + if (close(fd) < 0) + fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno)); + if (unmount(mountpoint, MNT_FORCE) < 0) + fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s", + mountpoint, strerror(errno)); } static int fuse_mount_core(const char *mountpoint, const char *opts) @@ -220,7 +223,8 @@ static int fuse_mount_core(const char *mountpoint, const char *opts) if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) { perror("fuse: failed to mount file system"); - close(fd); + if (close(fd) < 0) + perror("fuse: closing FD"); return -1; } From 7fe971b406706b25e2eb4064fa5dbd1c06c7c083 Mon Sep 17 00:00:00 2001 From: Vassili Tchersky <vt+git@vbcy.org> Date: Tue, 18 Feb 2025 13:24:40 +0100 Subject: [PATCH 066/105] mount_bsd: Remove unused headers Removed: - <sys/user.h>: used to be for kinfo_proc, long gone from the code - <paths.h>: used to be for _PATH_DEV and _PATH_DEVNULL, long gone - <limits.h>: used to be for _POSIX2_LINE_MAX, long gone - <sys/stat.h>: used to be for fstat/stat, long gone Signed-off-by: Vassili Tchersky <vt+git@vbcy.org> --- lib/mount_bsd.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c index 0e841df5f..bd95a76d3 100644 --- a/lib/mount_bsd.c +++ b/lib/mount_bsd.c @@ -17,9 +17,7 @@ #include <sys/param.h> #include "fuse_mount_compat.h" -#include <sys/stat.h> #include <sys/wait.h> -#include <sys/user.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> @@ -27,8 +25,6 @@ #include <fcntl.h> #include <errno.h> #include <string.h> -#include <paths.h> -#include <limits.h> #define FUSERMOUNT_PROG "mount_fusefs" #define FUSE_DEV_TRUNK "/dev/fuse" From c6ae7425aa9cac34e5ce320b1ac3a09753f6c66f Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 17 Feb 2025 23:39:04 +0100 Subject: [PATCH 067/105] Avoid nested function declarations in helper functions libfuse-3.17 introduced several functions that should only be called via inlined helper functions, never directly. To enforce this, these functions were declared within the inlined functions. However, this triggers the compiler warning "-Werror=nested-externs". While this warning is valid, the nested declarations were intentional to prevent direct usage of these functions. Rather than suppressing the warning with pragmas, move these function declarations outside the helper functions while maintaining the intended access restrictions through other means. Closes: https://github.com/libfuse/libfuse/issues/1134 Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- include/fuse.h | 57 +++++++++++++++-------------------------- include/fuse_lowlevel.h | 12 ++++----- lib/fuse.c | 13 ++-------- lib/fuse_lowlevel.c | 4 --- lib/helper.c | 4 --- 5 files changed, 28 insertions(+), 62 deletions(-) diff --git a/include/fuse.h b/include/fuse.h index c0857fdbe..c94b62851 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -882,6 +882,9 @@ struct fuse_context { * * Do not call this directly, use fuse_main() */ +int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); static inline int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, void *user_data) @@ -895,13 +898,6 @@ static inline int fuse_main_real(int argc, char *argv[], "%s is a libfuse internal function, please use fuse_main()\n", __func__); - /* not declared globally, to restrict usage of this function */ - int fuse_main_real_versioned(int argc, char *argv[], - const struct fuse_operations *op, - size_t op_size, - struct libfuse_version *version, - void *user_data); - return fuse_main_real_versioned(argc, argv, op, op_size, &version, user_data); } @@ -960,6 +956,9 @@ static inline int fuse_main_real(int argc, char *argv[], * * Example usage, see hello.c */ +int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); static inline int fuse_main_fn(int argc, char *argv[], const struct fuse_operations *op, void *user_data) @@ -971,12 +970,6 @@ static inline int fuse_main_fn(int argc, char *argv[], .padding = 0 }; - /* not declared globally, to restrict usage of this function */ - int fuse_main_real_versioned(int argc, char *argv[], - const struct fuse_operations *op, - size_t op_size, - struct libfuse_version *version, - void *user_data); return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version, user_data); } @@ -1000,6 +993,14 @@ static inline int fuse_main_fn(int argc, char *argv[], */ void fuse_lib_help(struct fuse_args *args); +/* Do not call this directly, use fuse_new() instead */ +struct fuse *_fuse_new_30(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); +struct fuse *_fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); + /** * Create a new FUSE filesystem. * @@ -1028,22 +1029,10 @@ void fuse_lib_help(struct fuse_args *args); * @return the created FUSE handle */ #if FUSE_USE_VERSION == 30 -struct fuse *_fuse_new_30(struct fuse_args *args, - const struct fuse_operations *op, - size_t op_size, - struct libfuse_version *version, - void *user_data); -static inline struct fuse * -fuse_new_fn(struct fuse_args *args, - const struct fuse_operations *op, size_t op_size, - void *user_data) +static inline struct fuse *fuse_new_fn(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) { - /* not declared globally, to restrict usage of this function */ - struct fuse *_fuse_new_30(struct fuse_args *args, - const struct fuse_operations *op, size_t op_size, - struct libfuse_version *version, - void *user_data); - struct libfuse_version version = { .major = FUSE_MAJOR_VERSION, .minor = FUSE_MINOR_VERSION, @@ -1054,10 +1043,9 @@ fuse_new_fn(struct fuse_args *args, return _fuse_new_30(args, op, op_size, &version, user_data); } #else /* FUSE_USE_VERSION */ -static inline struct fuse * -fuse_new_fn(struct fuse_args *args, - const struct fuse_operations *op, size_t op_size, - void *user_data) +static inline struct fuse *fuse_new_fn(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) { struct libfuse_version version = { .major = FUSE_MAJOR_VERSION, @@ -1066,11 +1054,6 @@ fuse_new_fn(struct fuse_args *args, .padding = 0 }; - /* not declared globally, to restrict usage of this function */ - struct fuse *_fuse_new_31(struct fuse_args *args, - const struct fuse_operations *op, - size_t op_size, struct libfuse_version *version, - void *user_data); return _fuse_new_31(args, op, op_size, &version, user_data); } #endif diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index b03b37aae..93bcba296 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -2049,6 +2049,12 @@ int fuse_parse_cmdline_312(struct fuse_args *args, #endif #endif +/* Do not call this directly, use fuse_session_new() instead */ +struct fuse_session * +fuse_session_new_versioned(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, size_t op_size, + struct libfuse_version *version, void *userdata); + /** * Create a low level session. * @@ -2088,12 +2094,6 @@ fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op, .padding = 0 }; - /* not declared globally, to restrict usage of this function */ - struct fuse_session *fuse_session_new_versioned( - struct fuse_args *args, const struct fuse_lowlevel_ops *op, - size_t op_size, struct libfuse_version *version, - void *userdata); - return fuse_session_new_versioned(args, op, op_size, &version, userdata); } diff --git a/lib/fuse.c b/lib/fuse.c index 6c69a6897..933542992 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -4924,13 +4924,8 @@ void fuse_stop_cleanup_thread(struct fuse *f) * through the fuse_new macro */ struct fuse *_fuse_new_31(struct fuse_args *args, - const struct fuse_operations *op, - size_t op_size, struct libfuse_version *version, - void *user_data); -struct fuse *_fuse_new_31(struct fuse_args *args, - const struct fuse_operations *op, - size_t op_size, struct libfuse_version *version, - void *user_data) + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data) { struct fuse *f; struct node *root; @@ -5075,10 +5070,6 @@ struct fuse *_fuse_new_31(struct fuse_args *args, } /* Emulates 3.0-style fuse_new(), which processes --help */ -struct fuse *_fuse_new_30(struct fuse_args *args, const struct fuse_operations *op, - size_t op_size, - struct libfuse_version *version, - void *user_data); FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0") struct fuse *_fuse_new_30(struct fuse_args *args, const struct fuse_operations *op, diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index e3e79d528..d650944a3 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3244,10 +3244,6 @@ int fuse_session_receive_buf_internal(struct fuse_session *se, return _fuse_session_receive_buf(se, buf, ch, true); } -struct fuse_session * -fuse_session_new_versioned(struct fuse_args *args, - const struct fuse_lowlevel_ops *op, size_t op_size, - struct libfuse_version *version, void *userdata); struct fuse_session * fuse_session_new_versioned(struct fuse_args *args, const struct fuse_lowlevel_ops *op, size_t op_size, diff --git a/lib/helper.c b/lib/helper.c index a1cf98c3e..a7b2fe002 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -304,10 +304,6 @@ int fuse_daemonize(int foreground) return 0; } -/* Not symboled, as not part of the official API */ -int fuse_main_real_versioned(int argc, char *argv[], - const struct fuse_operations *op, size_t op_size, - struct libfuse_version *version, void *user_data); int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data) From cf4170fcea5b23b22352a1915f77e69a90f80520 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 18 Feb 2025 20:23:09 +0100 Subject: [PATCH 068/105] Fix a typo in test/ci-build.sh (ct vs cat) Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- test/ci-build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ci-build.sh b/test/ci-build.sh index 3f0ff8546..40bb79e8d 100755 --- a/test/ci-build.sh +++ b/test/ci-build.sh @@ -81,7 +81,7 @@ sanitized_build() meson setup -Dprefix=${PREFIX_DIR} -D werror=true\ "${SOURCE_DIR}" \ - || (ct meson-logs/meson-log.txt; false) + || (cat meson-logs/meson-log.txt; false) meson configure $SAN # b_lundef=false is required to work around clang From d7507b4d6f1d126c0bd25dd947a40d9f064658cf Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 18 Feb 2025 21:45:05 +0100 Subject: [PATCH 069/105] github ci tests: Update the stable branch name to include fuse- The branch is actually called fuse-3.17.x Also disable checkpatch for branches except master, as it is to do basic checks, while stable branches do not need that check, assuming cherry-pick happens from master. Issue with it is that persistently complains about dependabot changes. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .github/workflows/abicheck.yml | 4 ++-- .github/workflows/checkpatch.yml | 8 +++++++- .github/workflows/codeql.yml | 4 ++-- .github/workflows/codespell.yml | 4 ++-- .github/workflows/pr-ci.yml | 4 ++-- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/abicheck.yml b/.github/workflows/abicheck.yml index 526fb4296..10a8e6d98 100644 --- a/.github/workflows/abicheck.yml +++ b/.github/workflows/abicheck.yml @@ -5,11 +5,11 @@ on: push: branches: - master - - '[0-9]+.[0-9]+' # This will match branches like 3.17, 3.18, 4.0, etc. + - 'fuse-[0-9]+.[0-9]+*' # This will match branches like 3.17, 3.18, 4.0, etc. pull_request: branches: - master - - '[0-9]+.[0-9]+' + - 'fuse-[0-9]+.[0-9]+*' permissions: contents: read diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml index 1adada4c5..622542c54 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -1,8 +1,14 @@ name: Checkpatch on: + push: + branches: + - master pull_request: - types: [opened, synchronize, reopened] + branches: + - master +permissions: + contents: read jobs: checkpatch: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index cefd1fcbf..474f2c8d2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -15,11 +15,11 @@ on: push: branches: - master - - '[0-9]+.[0-9]+' # This will match branches like 3.17, 3.18, 4.0, etc. + - 'fuse-[0-9]+.[0-9]+*' # This will match branches like 3.17, 3.18, 4.0, etc. pull_request: branches: - master - - '[0-9]+.[0-9]+' + - 'fuse-[0-9]+.[0-9]+*' jobs: analyze: diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 3b771c1ea..184fbcfe1 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -5,11 +5,11 @@ on: push: branches: - master - - '[0-9]+.[0-9]+' # This will match branches like 3.17, 3.18, 4.0, etc. + - 'fuse-[0-9]+.[0-9]+*' # This will match branches like 3.17, 3.18, 4.0, etc. pull_request: branches: - master - - '[0-9]+.[0-9]+' + - 'fuse-[0-9]+.[0-9]+*' permissions: contents: read diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index ccaaf7231..4bb7f66de 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -4,11 +4,11 @@ on: push: branches: - master - - '[0-9]+.[0-9]+' # This will match branches like 3.17, 3.18, 4.0, etc. + - 'fuse-[0-9]+.[0-9]+*' # This will match branches like 3.17, 3.18, 4.0, etc. pull_request: branches: - master - - '[0-9]+.[0-9]+' + - 'fuse-[0-9]+.[0-9]+*' permissions: contents: read From 97a2e538d7b11e2480b8b90f60e505cb0aa28428 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 18 Feb 2025 22:46:03 +0100 Subject: [PATCH 070/105] Released fuse-3.17.1-rc1 --- AUTHORS | 6 +++++- ChangeLog.rst | 12 ++++++++++-- meson.build | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 837a62c84..19a172068 100644 --- a/AUTHORS +++ b/AUTHORS @@ -260,6 +260,10 @@ Tyler Hall <tylerwhall@gmail.com> yangyun <yangyun50@huawei.com> Abhishek <abhi_45645@yahoo.com> -# New authors since 666b2c3fa5159e3c72a0d08117507475117c9319 +# New authors since fuse-3.17.0 Luis Henriques <luis@igalia.com> Zegang <zegang.luo@qq.com> + +# New authors since fuse-3.17.1-rc0 +Maksim Harbachou <maksim.harbachou@resilio.com> +Vassili Tchersky <vt+git@vbcy.org> diff --git a/ChangeLog.rst b/ChangeLog.rst index 658f898aa..6b04148c7 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,4 +1,12 @@ -libfuse 3.17.1-rc0 (2024-02.10) +libfuse 3.17.1-rc1 (2025-02-18) +=============================== +- several BSD fixes +- x86 (32bit) build fixes +- nested declarations moved out of the inlined functions to avoid + build warnings +- signify public key added for future 3.18 + +libfuse 3.17.1-rc0 (2025-02.10) =============================== * Fix libfuse build with FUSE_USE_VERSION 30 @@ -14,7 +22,7 @@ libfuse 3.17.1-rc0 (2024-02.10) exported to be able to silence leak checkers -libfuse 3.17 (2024-01-01, not officially releaesed) +libfuse 3.17 (2025-01-01, not officially releaesed) ================================================== * 3.11 and 3.14.2 introduced ABI incompatibilities, the ABI is restored diff --git a/meson.build b/meson.build index e191fc62c..c747a075b 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfuse3', ['c'], - version: '3.17.1-rc0', # Version with RC suffix + version: '3.17.1-rc1', # Version with RC suffix meson_version: '>= 0.51.0', default_options: [ 'buildtype=debugoptimized', From 239b40db654823a64b1f4a0098525bcceea30bc7 Mon Sep 17 00:00:00 2001 From: Vassili Tchersky <vt+git@vbc.su> Date: Wed, 19 Feb 2025 03:43:11 +0100 Subject: [PATCH 071/105] mount: fix closing stdout/err and error logs Don't spawn a setuid children with FD 1&2 closed. Check status and not errno after posix_spawn(p). Add comments to fix later error checking, as posix_spawn(p) returns non-zero status only if clone/vfork/rfork fails. If only setup (open, dup2) or execve fails, the forked process exit with 127 but posix_spawn returns zero. Signed-off-by: Vassili Tchersky <vt+git@vbc.su> --- lib/mount.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/mount.c b/lib/mount.c index 7dd727ce2..6ed4444b3 100644 --- a/lib/mount.c +++ b/lib/mount.c @@ -153,7 +153,7 @@ static int fusermount_posix_spawn(posix_spawn_file_actions_t *action, if (out_pid) *out_pid = pid; else - waitpid(pid, NULL, 0); + waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */ return 0; } @@ -341,8 +341,8 @@ void fuse_kern_unmount(const char *mountpoint, int fd) "--", mountpoint, NULL }; int status = fusermount_posix_spawn(NULL, argv, NULL); if(status != 0) { - fuse_log(FUSE_LOG_ERR, "Spawaning %s to unumount failed", - FUSERMOUNT_PROG); + fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s", + FUSERMOUNT_PROG, strerror(-status)); return; } } @@ -390,8 +390,8 @@ static int setup_auto_unmount(const char *mountpoint, int quiet) posix_spawn_file_actions_init(&action); if (quiet) { - posix_spawn_file_actions_addclose(&action, 1); - posix_spawn_file_actions_addclose(&action, 2); + posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); + posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0); } posix_spawn_file_actions_addclose(&action, fds[1]); @@ -406,7 +406,8 @@ static int setup_auto_unmount(const char *mountpoint, int quiet) if(status != 0) { close(fds[0]); close(fds[1]); - fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed"); + fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s", + strerror(-status)); return -1; } // passed to child now, so can close here. @@ -462,8 +463,8 @@ static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, posix_spawn_file_actions_init(&action); if (quiet) { - posix_spawn_file_actions_addclose(&action, 1); - posix_spawn_file_actions_addclose(&action, 2); + posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); + posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0); } posix_spawn_file_actions_addclose(&action, fds[1]); @@ -474,8 +475,8 @@ static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, if(status != 0) { close(fds[0]); close(fds[1]); - fuse_log(FUSE_LOG_ERR, "posix_spawnp() for %s failed", - FUSERMOUNT_PROG, strerror(errno)); + fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s", + FUSERMOUNT_PROG, strerror(-status)); return -1; } From 226f0bfa1c813fd2df579719402db031c2cd0e46 Mon Sep 17 00:00:00 2001 From: jnr0006 <jacob.nick.riley@gmail.com> Date: Wed, 12 Mar 2025 16:21:44 -0500 Subject: [PATCH 072/105] Add PanFS to whitelist Added PanFSto whitelist. This should allow us to mount gocryptfs onto the parallel filesystem. Signed-off-by: Jacob Riley <jnr0006@uah.edu> (cherry picked from commit 3a291c355f3a4966ff3ebb6841b31da0d3010f52) --- util/fusermount.c | 1 + 1 file changed, 1 insertion(+) diff --git a/util/fusermount.c b/util/fusermount.c index 683c5496f..acbff61fb 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -1136,6 +1136,7 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd) 0x7366746E /* NTFS3_SUPER_MAGIC */, 0x5346414f /* OPENAFS_SUPER_MAGIC */, 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */, + 0xAAD7AAEA /* PANFS_SUPER_MAGIC */, 0x52654973 /* REISERFS_SUPER_MAGIC */, 0xFE534D42 /* SMB2_SUPER_MAGIC */, 0x73717368 /* SQUASHFS_MAGIC */, From 4b1a3a5c6f41ddb8bd3389dee63b2340202bd2bf Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Thu, 13 Mar 2025 23:52:01 +0100 Subject: [PATCH 073/105] fuse_common.h: Convert back FUSE_CAP_ from enum to defines Some applications use that for detection of features between distributions/libfuse version. Closes: https://github.com/libfuse/libfuse/issues/1163 Signed-off-by: Bernd Schubert <bschubert@ddn.com> (cherry picked from commit 3ae5ca7443348aabad9bc71b9d5b0999f8292379) --- include/fuse_common.h | 637 +++++++++++++++++++++--------------------- 1 file changed, 315 insertions(+), 322 deletions(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index aba1c15c2..77efc5de5 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -174,355 +174,348 @@ struct fuse_loop_config_v1 { * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' * **************************************************************************/ -enum fuse_capability { - /** - * Indicates that the filesystem supports asynchronous read requests. - * - * If this capability is not requested/available, the kernel will - * ensure that there is at most one pending read request per - * file-handle at any time, and will attempt to order read requests by - * increasing offset. - * - * This feature is enabled by default when supported by the kernel. - */ - FUSE_CAP_ASYNC_READ = (1 << 0), - - /** - * Indicates that the filesystem supports "remote" locking. - * - * This feature is enabled by default when supported by the kernel, - * and if getlk() and setlk() handlers are implemented. - */ - FUSE_CAP_POSIX_LOCKS = (1 << 1), +/** + * Indicates that the filesystem supports asynchronous read requests. + * + * If this capability is not requested/available, the kernel will + * ensure that there is at most one pending read request per + * file-handle at any time, and will attempt to order read requests by + * increasing offset. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ASYNC_READ (1 << 0) - /** - * Indicates that the filesystem supports the O_TRUNC open flag. If - * disabled, and an application specifies O_TRUNC, fuse first calls - * truncate() and then open() with O_TRUNC filtered out. - * - * This feature is enabled by default when supported by the kernel. - */ - FUSE_CAP_ATOMIC_O_TRUNC = (1 << 3), +/** + * Indicates that the filesystem supports "remote" locking. + * + * This feature is enabled by default when supported by the kernel, + * and if getlk() and setlk() handlers are implemented. + */ +#define FUSE_CAP_POSIX_LOCKS (1 << 1) - /** - * Indicates that the filesystem supports lookups of "." and "..". - * - * When this flag is set, the filesystem must be prepared to receive requests - * for invalid inodes (i.e., for which a FORGET request was received or - * which have been used in a previous instance of the filesystem daemon) and - * must not reuse node-ids (even when setting generation numbers). - * - * This feature is disabled by default. - */ - FUSE_CAP_EXPORT_SUPPORT = (1 << 4), +/** + * Indicates that the filesystem supports the O_TRUNC open flag. If + * disabled, and an application specifies O_TRUNC, fuse first calls + * truncate() and then open() with O_TRUNC filtered out. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3) - /** - * Indicates that the kernel should not apply the umask to the - * file mode on create operations. - * - * This feature is disabled by default. - */ - FUSE_CAP_DONT_MASK = (1 << 6), +/** + * Indicates that the filesystem supports lookups of "." and "..". + * + * When this flag is set, the filesystem must be prepared to receive requests + * for invalid inodes (i.e., for which a FORGET request was received or + * which have been used in a previous instance of the filesystem daemon) and + * must not reuse node-ids (even when setting generation numbers). + * + * This feature is disabled by default. + */ +#define FUSE_CAP_EXPORT_SUPPORT (1 << 4) - /** - * Indicates that libfuse should try to use splice() when writing to - * the fuse device. This may improve performance. - * - * This feature is disabled by default. - */ - FUSE_CAP_SPLICE_WRITE = (1 << 7), +/** + * Indicates that the kernel should not apply the umask to the + * file mode on create operations. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_DONT_MASK (1 << 6) - /** - * Indicates that libfuse should try to move pages instead of copying when - * writing to / reading from the fuse device. This may improve performance. - * - * This feature is disabled by default. - */ - FUSE_CAP_SPLICE_MOVE = (1 << 8), +/** + * Indicates that libfuse should try to use splice() when writing to + * the fuse device. This may improve performance. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_SPLICE_WRITE (1 << 7) - /** - * Indicates that libfuse should try to use splice() when reading from - * the fuse device. This may improve performance. - * - * This feature is enabled by default when supported by the kernel and - * if the filesystem implements a write_buf() handler. - */ - FUSE_CAP_SPLICE_READ = (1 << 9), +/** + * Indicates that libfuse should try to move pages instead of copying when + * writing to / reading from the fuse device. This may improve performance. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_SPLICE_MOVE (1 << 8) - /** - * If set, the calls to flock(2) will be emulated using POSIX locks and must - * then be handled by the filesystem's setlock() handler. - * - * If not set, flock(2) calls will be handled by the FUSE kernel module - * internally (so any access that does not go through the kernel cannot be taken - * into account). - * - * This feature is enabled by default when supported by the kernel and - * if the filesystem implements a flock() handler. - */ - FUSE_CAP_FLOCK_LOCKS = (1 << 10), +/** + * Indicates that libfuse should try to use splice() when reading from + * the fuse device. This may improve performance. + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements a write_buf() handler. + */ +#define FUSE_CAP_SPLICE_READ (1 << 9) - /** - * Indicates that the filesystem supports ioctl's on directories. - * - * This feature is enabled by default when supported by the kernel. - */ - FUSE_CAP_IOCTL_DIR = (1 << 11), +/** + * If set, the calls to flock(2) will be emulated using POSIX locks and must + * then be handled by the filesystem's setlock() handler. + * + * If not set, flock(2) calls will be handled by the FUSE kernel module + * internally (so any access that does not go through the kernel cannot be taken + * into account). + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements a flock() handler. + */ +#define FUSE_CAP_FLOCK_LOCKS (1 << 10) - /** - * Traditionally, while a file is open the FUSE kernel module only - * asks the filesystem for an update of the file's attributes when a - * client attempts to read beyond EOF. This is unsuitable for - * e.g. network filesystems, where the file contents may change - * without the kernel knowing about it. - * - * If this flag is set, FUSE will check the validity of the attributes - * on every read. If the attributes are no longer valid (i.e., if the - * *attr_timeout* passed to fuse_reply_attr() or set in `struct - * fuse_entry_param` has passed), it will first issue a `getattr` - * request. If the new mtime differs from the previous value, any - * cached file *contents* will be invalidated as well. - * - * This flag should always be set when available. If all file changes - * go through the kernel, *attr_timeout* should be set to a very large - * number to avoid unnecessary getattr() calls. - * - * This feature is enabled by default when supported by the kernel. - */ - FUSE_CAP_AUTO_INVAL_DATA = (1 << 12), +/** + * Indicates that the filesystem supports ioctl's on directories. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_IOCTL_DIR (1 << 11) - /** - * Indicates that the filesystem supports readdirplus. - * - * This feature is enabled by default when supported by the kernel and if the - * filesystem implements a readdirplus() handler. - */ - FUSE_CAP_READDIRPLUS = (1 << 13), +/** + * Traditionally, while a file is open the FUSE kernel module only + * asks the filesystem for an update of the file's attributes when a + * client attempts to read beyond EOF. This is unsuitable for + * e.g. network filesystems, where the file contents may change + * without the kernel knowing about it. + * + * If this flag is set, FUSE will check the validity of the attributes + * on every read. If the attributes are no longer valid (i.e., if the + * *attr_timeout* passed to fuse_reply_attr() or set in `struct + * fuse_entry_param` has passed), it will first issue a `getattr` + * request. If the new mtime differs from the previous value, any + * cached file *contents* will be invalidated as well. + * + * This flag should always be set when available. If all file changes + * go through the kernel, *attr_timeout* should be set to a very large + * number to avoid unnecessary getattr() calls. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12) - /** - * Indicates that the filesystem supports adaptive readdirplus. - * - * If FUSE_CAP_READDIRPLUS is not set, this flag has no effect. - * - * If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel - * will always issue readdirplus() requests to retrieve directory - * contents. - * - * If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel - * will issue both readdir() and readdirplus() requests, depending on - * how much information is expected to be required. - * - * As of Linux 4.20, the algorithm is as follows: when userspace - * starts to read directory entries, issue a READDIRPLUS request to - * the filesystem. If any entry attributes have been looked up by the - * time userspace requests the next batch of entries continue with - * READDIRPLUS, otherwise switch to plain READDIR. This will reasult - * in eg plain "ls" triggering READDIRPLUS first then READDIR after - * that because it doesn't do lookups. "ls -l" should result in all - * READDIRPLUS, except if dentries are already cached. - * - * This feature is enabled by default when supported by the kernel and - * if the filesystem implements both a readdirplus() and a readdir() - * handler. - */ - FUSE_CAP_READDIRPLUS_AUTO = (1 << 14), +/** + * Indicates that the filesystem supports readdirplus. + * + * This feature is enabled by default when supported by the kernel and if the + * filesystem implements a readdirplus() handler. + */ +#define FUSE_CAP_READDIRPLUS (1 << 13) - /** - * Indicates that the filesystem supports asynchronous direct I/O submission. - * - * If this capability is not requested/available, the kernel will ensure that - * there is at most one pending read and one pending write request per direct - * I/O file-handle at any time. - * - * This feature is enabled by default when supported by the kernel. - */ - FUSE_CAP_ASYNC_DIO = (1 << 15), +/** + * Indicates that the filesystem supports adaptive readdirplus. + * + * If FUSE_CAP_READDIRPLUS is not set, this flag has no effect. + * + * If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel + * will always issue readdirplus() requests to retrieve directory + * contents. + * + * If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel + * will issue both readdir() and readdirplus() requests, depending on + * how much information is expected to be required. + * + * As of Linux 4.20, the algorithm is as follows: when userspace + * starts to read directory entries, issue a READDIRPLUS request to + * the filesystem. If any entry attributes have been looked up by the + * time userspace requests the next batch of entries continue with + * READDIRPLUS, otherwise switch to plain READDIR. This will reasult + * in eg plain "ls" triggering READDIRPLUS first then READDIR after + * that because it doesn't do lookups. "ls -l" should result in all + * READDIRPLUS, except if dentries are already cached. + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements both a readdirplus() and a readdir() + * handler. + */ +#define FUSE_CAP_READDIRPLUS_AUTO (1 << 14) - /** - * Indicates that writeback caching should be enabled. This means that - * individual write request may be buffered and merged in the kernel - * before they are send to the filesystem. - * - * This feature is disabled by default. - */ - FUSE_CAP_WRITEBACK_CACHE = (1 << 16), +/** + * Indicates that the filesystem supports asynchronous direct I/O submission. + * + * If this capability is not requested/available, the kernel will ensure that + * there is at most one pending read and one pending write request per direct + * I/O file-handle at any time. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ASYNC_DIO (1 << 15) - /** - * Indicates support for zero-message opens. If this flag is set in - * the `capable` field of the `fuse_conn_info` structure, then the - * filesystem may return `ENOSYS` from the open() handler to indicate - * success. Further attempts to open files will be handled in the - * kernel. (If this flag is not set, returning ENOSYS will be treated - * as an error and signaled to the caller). - * - * Setting this flag in the `want` field enables this behavior automatically - * within libfuse for low level API users. If non-low level users wish to have - * this behavior you must return `ENOSYS` from the open() handler on supporting - * kernels. - */ - FUSE_CAP_NO_OPEN_SUPPORT = (1 << 17), +/** + * Indicates that writeback caching should be enabled. This means that + * individual write request may be buffered and merged in the kernel + * before they are send to the filesystem. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_WRITEBACK_CACHE (1 << 16) - /** - * Indicates support for parallel directory operations. If this flag - * is unset, the FUSE kernel module will ensure that lookup() and - * readdir() requests are never issued concurrently for the same - * directory. - */ - FUSE_CAP_PARALLEL_DIROPS = (1 << 18), +/** + * Indicates support for zero-message opens. If this flag is set in + * the `capable` field of the `fuse_conn_info` structure, then the + * filesystem may return `ENOSYS` from the open() handler to indicate + * success. Further attempts to open files will be handled in the + * kernel. (If this flag is not set, returning ENOSYS will be treated + * as an error and signaled to the caller). + * + * Setting this flag in the `want` field enables this behavior automatically + * within libfuse for low level API users. If non-low level users wish to have + * this behavior you must return `ENOSYS` from the open() handler on supporting + * kernels. + */ +#define FUSE_CAP_NO_OPEN_SUPPORT (1 << 17) - /** - * Indicates support for POSIX ACLs. - * - * If this feature is enabled, the kernel will cache and have - * responsibility for enforcing ACLs. ACL will be stored as xattrs and - * passed to userspace, which is responsible for updating the ACLs in - * the filesystem, keeping the file mode in sync with the ACL, and - * ensuring inheritance of default ACLs when new filesystem nodes are - * created. Note that this requires that the file system is able to - * parse and interpret the xattr representation of ACLs. - * - * Enabling this feature implicitly turns on the - * ``default_permissions`` mount option (even if it was not passed to - * mount(2)). - * - * This feature is disabled by default. - */ - FUSE_CAP_POSIX_ACL = (1 << 19), +/** + * Indicates support for parallel directory operations. If this flag + * is unset, the FUSE kernel module will ensure that lookup() and + * readdir() requests are never issued concurrently for the same + * directory. + */ +#define FUSE_CAP_PARALLEL_DIROPS (1 << 18) - /** - * Indicates that the filesystem is responsible for unsetting - * setuid and setgid bits when a file is written, truncated, or - * its owner is changed. - * - * This feature is disabled by default. - */ - FUSE_CAP_HANDLE_KILLPRIV = (1 << 20), +/** + * Indicates support for POSIX ACLs. + * + * If this feature is enabled, the kernel will cache and have + * responsibility for enforcing ACLs. ACL will be stored as xattrs and + * passed to userspace, which is responsible for updating the ACLs in + * the filesystem, keeping the file mode in sync with the ACL, and + * ensuring inheritance of default ACLs when new filesystem nodes are + * created. Note that this requires that the file system is able to + * parse and interpret the xattr representation of ACLs. + * + * Enabling this feature implicitly turns on the + * ``default_permissions`` mount option (even if it was not passed to + * mount(2)). + * + * This feature is disabled by default. + */ +#define FUSE_CAP_POSIX_ACL (1 << 19) - /** - * Indicates that the filesystem is responsible for unsetting - * setuid and setgid bit and additionally cap (stored as xattr) when a - * file is written, truncated, or its owner is changed. - * Upon write/truncate suid/sgid is only killed if caller - * does not have CAP_FSETID. Additionally upon - * write/truncate sgid is killed only if file has group - * execute permission. (Same as Linux VFS behavior). - * KILLPRIV_V2 requires handling of - * - FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags) - * - FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid) - * - FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags) - * - * This feature is disabled by default. - */ - FUSE_CAP_HANDLE_KILLPRIV_V2 = (1 << 21), +/** + * Indicates that the filesystem is responsible for unsetting + * setuid and setgid bits when a file is written, truncated, or + * its owner is changed. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_HANDLE_KILLPRIV (1 << 20) - /** - * Indicates that the kernel supports caching symlinks in its page cache. - * - * When this feature is enabled, symlink targets are saved in the page cache. - * You can invalidate a cached link by calling: - * `fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);` - * - * This feature is disabled by default. - * If the kernel supports it (>= 4.20), you can enable this feature by - * setting this flag in the `want` field of the `fuse_conn_info` structure. - */ - FUSE_CAP_CACHE_SYMLINKS = (1 << 23), +/** + * Indicates that the filesystem is responsible for unsetting + * setuid and setgid bit and additionally cap (stored as xattr) when a + * file is written, truncated, or its owner is changed. + * Upon write/truncate suid/sgid is only killed if caller + * does not have CAP_FSETID. Additionally upon + * write/truncate sgid is killed only if file has group + * execute permission. (Same as Linux VFS behavior). + * KILLPRIV_V2 requires handling of + * - FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags) + * - FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid) + * - FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags) + * + * This feature is disabled by default. + */ +#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1 << 21) - /** - * Indicates support for zero-message opendirs. If this flag is set in - * the `capable` field of the `fuse_conn_info` structure, then the filesystem - * may return `ENOSYS` from the opendir() handler to indicate success. Further - * opendir and releasedir messages will be handled in the kernel. (If this - * flag is not set, returning ENOSYS will be treated as an error and signalled - * to the caller.) - * - * Setting this flag in the `want` field enables this behavior automatically - * within libfuse for low level API users. If non-low level users with to have - * this behavior you must return `ENOSYS` from the opendir() handler on - * supporting kernels. - */ - FUSE_CAP_NO_OPENDIR_SUPPORT = (1 << 24), +/** + * Indicates that the kernel supports caching symlinks in its page cache. + * + * When this feature is enabled, symlink targets are saved in the page cache. + * You can invalidate a cached link by calling: + * `fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);` + * + * This feature is disabled by default. + * If the kernel supports it (>= 4.20), you can enable this feature by + * setting this flag in the `want` field of the `fuse_conn_info` structure. + */ +#define FUSE_CAP_CACHE_SYMLINKS (1 << 23) - /** - * Indicates support for invalidating cached pages only on explicit request. - * - * If this flag is set in the `capable` field of the `fuse_conn_info` structure, - * then the FUSE kernel module supports invalidating cached pages only on - * explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() - * or fuse_invalidate_path(). - * - * By setting this flag in the `want` field of the `fuse_conn_info` structure, - * the filesystem is responsible for invalidating cached pages through explicit - * requests to the kernel. - * - * Note that setting this flag does not prevent the cached pages from being - * flushed by OS itself and/or through user actions. - * - * Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA - * are set in the `capable` field of the `fuse_conn_info` structure then - * FUSE_CAP_AUTO_INVAL_DATA takes precedence. - * - * This feature is disabled by default. - */ - FUSE_CAP_EXPLICIT_INVAL_DATA = (1 << 25), +/** + * Indicates support for zero-message opendirs. If this flag is set in + * the `capable` field of the `fuse_conn_info` structure, then the filesystem + * may return `ENOSYS` from the opendir() handler to indicate success. Further + * opendir and releasedir messages will be handled in the kernel. (If this + * flag is not set, returning ENOSYS will be treated as an error and signalled + * to the caller.) + * + * Setting this flag in the `want` field enables this behavior automatically + * within libfuse for low level API users. If non-low level users with to have + * this behavior you must return `ENOSYS` from the opendir() handler on + * supporting kernels. + */ +#define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24) - /** - * Indicates support that dentries can be expired. - * - * Expiring dentries, instead of invalidating them, makes a difference for - * overmounted dentries, where plain invalidation would detach all submounts - * before dropping the dentry from the cache. If only expiry is set on the - * dentry, then any overmounts are left alone and until ->d_revalidate() - * is called. - * - * Note: ->d_revalidate() is not called for the case of following a submount, - * so invalidation will only be triggered for the non-overmounted case. - * The dentry could also be mounted in a different mount instance, in which case - * any submounts will still be detached. - */ - FUSE_CAP_EXPIRE_ONLY = (1 << 26), +/** + * Indicates support for invalidating cached pages only on explicit request. + * + * If this flag is set in the `capable` field of the `fuse_conn_info` structure, + * then the FUSE kernel module supports invalidating cached pages only on + * explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() + * or fuse_invalidate_path(). + * + * By setting this flag in the `want` field of the `fuse_conn_info` structure, + * the filesystem is responsible for invalidating cached pages through explicit + * requests to the kernel. + * + * Note that setting this flag does not prevent the cached pages from being + * flushed by OS itself and/or through user actions. + * + * Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA + * are set in the `capable` field of the `fuse_conn_info` structure then + * FUSE_CAP_AUTO_INVAL_DATA takes precedence. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_EXPLICIT_INVAL_DATA (1 << 25) - /** - * Indicates that an extended 'struct fuse_setxattr' is used by the kernel - * side - extra_flags are passed, which are used (as of now by acl) processing. - * For example FUSE_SETXATTR_ACL_KILL_SGID might be set. - */ - FUSE_CAP_SETXATTR_EXT = (1 << 27), +/** + * Indicates support that dentries can be expired. + * + * Expiring dentries, instead of invalidating them, makes a difference for + * overmounted dentries, where plain invalidation would detach all submounts + * before dropping the dentry from the cache. If only expiry is set on the + * dentry, then any overmounts are left alone and until ->d_revalidate() + * is called. + * + * Note: ->d_revalidate() is not called for the case of following a submount, + * so invalidation will only be triggered for the non-overmounted case. + * The dentry could also be mounted in a different mount instance, in which case + * any submounts will still be detached. + */ +#define FUSE_CAP_EXPIRE_ONLY (1 << 26) - /** - * Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction - * is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). - * MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to - * ensure coherency between mount points (or network clients) and with kernel page - * cache as enforced by mmap that cannot be guaranteed anymore. - */ - FUSE_CAP_DIRECT_IO_ALLOW_MMAP = (1 << 28), +/** + * Indicates that an extended 'struct fuse_setxattr' is used by the kernel + * side - extra_flags are passed, which are used (as of now by acl) processing. + * For example FUSE_SETXATTR_ACL_KILL_SGID might be set. + */ +#define FUSE_CAP_SETXATTR_EXT (1 << 27) - /** - * Indicates support for passthrough mode access for read/write operations. - * - * If this flag is set in the `capable` field of the `fuse_conn_info` - * structure, then the FUSE kernel module supports redirecting read/write - * operations to the backing file instead of letting them to be handled - * by the FUSE daemon. - * - * This feature is disabled by default. - */ - FUSE_CAP_PASSTHROUGH = (1 << 29), +/** + * Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction + * is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). + * MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to + * ensure coherency between mount points (or network clients) and with kernel page + * cache as enforced by mmap that cannot be guaranteed anymore. + */ +#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1 << 28) - /** - * Indicates that the file system cannot handle NFS export - * - * If this flag is set NFS export and name_to_handle_at - * is not going to work at all and will fail with EOPNOTSUPP. - */ - FUSE_CAP_NO_EXPORT_SUPPORT = (1 << 30), +/** + * Indicates support for passthrough mode access for read/write operations. + * + * If this flag is set in the `capable` field of the `fuse_conn_info` + * structure, then the FUSE kernel module supports redirecting read/write + * operations to the backing file instead of letting them to be handled + * by the FUSE daemon. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_PASSTHROUGH (1 << 29) - /** - * Current maximum capability value. - */ - FUSE_CAP_CURRENT_MAX -}; +/** + * Indicates that the file system cannot handle NFS export + * + * If this flag is set NFS export and name_to_handle_at + * is not going to work at all and will fail with EOPNOTSUPP. + */ +#define FUSE_CAP_NO_EXPORT_SUPPORT (1 << 30) /** * Ioctl flags From cdad7b6001036f1a33bddab59558eec099bcb278 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 23 Feb 2025 00:17:39 +0100 Subject: [PATCH 074/105] fusermount: prevent stdio FDs from being reused Redirect stdin/stdout/stderr to /dev/null to prevent newly opened file descriptors from reusing these low numbers (0,1,2) and potential issues with that. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- meson.build | 2 +- util/fusermount.c | 80 +++++++++++++++++++++++++++++++---------------- 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/meson.build b/meson.build index c747a075b..1d961d021 100644 --- a/meson.build +++ b/meson.build @@ -72,7 +72,7 @@ private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) # Test for presence of some functions test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2', 'splice', 'vmsplice', 'posix_fallocate', 'fdatasync', - 'utimensat', 'copy_file_range', 'fallocate' ] + 'utimensat', 'copy_file_range', 'fallocate', 'close_range' ] foreach func : test_funcs private_cfg.set('HAVE_' + func.to_upper(), cc.has_function(func, prefix: include_default, args: args_default)) diff --git a/util/fusermount.c b/util/fusermount.c index acbff61fb..dbd947c60 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -7,7 +7,7 @@ */ /* This program does the mounting and unmounting of FUSE filesystems */ -#define _GNU_SOURCE /* for clone and strchrnul */ +#define _GNU_SOURCE /* for clone,strchrnul and close_range */ #include "fuse_config.h" #include "mount_util.h" #include "util.h" @@ -36,6 +36,10 @@ #include <stdbool.h> #include <sys/vfs.h> +#ifdef HAVE_CLOSE_RANGE +#include <linux/close_range.h> +#endif + #define FUSE_COMMFD_ENV "_FUSE_COMMFD" #define FUSE_DEV "/dev/fuse" @@ -1452,44 +1456,63 @@ static void show_version(void) exit(0); } +static void close_range_loop(int min_fd, int max_fd, int cfd) +{ + for (int fd = min_fd; fd <= max_fd; fd++) + if (fd != cfd) + close(fd); +} + /* * Close all inherited fds that are not needed * Ideally these wouldn't come up at all, applications should better * use FD_CLOEXEC / O_CLOEXEC */ -static void close_inherited_fds(int cfd) +static int close_inherited_fds(int cfd) { - int max_fd = sysconf(_SC_OPEN_MAX); - int rc; + int rc = -1; + int nullfd; -#ifdef CLOSE_RANGE_CLOEXEC - /* high range first to be able to log errors through stdout/err*/ - rc = close_range(cfd + 1, ~0U, 0); - if (rc < 0) { - fprintf(stderr, "Failed to close high range of FDs: %s", - strerror(errno)); - goto fallback; - } + /* We can't even report an error */ + if (cfd <= STDERR_FILENO) + return -EINVAL; - rc = close_range(0, cfd - 1, 0); - if (rc < 0) { - fprintf(stderr, "Failed to close low range of FDs: %s", - strerror(errno)); - goto fallback; +#ifdef HAVE_CLOSE_RANGE + if (cfd < STDERR_FILENO + 2) { + close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd); + } else { + rc = close_range(STDERR_FILENO + 1, cfd - 1, 0); + if (rc < 0) + goto fallback; } + + /* Close high range */ + rc = close_range(cfd + 1, ~0U, 0); +#else + goto fallback; /* make use of fallback to avoid compiler warnings */ #endif fallback: - /* - * This also needs to close stdout/stderr, as the application - * using libfuse might have closed these FDs and might be using - * it. Although issue is now that logging errors won't be possible - * after that. - */ - for (int fd = 0; fd <= max_fd; fd++) { - if (fd != cfd) - close(fd); + if (rc < 0) { + int max_fd = sysconf(_SC_OPEN_MAX) - 1; + + close_range_loop(STDERR_FILENO + 1, max_fd, cfd); } + + nullfd = open("/dev/null", O_RDWR); + if (nullfd < 0) { + perror("fusermount: cannot open /dev/null"); + return -errno; + } + + /* Redirect stdin, stdout, stderr to /dev/null */ + dup2(nullfd, STDIN_FILENO); + dup2(nullfd, STDOUT_FILENO); + dup2(nullfd, STDERR_FILENO); + if (nullfd > STDERR_FILENO) + close(nullfd); + + return 0; } int main(int argc, char *argv[]) @@ -1616,6 +1639,7 @@ int main(int argc, char *argv[]) goto err_out; } + { struct stat statbuf; fstat(cfd, &statbuf); @@ -1653,7 +1677,9 @@ int main(int argc, char *argv[]) Btw, we don't want to use daemon() function here because it forks and messes with the file descriptors. */ - close_inherited_fds(cfd); + res = close_inherited_fds(cfd); + if (res < 0) + exit(EXIT_FAILURE); setsid(); res = chdir("/"); From f52f71df3373185c4d18414aad57c4e8995a4393 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 22 Mar 2025 23:57:55 +0100 Subject: [PATCH 075/105] fuse: Fix want flag conversion 32-bit conn->want flags been left to be ABI compatible to 3.10, even though the so version was changed. The more recent way is to use fuse_set_feature_flag(), which will use conn->want_ext. Given that we now have two flags (want and want_ext), we need to convert and that brought several issues - If the application sets conn->want, that needs to be set into the lower 32 bit of conn->want_ext. As the application might actually unset values, it really has to be a copy and not just 'or' - fixed now. - convert_to_conn_want_ext() actually needs to check for _modified_ conn->want and conn->want_ext - convert_to_conn_want_ext() must consider being called from high and lowlevel interfact, with different want_ext_default and want_default values. It is only a failure, if the application changed both, conn->want and conn->want_ext. This function was failing in issue #1171, because high level fuse_fs_init() was changing values and then lowlevel do_init() was incorrectly failing on that. This also adds a new test (test_want_conversion) and sets values into example/{hello.c,hello_ll.c} Also some more internal users of conn->want are converted to fuse_{set,unset}_feature_flag(). Closes: https://github.com/libfuse/libfuse/issues/1171 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> (cherry picked from commit f68970cd235a7e14026ca0f6240428bbebe8223b) --- example/hello.c | 5 ++ example/hello_ll.c | 4 + lib/fuse.c | 33 +++++++- lib/fuse_i.h | 34 ++++++++ lib/fuse_lowlevel.c | 30 ++----- lib/helper.c | 4 +- lib/util.h | 27 +++++++ test/meson.build | 3 + test/test_want_conversion.c | 152 ++++++++++++++++++++++++++++++++++++ test/test_write_cache.c | 2 +- 10 files changed, 264 insertions(+), 30 deletions(-) create mode 100644 test/test_want_conversion.c diff --git a/example/hello.c b/example/hello.c index 6df817337..90919f4d9 100644 --- a/example/hello.c +++ b/example/hello.c @@ -57,6 +57,11 @@ static void *hello_init(struct fuse_conn_info *conn, { (void) conn; cfg->kernel_cache = 1; + + /* Test setting flags the old way */ + conn->want = FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_ASYNC_READ; + return NULL; } diff --git a/example/hello_ll.c b/example/hello_ll.c index 0fcb7fe55..12927cc2f 100644 --- a/example/hello_ll.c +++ b/example/hello_ll.c @@ -59,6 +59,10 @@ static void hello_ll_init(void *userdata, struct fuse_conn_info *conn) /* Disable the receiving and processing of FUSE_INTERRUPT requests */ conn->no_interrupt = 1; + + /* Test setting flags the old way */ + conn->want = FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_ASYNC_READ; } static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino, diff --git a/lib/fuse.c b/lib/fuse.c index 933542992..136f0c2bd 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -10,6 +10,8 @@ */ #define _GNU_SOURCE +#include "fuse.h" +#include <pthread.h> #include "fuse_config.h" #include "fuse_i.h" @@ -17,7 +19,9 @@ #include "fuse_opt.h" #include "fuse_misc.h" #include "fuse_kernel.h" +#include "util.h" +#include <stdint.h> #include <stdio.h> #include <string.h> #include <stdlib.h> @@ -2606,13 +2610,34 @@ void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, { fuse_get_context()->private_data = fs->user_data; if (!fs->op.write_buf) - conn->want &= ~FUSE_CAP_SPLICE_READ; + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); if (!fs->op.lock) - conn->want &= ~FUSE_CAP_POSIX_LOCKS; + fuse_unset_feature_flag(conn, FUSE_CAP_POSIX_LOCKS); if (!fs->op.flock) - conn->want &= ~FUSE_CAP_FLOCK_LOCKS; - if (fs->op.init) + fuse_unset_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + if (fs->op.init) { + uint64_t want_ext_default = conn->want_ext; + uint32_t want_default = fuse_lower_32_bits(conn->want_ext); + int rc; + + conn->want = want_default; fs->user_data = fs->op.init(conn, cfg); + + rc = convert_to_conn_want_ext(conn, want_ext_default, + want_default); + + if (rc != 0) { + /* + * This is a grave developer error, but + * we cannot return an error here, as the function + * signature does not allow it. + */ + fuse_log( + FUSE_LOG_ERR, + "fuse: Aborting due to invalid conn want flags.\n"); + _exit(EXIT_FAILURE); + } + } } static int fuse_init_intr_signal(int signum, int *installed); diff --git a/lib/fuse_i.h b/lib/fuse_i.h index ea04c34f4..6fbfc2d6a 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -8,8 +8,11 @@ #include "fuse.h" #include "fuse_lowlevel.h" +#include "util.h" +#include <stdint.h> #include <stdbool.h> +#include <errno.h> #define MIN(a, b) \ ({ \ @@ -222,3 +225,34 @@ int fuse_loop_cfg_verify(struct fuse_loop_config *config); /* room needed in buffer to accommodate header */ #define FUSE_BUFFER_HEADER_SIZE 0x1000 +/** + * Get the wanted capability flags, converting from old format if necessary + */ +static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn, + uint64_t want_ext_default, + uint32_t want_default) +{ + /* + * Convert want to want_ext if necessary. + * For the high level interface this function might be called + * twice, once from the high level interface and once from the + * low level interface. Both, with different want_ext_default and + * want_default values. In order to suppress a failure for the + * second call, we check if the lower 32 bits of want_ext are + * already set to the value of want. + */ + if (conn->want != want_default && + fuse_lower_32_bits(conn->want_ext) != conn->want) { + if (conn->want_ext != want_ext_default) { + fuse_log(FUSE_LOG_ERR, + "fuse: both 'want' and 'want_ext' are set\n"); + return -EINVAL; + } + + /* high bits from want_ext, low bits from want */ + conn->want_ext = fuse_higher_32_bits(conn->want_ext) | + conn->want; + } + + return 0; +} diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index d650944a3..c22b4a2d0 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -9,7 +9,6 @@ See the file COPYING.LIB */ -#include <stdbool.h> #define _GNU_SOURCE #include "fuse_config.h" @@ -20,6 +19,8 @@ #include "mount_util.h" #include "util.h" +#include <stdint.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <stddef.h> @@ -1997,25 +1998,6 @@ static bool want_flags_valid(uint64_t capable, uint64_t want) return true; } -/** - * Get the wanted capability flags, converting from old format if necessary - */ -static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn, - uint64_t want_ext_default) -{ - /* Convert want to want_ext if necessary */ - if (conn->want != 0) { - if (conn->want_ext != want_ext_default) { - fuse_log(FUSE_LOG_ERR, - "fuse: both 'want' and 'want_ext' are set\n"); - return -EINVAL; - } - conn->want_ext |= conn->want; - } - - return 0; -} - /* Prevent bogus data races (bogus since "init" is called before * multi-threading becomes relevant */ static __attribute__((no_sanitize("thread"))) @@ -2177,11 +2159,12 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) se->got_init = 1; if (se->op.init) { uint64_t want_ext_default = se->conn.want_ext; + uint32_t want_default = fuse_lower_32_bits(se->conn.want_ext); int rc; // Apply the first 32 bits of capable_ext to capable - se->conn.capable = - (uint32_t)(se->conn.capable_ext & 0xFFFFFFFF); + se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext); + se->conn.want = want_default; se->op.init(se->userdata, &se->conn); @@ -2190,7 +2173,8 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) * se->conn.want_ext * Userspace might still use conn.want - we need to convert it */ - rc = convert_to_conn_want_ext(&se->conn, want_ext_default); + rc = convert_to_conn_want_ext(&se->conn, want_ext_default, + want_default); if (rc != 0) { fuse_reply_err(req, EPROTO); se->error = -EPROTO; diff --git a/lib/helper.c b/lib/helper.c index a7b2fe002..59dd48881 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -424,9 +424,9 @@ void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, conn->max_readahead = opts->max_readahead; #define LL_ENABLE(cond,cap) \ - if (cond) conn->want |= (cap) + if (cond) conn->want_ext |= (cap) #define LL_DISABLE(cond,cap) \ - if (cond) conn->want &= ~(cap) + if (cond) conn->want_ext &= ~(cap) LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ); LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ); diff --git a/lib/util.h b/lib/util.h index 74ce74845..0c4c25839 100644 --- a/lib/util.h +++ b/lib/util.h @@ -1,3 +1,30 @@ +#ifndef FUSE_UTIL_H_ +#define FUSE_UTIL_H_ + +#include <stdint.h> + #define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1)) int libfuse_strtol(const char *str, long *res); + +/** + * Return the low bits of a number + */ +static inline uint32_t fuse_lower_32_bits(uint64_t nr) +{ + return (uint32_t)(nr & 0xffffffff); +} + +/** + * Return the high bits of a number + */ +static inline uint64_t fuse_higher_32_bits(uint64_t nr) +{ + return nr & ~0xffffffffULL; +} + +#ifndef FUSE_VAR_UNUSED +#define FUSE_VAR_UNUSED(var) (__attribute__((unused)) var) +#endif + +#endif diff --git a/test/meson.build b/test/meson.build index 3d74b9ae0..599703064 100644 --- a/test/meson.build +++ b/test/meson.build @@ -16,6 +16,9 @@ td += executable('readdir_inode', 'readdir_inode.c', td += executable('release_unlink_race', 'release_unlink_race.c', dependencies: [ libfuse_dep ], install: false) +td += executable('test_want_conversion', 'test_want_conversion.c', + dependencies: [ libfuse_dep ], + install: false) test_scripts = [ 'conftest.py', 'pytest.ini', 'test_examples.py', 'util.py', 'test_ctests.py', 'test_custom_io.py' ] diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c new file mode 100644 index 000000000..935b58d7d --- /dev/null +++ b/test/test_want_conversion.c @@ -0,0 +1,152 @@ +#include "util.h" +#include <string.h> +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17) + +#include "fuse_i.h" +#include <stdio.h> +#include <assert.h> +#include <inttypes.h> + +static void print_conn_info(const char *prefix, struct fuse_conn_info *conn) +{ + printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64 "\n", prefix, + conn->want, conn->want_ext); +} + +static void application_init(struct fuse_conn_info *conn) +{ + /* Simulate application init */ + conn->want |= FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_SPLICE_READ; +} + +static void test_fuse_fs_init(struct fuse_conn_info *conn) +{ + uint64_t want_ext_default = conn->want_ext; + uint32_t want_default = fuse_lower_32_bits(conn->want_ext); + int rc; + + /* High-level init */ + fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT); + + conn->want = want_default; + + application_init(conn); + + rc = convert_to_conn_want_ext(conn, want_ext_default, want_default); + assert(rc == 0); +} + +static void test_do_init(struct fuse_conn_info *conn) +{ + /* Initial setup */ + conn->capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | + FUSE_CAP_FLOCK_LOCKS | FUSE_CAP_EXPORT_SUPPORT; + conn->capable = fuse_lower_32_bits(conn->capable_ext); + conn->want_ext = conn->capable_ext; + + print_conn_info("Initial state", conn); + + uint64_t want_ext_default = conn->want_ext; + uint32_t want_default = fuse_lower_32_bits(conn->want_ext); + int rc; + + conn->want = want_default; + conn->capable = fuse_lower_32_bits(conn->capable_ext); + + test_fuse_fs_init(conn); + + rc = convert_to_conn_want_ext(conn, want_ext_default, want_default); + assert(rc == 0); + + /* Verify all expected flags are set */ + assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ)); + assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE); + assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE); + assert(conn->want_ext & FUSE_CAP_POSIX_LOCKS); + assert(conn->want_ext & FUSE_CAP_FLOCK_LOCKS); + assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT); + assert(conn->want_ext & FUSE_CAP_ASYNC_READ); + /* Verify no other flags are set */ + assert(conn->want_ext == + (FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE | + FUSE_CAP_POSIX_LOCKS | FUSE_CAP_FLOCK_LOCKS | + FUSE_CAP_EXPORT_SUPPORT | FUSE_CAP_ASYNC_READ)); + + print_conn_info("After init", conn); +} + +static void test_want_conversion_basic(void) +{ + struct fuse_conn_info conn = { 0 }; + + printf("\nTesting basic want conversion:\n"); + test_do_init(&conn); + print_conn_info("After init", &conn); +} + +static void test_want_conversion_conflict(void) +{ + struct fuse_conn_info conn = { 0 }; + int rc; + + printf("\nTesting want conversion conflict:\n"); + + /* Test conflicting values */ + /* Initialize like fuse_lowlevel.c does */ + conn.capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | + FUSE_CAP_FLOCK_LOCKS; + conn.capable = fuse_lower_32_bits(conn.capable_ext); + conn.want_ext = conn.capable_ext; + conn.want = fuse_lower_32_bits(conn.want_ext); + print_conn_info("Test conflict initial", &conn); + + /* Initialize default values like in basic test */ + uint64_t want_ext_default_ll = conn.want_ext; + uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll); + + /* Simulate application init modifying capabilities */ + conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */ + conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */ + + rc = convert_to_conn_want_ext(&conn, want_ext_default_ll, + want_default_ll); + assert(rc == -EINVAL); + print_conn_info("Test conflict after", &conn); + + printf("Want conversion conflict test passed\n"); +} + +static void test_want_conversion_high_bits(void) +{ + struct fuse_conn_info conn = { 0 }; + int rc; + + printf("\nTesting want conversion high bits preservation:\n"); + + /* Test high bits preservation */ + conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ; + conn.want = fuse_lower_32_bits(conn.want_ext); + print_conn_info("Test high bits initial", &conn); + + uint64_t want_ext_default_ll = conn.want_ext; + uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll); + + rc = convert_to_conn_want_ext(&conn, want_ext_default_ll, + want_default_ll); + assert(rc == 0); + assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ)); + print_conn_info("Test high bits after", &conn); + + printf("Want conversion high bits test passed\n"); +} + +int main(void) +{ + test_want_conversion_basic(); + test_want_conversion_conflict(); + test_want_conversion_high_bits(); + return 0; +} diff --git a/test/test_write_cache.c b/test/test_write_cache.c index 0c8fa7e8b..9f21f02fb 100644 --- a/test/test_write_cache.c +++ b/test/test_write_cache.c @@ -69,7 +69,7 @@ static void tfs_init (void *userdata, struct fuse_conn_info *conn) if(options.writeback) { assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE)); - conn->want |= FUSE_CAP_WRITEBACK_CACHE; + fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); } } From 15bb63bbebc7bd11871f60e9c535035e158eb065 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 24 Mar 2025 11:40:21 +0100 Subject: [PATCH 076/105] Release version 3.17.1 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- AUTHORS | 4 ++++ ChangeLog.rst | 14 ++++++++++---- meson.build | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 19a172068..96f25d9f0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -267,3 +267,7 @@ Zegang <zegang.luo@qq.com> # New authors since fuse-3.17.1-rc0 Maksim Harbachou <maksim.harbachou@resilio.com> Vassili Tchersky <vt+git@vbcy.org> + +# New authors since fuse-3.17.1-rc1 +jnr0006 <jacob.nick.riley@gmail.com> +Vassili Tchersky <vt+git@vbc.su> diff --git a/ChangeLog.rst b/ChangeLog.rst index 6b04148c7..6c157da52 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,10 +1,16 @@ +libfuse 3.17.1 (2025-03-24) +=========================== +* fuse: Fix want conn.want flag conversion +* Prevent re-usage of stdio FDs for fusermount +* PanFS added to fusermount whitelist + libfuse 3.17.1-rc1 (2025-02-18) =============================== -- several BSD fixes -- x86 (32bit) build fixes -- nested declarations moved out of the inlined functions to avoid +* several BSD fixes +* x86 (32bit) build fixes +* nested declarations moved out of the inlined functions to avoid build warnings -- signify public key added for future 3.18 +* signify public key added for future 3.18 libfuse 3.17.1-rc0 (2025-02.10) =============================== diff --git a/meson.build b/meson.build index 1d961d021..494bb3d31 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfuse3', ['c'], - version: '3.17.1-rc1', # Version with RC suffix + version: '3.17.1', meson_version: '>= 0.51.0', default_options: [ 'buildtype=debugoptimized', From e65395521bae162b72f038f02ccbe5686f7bf88c Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Fri, 7 Mar 2025 22:43:50 +0100 Subject: [PATCH 077/105] fuse_session_receive_buf: Fix the pipe buf size This fixes dynamic buffer allocation in commit 0e0f43b79b9b ("Reallocate fuse_session buffer...") I noticed that when I increased the default fuse buf size as possible in recent kernels. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_i.h | 4 ++-- lib/fuse_lowlevel.c | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 6fbfc2d6a..ee0d2980d 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -75,14 +75,14 @@ struct fuse_session { int broken_splice_nonblock; uint64_t notify_ctr; struct fuse_notify_req notify_list; - size_t bufsize; + _Atomic size_t bufsize; int error; /* This is useful if any kind of ABI incompatibility is found at * a later version, to 'fix' it at run time. */ struct libfuse_version version; - bool buf_reallocable; + _Atomic bool buf_reallocable; }; struct fuse_chan { diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index c22b4a2d0..7df3c7ab7 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3026,11 +3026,14 @@ static int _fuse_session_receive_buf(struct fuse_session *se, { int err; ssize_t res; - size_t bufsize = se->bufsize; + size_t bufsize; #ifdef HAVE_SPLICE struct fuse_ll_pipe *llp; struct fuse_buf tmpbuf; +pipe_retry: + bufsize = se->bufsize; + if (se->conn.proto_minor < 14 || !(se->conn.want_ext & FUSE_CAP_SPLICE_READ)) goto fallback; @@ -3075,6 +3078,13 @@ static int _fuse_session_receive_buf(struct fuse_session *se, fuse_session_exit(se); return 0; } + + /* FUSE_INIT might have increased the required bufsize */ + if (err == EINVAL && bufsize < se->bufsize) { + fuse_ll_clear_pipe(se); + goto pipe_retry; + } + if (err != EINTR && err != EAGAIN) perror("fuse: splice from device"); return -err; From 42a8b7614da0e0151b38e276b1cb6c6832126d50 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 11 Mar 2025 22:21:09 +0100 Subject: [PATCH 078/105] fuse_lowlevel: Simplify se->buf_reallocable se->buf_reallocable is true when reading /dev/fuse is handled from internal functions - we can set the variable in fuse_session_receive_buf_internal(). With that we also don't need to have it an _Atomic variable anymore. In _fuse_session_receive_buf() we can use "bool internal" to check if the buffer can be re-allocated. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_i.h | 4 +++- lib/fuse_lowlevel.c | 23 ++++++++++++----------- lib/util.h | 3 +++ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/fuse_i.h b/lib/fuse_i.h index ee0d2980d..23fcaa6d4 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -82,7 +82,9 @@ struct fuse_session { * a later version, to 'fix' it at run time. */ struct libfuse_version version; - _Atomic bool buf_reallocable; + + /* true if reading requests from /dev/fuse are handled internally */ + bool buf_reallocable; }; struct fuse_chan { diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 7df3c7ab7..17bc81692 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3120,8 +3120,6 @@ static int _fuse_session_receive_buf(struct fuse_session *se, return -ENOMEM; } buf->mem_size = se->bufsize; - if (internal) - se->buf_reallocable = true; } buf->size = se->bufsize; buf->flags = 0; @@ -3161,13 +3159,9 @@ static int _fuse_session_receive_buf(struct fuse_session *se, return -ENOMEM; } buf->mem_size = se->bufsize; - if (internal) - se->buf_reallocable = true; } restart: - if (se->buf_reallocable) - bufsize = buf->mem_size; if (se->io != NULL) { /* se->io->read is never NULL if se->io is not NULL as specified by fuse_session_custom_io()*/ @@ -3181,9 +3175,10 @@ static int _fuse_session_receive_buf(struct fuse_session *se, if (fuse_session_exited(se)) return 0; if (res == -1) { - if (err == EINVAL && se->buf_reallocable && - se->bufsize > buf->mem_size) { - void *newbuf = buf_alloc(se->bufsize, internal); + if (err == EINVAL && internal && se->bufsize > buf->mem_size) { + /* FUSE_INIT might have increased the required bufsize */ + bufsize = se->bufsize; + void *newbuf = buf_alloc(bufsize, internal); if (!newbuf) { fuse_log( FUSE_LOG_ERR, @@ -3192,8 +3187,7 @@ static int _fuse_session_receive_buf(struct fuse_session *se, } fuse_buf_free(buf); buf->mem = newbuf; - buf->mem_size = se->bufsize; - se->buf_reallocable = true; + buf->mem_size = bufsize; goto restart; } @@ -3235,6 +3229,13 @@ int fuse_session_receive_buf_internal(struct fuse_session *se, struct fuse_buf *buf, struct fuse_chan *ch) { + /* + * if run internally thread buffers are from libfuse - we can + * reallocate them + */ + if (unlikely(!se->got_init) && !se->buf_reallocable) + se->buf_reallocable = true; + return _fuse_session_receive_buf(se, buf, ch, true); } diff --git a/lib/util.h b/lib/util.h index 0c4c25839..508fafb12 100644 --- a/lib/util.h +++ b/lib/util.h @@ -5,6 +5,9 @@ #define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1)) +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + int libfuse_strtol(const char *str, long *res); /** From fe5f26a194355317e884ed20fe43c5546e40fabe Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Wed, 19 Mar 2025 18:46:33 +0100 Subject: [PATCH 079/105] Fix a comment typo "passed to the filesystem.n" Closes: https://github.com/libfuse/libfuse/issues/1168 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- include/fuse_lowlevel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 93bcba296..c7b44d963 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -292,7 +292,7 @@ struct fuse_lowlevel_ops { * If writeback caching is enabled, the kernel may have a * better idea of a file's length than the FUSE file system * (eg if there has been a write that extended the file size, - * but that has not yet been passed to the filesystem.n + * but that has not yet been passed to the filesystem. * * In this case, the st_size value provided by the file system * will be ignored. From 120868e75c824063f8010d49d27221db41e3c6f3 Mon Sep 17 00:00:00 2001 From: Giulio Benetti <giulio.benetti@benettiengineering.com> Date: Tue, 1 Apr 2025 00:53:07 +0200 Subject: [PATCH 080/105] Fix build with kernel < 5.9 linux/close_range.h is only available since kernel 5.9 and https://github.com/torvalds/linux/commit/60997c3d45d9a67daf01c56d805ae4fec37e0bd8 resulting in the following build failure: ../util/fusermount.c:40:10: fatal error: linux/close_range.h: No such file or directory So let's check for header presence and emit HAVE_LINUX_CLOSE_RANGE_H accordingly and check for it when including <linux/close_range.h> and calling close_range() instead of checking for close_range() function in meson and check against HAVE_CLOSE_RANGE. Signed-off-by: Giulio Benetti <giulio.benetti@benettiengineering.com> --- meson.build | 6 +++++- util/fusermount.c | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 494bb3d31..ffe46239a 100644 --- a/meson.build +++ b/meson.build @@ -72,7 +72,7 @@ private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) # Test for presence of some functions test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2', 'splice', 'vmsplice', 'posix_fallocate', 'fdatasync', - 'utimensat', 'copy_file_range', 'fallocate', 'close_range' ] + 'utimensat', 'copy_file_range', 'fallocate' ] foreach func : test_funcs private_cfg.set('HAVE_' + func.to_upper(), cc.has_function(func, prefix: include_default, args: args_default)) @@ -84,6 +84,10 @@ private_cfg.set('HAVE_ICONV', private_cfg.set('HAVE_BACKTRACE', cc.has_function('backtrace', prefix: '#include <execinfo.h>')) +# Test if headers exist +private_cfg.set('HAVE_LINUX_CLOSE_RANGE_H', + cc.check_header('#include <linux/close_range.h>')) + # Test if structs have specific member private_cfg.set('HAVE_STRUCT_STAT_ST_ATIM', cc.has_member('struct stat', 'st_atim', diff --git a/util/fusermount.c b/util/fusermount.c index dbd947c60..da6d5f2be 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -36,7 +36,7 @@ #include <stdbool.h> #include <sys/vfs.h> -#ifdef HAVE_CLOSE_RANGE +#ifdef HAVE_LINUX_CLOSE_RANGE_H #include <linux/close_range.h> #endif @@ -1477,7 +1477,7 @@ static int close_inherited_fds(int cfd) if (cfd <= STDERR_FILENO) return -EINVAL; -#ifdef HAVE_CLOSE_RANGE +#ifdef HAVE_LINUX_CLOSE_RANGE_H if (cfd < STDERR_FILENO + 2) { close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd); } else { From cba63f915c6217f64d253ba2a304c4b3009666cb Mon Sep 17 00:00:00 2001 From: Giulio Benetti <giulio.benetti@benettiengineering.com> Date: Fri, 4 Apr 2025 22:17:49 +0200 Subject: [PATCH 081/105] Fix static_assert build failure with C++ version < 11 At the moment build fails due to lack of static_assert: https://gitlab.com/jolivain/buildroot/-/jobs/9606292537 this means that the check per date is not enough, so let's use meson to check if static_assert() is present or not and simplify fuse_static_assert() definition by only checking HAVE_STATIC_ASSERT. Signed-off-by: Giulio Benetti <giulio.benetti@benettiengineering.com> --- include/fuse_common.h | 4 +--- meson.build | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index 77efc5de5..582505fa9 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -30,9 +30,7 @@ #define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min)) #define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION) -#if (defined(__cplusplus) && __cplusplus >= 201103L) || \ - (!defined(__cplusplus) && defined(__STDC_VERSION__) && \ - __STDC_VERSION__ >= 201112L) +#ifdef HAVE_STATIC_ASSERT #define fuse_static_assert(condition, message) static_assert(condition, message) #else #define fuse_static_assert(condition, message) diff --git a/meson.build b/meson.build index ffe46239a..bd505aea9 100644 --- a/meson.build +++ b/meson.build @@ -72,7 +72,7 @@ private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) # Test for presence of some functions test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2', 'splice', 'vmsplice', 'posix_fallocate', 'fdatasync', - 'utimensat', 'copy_file_range', 'fallocate' ] + 'utimensat', 'copy_file_range', 'fallocate', 'static_assert' ] foreach func : test_funcs private_cfg.set('HAVE_' + func.to_upper(), cc.has_function(func, prefix: include_default, args: args_default)) From 8adbc60284ee6636912368c68bea3f94829e6992 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 7 Apr 2025 11:50:19 +0200 Subject: [PATCH 082/105] fuse_lowlevel: Set bufsize if HAVE_SPLICE is not define and avoid race These are several buf size fixes 1) Set bufsize when HAVE_SPLICE is not defined. Addresses https://github.com/libfuse/libfuse/issues/1184 2) Check in the read retry condition for bufsize, i.e. the value passed to read and not for the buf->mem_size. Using buf->mem_size can be startup racy. Additionally we now also set bufsize on allocation to avoid these races. 3) Allocation and value assigned needs to follow the pattern bufsize = se->bufsize; buf_alloc(bufsize, internal) buf->mem_size = bufsize; I.e. bufsize has to be retrieved first, as se->bufsize might change at anytime - the value used for allocation and must not differ from the value actually used. This also tries to set large sizes in passthrough_hp, to catch issues in xfstests - though requires to set /proc/sys/fs/fuse/max_pages_limit Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- example/passthrough_hp.cc | 3 +++ lib/fuse_lowlevel.c | 16 ++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index 41904e501..d87ca5f66 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -226,6 +226,9 @@ static void sfs_init(void *userdata, fuse_conn_info *conn) { /* Disable the receiving and processing of FUSE_INTERRUPT requests */ conn->no_interrupt = 1; + + /* Try a large IO by default */ + conn->max_write = 4 * 1024 * 1024; } diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 17bc81692..9ebaaf08e 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3112,16 +3112,16 @@ static int _fuse_session_receive_buf(struct fuse_session *se, struct fuse_bufvec dst = { .count = 1 }; if (!buf->mem) { - buf->mem = buf_alloc(se->bufsize, internal); + buf->mem = buf_alloc(bufsize, internal); if (!buf->mem) { fuse_log( FUSE_LOG_ERR, "fuse: failed to allocate read buffer\n"); return -ENOMEM; } - buf->mem_size = se->bufsize; + buf->mem_size = bufsize; } - buf->size = se->bufsize; + buf->size = bufsize; buf->flags = 0; dst.buf[0] = *buf; @@ -3151,14 +3151,18 @@ static int _fuse_session_receive_buf(struct fuse_session *se, fallback: #endif + bufsize = internal ? buf->mem_size : se->bufsize; if (!buf->mem) { - buf->mem = buf_alloc(se->bufsize, internal); + bufsize = se->bufsize; /* might have changed */ + buf->mem = buf_alloc(bufsize, internal); if (!buf->mem) { fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate read buffer\n"); return -ENOMEM; } - buf->mem_size = se->bufsize; + + if (internal) + buf->mem_size = bufsize; } restart: @@ -3175,7 +3179,7 @@ static int _fuse_session_receive_buf(struct fuse_session *se, if (fuse_session_exited(se)) return 0; if (res == -1) { - if (err == EINVAL && internal && se->bufsize > buf->mem_size) { + if (err == EINVAL && internal && se->bufsize > bufsize) { /* FUSE_INIT might have increased the required bufsize */ bufsize = se->bufsize; void *newbuf = buf_alloc(bufsize, internal); From cf87c46bcdadb67a9b62833178feb24ab3d69708 Mon Sep 17 00:00:00 2001 From: Ben Dooks <ben.dooks@codethink.co.uk> Date: Wed, 9 Apr 2025 07:38:45 +0100 Subject: [PATCH 083/105] lib: remove second fuse_main_real_versioned declaration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Newer gccs now use -Werror=redundant-decls which means that anyone including fuse.h is getting an error of: /usr/include/fuse3/fuse.h:959:5: error: redundant redeclaration of ‘fuse_main_real_versioned’ [-Werror=redundant-decls] 959 | int fuse_main_real_versioned(int argc, char *argv[], | ^~~~~~~~~~~~~~~~~~~~~~~~ /usr/include/fuse3/fuse.h:885:5: note: previous declaration of ‘fuse_main_real_versioned’ with type ‘int(int, char **, const struct fuse_operations *, size_t, struct libfuse_version *, void *)’ {aka ‘int(int, char **, const struct fuse_operations *, long unsigned int, struct libfuse_version *, void *)’} 885 | int fuse_main_real_versioned(int argc, char *argv[], | ^~~~~~~~~~~~~~~~~~~~~~~~ Signed-off-by: Ben Dooks <ben.dooks@codethink.co.uk> --- include/fuse.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/fuse.h b/include/fuse.h index c94b62851..4582cc7ac 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -956,9 +956,6 @@ static inline int fuse_main_real(int argc, char *argv[], * * Example usage, see hello.c */ -int fuse_main_real_versioned(int argc, char *argv[], - const struct fuse_operations *op, size_t op_size, - struct libfuse_version *version, void *user_data); static inline int fuse_main_fn(int argc, char *argv[], const struct fuse_operations *op, void *user_data) From db59dd41ab4ed2ca89dae32aa6f106f82bb1056e Mon Sep 17 00:00:00 2001 From: Joanne Koong <joannelkoong@gmail.com> Date: Wed, 12 Feb 2025 11:00:11 -0800 Subject: [PATCH 084/105] passthrough_hp: enable splice move by default This commit enables splice moves by default, unless the caller opts into nosplice. Signed-off-by: Joanne Koong <joannelkoong@gmail.com> --- example/passthrough_hp.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index d87ca5f66..8b5214c21 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -165,7 +165,7 @@ static Fs fs{}; #define FUSE_BUF_COPY_FLAGS \ (fs.nosplice ? \ FUSE_BUF_NO_SPLICE : \ - static_cast<fuse_buf_copy_flags>(0)) + static_cast<fuse_buf_copy_flags>(FUSE_BUF_SPLICE_MOVE)) static Inode& get_inode(fuse_ino_t ino) { @@ -202,13 +202,15 @@ static void sfs_init(void *userdata, fuse_conn_info *conn) { if (fs.nosplice) { // FUSE_CAP_SPLICE_READ is enabled in libfuse3 by default, // see do_init() in in fuse_lowlevel.c - // Just unset both, in case FUSE_CAP_SPLICE_WRITE would also get enabled - // by default. + // Just unset all, in case FUSE_CAP_SPLICE_WRITE or + // FUSE_CAP_SPLICE_MOVE would also get enabled by default. fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_WRITE); + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_MOVE); } else { fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_WRITE); fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_READ); + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_MOVE); } /* This is a local file system - no network coherency needed */ From 08ac8aee19938ce86d45c5776c013bc4d9303750 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Wed, 16 Apr 2025 00:24:42 +0200 Subject: [PATCH 085/105] conn: prevent duplicate flag conversion in high-level interface The high-level interface triggers flag conversion twice: once in the high-level init and once in the low-level init. This caused false "both 'want' and 'want_ext' are set" errors when using fuse_set_feature_flag() or fuse_unset_feature_flag(). The existing check for duplicate conversion only worked when 32-bit flags were set directly. When using the preferred flag manipulation functions, conn->want and the lower 32 bits of conn->want_ext would differ, triggering the error. Fix this by synchronizing conn->want with the lower 32 bits of conn->want_ext after conversion, ensuring consistent state for subsequent calls. Closes: https://github.com/libfuse/libfuse/issues/1171 Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- example/hello.c | 4 +- lib/fuse_i.h | 3 + test/hello.c | 180 ++++++++++++++++++++++++++++++++++++ test/meson.build | 2 +- test/test_examples.py | 11 ++- test/test_want_conversion.c | 30 ++++-- 6 files changed, 215 insertions(+), 15 deletions(-) create mode 100644 test/hello.c diff --git a/example/hello.c b/example/hello.c index 90919f4d9..d9f01b9f0 100644 --- a/example/hello.c +++ b/example/hello.c @@ -59,8 +59,8 @@ static void *hello_init(struct fuse_conn_info *conn, cfg->kernel_cache = 1; /* Test setting flags the old way */ - conn->want = FUSE_CAP_ASYNC_READ; - conn->want &= ~FUSE_CAP_ASYNC_READ; + fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ); + fuse_unset_feature_flag(conn, FUSE_CAP_ASYNC_READ); return NULL; } diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 23fcaa6d4..48b8294f3 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -256,5 +256,8 @@ static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn, conn->want; } + /* ensure there won't be a second conversion */ + conn->want = fuse_lower_32_bits(conn->want_ext); + return 0; } diff --git a/test/hello.c b/test/hello.c new file mode 100644 index 000000000..a07df0e48 --- /dev/null +++ b/test/hello.c @@ -0,0 +1,180 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> + * + * This program can be distributed under the terms of the GNU GPLv2. + * See the file COPYING. + */ + +/** @file + * + * minimal example filesystem using high-level API + * + * Compile with: + * + * gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello + * + * ## Source code ## + * \include hello.c + */ + +#define FUSE_USE_VERSION 31 + +#include <fuse.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <assert.h> + +/* + * Command line options + * + * We can't set default values for the char* fields here because + * fuse_opt_parse would attempt to free() them when the user specifies + * different values on the command line. + */ +static struct options { + const char *filename; + const char *contents; + int show_help; +} options; + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--name=%s", filename), OPTION("--contents=%s", contents), + OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END +}; + +static void *hello_init(struct fuse_conn_info *conn, struct fuse_config *cfg) +{ + (void)conn; + cfg->kernel_cache = 1; + + /* Test setting flags the old way */ + conn->want = FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_ASYNC_READ; + + return NULL; +} + +static int hello_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void)fi; + int res = 0; + + memset(stbuf, 0, sizeof(struct stat)); + if (strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + } else if (strcmp(path + 1, options.filename) == 0) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(options.contents); + } else + res = -ENOENT; + + return res; +} + +static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + (void)offset; + (void)fi; + (void)flags; + + if (strcmp(path, "/") != 0) + return -ENOENT; + + filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + + return 0; +} + +static int hello_open(const char *path, struct fuse_file_info *fi) +{ + if (strcmp(path + 1, options.filename) != 0) + return -ENOENT; + + if ((fi->flags & O_ACCMODE) != O_RDONLY) + return -EACCES; + + return 0; +} + +static int hello_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + size_t len; + (void)fi; + if (strcmp(path + 1, options.filename) != 0) + return -ENOENT; + + len = strlen(options.contents); + if (offset < len) { + if (offset + size > len) + size = len - offset; + memcpy(buf, options.contents + offset, size); + } else + size = 0; + + return size; +} + +static const struct fuse_operations hello_oper = { + .init = hello_init, + .getattr = hello_getattr, + .readdir = hello_readdir, + .open = hello_open, + .read = hello_read, +}; + +static void show_help(const char *progname) +{ + printf("usage: %s [options] <mountpoint>\n\n", progname); + printf("File-system specific options:\n" + " --name=<s> Name of the \"hello\" file\n" + " (default: \"hello\")\n" + " --contents=<s> Contents \"hello\" file\n" + " (default \"Hello, World!\\n\")\n" + "\n"); +} + +int main(int argc, char *argv[]) +{ + int ret; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + + /* Set defaults -- we have to use strdup so that + * fuse_opt_parse can free the defaults if other + * values are specified + */ + options.filename = strdup("hello"); + options.contents = strdup("Hello World!\n"); + + /* Parse options */ + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + /* When --help is specified, first print our own file-system + * specific help text, then signal fuse_main to show + * additional help (by adding `--help` to the options again) + * without usage: line (by setting argv[0] to the empty + * string) + */ + if (options.show_help) { + show_help(argv[0]); + assert(fuse_opt_add_arg(&args, "--help") == 0); + args.argv[0][0] = '\0'; + } + + ret = fuse_main(args.argc, args.argv, &hello_oper, NULL); + fuse_opt_free_args(&args); + return ret; +} diff --git a/test/meson.build b/test/meson.build index 599703064..56568d8f7 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,6 +1,6 @@ # Compile helper programs td = [] -foreach prog: [ 'test_write_cache', 'test_setattr' ] +foreach prog: [ 'test_write_cache', 'test_setattr', 'hello' ] td += executable(prog, prog + '.c', include_directories: include_dirs, link_with: [ libfuse ], diff --git a/test/test_examples.py b/test/test_examples.py index 54a2f88f9..9c8b77eec 100755 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -44,8 +44,13 @@ def name_generator(__ctr=[0]): options.append('clone_fd') def invoke_directly(mnt_dir, name, options): - cmdline = base_cmdline + [ pjoin(basename, 'example', name), - '-f', mnt_dir, '-o', ','.join(options) ] + # Handle test/hello specially since it's not in example/ + if name.startswith('test/'): + path = pjoin(basename, name) + else: + path = pjoin(basename, 'example', name) + + cmdline = base_cmdline + [ path, '-f', mnt_dir, '-o', ','.join(options) ] if name == 'hello_ll': # supports single-threading only cmdline.append('-s') @@ -88,7 +93,7 @@ def readdir_inode(dir): @pytest.mark.parametrize("cmdline_builder", (invoke_directly, invoke_mount_fuse, invoke_mount_fuse_drop_privileges)) @pytest.mark.parametrize("options", powerset(options)) -@pytest.mark.parametrize("name", ('hello', 'hello_ll')) +@pytest.mark.parametrize("name", ('hello', 'hello_ll', 'test/hello')) def test_hello(tmpdir, name, options, cmdline_builder, output_checker): logger = logging.getLogger(__name__) mnt_dir = str(tmpdir) diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c index 935b58d7d..bee23cc6e 100644 --- a/test/test_want_conversion.c +++ b/test/test_want_conversion.c @@ -1,11 +1,11 @@ #include "util.h" -#include <string.h> #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17) #include "fuse_i.h" #include <stdio.h> #include <assert.h> #include <inttypes.h> +#include <stdbool.h> static void print_conn_info(const char *prefix, struct fuse_conn_info *conn) { @@ -13,14 +13,21 @@ static void print_conn_info(const char *prefix, struct fuse_conn_info *conn) conn->want, conn->want_ext); } -static void application_init(struct fuse_conn_info *conn) +static void application_init_old_style(struct fuse_conn_info *conn) { - /* Simulate application init */ + /* Simulate application init the old style */ conn->want |= FUSE_CAP_ASYNC_READ; conn->want &= ~FUSE_CAP_SPLICE_READ; } -static void test_fuse_fs_init(struct fuse_conn_info *conn) +static void application_init_new_style(struct fuse_conn_info *conn) +{ + /* Simulate application init the new style */ + fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ); + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); +} + +static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style) { uint64_t want_ext_default = conn->want_ext; uint32_t want_default = fuse_lower_32_bits(conn->want_ext); @@ -31,18 +38,22 @@ static void test_fuse_fs_init(struct fuse_conn_info *conn) conn->want = want_default; - application_init(conn); + if (new_style) + application_init_new_style(conn); + else + application_init_old_style(conn); rc = convert_to_conn_want_ext(conn, want_ext_default, want_default); assert(rc == 0); } -static void test_do_init(struct fuse_conn_info *conn) +static void test_do_init(struct fuse_conn_info *conn, bool new_style) { /* Initial setup */ conn->capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | - FUSE_CAP_FLOCK_LOCKS | FUSE_CAP_EXPORT_SUPPORT; + FUSE_CAP_FLOCK_LOCKS | FUSE_CAP_EXPORT_SUPPORT | + FUSE_CAP_ASYNC_READ; conn->capable = fuse_lower_32_bits(conn->capable_ext); conn->want_ext = conn->capable_ext; @@ -55,7 +66,7 @@ static void test_do_init(struct fuse_conn_info *conn) conn->want = want_default; conn->capable = fuse_lower_32_bits(conn->capable_ext); - test_fuse_fs_init(conn); + test_fuse_fs_init(conn, new_style); rc = convert_to_conn_want_ext(conn, want_ext_default, want_default); assert(rc == 0); @@ -82,7 +93,8 @@ static void test_want_conversion_basic(void) struct fuse_conn_info conn = { 0 }; printf("\nTesting basic want conversion:\n"); - test_do_init(&conn); + test_do_init(&conn, false); + test_do_init(&conn, true); print_conn_info("After init", &conn); } From 2dcc57693c3ff1d4bcaa52f103633b1fc0042ba4 Mon Sep 17 00:00:00 2001 From: Giulio Benetti <giulio.benetti@benettiengineering.com> Date: Wed, 23 Apr 2025 14:28:29 +0200 Subject: [PATCH 086/105] Check if pthread_setname_np() exists before use it Since pthread_setname_np() is the only pthread function that requires NPTL and it basically only set thread name, let's check if pthread_setname_np() does exist, otherwise let's not call pthread_setname_np() to shrink dependencies. Signed-off-by: Giulio Benetti <giulio.benetti@benettiengineering.com> --- lib/fuse.c | 2 ++ lib/fuse_loop_mt.c | 2 ++ meson.build | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/fuse.c b/lib/fuse.c index 136f0c2bd..49f57112a 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -4917,7 +4917,9 @@ static void *fuse_prune_nodes(void *fuse) struct fuse *f = fuse; int sleep_time; +#ifdef HAVE_PTHREAD_SETNAME_NP pthread_setname_np(pthread_self(), "fuse_prune_nodes"); +#endif while(1) { sleep_time = fuse_clean_cache(f); diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index 95316f7fd..d6be99849 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -132,7 +132,9 @@ static void *fuse_do_work(void *data) struct fuse_worker *w = (struct fuse_worker *) data; struct fuse_mt *mt = w->mt; +#ifdef HAVE_PTHREAD_SETNAME_NP pthread_setname_np(pthread_self(), "fuse_worker"); +#endif while (!fuse_session_exited(mt->se)) { int isforget = 0; diff --git a/meson.build b/meson.build index bd505aea9..39cff23ba 100644 --- a/meson.build +++ b/meson.build @@ -72,7 +72,8 @@ private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) # Test for presence of some functions test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2', 'splice', 'vmsplice', 'posix_fallocate', 'fdatasync', - 'utimensat', 'copy_file_range', 'fallocate', 'static_assert' ] + 'utimensat', 'copy_file_range', 'fallocate', 'static_assert', + 'pthread_setname_np' ] foreach func : test_funcs private_cfg.set('HAVE_' + func.to_upper(), cc.has_function(func, prefix: include_default, args: args_default)) From 4bc1a9222a64442bb2bcbdabca2c3c0eb4f9dad6 Mon Sep 17 00:00:00 2001 From: swj <1186093704@qq.com> Date: Wed, 23 Apr 2025 22:39:28 +0800 Subject: [PATCH 087/105] example: fix memfs_rename deadlock error Signed-off-by: swj <1186093704@qq.com> --- example/memfs_ll.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index 9898f2528..603885017 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -900,6 +900,7 @@ static void memfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, Inode *parentInode = nullptr; Inode *newparentInode = nullptr; Dentry *child_dentry = nullptr; + Dentry *child_dentry_copy = nullptr; Dentry *existing_dentry = nullptr; if (flags & (RENAME_EXCHANGE | RENAME_NOREPLACE)) { @@ -909,8 +910,8 @@ static void memfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, Inodes.lock(); - parentInode = Inodes.find(parent); - newparentInode = Inodes.find(newparent); + parentInode = Inodes.find_locked(parent); + newparentInode = Inodes.find_locked(newparent); if (!parentInode || !parentInode->is_dir() || !newparentInode || !newparentInode->is_dir()) { error = ENOENT; @@ -941,9 +942,9 @@ static void memfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, existing_dentry->get_inode()->dec_nlink(); } + child_dentry_copy = new Dentry(newname, child_dentry->get_inode()); parentInode->remove_child(name); - child_dentry->name = newname; - newparentInode->add_child(newname, child_dentry); + newparentInode->add_child_locked(newname, child_dentry_copy); out_unlock: parentInode->unlock(); From 712c885098156b5d66d87ef3f017b395e91f281b Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Wed, 23 Apr 2025 15:16:42 +0200 Subject: [PATCH 088/105] signal handlers: Store fuse_session unconditionally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit dae1184 ("Add syslog and fatal signal handler feature") added fuse_set_fail_signal_handlers() which can store "se". But as fuse_set_signal_handlers() also stores the object storing it was made conditionally if not set already. As per https://github.com/libfuse/libfuse/issues/1182 this breaks some applications like osspd, that have multiple sessions and rely on the right order and that the last call of fuse_set_signal_handlers() wins. Special thanks to Sébastien Noel to debug this issue. Closes: https://github.com/libfuse/libfuse/issues/1182 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- lib/fuse_signals.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/fuse_signals.c b/lib/fuse_signals.c index fb1304c0d..6ac72308a 100644 --- a/lib/fuse_signals.c +++ b/lib/fuse_signals.c @@ -150,8 +150,13 @@ int fuse_set_signal_handlers(struct fuse_session *se) if (rc < 0) return rc; - if (fuse_instance == NULL) - fuse_instance = se; + /* + * needs to be set independently if already set, as some applications + * may have multiple sessions and might rely on traditional behavior + * that the last session is used. + */ + fuse_instance = se; + return 0; } @@ -164,8 +169,8 @@ int fuse_set_fail_signal_handlers(struct fuse_session *se) if (rc < 0) return rc; - if (fuse_instance == NULL) - fuse_instance = se; + /* See fuse_set_signal_handlers, why set unconditionally */ + fuse_instance = se; return 0; } From db2a1652f0351162a4f39d9f15f1e8ff036f7a6a Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Wed, 23 Apr 2025 23:14:56 +0200 Subject: [PATCH 089/105] Update to 3.17.2 --- AUTHORS | 4 ++++ ChangeLog.rst | 16 ++++++++++++++++ meson.build | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 96f25d9f0..c2ea83a88 100644 --- a/AUTHORS +++ b/AUTHORS @@ -271,3 +271,7 @@ Vassili Tchersky <vt+git@vbcy.org> # New authors since fuse-3.17.1-rc1 jnr0006 <jacob.nick.riley@gmail.com> Vassili Tchersky <vt+git@vbc.su> + +# New authors since fuse-3.17.1 +swj <1186093704@qq.com> +Ben Dooks <ben.dooks@codethink.co.uk> diff --git a/ChangeLog.rst b/ChangeLog.rst index 6c157da52..5113f44ef 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,19 @@ +libfuse 3.17.2 (2025-04-23) +=========================== +* Fixed uninitized bufsize value (compilation warning and real + issue when HAVE_SPLICE was not defined) +* Fixed initialization races related to buffer realocation when + large buf sizes are used (/proc/sys/fs/fuse/max_pages_limit) +* Fix build with kernel < 5.9 +* Fix static_assert build failure with C++ version < 11 +* Compilation fix (remove second fuse_main_real_versioned declaration) +* Another conn.want flag conversion fix for high-level applications +* Check if pthread_setname_np() exists before use it +* fix example/memfs_ll rename deadlock error +* signal handlers: Store fuse_session unconditionally and restore + previous behavior that with multiple sessions the last session + was used for the signal exist handler + libfuse 3.17.1 (2025-03-24) =========================== * fuse: Fix want conn.want flag conversion diff --git a/meson.build b/meson.build index 39cff23ba..ba551ed53 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfuse3', ['c'], - version: '3.17.1', + version: '3.17.2', meson_version: '>= 0.51.0', default_options: [ 'buildtype=debugoptimized', From 1df74208ea060b32e11b14e9b97049207ed03dd3 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Thu, 24 Apr 2025 16:49:08 +0200 Subject: [PATCH 090/105] Fix meson function tests Several meson tests were incorrectly failing Checking for function "static_assert" : NO (cached) Checking for function "pthread_setname_np" : NO (cached) Check usable header "#include <linux/close_range.h>" : NO (cached) These functions get now tested with compilation tests and get found on my system. Checking if "static_assert check" compiles: YES Checking if "pthread_setname_np check" compiles: YES Checking if "close_range check" compiles: YES Signed-off-by: Bernd Schubert <bschubert@ddn.com> Signed-off-by: Giulio Benetti <giulio.benetti@benettiengineering.com> --- meson.build | 67 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/meson.build b/meson.build index ba551ed53..d1346d090 100644 --- a/meson.build +++ b/meson.build @@ -59,6 +59,8 @@ include_default = ''' #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> +#include <assert.h> /* For static_assert */ +#include <pthread.h> /* For pthread_setname_np */ ''' args_default = [ '-D_GNU_SOURCE' ] @@ -72,32 +74,61 @@ private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) # Test for presence of some functions test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2', 'splice', 'vmsplice', 'posix_fallocate', 'fdatasync', - 'utimensat', 'copy_file_range', 'fallocate', 'static_assert', - 'pthread_setname_np' ] + 'utimensat', 'copy_file_range', 'fallocate' ] foreach func : test_funcs private_cfg.set('HAVE_' + func.to_upper(), cc.has_function(func, prefix: include_default, args: args_default)) endforeach -private_cfg.set('HAVE_SETXATTR', - cc.has_function('setxattr', prefix: '#include <sys/xattr.h>')) -private_cfg.set('HAVE_ICONV', - cc.has_function('iconv', prefix: '#include <iconv.h>')) -private_cfg.set('HAVE_BACKTRACE', - cc.has_function('backtrace', prefix: '#include <execinfo.h>')) -# Test if headers exist -private_cfg.set('HAVE_LINUX_CLOSE_RANGE_H', - cc.check_header('#include <linux/close_range.h>')) +# Special case checks that need custom code +special_funcs = { + 'static_assert': ''' + #include <assert.h> + static_assert(1, "test"); + int main(void) { return 0; } + ''', + 'pthread_setname_np': ''' + #include <pthread.h> + int main(void) { + pthread_t thread = pthread_self(); + pthread_setname_np(thread, "test"); + return 0; + } + ''', + 'close_range': ''' + #include <unistd.h> + #include <fcntl.h> + #include <linux/close_range.h> + int main(void) { + unsigned int flags = CLOSE_RANGE_UNSHARE; + return close_range(3, ~0U, flags); + } + ''' +} + +foreach name, code : special_funcs + private_cfg.set('HAVE_' + name.to_upper(), + cc.compiles(code, args: ['-Werror'] + args_default, + name: name + ' check')) +endforeach + +# Regular function checks +private_cfg.set('HAVE_SETXATTR', + cc.has_function('setxattr', prefix: '#include <sys/xattr.h>')) +private_cfg.set('HAVE_ICONV', + cc.has_function('iconv', prefix: '#include <iconv.h>')) +private_cfg.set('HAVE_BACKTRACE', + cc.has_function('backtrace', prefix: '#include <execinfo.h>')) -# Test if structs have specific member +# Struct member checks private_cfg.set('HAVE_STRUCT_STAT_ST_ATIM', - cc.has_member('struct stat', 'st_atim', - prefix: include_default, - args: args_default)) + cc.has_member('struct stat', 'st_atim', + prefix: include_default + '#include <sys/stat.h>', + args: args_default)) private_cfg.set('HAVE_STRUCT_STAT_ST_ATIMESPEC', - cc.has_member('struct stat', 'st_atimespec', - prefix: include_default, - args: args_default)) + cc.has_member('struct stat', 'st_atimespec', + prefix: include_default + '#include <sys/stat.h>', + args: args_default)) # # Compiler configuration From 8e226c6af6f386a88ed53d0a0940ff6b869b372d Mon Sep 17 00:00:00 2001 From: Giulio Benetti <giulio.benetti@benettiengineering.com> Date: Fri, 25 Apr 2025 19:00:14 +0200 Subject: [PATCH 091/105] meson.build: make special_funcs check more reliable Unfortunately while cross-compiling with build tools like Buildroot it happens to have repeated flags or anything that could lead to a warning. This way the check fails because of a warning not related to the special function. So let's use cc.links() and increase minimum meson_version to 0.60 since cc.links() has been added during that version. Signed-off-by: Giulio Benetti <giulio.benetti@benettiengineering.com> --- meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index d1346d090..d3d236dc4 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('libfuse3', ['c'], version: '3.17.2', - meson_version: '>= 0.51.0', + meson_version: '>= 0.60.0', default_options: [ 'buildtype=debugoptimized', 'c_std=gnu11', @@ -108,7 +108,7 @@ special_funcs = { foreach name, code : special_funcs private_cfg.set('HAVE_' + name.to_upper(), - cc.compiles(code, args: ['-Werror'] + args_default, + cc.links(code, args: args_default, name: name + ' check')) endforeach From c27bbcec3d4ef6cbb279d589d2bf73dc595eb51e Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sat, 17 May 2025 23:52:47 +0200 Subject: [PATCH 092/105] Make conn->want/want_ext conversion non fatal there are too many issues with conn->want and conn->want_ext conversion, for now just log a warning, but setting both flags is now not fatal anymore. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse.c | 16 +--------------- lib/fuse_i.h | 5 +---- lib/fuse_lowlevel.c | 9 +-------- 3 files changed, 3 insertions(+), 27 deletions(-) diff --git a/lib/fuse.c b/lib/fuse.c index 49f57112a..c0d00edbc 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -2618,25 +2618,11 @@ void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, if (fs->op.init) { uint64_t want_ext_default = conn->want_ext; uint32_t want_default = fuse_lower_32_bits(conn->want_ext); - int rc; conn->want = want_default; fs->user_data = fs->op.init(conn, cfg); - rc = convert_to_conn_want_ext(conn, want_ext_default, - want_default); - - if (rc != 0) { - /* - * This is a grave developer error, but - * we cannot return an error here, as the function - * signature does not allow it. - */ - fuse_log( - FUSE_LOG_ERR, - "fuse: Aborting due to invalid conn want flags.\n"); - _exit(EXIT_FAILURE); - } + convert_to_conn_want_ext(conn, want_ext_default, want_default); } } diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 48b8294f3..bf5e2ca41 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -245,11 +245,8 @@ static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn, */ if (conn->want != want_default && fuse_lower_32_bits(conn->want_ext) != conn->want) { - if (conn->want_ext != want_ext_default) { - fuse_log(FUSE_LOG_ERR, - "fuse: both 'want' and 'want_ext' are set\n"); + if (conn->want_ext != want_ext_default) return -EINVAL; - } /* high bits from want_ext, low bits from want */ conn->want_ext = fuse_higher_32_bits(conn->want_ext) | diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 9ebaaf08e..9ee88b160 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2160,7 +2160,6 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) if (se->op.init) { uint64_t want_ext_default = se->conn.want_ext; uint32_t want_default = fuse_lower_32_bits(se->conn.want_ext); - int rc; // Apply the first 32 bits of capable_ext to capable se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext); @@ -2173,14 +2172,8 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) * se->conn.want_ext * Userspace might still use conn.want - we need to convert it */ - rc = convert_to_conn_want_ext(&se->conn, want_ext_default, + convert_to_conn_want_ext(&se->conn, want_ext_default, want_default); - if (rc != 0) { - fuse_reply_err(req, EPROTO); - se->error = -EPROTO; - fuse_session_exit(se); - return; - } } if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) { From 9925b6e7548d65696f745732617d25481b76acbe Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 15 Apr 2025 22:03:09 +0200 Subject: [PATCH 093/105] Add container_of and ROUND_UP macros Needed by follow up commits. container_of is actually just moved/consolidated to util.h. Signed-off-by: Bernd Schubert <bschubert@ddn.com> (cherry picked from commit c5a032b3410d7225ac0355355faa63565a209943) --- lib/fuse.c | 4 ---- lib/fuse_lowlevel.c | 4 ---- lib/util.h | 6 ++++++ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/fuse.c b/lib/fuse.c index c0d00edbc..4964de20f 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -92,10 +92,6 @@ struct node_table { size_t split; }; -#define container_of(ptr, type, member) ({ \ - const typeof( ((type *)0)->member ) *__mptr = (ptr); \ - (type *)( (char *)__mptr - offsetof(type,member) );}) - #define list_entry(ptr, type, member) \ container_of(ptr, type, member) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 9ee88b160..cb046aae0 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -44,10 +44,6 @@ #define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg))) #define OFFSET_MAX 0x7fffffffffffffffLL -#define container_of(ptr, type, member) ({ \ - const typeof( ((type *)0)->member ) *__mptr = (ptr); \ - (type *)( (char *)__mptr - offsetof(type,member) );}) - struct fuse_pollhandle { uint64_t kh; struct fuse_session *se; diff --git a/lib/util.h b/lib/util.h index 508fafb12..ed03ad40e 100644 --- a/lib/util.h +++ b/lib/util.h @@ -30,4 +30,10 @@ static inline uint64_t fuse_higher_32_bits(uint64_t nr) #define FUSE_VAR_UNUSED(var) (__attribute__((unused)) var) #endif +#define container_of(ptr, type, member) \ + ({ \ + unsigned long __mptr = (unsigned long)(ptr); \ + ((type *)(__mptr - offsetof(type, member))); \ + }) + #endif From 53607e073d075ad1984f7015187d203a3ae83297 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 18 May 2025 00:24:07 +0200 Subject: [PATCH 094/105] conn->want conversion: Fix fuse_apply_conn_info_opts() fuse_apply_conn_info_opts() was applying to 'want_ext', which would cause conflicts with 'want' if the application applied its own flags to 'conn->want'. Solution is: - to move fuse_{set,unset,get}_feature_flag and convert_to_conn_want_ext() to fuse_lowlevel.c and to define them as part of the public API, although convert_to_conn_want_ext() should not be used - it is currently needed to be a public function due as it needs to be defined for the tests. Related to https://github.com/libfuse/libfuse/issues/1171 and https://github.com/libfuse/libfuse/pull/1172. Closes: https://github.com/libfuse/libfuse/issues/1171 Signed-off-by: Bernd Schubert <bschubert@ddn.com> (cherry picked from commit baadab0492a495fda98216b351976d2e5d6d0866) --- include/fuse_common.h | 50 +++++++++++-------- lib/fuse.c | 9 +--- lib/fuse_i.h | 38 +++------------ lib/fuse_lowlevel.c | 78 ++++++++++++++++++++++++++--- lib/fuse_versionscript | 10 ++++ lib/helper.c | 15 ++++-- lib/util.c | 8 +++ lib/util.h | 3 ++ test/test_want_conversion.c | 97 ++++++++++++++++++++++--------------- 9 files changed, 200 insertions(+), 108 deletions(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index 582505fa9..dd08f444a 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -1096,28 +1096,40 @@ void fuse_loop_cfg_convert(struct fuse_loop_config *config, struct fuse_loop_config_v1 *v1_conf); #endif +/** + * Set a feature flag in the want_ext field of fuse_conn_info. + * + * @param conn connection information + * @param flag feature flag to be set + * @return true if the flag was set, false if the flag is not supported + */ +bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag); -static inline bool fuse_set_feature_flag(struct fuse_conn_info *conn, - uint64_t flag) -{ - if (conn->capable_ext & flag) { - conn->want_ext |= flag; - return true; - } - return false; -} +/** + * Unset a feature flag in the want_ext field of fuse_conn_info. + * + * @param conn connection information + * @param flag feature flag to be unset + */ +void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag); + +/** + * Get the value of a feature flag in the want_ext field of fuse_conn_info. + * + * @param conn connection information + * @param flag feature flag to be checked + * @return true if the flag is set, false otherwise + */ +bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag); + +/* + * DO NOT USE: Not part of public API, for internal test use only. + * The function signature or any use of it is not guaranteeed to + * remain stable. And neither are results of what this function does. + */ +int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn); -static inline void fuse_unset_feature_flag(struct fuse_conn_info *conn, - uint64_t flag) -{ - conn->want_ext &= ~flag; -} -static inline bool fuse_get_feature_flag(struct fuse_conn_info *conn, - uint64_t flag) -{ - return conn->capable_ext & flag ? true : false; -} /* ----------------------------------------------------------- * * Compatibility stuff * diff --git a/lib/fuse.c b/lib/fuse.c index 4964de20f..85914546e 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -2611,15 +2611,8 @@ void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, fuse_unset_feature_flag(conn, FUSE_CAP_POSIX_LOCKS); if (!fs->op.flock) fuse_unset_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); - if (fs->op.init) { - uint64_t want_ext_default = conn->want_ext; - uint32_t want_default = fuse_lower_32_bits(conn->want_ext); - - conn->want = want_default; + if (fs->op.init) fs->user_data = fs->op.init(conn, cfg); - - convert_to_conn_want_ext(conn, want_ext_default, want_default); - } } static int fuse_init_intr_signal(int signum, int *installed); diff --git a/lib/fuse_i.h b/lib/fuse_i.h index bf5e2ca41..718fa142c 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -85,6 +85,13 @@ struct fuse_session { /* true if reading requests from /dev/fuse are handled internally */ bool buf_reallocable; + + /* + * conn->want and conn_want_ext options set by libfuse , needed + * to correctly convert want to want_ext + */ + uint32_t conn_want; + uint64_t conn_want_ext; }; struct fuse_chan { @@ -227,34 +234,3 @@ int fuse_loop_cfg_verify(struct fuse_loop_config *config); /* room needed in buffer to accommodate header */ #define FUSE_BUFFER_HEADER_SIZE 0x1000 -/** - * Get the wanted capability flags, converting from old format if necessary - */ -static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn, - uint64_t want_ext_default, - uint32_t want_default) -{ - /* - * Convert want to want_ext if necessary. - * For the high level interface this function might be called - * twice, once from the high level interface and once from the - * low level interface. Both, with different want_ext_default and - * want_default values. In order to suppress a failure for the - * second call, we check if the lower 32 bits of want_ext are - * already set to the value of want. - */ - if (conn->want != want_default && - fuse_lower_32_bits(conn->want_ext) != conn->want) { - if (conn->want_ext != want_ext_default) - return -EINVAL; - - /* high bits from want_ext, low bits from want */ - conn->want_ext = fuse_higher_32_bits(conn->want_ext) | - conn->want; - } - - /* ensure there won't be a second conversion */ - conn->want = fuse_lower_32_bits(conn->want_ext); - - return 0; -} diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index cb046aae0..1276a0fd9 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -1994,6 +1994,77 @@ static bool want_flags_valid(uint64_t capable, uint64_t want) return true; } +/** + * Get the wanted capability flags, converting from old format if necessary + */ +int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + /* + * Convert want to want_ext if necessary. + * For the high level interface this function might be called + * twice, once from the high level interface and once from the + * low level interface. Both, with different want_ext_default and + * want_default values. In order to suppress a failure for the + * second call, we check if the lower 32 bits of want_ext are + * already set to the value of want. + */ + if (conn->want != se->conn_want && + fuse_lower_32_bits(conn->want_ext) != conn->want) { + if (conn->want_ext != se->conn_want_ext) { + fuse_log(FUSE_LOG_ERR, + "%s: Both conn->want_ext and conn->want are set.\n" + "want=%x, want_ext=%lx, se->want=%lx se->want_ext=%lx\n", + __func__, conn->want, conn->want_ext, + se->conn_want, se->conn_want_ext); + return -EINVAL; + } + + /* high bits from want_ext, low bits from want */ + conn->want_ext = fuse_higher_32_bits(conn->want_ext) | + conn->want; + } + + /* ensure there won't be a second conversion */ + conn->want = fuse_lower_32_bits(conn->want_ext); + + return 0; +} + +bool fuse_set_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + if (conn->capable_ext & flag) { + conn->want_ext |= flag; + se->conn_want_ext |= flag; + conn->want |= flag; + se->conn_want |= flag; + return true; + } + return false; +} + +void fuse_unset_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + conn->want_ext &= ~flag; + se->conn_want_ext &= ~flag; + conn->want &= ~flag; + se->conn_want &= ~flag; +} + +bool fuse_get_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + return conn->capable_ext & flag ? true : false; +} + + /* Prevent bogus data races (bogus since "init" is called before * multi-threading becomes relevant */ static __attribute__((no_sanitize("thread"))) @@ -2154,12 +2225,8 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) se->got_init = 1; if (se->op.init) { - uint64_t want_ext_default = se->conn.want_ext; - uint32_t want_default = fuse_lower_32_bits(se->conn.want_ext); - // Apply the first 32 bits of capable_ext to capable se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext); - se->conn.want = want_default; se->op.init(se->userdata, &se->conn); @@ -2168,8 +2235,7 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) * se->conn.want_ext * Userspace might still use conn.want - we need to convert it */ - convert_to_conn_want_ext(&se->conn, want_ext_default, - want_default); + fuse_convert_to_conn_want_ext(&se->conn); } if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) { diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index 6c5fc83eb..a2653fcdd 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -202,6 +202,16 @@ FUSE_3.17 { fuse_log_close_syslog; } FUSE_3.12; +FUSE_3.17.3 { + global: + fuse_set_feature_flag; + fuse_unset_feature_flag; + fuse_get_feature_flag; + + # Not part of public API, for internal test use only + fuse_convert_to_conn_want_ext; +} FUSE_3.17; + # Local Variables: # indent-tabs-mode: t # End: diff --git a/lib/helper.c b/lib/helper.c index 59dd48881..aceff9fd5 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -423,10 +423,17 @@ void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, if(opts->set_max_readahead) conn->max_readahead = opts->max_readahead; -#define LL_ENABLE(cond,cap) \ - if (cond) conn->want_ext |= (cap) -#define LL_DISABLE(cond,cap) \ - if (cond) conn->want_ext &= ~(cap) +#define LL_ENABLE(cond, cap) \ + do { \ + if (cond) \ + fuse_set_feature_flag(conn, cap); \ + } while (0) + +#define LL_DISABLE(cond, cap) \ + do { \ + if (cond) \ + fuse_unset_feature_flag(conn, cap); \ + } while (0) LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ); LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ); diff --git a/lib/util.c b/lib/util.c index a529d383c..956c3d2e9 100644 --- a/lib/util.c +++ b/lib/util.c @@ -1,7 +1,14 @@ #include <stdlib.h> #include <errno.h> +#ifndef FUSE_USE_VERSION +#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 18)) +#endif + #include "util.h" +#include "fuse_log.h" +#include "fuse_lowlevel.h" +#include <stdio.h> int libfuse_strtol(const char *str, long *res) { @@ -25,3 +32,4 @@ int libfuse_strtol(const char *str, long *res) *res = val; return 0; } + diff --git a/lib/util.h b/lib/util.h index ed03ad40e..f24401a29 100644 --- a/lib/util.h +++ b/lib/util.h @@ -2,12 +2,15 @@ #define FUSE_UTIL_H_ #include <stdint.h> +#include <stdbool.h> #define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1)) #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) +struct fuse_conn_info; + int libfuse_strtol(const char *str, long *res); /** diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c index bee23cc6e..db731edbf 100644 --- a/test/test_want_conversion.c +++ b/test/test_want_conversion.c @@ -1,16 +1,22 @@ -#include "util.h" #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17) +#include "util.h" #include "fuse_i.h" +#include "fuse_lowlevel.h" #include <stdio.h> #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <err.h> static void print_conn_info(const char *prefix, struct fuse_conn_info *conn) { - printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64 "\n", prefix, - conn->want, conn->want_ext); + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64 + " want_default=0x%" PRIx32 " want_ext_default=0x%" PRIx64 "\n", + prefix, conn->want, conn->want_ext, se->conn_want, + se->conn_want_ext); } static void application_init_old_style(struct fuse_conn_info *conn) @@ -18,33 +24,31 @@ static void application_init_old_style(struct fuse_conn_info *conn) /* Simulate application init the old style */ conn->want |= FUSE_CAP_ASYNC_READ; conn->want &= ~FUSE_CAP_SPLICE_READ; + + /* + * Also use new style API, as that might happen through + * fuse_apply_conn_info_opts() + */ + fuse_set_feature_flag(conn, FUSE_CAP_IOCTL_DIR); } static void application_init_new_style(struct fuse_conn_info *conn) { /* Simulate application init the new style */ fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ); + fuse_set_feature_flag(conn, FUSE_CAP_IOCTL_DIR); fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); } static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style) { - uint64_t want_ext_default = conn->want_ext; - uint32_t want_default = fuse_lower_32_bits(conn->want_ext); - int rc; - /* High-level init */ fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT); - conn->want = want_default; - if (new_style) application_init_new_style(conn); else application_init_old_style(conn); - - rc = convert_to_conn_want_ext(conn, want_ext_default, want_default); - assert(rc == 0); } static void test_do_init(struct fuse_conn_info *conn, bool new_style) @@ -53,49 +57,71 @@ static void test_do_init(struct fuse_conn_info *conn, bool new_style) conn->capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | FUSE_CAP_FLOCK_LOCKS | FUSE_CAP_EXPORT_SUPPORT | - FUSE_CAP_ASYNC_READ; + FUSE_CAP_ASYNC_READ | FUSE_CAP_IOCTL_DIR; conn->capable = fuse_lower_32_bits(conn->capable_ext); - conn->want_ext = conn->capable_ext; + + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_READ | + FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE); print_conn_info("Initial state", conn); - uint64_t want_ext_default = conn->want_ext; - uint32_t want_default = fuse_lower_32_bits(conn->want_ext); int rc; - conn->want = want_default; - conn->capable = fuse_lower_32_bits(conn->capable_ext); - test_fuse_fs_init(conn, new_style); + print_conn_info("After init", conn); - rc = convert_to_conn_want_ext(conn, want_ext_default, want_default); + rc = fuse_convert_to_conn_want_ext(conn); assert(rc == 0); /* Verify all expected flags are set */ assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ)); assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE); assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE); - assert(conn->want_ext & FUSE_CAP_POSIX_LOCKS); - assert(conn->want_ext & FUSE_CAP_FLOCK_LOCKS); assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT); assert(conn->want_ext & FUSE_CAP_ASYNC_READ); + assert(conn->want_ext & FUSE_CAP_IOCTL_DIR); + /* Verify no other flags are set */ assert(conn->want_ext == (FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE | - FUSE_CAP_POSIX_LOCKS | FUSE_CAP_FLOCK_LOCKS | - FUSE_CAP_EXPORT_SUPPORT | FUSE_CAP_ASYNC_READ)); + FUSE_CAP_EXPORT_SUPPORT | FUSE_CAP_ASYNC_READ | + FUSE_CAP_IOCTL_DIR)); print_conn_info("After init", conn); } static void test_want_conversion_basic(void) { - struct fuse_conn_info conn = { 0 }; + const struct fuse_lowlevel_ops ops = { 0 }; + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + struct fuse_session *se; + struct fuse_conn_info *conn; + + /* Add the program name to arg[0] */ + if (fuse_opt_add_arg(&args, "test_signals")) { + fprintf(stderr, "Failed to add argument\n"); + errx(1, "Failed to add argument"); + } + + + se = fuse_session_new(&args, &ops, sizeof(ops), NULL); + assert(se); + conn = &se->conn; + printf("\nTesting basic want conversion, old style:\n"); + test_do_init(conn, false); + fuse_session_destroy(se); + + se = fuse_session_new(&args, &ops, sizeof(ops), NULL); + assert(se); + conn = &se->conn; + printf("\nTesting basic want conversion, new style:\n"); + test_do_init(conn, true); + print_conn_info("After init", conn); + fuse_session_destroy(se); + + fuse_opt_free_args(&args); - printf("\nTesting basic want conversion:\n"); - test_do_init(&conn, false); - test_do_init(&conn, true); - print_conn_info("After init", &conn); } static void test_want_conversion_conflict(void) @@ -115,16 +141,11 @@ static void test_want_conversion_conflict(void) conn.want = fuse_lower_32_bits(conn.want_ext); print_conn_info("Test conflict initial", &conn); - /* Initialize default values like in basic test */ - uint64_t want_ext_default_ll = conn.want_ext; - uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll); - /* Simulate application init modifying capabilities */ conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */ conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */ - rc = convert_to_conn_want_ext(&conn, want_ext_default_ll, - want_default_ll); + rc = fuse_convert_to_conn_want_ext(&conn); assert(rc == -EINVAL); print_conn_info("Test conflict after", &conn); @@ -143,11 +164,7 @@ static void test_want_conversion_high_bits(void) conn.want = fuse_lower_32_bits(conn.want_ext); print_conn_info("Test high bits initial", &conn); - uint64_t want_ext_default_ll = conn.want_ext; - uint32_t want_default_ll = fuse_lower_32_bits(want_ext_default_ll); - - rc = convert_to_conn_want_ext(&conn, want_ext_default_ll, - want_default_ll); + rc = fuse_convert_to_conn_want_ext(&conn); assert(rc == 0); assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ)); print_conn_info("Test high bits after", &conn); From 5105080b6bdc45a2a0ed9f2bccc9b511c572d1b6 Mon Sep 17 00:00:00 2001 From: Long Li <leo.lilong@huawei.com> Date: Wed, 16 Jul 2025 09:54:07 +0800 Subject: [PATCH 095/105] memfs_ll: fix deadlock in truncate operation Remove redundant mutex lock acquisition in the truncate() method to prevent deadlock. The issue occurs when memfs_setattr() already holds the mutex lock and then calls truncate(), which attempts to acquire the same lock again. Signed-off-by: Long Li <leo.lilong@huawei.com> (cherry picked from commit b96c738b10e9a308725c62b9392ba7d553eec254) --- example/memfs_ll.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index 603885017..e5877565b 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -197,7 +197,6 @@ class Inode { void truncate(off_t size) { - std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> attr_lock(attr_mutex); if (size < content.size()) { content.resize(size); From 069745b21b90709a863ae5f62fb746667768f989 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 15 Jul 2025 20:09:17 +0200 Subject: [PATCH 096/105] Avoid double unmount on FUSE_DESTROY This is a long standing issue, a system could have unmounted /path/to/mnt and then fuse-client/kernel would send FUSE_DESTROY, which would then again try a umount. Given that FUSE_DESTROY is async, that umount might arrive any time later and might possibly unmount a wrong mount point. A warning as in issue #1286 is just minor to that. Code wise this uses atomics to free the char *, as FUSE_DESTROY might race with a signal and a double free might be possible without proctection. A lock might run into the same issue, if the signal would arrive at the wrong time a double lock would be possible. Additionally, fuse_session_mount() is updated, to first duplicatate the pointer and to then do the kernel mount - reverting the kernel mount in case of strdup() failure is much harder. Closes: https://github.com/libfuse/libfuse/issues/1286 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> (cherry picked from commit d8253770ac2cf4b8769e8cf41eb3c629f30ee80f) --- lib/fuse_i.h | 3 ++- lib/fuse_lowlevel.c | 33 ++++++++++++++++++++++----------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 718fa142c..acf9d5ae2 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -13,6 +13,7 @@ #include <stdint.h> #include <stdbool.h> #include <errno.h> +#include <stdatomic.h> #define MIN(a, b) \ ({ \ @@ -54,7 +55,7 @@ struct fuse_notify_req { }; struct fuse_session { - char *mountpoint; + _Atomic(char *)mountpoint; volatile int exited; int fd; struct fuse_custom_io *io; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 1276a0fd9..3c9994fd3 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2378,10 +2378,14 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_session *se = req->se; + char *mountpoint; (void) nodeid; (void) inarg; + mountpoint = atomic_exchange(&se->mountpoint, NULL); + free(mountpoint); + se->got_destroy = 1; se->got_init = 0; if (se->op.destroy) @@ -3466,15 +3470,23 @@ int fuse_session_custom_io_30(struct fuse_session *se, offsetof(struct fuse_custom_io, clone_fd), fd); } -int fuse_session_mount(struct fuse_session *se, const char *mountpoint) +int fuse_session_mount(struct fuse_session *se, const char *_mountpoint) { int fd; + char *mountpoint; - if (mountpoint == NULL) { + if (_mountpoint == NULL) { fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n"); return -1; } + mountpoint = strdup(_mountpoint); + if (mountpoint == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for mountpoint. Error: %s\n", + strerror(errno)); + return -1; + } + /* * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos * would ensue. @@ -3497,7 +3509,7 @@ int fuse_session_mount(struct fuse_session *se, const char *mountpoint) fuse_log(FUSE_LOG_ERR, "fuse: Invalid file descriptor /dev/fd/%u\n", fd); - return -1; + goto error_out; } se->fd = fd; return 0; @@ -3506,18 +3518,16 @@ int fuse_session_mount(struct fuse_session *se, const char *mountpoint) /* Open channel */ fd = fuse_kern_mount(mountpoint, se->mo); if (fd == -1) - return -1; + goto error_out; se->fd = fd; /* Save mountpoint */ - se->mountpoint = strdup(mountpoint); - if (se->mountpoint == NULL) - goto error_out; + se->mountpoint = mountpoint; return 0; error_out: - fuse_kern_unmount(mountpoint, fd); + free(mountpoint); return -1; } @@ -3529,10 +3539,11 @@ int fuse_session_fd(struct fuse_session *se) void fuse_session_unmount(struct fuse_session *se) { if (se->mountpoint != NULL) { - fuse_kern_unmount(se->mountpoint, se->fd); + char *mountpoint = atomic_exchange(&se->mountpoint, NULL); + + fuse_kern_unmount(mountpoint, se->fd); se->fd = -1; - free(se->mountpoint); - se->mountpoint = NULL; + free(mountpoint); } } From 9385e82e0e64e028ea58aa9a221e6c358c187204 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 15 Jul 2025 23:54:27 +0200 Subject: [PATCH 097/105] fusermount: Fix the close_range ifdef This fixes commit 82bcd818 That commit had removed HAVE_LINUX_CLOSE_RANGE in meson generation, but didn't remove the usage in fusermount.c - fusermount was then not using the close_range syscall. Closes: https://github.com/libfuse/libfuse/issues/1284 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> (cherry picked from commit 194023c5999651386a3ddbf91fdd710d661d083b) --- util/fusermount.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/fusermount.c b/util/fusermount.c index da6d5f2be..dbd947c60 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -36,7 +36,7 @@ #include <stdbool.h> #include <sys/vfs.h> -#ifdef HAVE_LINUX_CLOSE_RANGE_H +#ifdef HAVE_CLOSE_RANGE #include <linux/close_range.h> #endif @@ -1477,7 +1477,7 @@ static int close_inherited_fds(int cfd) if (cfd <= STDERR_FILENO) return -EINVAL; -#ifdef HAVE_LINUX_CLOSE_RANGE_H +#ifdef HAVE_CLOSE_RANGE if (cfd < STDERR_FILENO + 2) { close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd); } else { From b0c7295005fa0c944155253113a5fa046515fed3 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Wed, 16 Jul 2025 22:12:48 +0200 Subject: [PATCH 098/105] Increase version to 3.17.3 --- ChangeLog.rst | 6 ++++++ meson.build | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 5113f44ef..d07de3262 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,9 @@ +libfuse 3.17.3 (2025-07-16) +=========================== +* more conn->want / conn->want_ext conversion fixes +* Fix feature detection for close_range +* Avoid double unmount on FUSE_DESTROY + libfuse 3.17.2 (2025-04-23) =========================== * Fixed uninitized bufsize value (compilation warning and real diff --git a/meson.build b/meson.build index d3d236dc4..4e7c0a1fa 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfuse3', ['c'], - version: '3.17.2', + version: '3.17.3', meson_version: '>= 0.60.0', default_options: [ 'buildtype=debugoptimized', From caf108881fcf7b6fee8a67bd833cd48ad2eddbfa Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 19 Jul 2025 14:28:44 +0200 Subject: [PATCH 099/105] Update AUTHORS file for 3.17.3 --- AUTHORS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AUTHORS b/AUTHORS index c2ea83a88..dc850c2f2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -275,3 +275,6 @@ Vassili Tchersky <vt+git@vbc.su> # New authors since fuse-3.17.1 swj <1186093704@qq.com> Ben Dooks <ben.dooks@codethink.co.uk> + +# New authors since fuse-3.17.2 +Long Li <leo.lilong@huawei.com> From b64c23091e5505fa4a3e579ac4caa676a6f3cbab Mon Sep 17 00:00:00 2001 From: Georgi Valkov <gvalkov@gmail.com> Date: Thu, 12 Jun 2025 07:36:14 +0300 Subject: [PATCH 100/105] fuse_signals.c: fix build warning when HAVE_BACKTRACE is undefined BT_STACK_SZ and backtrace_buffer are not used when HAVE_BACKTRACE is undefined. Wrap them in #ifdef to avoid a build warning: ../lib/fuse_signals.c:31:14: warning: 'backtrace_buffer' defined but not used [-Wunused-variable] 31 | static void *backtrace_buffer[BT_STACK_SZ]; | ^~~~~~~~~~~~~~~~ Signed-off-by: Georgi Valkov <gvalkov@gmail.com> (cherry picked from commit 6787608e5655b3d833a541a9f0d5a3159ae51f0e) Signed-off-by: Georgi Valkov <gvalkov@gmail.com> --- lib/fuse_signals.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/fuse_signals.c b/lib/fuse_signals.c index 6ac72308a..b88278e42 100644 --- a/lib/fuse_signals.c +++ b/lib/fuse_signals.c @@ -27,8 +27,10 @@ static int ignore_sigs[] = { SIGPIPE}; static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV }; static struct fuse_session *fuse_instance; +#ifdef HAVE_BACKTRACE #define BT_STACK_SZ (1024 * 1024) static void *backtrace_buffer[BT_STACK_SZ]; +#endif static void dump_stack(void) { From 6d9ff1c57c1c3107c3e012373f28b871cd6ef2c9 Mon Sep 17 00:00:00 2001 From: Georgi Valkov <gvalkov@gmail.com> Date: Sun, 15 Jun 2025 15:34:57 +0300 Subject: [PATCH 101/105] mount_util.c: check if utab exists before update Do not attempt to update /run/mount/utab if it doesn't exist. Note: if this path ever changes, utab updates will break. Fixes the following error when mounting iPhone using ifuse: ifuse /mnt --container com.httpstorm.httpstorm mount: mounting ifuse on /mnt failed: Invalid argument On OpenWRT by default mount-utils is not installed and utab does not exist. /bin/mount is a symlink to /bin/busybox and does not support updating of utab. If mount-utils is installed: /run/mount/ exists, but does not contain utab. The mount-utils instance is under /usr/bin/mount, so a hard-coded call to /bin/mount will still fail. Using /usr/bin/mount succeeds but does not create utab. [1] https://github.com/libfuse/libfuse/pull/1247 Signed-off-by: Georgi Valkov <gvalkov@gmail.com> (cherry picked from commit 3793b1748ad151c8043dee1db198fffa3dbb5a67) Signed-off-by: Georgi Valkov <gvalkov@gmail.com> --- lib/mount_util.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/mount_util.c b/lib/mount_util.c index 089ca4548..f19dfb4c8 100644 --- a/lib/mount_util.c +++ b/lib/mount_util.c @@ -75,6 +75,10 @@ static int mtab_needs_update(const char *mnt) if (err == EROFS) return 0; + + res = access("/run/mount/utab", F_OK); + if (res == -1) + return 0; } return 1; From af48907c0b534f7f81f639f546bfcea6bc8e99b1 Mon Sep 17 00:00:00 2001 From: CismonX <admin@cismon.net> Date: Tue, 12 Aug 2025 08:55:42 +0800 Subject: [PATCH 102/105] fuse_loop_mt.c: fix close-on-exec flag on clone fd Closes: https://github.com/libfuse/libfuse/issues/1310 Signed-off-by: CismonX <admin@cismon.net> (cherry picked from commit 42b9b3bbb71d38be6a5670bbd0948cf406c1573c) --- lib/fuse_loop_mt.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index d6be99849..01c51ccac 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -275,9 +275,15 @@ static int fuse_clone_chan_fd_default(struct fuse_session *se) strerror(errno)); return -1; } -#ifndef O_CLOEXEC - fcntl(clonefd, F_SETFD, FD_CLOEXEC); -#endif + if (!O_CLOEXEC) { + res = fcntl(clonefd, F_SETFD, FD_CLOEXEC); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to set CLOEXEC: %s\n", + strerror(errno)); + close(clonefd); + return -1; + } + } masterfd = se->fd; res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd); From d9e500c1acae30d809ade8197f015b8437305e65 Mon Sep 17 00:00:00 2001 From: CismonX <admin@cismon.net> Date: Tue, 19 Aug 2025 08:05:45 +0800 Subject: [PATCH 103/105] tests: move struct size assertions into a test These checks are meant for libfuse maintainers only, and should not be exposed to users. Signed-off-by: CismonX <admin@cismon.net> (cherry picked from commit a63fc71a55038f49671eb56d0ade99a48ad8d7ab) --- include/fuse_common.h | 10 ---------- meson.build | 6 ------ test/meson.build | 3 +++ test/test_abi.c | 18 ++++++++++++++++++ test/test_ctests.py | 4 ++++ 5 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 test/test_abi.c diff --git a/include/fuse_common.h b/include/fuse_common.h index dd08f444a..f96899075 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -30,12 +30,6 @@ #define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min)) #define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION) -#ifdef HAVE_STATIC_ASSERT -#define fuse_static_assert(condition, message) static_assert(condition, message) -#else -#define fuse_static_assert(condition, message) -#endif - #ifdef __cplusplus extern "C" { #endif @@ -129,8 +123,6 @@ struct fuse_file_info { uint64_t reserved[2]; }; -fuse_static_assert(sizeof(struct fuse_file_info) == 64, - "fuse_file_info size mismatch"); /** * Configuration parameters passed to fuse_session_loop_mt() and @@ -708,8 +700,6 @@ struct fuse_conn_info { */ uint32_t reserved[16]; }; -fuse_static_assert(sizeof(struct fuse_conn_info) == 128, - "Size of struct fuse_conn_info must be 128 bytes"); struct fuse_session; struct fuse_pollhandle; diff --git a/meson.build b/meson.build index 4e7c0a1fa..12a881e8a 100644 --- a/meson.build +++ b/meson.build @@ -59,7 +59,6 @@ include_default = ''' #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> -#include <assert.h> /* For static_assert */ #include <pthread.h> /* For pthread_setname_np */ ''' args_default = [ '-D_GNU_SOURCE' ] @@ -82,11 +81,6 @@ endforeach # Special case checks that need custom code special_funcs = { - 'static_assert': ''' - #include <assert.h> - static_assert(1, "test"); - int main(void) { return 0; } - ''', 'pthread_setname_np': ''' #include <pthread.h> int main(void) { diff --git a/test/meson.build b/test/meson.build index 56568d8f7..a9e339fc1 100644 --- a/test/meson.build +++ b/test/meson.build @@ -19,6 +19,9 @@ td += executable('release_unlink_race', 'release_unlink_race.c', td += executable('test_want_conversion', 'test_want_conversion.c', dependencies: [ libfuse_dep ], install: false) +td += executable('test_abi', 'test_abi.c', + dependencies: [ libfuse_dep ], + install: false) test_scripts = [ 'conftest.py', 'pytest.ini', 'test_examples.py', 'util.py', 'test_ctests.py', 'test_custom_io.py' ] diff --git a/test/test_abi.c b/test/test_abi.c new file mode 100644 index 000000000..99daa0988 --- /dev/null +++ b/test/test_abi.c @@ -0,0 +1,18 @@ +#define FUSE_USE_VERSION 30 + +#include "fuse.h" + +#include <stdio.h> +#include <stdlib.h> + +int main(void) +{ + if (sizeof(struct fuse_file_info) != 64) { + fprintf(stderr, "struct fuse_file_info size mismatch\n"); + exit(1); + } + if (sizeof(struct fuse_conn_info) != 128) { + fprintf(stderr, "struct fuse_conn_info size mismatch\n"); + exit(1); + } +} diff --git a/test/test_ctests.py b/test/test_ctests.py index feefc0707..36b5ff347 100644 --- a/test/test_ctests.py +++ b/test/test_ctests.py @@ -20,6 +20,10 @@ pytestmark = fuse_test_marker() +def test_abi(): + cmdline = [ pjoin(basename, 'test', 'test_abi') ] + subprocess.check_call(cmdline) + @pytest.mark.skipif('FUSE_CAP_WRITEBACK_CACHE' not in fuse_caps, reason='not supported by running kernel') @pytest.mark.parametrize("writeback", (False, True)) From 8c8a62b1ef8b22641325ed8584d0e23e039ac42d Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 12 Aug 2025 17:20:45 +0200 Subject: [PATCH 104/105] Add signify verification in make_release_tarball.sh Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- make_release_tarball.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/make_release_tarball.sh b/make_release_tarball.sh index a0040638b..ce2fe16c5 100755 --- a/make_release_tarball.sh +++ b/make_release_tarball.sh @@ -30,6 +30,7 @@ cp -a doc/html "${TAG}/doc/" tar -czf "${TAG}.tar.gz" "${TAG}/" signify-openbsd -S -s signify/$MAJOR_REV.sec -m $TAG.tar.gz +signify-openbsd -V -m ${TAG}.tar.gz -p signify/.$MAJOR_REV.pub echo "Contributors from ${PREV_TAG} to ${TAG}:" From ddcb1a075995500bd6c91327a1dfcee346acd075 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 12 Aug 2025 17:19:12 +0200 Subject: [PATCH 105/105] Release 3.17.4 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- AUTHORS | 3 +++ ChangeLog.rst | 8 ++++++++ meson.build | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index dc850c2f2..9f6ed908d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -278,3 +278,6 @@ Ben Dooks <ben.dooks@codethink.co.uk> # New authors since fuse-3.17.2 Long Li <leo.lilong@huawei.com> + +# New authors since fuse-3.17.3 +Georgi Valkov <gvalkov@gmail.com> diff --git a/ChangeLog.rst b/ChangeLog.rst index d07de3262..730cb0ffd 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,11 @@ +libfuse 3.17.4 (2025-08-19) +=========================== +- Try to detect mount-utils by checking for /run/mount/utab + and don't try to update mtab if it does not exist +- Fix a build warning when HAVE_BACKTRACE is undefined +- fuse_loop_mt.c: fix close-on-exec flag on clone fd +- Remove struct size assertions from fuse_common.h + libfuse 3.17.3 (2025-07-16) =========================== * more conn->want / conn->want_ext conversion fixes diff --git a/meson.build b/meson.build index 12a881e8a..f5834aa7e 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfuse3', ['c'], - version: '3.17.3', + version: '3.17.4', meson_version: '>= 0.60.0', default_options: [ 'buildtype=debugoptimized',